82 lines
3.0 KiB
Rust
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()))
|
|
);
|
|
}
|
|
}
|