{"record":{"id":"5634d558034e5e66","repo":"nautechsystems/nautilus_trader","slug":"invalid-quantity-value-e","errorCode":null,"errorMessage":"invalid quantity `{value}`: {e}","messagePattern":"invalid quantity `(.+?)`: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/lighter/src/common/parse.rs","lineNumber":118,"sourceCode":"}\n\n/// Converts a decimal string into a non-negative Nautilus [`Quantity`].\n///\n/// Zero is allowed because Lighter sends zero-size book levels to delete\n/// existing orders.\n///\n/// # Errors\n///\n/// Returns an error if the string is not a decimal, if `precision` exceeds\n/// [`MAX_DECIMALS`], if the value is negative, or if the resulting quantity\n/// is out of range.\npub fn parse_quantity(value: &str, precision: u8) -> anyhow::Result<Quantity> {\n    anyhow::ensure!(\n        precision <= MAX_DECIMALS,\n        \"size precision {precision} exceeds maximum {MAX_DECIMALS}\",\n    );\n    let decimal =\n        Decimal::from_str(value).map_err(|e| anyhow::anyhow!(\"invalid quantity `{value}`: {e}\"))?;\n    anyhow::ensure!(decimal.is_sign_positive(), \"negative quantity `{value}`\");\n    Quantity::from_decimal_dp(decimal, precision)\n        .map_err(|e| anyhow::anyhow!(\"invalid quantity `{value}` at precision {precision}: {e}\"))\n}\n\n/// Converts a [`Decimal`] into a Nautilus [`Price`] at the requested precision.\n///\n/// Use this when the wire value has already been deserialized as a [`Decimal`]\n/// (the standard pattern for model fields tagged with `deserialize_decimal`).\n///\n/// # Errors\n///\n/// Returns an error if `precision` exceeds [`MAX_DECIMALS`] or if the value\n/// is out of [`Price`] range.\npub fn price_from_decimal(value: Decimal, precision: u8) -> anyhow::Result<Price> {\n    anyhow::ensure!(\n        precision <= MAX_DECIMALS,\n        \"price precision {precision} exceeds maximum {MAX_DECIMALS}\",","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/lighter/src/common/parse.rs#L100-L136","documentation":"parse_quantity parses the input string with Decimal::from_str before any quantity checks. This error means the string is not a valid decimal number — bad characters, empty string, thousands separators, multiple decimal points, or other formats rust_decimal cannot parse. The raw value is embedded in the message.","triggerScenarios":"Calling parse_quantity with a non-decimal string — e.g. \"\", \"none\", \"1.5e3\" variants rejected by rust_decimal, \"1,000.5\", \"0.5 BTC\", or a null-like sentinel string from the payload.","commonSituations":"Upstream responses where optional size fields are serialized as sentinel strings; display-formatted quantities; locale-formatted numbers (comma decimal separator); wiring the wrong field into the parser.","solutions":["Normalize the string first: trim, strip currency/ticker suffixes and thousands separators, convert locale decimal commas to dots.","Confirm you are passing the raw wire value exactly as Lighter sends it (plain decimal string).","If the field is optional, check for the sentinel/absent case before calling and skip parsing.","Deserialize to Decimal via the adapter's deserialize_decimal helper and use Quantity::from_decimal_dp-based paths instead of strings."],"exampleFix":"// before\nlet qty = parse_quantity(raw_size, precision)?;\n// after\nif raw_size.is_empty() || raw_size == \"none\" { return Ok(Quantity::zero(precision)); }\nlet qty = parse_quantity(raw_size.trim(), precision)?;","handlingStrategy":"try-catch","validationCode":"fn is_plain_decimal(s: &str) -> bool {\n    let s = s.trim();\n    !s.is_empty()\n        && s.chars().all(|c| c.is_ascii_digit() || c == '.' || c == '-' || c == '+')\n        && s.matches('.').count() <= 1\n}","typeGuard":null,"tryCatchPattern":"match parse_quantity(raw, precision) {\n    Ok(q) => q,\n    Err(e) if e.to_string().starts_with(\"invalid quantity\") && !e.to_string().contains(\"negative\") => {\n        tracing::warn!(raw = %raw, \"unparseable quantity string; skipping\");\n        return Ok(None);\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Check optional/absent fields for sentinel values before calling the parser","Pass the exact raw wire string with no display or locale formatting","Handle empty strings explicitly (they will not parse) rather than relying on the parser","Prefer Decimal-typed deserialization paths over strings where the wire format allows"],"tags":["rust","parsing","decimal","quantity","format"],"backgroundTag":"invalid-argument-format","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}