{"record":{"id":"7523b2ea3b52cce0","repo":"windmill-labs/windmill","slug":"cannot-parse-s-as-real-e","errorCode":null,"errorMessage":"Cannot parse '{s}' as real: {e}","messagePattern":"Cannot parse '(.+?)' as real: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-worker/src/pg_executor.rs","lineNumber":1648,"sourceCode":"        Value::String(s) if arg_t == \"bytea\" => {\n            let bytes = engine::general_purpose::STANDARD\n                .decode(s)\n                .unwrap_or(vec![]);\n            Ok((Box::new(bytes), Type::BYTEA))\n        }\n        // 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(","sourceCodeStart":1630,"sourceCodeEnd":1666,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-worker/src/pg_executor.rs#L1630-L1666","documentation":"convert_val converts a JSON string argument into a tokio-postgres ToSql parameter. When the declared arg type is real/float4, it parses the string with f32::from_str and wraps any ParseFloatError as 'Cannot parse ... as real'. It exists so a valid FLOAT4 parameter is built client-side rather than failing server-side with a type mismatch.","triggerScenarios":"A step argument typed real/float4 receives a Value::String that is not a valid f32 ('1,5', 'abc', '', 'Infinity', or a hex float).","commonSituations":"Comma decimal separators from European locales; sensor data with units attached ('3.2 kg'); empty form fields; values that legitimately need double precision being forced into float4 with precision loss complaints.","solutions":["Ensure the string is a valid float literal (dot separator) before invoking the step","Replace the locale decimal comma with a dot and trim whitespace/units","If values are non-finite or need more precision, switch the arg/column to double/float8 or numeric","Pass as a JSON number instead of a string when precision allows"],"exampleFix":"// before\nargs: { temperature: \"21,4\" }  // comma decimal -> ParseFloatError\n// after\nargs: { temperature: \"21.4\" }","handlingStrategy":"validation","validationCode":"function assertFloat4(s) {\n  const n = Number(typeof s === \"string\" ? s.trim().replace(\",\", \".\") : s);\n  if (Number.isNaN(n) || !Number.isFinite(n)) {\n    throw new Error(`value ${JSON.stringify(s)} is not a valid float4`);\n  }\n}","typeGuard":"const isFloat4 = (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 real\")) {\n    throw new Error(`Argument must be a finite f32 literal, got: ${e.message}`);\n  }\n  throw e;\n}","preventionTips":["Convert decimal commas to dots before passing","Strip units/symbols from measurements","Use double/float8 when float4 precision is insufficient"],"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"}