1use std::cmp::Ordering;
4use std::collections::BTreeSet;
5use std::fmt;
6
7use serde::de::Error as _;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10use crate::decimal::parse_decimal;
11use crate::diagnostic::{Diagnostic, DiagnosticCategory};
12use crate::limits::MAX_CANONICAL_STRING_BYTES;
13use crate::temporal::{CanonicalDate, CanonicalDateTime, CanonicalDateTimeTz, CanonicalDuration};
14
15#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
17#[serde(transparent)]
18pub struct CanonicalString(String);
19
20impl CanonicalString {
21 pub fn new(value: impl Into<String>) -> Result<Self, Diagnostic> {
23 let value = value.into();
24 if value.len() > MAX_CANONICAL_STRING_BYTES {
25 return Err(Diagnostic::stable(
26 DiagnosticCategory::ResourceLimit,
27 "canonical_string_limit_exceeded",
28 "canonical string exceeds the UTF-8 byte ceiling",
29 )
30 .with_detail(
31 "actual_bytes",
32 i64::try_from(value.len()).unwrap_or(i64::MAX),
33 )
34 .with_detail(
35 "maximum_bytes",
36 i64::try_from(MAX_CANONICAL_STRING_BYTES).expect("canonical string limit fits i64"),
37 ));
38 }
39 Ok(Self(value))
40 }
41
42 pub fn as_str(&self) -> &str {
44 &self.0
45 }
46}
47
48impl fmt::Display for CanonicalString {
49 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
50 formatter.write_str(self.as_str())
51 }
52}
53
54impl<'de> Deserialize<'de> for CanonicalString {
55 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
56 where
57 D: Deserializer<'de>,
58 {
59 Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
65pub struct CanonicalDouble(u64);
66
67impl CanonicalDouble {
68 pub fn new(value: f64) -> Result<Self, Diagnostic> {
70 if value.is_finite() {
71 Ok(Self(value.to_bits()))
72 } else {
73 Err(invalid_scalar("double"))
74 }
75 }
76 pub fn from_bits(bits: u64) -> Result<Self, Diagnostic> {
78 Self::new(f64::from_bits(bits))
79 }
80 pub const fn bits(self) -> u64 {
82 self.0
83 }
84 pub fn get(self) -> f64 {
86 f64::from_bits(self.0)
87 }
88 pub fn bits_hex(self) -> String {
90 format!("{:016x}", self.0)
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
96pub struct DecimalValue(String);
97
98impl DecimalValue {
99 pub fn new(value: impl AsRef<str>) -> Result<Self, Diagnostic> {
101 parse_decimal(value.as_ref())
102 .map(|value| Self(value.canonical_string()))
103 .ok_or_else(|| invalid_scalar("decimal"))
104 }
105 pub fn as_str(&self) -> &str {
107 &self.0
108 }
109}
110impl fmt::Display for DecimalValue {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 f.write_str(self.as_str())
113 }
114}
115impl Serialize for DecimalValue {
116 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
117 where
118 S: Serializer,
119 {
120 serializer.serialize_str(self.as_str())
121 }
122}
123impl<'de> Deserialize<'de> for DecimalValue {
124 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
125 where
126 D: Deserializer<'de>,
127 {
128 Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
129 }
130}
131
132fn invalid_scalar(value_type: &'static str) -> Diagnostic {
133 Diagnostic::stable(
134 DiagnosticCategory::InvalidContract,
135 "invalid_canonical_scalar",
136 "scalar value is outside its canonical domain",
137 )
138 .with_detail("value_type", value_type)
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
143#[serde(rename_all = "snake_case")]
144pub enum ValueTypeTag {
145 String,
147 Long,
149 Double,
151 Boolean,
153 Date,
155 #[serde(rename = "datetime")]
157 DateTime,
158 #[serde(rename = "datetime_tz")]
160 DateTimeTz,
161 Decimal,
163 Duration,
165}
166
167impl ValueTypeTag {
168 pub const fn as_str(self) -> &'static str {
170 match self {
171 Self::String => "string",
172 Self::Long => "long",
173 Self::Double => "double",
174 Self::Boolean => "boolean",
175 Self::Date => "date",
176 Self::DateTime => "datetime",
177 Self::DateTimeTz => "datetime_tz",
178 Self::Decimal => "decimal",
179 Self::Duration => "duration",
180 }
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
186pub enum CanonicalValue {
187 String(CanonicalString),
189 Long(i64),
191 Double(CanonicalDouble),
193 Boolean(bool),
195 Date(CanonicalDate),
197 DateTime(CanonicalDateTime),
199 DateTimeTz(CanonicalDateTimeTz),
201 Decimal(DecimalValue),
203 Duration(CanonicalDuration),
205}
206
207impl CanonicalValue {
208 pub const fn value_type(&self) -> ValueTypeTag {
210 match self {
211 Self::String(_) => ValueTypeTag::String,
212 Self::Long(_) => ValueTypeTag::Long,
213 Self::Double(_) => ValueTypeTag::Double,
214 Self::Boolean(_) => ValueTypeTag::Boolean,
215 Self::Date(_) => ValueTypeTag::Date,
216 Self::DateTime(_) => ValueTypeTag::DateTime,
217 Self::DateTimeTz(_) => ValueTypeTag::DateTimeTz,
218 Self::Decimal(_) => ValueTypeTag::Decimal,
219 Self::Duration(_) => ValueTypeTag::Duration,
220 }
221 }
222
223 pub fn semantic_cmp_same_domain(&self, other: &Self) -> Option<Ordering> {
231 match (self, other) {
232 (Self::String(left), Self::String(right)) => Some(left.cmp(right)),
233 (Self::Long(left), Self::Long(right)) => Some(left.cmp(right)),
234 (Self::Double(left), Self::Double(right)) => left.get().partial_cmp(&right.get()),
235 (Self::Boolean(left), Self::Boolean(right)) => Some(left.cmp(right)),
236 (Self::Date(left), Self::Date(right)) => Some(left.cmp(right)),
237 (Self::DateTime(left), Self::DateTime(right)) => Some(left.cmp(right)),
238 (Self::DateTimeTz(left), Self::DateTimeTz(right)) => Some(
239 left.semantic_utc_nanoseconds()
240 .cmp(&right.semantic_utc_nanoseconds()),
241 ),
242 (Self::Decimal(left), Self::Decimal(right)) => {
243 let left = parse_decimal(left.as_str()).expect("DecimalValue is always validated");
244 let right =
245 parse_decimal(right.as_str()).expect("DecimalValue is always validated");
246 Some(left.compare(&right))
247 }
248 _ => None,
249 }
250 }
251}
252
253#[derive(Serialize, Deserialize)]
254#[serde(tag = "kind", rename_all = "snake_case")]
255enum ValueWire {
256 String {
257 value: CanonicalString,
258 },
259 Long {
260 value: String,
261 },
262 Double {
263 bits: String,
264 },
265 Boolean {
266 value: bool,
267 },
268 Date {
269 value: CanonicalDate,
270 },
271 #[serde(rename = "datetime")]
272 DateTime {
273 value: CanonicalDateTime,
274 },
275 #[serde(rename = "datetime_tz")]
276 DateTimeTz {
277 value: CanonicalDateTimeTz,
278 },
279 Decimal {
280 value: DecimalValue,
281 },
282 Duration {
283 value: CanonicalDuration,
284 },
285}
286
287impl Serialize for CanonicalValue {
288 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
289 where
290 S: Serializer,
291 {
292 let wire = match self {
293 Self::String(value) => ValueWire::String {
294 value: value.clone(),
295 },
296 Self::Long(value) => ValueWire::Long {
297 value: value.to_string(),
298 },
299 Self::Double(value) => ValueWire::Double {
300 bits: value.bits_hex(),
301 },
302 Self::Boolean(value) => ValueWire::Boolean { value: *value },
303 Self::Date(value) => ValueWire::Date { value: *value },
304 Self::DateTime(value) => ValueWire::DateTime { value: *value },
305 Self::DateTimeTz(value) => ValueWire::DateTimeTz {
306 value: value.clone(),
307 },
308 Self::Decimal(value) => ValueWire::Decimal {
309 value: value.clone(),
310 },
311 Self::Duration(value) => ValueWire::Duration { value: *value },
312 };
313 wire.serialize(serializer)
314 }
315}
316
317impl<'de> Deserialize<'de> for CanonicalValue {
318 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
319 where
320 D: Deserializer<'de>,
321 {
322 match ValueWire::deserialize(deserializer)? {
323 ValueWire::String { value } => Ok(Self::String(value)),
324 ValueWire::Boolean { value } => Ok(Self::Boolean(value)),
325 ValueWire::Date { value } => Ok(Self::Date(value)),
326 ValueWire::DateTime { value } => Ok(Self::DateTime(value)),
327 ValueWire::DateTimeTz { value } => Ok(Self::DateTimeTz(value)),
328 ValueWire::Decimal { value } => Ok(Self::Decimal(value)),
329 ValueWire::Duration { value } => Ok(Self::Duration(value)),
330 ValueWire::Long { value } => {
331 let parsed = value.parse::<i64>().map_err(D::Error::custom)?;
332 if parsed.to_string() != value {
333 return Err(D::Error::custom("long value is not canonical"));
334 }
335 Ok(Self::Long(parsed))
336 }
337 ValueWire::Double { bits } => {
338 if bits.len() != 16
339 || bits
340 .bytes()
341 .any(|b| !b.is_ascii_digit() && !(b'a'..=b'f').contains(&b))
342 {
343 return Err(D::Error::custom(
344 "double bits are not canonical lowercase hex",
345 ));
346 }
347 let bits = u64::from_str_radix(&bits, 16).map_err(D::Error::custom)?;
348 CanonicalDouble::from_bits(bits)
349 .map(Self::Double)
350 .map_err(D::Error::custom)
351 }
352 }
353 }
354}
355
356#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
358pub struct Cardinality {
359 min: u64,
360 max: Option<u64>,
361}
362
363impl Cardinality {
364 pub fn new(min: u64, max: Option<u64>) -> Result<Self, Diagnostic> {
366 if max.is_some_and(|max| max < min || max == 0) {
367 Err(Diagnostic::stable(
368 DiagnosticCategory::InvalidContract,
369 "invalid_cardinality",
370 "cardinality maximum is below its minimum or exactly zero",
371 ))
372 } else {
373 Ok(Self { min, max })
374 }
375 }
376 pub const fn min(self) -> u64 {
378 self.min
379 }
380 pub const fn max(self) -> Option<u64> {
382 self.max
383 }
384}
385
386#[derive(Serialize, Deserialize)]
387struct CardinalityWire {
388 kind: CardinalityKind,
389 min: String,
390 max: String,
391}
392#[derive(Serialize, Deserialize)]
393#[serde(rename_all = "snake_case")]
394enum CardinalityKind {
395 Cardinality,
396}
397
398impl Serialize for Cardinality {
399 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
400 where
401 S: Serializer,
402 {
403 CardinalityWire {
404 kind: CardinalityKind::Cardinality,
405 min: self.min.to_string(),
406 max: self
407 .max
408 .map_or_else(|| "unbounded".to_owned(), |max| max.to_string()),
409 }
410 .serialize(serializer)
411 }
412}
413impl<'de> Deserialize<'de> for Cardinality {
414 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
415 where
416 D: Deserializer<'de>,
417 {
418 let wire = CardinalityWire::deserialize(deserializer)?;
419 let min = wire.min.parse::<u64>().map_err(D::Error::custom)?;
420 if min.to_string() != wire.min {
421 return Err(D::Error::custom("cardinality minimum is not canonical"));
422 }
423 let max = if wire.max == "unbounded" {
424 None
425 } else {
426 let max = wire.max.parse::<u64>().map_err(D::Error::custom)?;
427 if max.to_string() != wire.max {
428 return Err(D::Error::custom("cardinality maximum is not canonical"));
429 }
430 Some(max)
431 };
432 Self::new(min, max).map_err(D::Error::custom)
433 }
434}
435
436#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
438#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
439pub enum AnnotationValue {
440 Unit,
442 Scalar(CanonicalValue),
444 Ordered(Vec<CanonicalValue>),
446 Unordered(BTreeSet<CanonicalValue>),
448 Cardinality(Cardinality),
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 #[test]
457 fn long_decimal_double_and_cardinality_have_binding_safe_shapes() {
458 assert_eq!(
459 serde_json::to_string(&CanonicalValue::Long(9_007_199_254_740_993)).unwrap(),
460 r#"{"kind":"long","value":"9007199254740993"}"#
461 );
462 let decimal = CanonicalValue::Decimal(DecimalValue::new("+001.2300dec").unwrap());
463 assert_eq!(
464 serde_json::to_string(&decimal).unwrap(),
465 r#"{"kind":"decimal","value":"1.23"}"#
466 );
467 let negative_zero = CanonicalValue::Double(CanonicalDouble::new(-0.0).unwrap());
468 assert_eq!(
469 serde_json::to_string(&negative_zero).unwrap(),
470 r#"{"kind":"double","bits":"8000000000000000"}"#
471 );
472 assert!(Cardinality::new(0, Some(0)).is_err());
473 }
474
475 #[test]
476 fn double_policy_rejects_nonfinite_and_preserves_exact_finite_bits() {
477 for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
478 assert!(CanonicalDouble::new(value).is_err());
479 }
480
481 let positive_zero = CanonicalDouble::new(0.0).unwrap();
482 let negative_zero = CanonicalDouble::new(-0.0).unwrap();
483 assert_ne!(positive_zero, negative_zero);
484
485 for bits in [0, 0x8000_0000_0000_0000, 1] {
486 let value = CanonicalValue::Double(CanonicalDouble::from_bits(bits).unwrap());
487 let bytes = serde_json::to_vec(&value).unwrap();
488 assert_eq!(
489 serde_json::from_slice::<CanonicalValue>(&bytes).unwrap(),
490 value,
491 );
492 }
493
494 assert!(
495 serde_json::from_str::<CanonicalValue>(
496 r#"{"kind":"double","bits":"7ff0000000000000"}"#
497 )
498 .is_err()
499 );
500 }
501
502 #[test]
503 fn canonical_strings_enforce_the_byte_limit_without_changing_wire_shape() {
504 let boundary = CanonicalString::new("x".repeat(MAX_CANONICAL_STRING_BYTES)).unwrap();
505 assert_eq!(boundary.as_str().len(), MAX_CANONICAL_STRING_BYTES);
506 let error = CanonicalString::new("x".repeat(MAX_CANONICAL_STRING_BYTES + 1)).unwrap_err();
507 assert_eq!(error.category(), DiagnosticCategory::ResourceLimit);
508 assert_eq!(error.code().as_str(), "canonical_string_limit_exceeded");
509 assert_eq!(
510 serde_json::to_string(&CanonicalValue::String(
511 CanonicalString::new("text").unwrap()
512 ))
513 .unwrap(),
514 r#"{"kind":"string","value":"text"}"#,
515 );
516 }
517
518 #[test]
519 fn semantic_order_is_numeric_and_distinct_from_representation_order() {
520 let decimal_two = CanonicalValue::Decimal(DecimalValue::new("2").unwrap());
521 let decimal_ten = CanonicalValue::Decimal(DecimalValue::new("10").unwrap());
522 assert_eq!(
523 decimal_two.semantic_cmp_same_domain(&decimal_ten),
524 Some(Ordering::Less)
525 );
526
527 let negative_zero = CanonicalValue::Double(CanonicalDouble::new(-0.0).unwrap());
528 let positive_zero = CanonicalValue::Double(CanonicalDouble::new(0.0).unwrap());
529 assert_ne!(negative_zero, positive_zero);
530 assert_eq!(
531 negative_zero.semantic_cmp_same_domain(&positive_zero),
532 Some(Ordering::Equal)
533 );
534
535 assert_eq!(
536 CanonicalValue::Long(-10).semantic_cmp_same_domain(&CanonicalValue::Long(-2)),
537 Some(Ordering::Less),
538 );
539 assert_eq!(
540 CanonicalValue::String(CanonicalString::new("alpha").unwrap())
541 .semantic_cmp_same_domain(&CanonicalValue::String(
542 CanonicalString::new("beta").unwrap()
543 ),),
544 Some(Ordering::Less),
545 );
546 assert_eq!(
547 CanonicalValue::Long(1).semantic_cmp_same_domain(&CanonicalValue::Double(
548 CanonicalDouble::new(1.0).unwrap()
549 )),
550 None,
551 );
552 }
553}