Files
komp_ac/komp-app/src/value.rs
2026-08-30 07:49:19 +02:00

82 lines
3.0 KiB
Rust

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<bool> {
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<Value, String> {
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::<i64>()
.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::<i32>()
.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<String, String>,
schema: &TableStructureResponse,
) -> Result<HashMap<String, Value>, 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()))
);
}
}