type hardening

This commit is contained in:
Priec
2026-08-16 16:52:28 +02:00
parent 99212252b2
commit 8b63b51bec
12 changed files with 67 additions and 6 deletions

View File

@@ -7,6 +7,7 @@ license.workspace = true
[dependencies]
prost-types = { workspace = true }
rust_decimal = { workspace = true }
rusty-money = { workspace = true }
tonic = "0.14.6"
prost = "0.14.4"

View File

@@ -131,7 +131,8 @@ message PostTableDataRequest {
// - BOOLEAN: bool value
// - TIMESTAMPTZ: ISO 8601/RFC 3339 string (parsed to TIMESTAMPTZ)
// - INTEGER: number with no fractional part and within i32 range
// - BIGINT: number with no fractional part and within i64 range
// - BIGINT: canonical integer string for the full i64 range; a number is
// accepted only within protobuf NumberValue's exact integer range
// - NUMERIC(p,s): string representation only; empty string becomes NULL
// (numbers for NUMERIC are rejected to avoid precision loss)
//

View File

@@ -3,6 +3,7 @@
pub mod search;
pub mod decimal;
pub mod grpc_error;
pub mod money;
pub mod relationship;
pub mod system_column;

53
common/src/money.rs Normal file
View File

@@ -0,0 +1,53 @@
// common/src/money.rs
//!
//! The one canonical currency spelling shared by the client and the server.
//!
//! A currency code is stored, sent and compared as its canonical uppercase
//! ISO-4217 alphabetic code. Both ends resolve it with
//! [`require_iso_currency_code`], so the client rejects exactly what the server
//! would reject, and each side renders the shared error as its own error type.
pub use rusty_money::iso;
/// Resolves a canonical uppercase ISO-4217 alphabetic code to its currency.
///
/// Rejects anything not already canonical: lowercase (`eur`), surrounding
/// whitespace, and any length but three. Normalising here instead would let a
/// value be written in one spelling and compared in another.
pub fn require_iso_currency_code(currency_code: &str) -> Result<&'static iso::Currency, String> {
if currency_code.len() != 3
|| !currency_code
.chars()
.all(|character| character.is_ascii_uppercase())
{
return Err("Currency must be a canonical uppercase ISO-4217 code".to_string());
}
iso::find(currency_code).ok_or_else(|| format!("Unknown ISO-4217 currency: {currency_code}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn canonical_codes_resolve_to_their_currency() {
assert_eq!(require_iso_currency_code("EUR").unwrap(), iso::EUR);
assert_eq!(
require_iso_currency_code("USD").unwrap().iso_alpha_code,
"USD"
);
}
#[test]
fn non_canonical_or_unknown_codes_are_rejected() {
for currency_code in [
"", "E", "EU", "EURO", "eur", "Eur", " EUR", "EUR ", "E R", "E\u{20AC}R", "AAA", "123",
] {
assert!(
require_iso_currency_code(currency_code).is_err(),
"unexpectedly accepted {currency_code:?}"
);
}
}
}

Binary file not shown.

View File

@@ -27,7 +27,8 @@ pub struct PostTableDataRequest {
/// * BOOLEAN: bool value
/// * TIMESTAMPTZ: ISO 8601/RFC 3339 string (parsed to TIMESTAMPTZ)
/// * INTEGER: number with no fractional part and within i32 range
/// * BIGINT: number with no fractional part and within i64 range
/// * BIGINT: canonical integer string for the full i64 range; a number is
/// accepted only within protobuf NumberValue's exact integer range
/// * NUMERIC(p,s): string representation only; empty string becomes NULL
/// (numbers for NUMERIC are rejected to avoid precision loss)
///