// src/table_definition/models.rs use tonic::Status; /// Predefined static field mappings // TODO CRITICAL add decimal with optional precision" pub const PREDEFINED_FIELD_TYPES: &[(&str, &str)] = &[ ("text", "TEXT"), ("string", "TEXT"), ("boolean", "BOOLEAN"), ("timestamp", "TIMESTAMPTZ"), ("timestamptz", "TIMESTAMPTZ"), ("time", "TIMESTAMPTZ"), ("money", "NUMERIC(14, 4)"), ("integer", "INTEGER"), ("int", "INTEGER"), ("biginteger", "BIGINT"), ("bigint", "BIGINT"), ("date", "DATE"), ]; /// reusable decimal number validation pub fn validate_decimal_number_format(num_str: &str, param_name: &str) -> Result<(), Status> { if num_str.is_empty() { return Err(Status::invalid_argument(format!("{} cannot be empty", param_name))); } if num_str.starts_with('+') || num_str.starts_with('-') { return Err(Status::invalid_argument(format!( "{} cannot have explicit positive/negative signs", param_name ))); } if num_str.contains('.') { return Err(Status::invalid_argument(format!( "{} must be a whole number (no decimal point)", param_name ))); } if num_str.len() > 1 && num_str.starts_with('0') { let trimmed = num_str.trim_start_matches('0'); let suggestion = if trimmed.is_empty() { "0" } else { trimmed }; return Err(Status::invalid_argument(format!( "{} cannot have leading zeros (use '{}' instead of '{}')", param_name, suggestion, num_str ))); } if !num_str.chars().all(|c| c.is_ascii_digit()) { return Err(Status::invalid_argument(format!( "{} contains invalid characters. Only digits allowed", param_name ))); } Ok(()) } /// reusable field type mapper pub fn map_field_type(field_type: &str) -> Result { let lower_field_type = field_type.to_lowercase(); if lower_field_type.starts_with("decimal(") && lower_field_type.ends_with(')') { let args = lower_field_type.strip_prefix("decimal(").unwrap() .strip_suffix(')').unwrap(); if let Some((p_str, s_str)) = args.split_once(',') { let precision_str = p_str.trim(); let scale_str = s_str.trim(); validate_decimal_number_format(precision_str, "precision")?; validate_decimal_number_format(scale_str, "scale")?; let precision = precision_str.parse::() .map_err(|_| Status::invalid_argument("Invalid precision"))?; let scale = scale_str.parse::() .map_err(|_| Status::invalid_argument("Invalid scale"))?; if precision < 1 { return Err(Status::invalid_argument("Precision must be >= 1")); } if scale > precision { return Err(Status::invalid_argument("Scale cannot be > precision")); } return Ok(format!("NUMERIC({}, {})", precision, scale)); } else { return Err(Status::invalid_argument( "Invalid decimal format. Expected decimal(precision, scale)" )); } } PREDEFINED_FIELD_TYPES .iter() .find(|(key, _)| *key == lower_field_type.as_str()) .map(|(_, sql_type)| sql_type.to_string()) .ok_or_else(|| Status::invalid_argument(format!("Invalid field type: {}", field_type))) }