{"record":{"id":"3dd8537794cb4164","repo":"windmill-labs/windmill","slug":"cannot-parse-s-as-double-e","errorCode":null,"errorMessage":"Cannot parse '{s}' as double: {e}","messagePattern":"Cannot parse '(.+?)' as double: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-worker/src/pg_executor.rs","lineNumber":1654,"sourceCode":"        // Parse Strings into the matching native Rust type for the remaining\n        // recognised arg_ts that didn't have a dedicated arm. Without these,\n        // a string value lands in the generic Value::String fallback below\n        // (Box<String> + TEXT) and the server-side comparison\n        // `<numeric|real|...> = text` fails since PG has no implicit cast.\n        Value::String(s) if arg_t == \"numeric\" || arg_t == \"decimal\" => s\n            .parse::<Decimal>()\n            .map(|d| (Box::new(d) as Box<dyn ToSql + Sync + Send>, Type::NUMERIC))\n            .map_err(|e| anyhow::anyhow!(\"Cannot parse '{s}' as numeric: {e}\").into()),\n        Value::String(s) if arg_t == \"real\" || arg_t == \"float4\" => s\n            .parse::<f32>()\n            .map(|n| (Box::new(n) as Box<dyn ToSql + Sync + Send>, Type::FLOAT4))\n            .map_err(|e| anyhow::anyhow!(\"Cannot parse '{s}' as real: {e}\").into()),\n        Value::String(s)\n            if arg_t == \"double\" || arg_t == \"double precision\" || arg_t == \"float8\" =>\n        {\n            s.parse::<f64>()\n                .map(|n| (Box::new(n) as Box<dyn ToSql + Sync + Send>, Type::FLOAT8))\n                .map_err(|e| anyhow::anyhow!(\"Cannot parse '{s}' as double: {e}\").into())\n        }\n        Value::String(s) if arg_t == \"oid\" => s\n            .parse::<u32>()\n            .map(|n| (Box::new(n) as Box<dyn ToSql + Sync + Send>, Type::OID))\n            .map_err(|e| anyhow::anyhow!(\"Cannot parse '{s}' as oid: {e}\").into()),\n        Value::String(s) if arg_t == \"bool\" || arg_t == \"boolean\" => {\n            // Accept the same literals Postgres' boolin() does.\n            let b = match s.to_ascii_lowercase().as_str() {\n                \"true\" | \"t\" | \"yes\" | \"y\" | \"1\" | \"on\" => true,\n                \"false\" | \"f\" | \"no\" | \"n\" | \"0\" | \"off\" => false,\n                _ => {\n                    return Err(\n                        anyhow::anyhow!(\"Cannot parse '{s}' as bool: invalid literal\").into(),\n                    )\n                }\n            };\n            Ok((Box::new(b), Type::BOOL))\n        }","sourceCodeStart":1636,"sourceCodeEnd":1672,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-worker/src/pg_executor.rs#L1636-L1672","documentation":"convert_val converts a JSON string argument into a tokio-postgres ToSql parameter. When the declared arg type is double/double precision/float8, it parses the string with f64::from_str and wraps any ParseFloatError as 'Cannot parse ... as double'. It exists to produce a valid FLOAT8 parameter client-side.","triggerScenarios":"A step argument typed double/float8 receives a Value::String f64::from_str rejects: 'NaN' (Rust f64 parse accepts NaN actually; failures come from '', '1,5', '12%', or arbitrary text), or numbers with units/spaces.","commonSituations":"Percentages or measurements scraped with symbols ('97.5%'); comma decimal locales; blank inputs from forms; coordinates pasted with degree signs.","solutions":["Ensure the string is a plain f64-compatible literal before invoking the step","Strip symbols/units and convert locale decimal commas to dots in the step script","Reject empty strings earlier in the UI/form validation instead of sending them to the DB step","Pass as a JSON number where the value round-trips safely"],"exampleFix":"// before\nargs: { ratio: \"97.5%\" }\n// after\nargs: { ratio: \"0.975\" }  // or strip '%' and divide by 100 before passing","handlingStrategy":"validation","validationCode":"function assertFloat8(s) {\n  const raw = typeof s === \"string\" ? s.trim().replace(\",\", \".\") : s;\n  const n = Number(raw);\n  if (Number.isNaN(n) || !Number.isFinite(n)) {\n    throw new Error(`value ${JSON.stringify(s)} is not a valid float8`);\n  }\n}","typeGuard":"const isFloat8 = (v) => Number.isFinite(Number(typeof v === \"string\" ? v.trim().replace(\",\", \".\") : v));","tryCatchPattern":"try {\n  await runPgStep(args);\n} catch (e) {\n  if (String(e.message).includes(\"as double\")) {\n    throw new Error(`Argument must be a finite f64 literal, got: ${e.message}`);\n  }\n  throw e;\n}","preventionTips":["Sanitize scraped values (strip %, °, units) before passing","Validate emptiness at the form layer — empty strings never parse as floats","Pass JSON numbers where values round-trip safely"],"tags":["postgresql","parse-error","float","type-conversion"],"backgroundTag":"invalid-number-format","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}