use common::decimal::{is_decimal_data_type, parse_decimal_exact}; use common::proto::komp_ac::table_structure::TableStructureResponse; use prost_types::{NullValue, Value, value::Kind}; use std::collections::HashMap; pub const BOOLEAN_INPUT_VOCABULARY: &str = "must be one of true/t/yes/y/1 or false/f/no/n/0, in any case"; pub fn parse_boolean_input(raw: &str) -> Option { match raw.to_ascii_lowercase().as_str() { "true" | "t" | "yes" | "y" | "1" => Some(true), "false" | "f" | "no" | "n" | "0" => Some(false), _ => None, } } pub fn convert_input_value(raw: &str, data_type: &str, field: &str) -> Result { let normalized = data_type.to_ascii_uppercase(); let kind = if raw.is_empty() { Kind::NullValue(NullValue::NullValue.into()) } else if matches!(normalized.as_str(), "BOOL" | "BOOLEAN") { parse_boolean_input(raw) .map(Kind::BoolValue) .ok_or_else(|| format!("Invalid boolean for '{field}': {BOOLEAN_INPUT_VOCABULARY}"))? } else if matches!(normalized.as_str(), "INT8" | "BIGINT" | "BIGSERIAL") { let value = raw .parse::() .map_err(|_| format!("Invalid big integer for '{field}': value must fit in 64 bits"))?; Kind::StringValue(value.to_string()) } else if matches!(normalized.as_str(), "INT" | "INT4" | "INTEGER" | "SERIAL") { let value = raw .parse::() .map_err(|_| format!("Invalid integer for '{field}': value must fit in 32 bits"))?; Kind::NumberValue(f64::from(value)) } else if is_decimal_data_type(&normalized) { let decimal = parse_decimal_exact(raw) .map_err(|error| format!("Invalid decimal for '{field}': {error}"))?; Kind::StringValue(decimal.to_string()) } else { Kind::StringValue(raw.to_string()) }; Ok(Value { kind: Some(kind) }) } pub fn convert_and_validate_data( data: &HashMap, schema: &TableStructureResponse, ) -> Result, String> { let type_map: HashMap<_, _> = schema .columns .iter() .map(|column| (column.name.as_str(), column.data_type.as_str())) .collect(); data.iter() .map(|(field, raw)| { let data_type = type_map.get(field.as_str()).copied().unwrap_or("TEXT"); convert_input_value(raw, data_type, field).map(|value| (field.clone(), value)) }) .collect() } #[cfg(test)] mod tests { use super::*; #[test] fn preserves_large_integers_and_decimals_as_strings() { assert_eq!( convert_input_value("9223372036854775807", "BIGINT", "amount") .unwrap() .kind, Some(Kind::StringValue("9223372036854775807".to_string())) ); assert_eq!( convert_input_value("123456789012345678901.25", "NUMERIC", "amount") .unwrap() .kind, Some(Kind::StringValue("123456789012345678901.25".to_string())) ); } }