From 082b77d35a69d7f7eb8eb040b881b01b0fdc0481 Mon Sep 17 00:00:00 2001 From: Priec Date: Mon, 7 Sep 2026 10:25:14 +0200 Subject: [PATCH] strings not used internally11 --- common/proto/analytics.proto | 4 +- common/proto/document_data.proto | 8 ++- common/proto/exchange_rates.proto | 6 +- common/src/money.rs | 71 +++++++++++++++++++++ common/src/proto/descriptor.bin | Bin 252865 -> 253439 bytes common/src/proto/komp_ac.analytics.rs | 4 +- common/src/proto/komp_ac.document_data.rs | 34 +++++++++- common/src/proto/komp_ac.exchange_rates.rs | 8 +-- server | 2 +- web/src/pages/analytics/loader.rs | 41 ++++++------ 10 files changed, 147 insertions(+), 31 deletions(-) diff --git a/common/proto/analytics.proto b/common/proto/analytics.proto index aabd4389..2e890b71 100644 --- a/common/proto/analytics.proto +++ b/common/proto/analytics.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package komp_ac.analytics; +import "table_definition.proto"; + import "google/protobuf/struct.proto"; // Runs read-only analytical SQL over runtime-defined profile tables. @@ -37,7 +39,7 @@ message AnalyticsCatalogColumn { string name = 1; string field_type = 2; bool is_system = 3; - string rounding = 4; + komp_ac.table_definition.MoneyRounding rounding = 4; // Canonical ISO-4217 code for MONEY; empty for every other type. string currency = 5; } diff --git a/common/proto/document_data.proto b/common/proto/document_data.proto index 5f6ee18b..b184c24b 100644 --- a/common/proto/document_data.proto +++ b/common/proto/document_data.proto @@ -78,13 +78,19 @@ message GetDocumentsRequest { optional string source_table_name = 5; } +enum DocumentDataChangeKind { + DOCUMENT_DATA_CHANGE_KIND_UNSPECIFIED = 0; + DOCUMENT_DATA_CHANGE_KIND_GENERATED = 1; + DOCUMENT_DATA_CHANGE_KIND_USER_ADJUSTMENT = 2; +} + message Document { int64 snapshot_id = 1; int64 document_id = 2; int64 schema_id = 3; repeated SourceRecord source_records = 4; string data_json = 5; - string change_kind = 6; + DocumentDataChangeKind change_kind = 6; int32 version_number = 7; string created_at = 8; string updated_at = 9; diff --git a/common/proto/exchange_rates.proto b/common/proto/exchange_rates.proto index 3234b96e..7d21fdec 100644 --- a/common/proto/exchange_rates.proto +++ b/common/proto/exchange_rates.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package komp_ac.exchange_rates; +import "table_definition.proto"; + import "common.proto"; // Selects WHICH DATE supplies the rate; this is independent of where the rate @@ -106,7 +108,7 @@ message PreviewDirectConversionResponse { string accounting_currency = 4; string conversion_basis_date = 5; string determination_method = 6; - string rounding_method = 7; + komp_ac.table_definition.MoneyRounding rounding_method = 7; optional string rate_date = 8; optional string accounting_units = 9; optional string foreign_currency = 10; @@ -149,7 +151,7 @@ message ConversionEvidence { string conversion_context = 10; string conversion_basis_date = 11; string determination_method = 12; - string rounding_method = 13; + komp_ac.table_definition.MoneyRounding rounding_method = 13; optional string rate_date = 14; string source_id = 15; optional int64 official_observation_id = 16; diff --git a/common/src/money.rs b/common/src/money.rs index 944931cf..f2b92c7e 100644 --- a/common/src/money.rs +++ b/common/src/money.rs @@ -8,6 +8,49 @@ //! would reject, and each side renders the shared error as its own error type. pub use rusty_money::iso; +pub use crate::proto::komp_ac::table_definition::MoneyRounding; + +impl MoneyRounding { + pub fn as_storage_str(self) -> &'static str { + match self { + Self::None => "none", + Self::HalfUp => "half_up", + } + } +} + +impl std::str::FromStr for MoneyRounding { + type Err = serde::de::value::Error; + + fn from_str(value: &str) -> Result { + match value { + "none" => Ok(Self::None), + "half_up" => Ok(Self::HalfUp), + _ => Err(serde::de::Error::custom(format!("Invalid money rounding: {value}"))), + } + } +} + +impl TryFrom for MoneyRounding { + type Error = serde::de::value::Error; + + fn try_from(value: String) -> Result { + value.parse() + } +} + +pub mod rounding_serde { + use super::MoneyRounding; + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(value: &MoneyRounding, serializer: S) -> Result { + serializer.serialize_str(value.as_storage_str()) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom) + } +} /// Resolves a canonical uppercase ISO-4217 alphabetic code to its currency. /// @@ -40,6 +83,34 @@ pub fn iso_currency_codes() -> Vec<&'static str> { mod tests { use super::*; + #[test] + fn rounding_storage_and_json_boundaries_are_strict() { + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct StoredPolicy { + #[serde(with = "super::rounding_serde")] + rounding: MoneyRounding, + } + + for (text, policy) in [("none", MoneyRounding::None), ("half_up", MoneyRounding::HalfUp)] { + assert_eq!(text.parse::().unwrap(), policy); + assert_eq!(MoneyRounding::try_from(text.to_string()).unwrap(), policy); + assert_eq!(policy.as_storage_str(), text); + let stored = StoredPolicy { rounding: policy }; + let json = serde_json::json!({ "rounding": text }); + assert_eq!(serde_json::to_value(&stored).unwrap(), json); + assert_eq!(serde_json::from_value::(json).unwrap(), stored); + } + for text in ["", "NONE", "HalfUp", "half-up", " half_up", "half_up ", "unknown"] { + assert!(text.parse::().is_err()); + assert!(serde_json::from_value::(serde_json::json!({ "rounding": text })).is_err()); + } + for value in [serde_json::Value::Null, serde_json::json!(0), serde_json::json!(1)] { + assert!(serde_json::from_value::(serde_json::json!({ "rounding": value })).is_err()); + } + assert!(MoneyRounding::try_from(-1).is_err()); + assert!(MoneyRounding::try_from(2).is_err()); + } + #[test] fn canonical_codes_resolve_to_their_currency() { assert_eq!(require_iso_currency_code("EUR").unwrap(), iso::EUR); diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 091b44a1bdb8cf59f7f848df1511015335aa033e..9b3568c209ff59704a0402657090bc829d23918c 100644 GIT binary patch delta 12151 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 delta 11798 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 WUT*92@}z#@xM}>ii, #[prost(string, tag = "5")] pub data_json: ::prost::alloc::string::String, - #[prost(string, tag = "6")] - pub change_kind: ::prost::alloc::string::String, + #[prost(enumeration = "DocumentDataChangeKind", tag = "6")] + pub change_kind: i32, #[prost(int32, tag = "7")] pub version_number: i32, #[prost(string, tag = "8")] @@ -221,6 +221,36 @@ pub struct GetTypstTemplateVersionResponse { #[prost(message, optional, tag = "1")] pub template_version: ::core::option::Option, } +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum DocumentDataChangeKind { + Unspecified = 0, + Generated = 1, + UserAdjustment = 2, +} +impl DocumentDataChangeKind { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "DOCUMENT_DATA_CHANGE_KIND_UNSPECIFIED", + Self::Generated => "DOCUMENT_DATA_CHANGE_KIND_GENERATED", + Self::UserAdjustment => "DOCUMENT_DATA_CHANGE_KIND_USER_ADJUSTMENT", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DOCUMENT_DATA_CHANGE_KIND_UNSPECIFIED" => Some(Self::Unspecified), + "DOCUMENT_DATA_CHANGE_KIND_GENERATED" => Some(Self::Generated), + "DOCUMENT_DATA_CHANGE_KIND_USER_ADJUSTMENT" => Some(Self::UserAdjustment), + _ => None, + } + } +} /// Generated client implementations. pub mod document_data_service_client { #![allow( diff --git a/common/src/proto/komp_ac.exchange_rates.rs b/common/src/proto/komp_ac.exchange_rates.rs index a37a3aea..c20acb22 100644 --- a/common/src/proto/komp_ac.exchange_rates.rs +++ b/common/src/proto/komp_ac.exchange_rates.rs @@ -59,8 +59,8 @@ pub struct PreviewDirectConversionResponse { pub conversion_basis_date: ::prost::alloc::string::String, #[prost(string, tag = "6")] pub determination_method: ::prost::alloc::string::String, - #[prost(string, tag = "7")] - pub rounding_method: ::prost::alloc::string::String, + #[prost(enumeration = "super::table_definition::MoneyRounding", tag = "7")] + pub rounding_method: i32, #[prost(string, optional, tag = "8")] pub rate_date: ::core::option::Option<::prost::alloc::string::String>, #[prost(string, optional, tag = "9")] @@ -141,8 +141,8 @@ pub struct ConversionEvidence { pub conversion_basis_date: ::prost::alloc::string::String, #[prost(string, tag = "12")] pub determination_method: ::prost::alloc::string::String, - #[prost(string, tag = "13")] - pub rounding_method: ::prost::alloc::string::String, + #[prost(enumeration = "super::table_definition::MoneyRounding", tag = "13")] + pub rounding_method: i32, #[prost(string, optional, tag = "14")] pub rate_date: ::core::option::Option<::prost::alloc::string::String>, #[prost(string, tag = "15")] diff --git a/server b/server index 6d6f241b..cac5233a 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 6d6f241b0b290e0fed5522be0c61a6d1dd4ff8a5 +Subproject commit cac5233a110d778f5e9467131ffddc02e9e14cb2 diff --git a/web/src/pages/analytics/loader.rs b/web/src/pages/analytics/loader.rs index 695d7b74..6f0f5300 100644 --- a/web/src/pages/analytics/loader.rs +++ b/web/src/pages/analytics/loader.rs @@ -2,6 +2,7 @@ //! responses into the view models the templates render. use axum::http::HeaderMap; +use common::money::MoneyRounding; use crate::{ AppState, @@ -60,7 +61,7 @@ pub(crate) async fn load_catalog( .await .map_err(|error| LoadError::Backend(error.message().to_string()))? .into_inner(); - Ok(catalog_view(&catalog)) + catalog_view(&catalog) } pub(crate) async fn run_query( @@ -141,15 +142,15 @@ fn value_output(value: crate::analytics::AnalyticsValue) -> serde_json::Value { } } -fn catalog_view(catalog: &GetAnalyticsCatalogResponse) -> CatalogView { - CatalogView { - tables: catalog.tables.iter().map(catalog_table_view).collect(), - llm_context: build_llm_context(catalog), - } +fn catalog_view(catalog: &GetAnalyticsCatalogResponse) -> Result { + Ok(CatalogView { + tables: catalog.tables.iter().map(catalog_table_view).collect::>()?, + llm_context: build_llm_context(catalog)?, + }) } -fn catalog_table_view(table: &AnalyticsTable) -> CatalogTableView { - CatalogTableView { +fn catalog_table_view(table: &AnalyticsTable) -> Result { + Ok(CatalogTableView { name: table.name.clone(), starter_query: format!("SELECT *\nFROM {}\nLIMIT 100;", quote_identifier(&table.name)), columns: table @@ -160,19 +161,21 @@ fn catalog_table_view(table: &AnalyticsTable) -> CatalogTableView { if column.is_system { details.push_str(", system"); } - if !column.rounding.is_empty() { - details.push_str(&format!(", rounding {}", column.rounding)); + let rounding = MoneyRounding::try_from(column.rounding) + .map_err(|error| LoadError::Backend(error.to_string()))?; + if !column.is_system { + details.push_str(&format!(", rounding {}", rounding.as_storage_str())); } if !column.currency.is_empty() { details.push_str(&format!(", currency {}", column.currency)); } - CatalogColumnView { + Ok::<_, LoadError>(CatalogColumnView { name: column.name.clone(), insert_text: quote_identifier(&column.name), details, - } + }) }) - .collect(), + .collect::>()?, links: table .links .iter() @@ -182,10 +185,10 @@ fn catalog_table_view(table: &AnalyticsTable) -> CatalogTableView { required: link.required, }) .collect(), - } + }) } -fn build_llm_context(catalog: &GetAnalyticsCatalogResponse) -> String { +fn build_llm_context(catalog: &GetAnalyticsCatalogResponse) -> Result { let mut text = format!( "Write one read-only PostgreSQL SELECT query for the komp_ac analytics API.\n\ Profile: {}\n\n\ @@ -212,8 +215,10 @@ AVAILABLE ANALYTICS SCHEMA\n", if column.is_system { text.push_str(" [system]"); } - if !column.rounding.is_empty() { - text.push_str(&format!(" [rounding: {}]", column.rounding)); + let rounding = MoneyRounding::try_from(column.rounding) + .map_err(|error| LoadError::Backend(error.to_string()))?; + if !column.is_system { + text.push_str(&format!(" [rounding: {}]", rounding.as_storage_str())); } if !column.currency.is_empty() { text.push_str(&format!(" [currency: {}]", column.currency)); @@ -232,7 +237,7 @@ AVAILABLE ANALYTICS SCHEMA\n", } } } - text + Ok(text) } fn quote_identifier(identifier: &str) -> String {