1use std::fmt;
4
5use crate::limits::{
6 MAX_BYTES_VALUE_BYTES, MAX_LIST_ENTRIES, MAX_NESTING_DEPTH, MAX_RECORD_FIELDS, MAX_STRING_BYTES,
7};
8use crate::{CanonicalVector, Date, Decimal, EnumTypeId, EnumVariantId, FieldId, Money, Timestamp};
9
10#[derive(Clone, Eq, PartialEq)]
16pub enum CanonicalValue {
17 Null,
19 Bool(bool),
21 I64(i64),
23 U64(u64),
25 Decimal(Decimal),
27 Money(Money),
29 String(CanonicalString),
31 Bytes(CanonicalBytes),
33 Timestamp(Timestamp),
35 Date(Date),
37 Uuid([u8; 16]),
39 Enum {
41 type_id: EnumTypeId,
43 variant_id: EnumVariantId,
45 },
46 List(CanonicalList),
48 Record(CanonicalRecord),
50 Vector(CanonicalVector),
52}
53
54impl crate::InspectOwnedCapacityV1 for CanonicalValue {
55 fn inspect_owned_capacity(
56 &self,
57 inspection: &mut crate::OwnedCapacityInspectionV1,
58 ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
59 match self {
60 Self::String(value) => inspection.visit(value),
61 Self::Bytes(value) => inspection.visit(value),
62 Self::List(value) => inspection.visit(value),
63 Self::Record(value) => inspection.visit(value),
64 Self::Vector(value) => inspection.visit(value),
65 Self::Null
66 | Self::Bool(_)
67 | Self::I64(_)
68 | Self::U64(_)
69 | Self::Decimal(_)
70 | Self::Money(_)
71 | Self::Timestamp(_)
72 | Self::Date(_)
73 | Self::Uuid(_)
74 | Self::Enum {
75 type_id: _,
76 variant_id: _,
77 } => Ok(()),
78 }
79 }
80}
81
82impl fmt::Debug for CanonicalValue {
83 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
84 match self {
85 Self::Null => formatter.write_str("CanonicalValue::Null"),
86 Self::Bool(_) => formatter.write_str("CanonicalValue::Bool([REDACTED])"),
87 Self::I64(_) => formatter.write_str("CanonicalValue::I64([REDACTED])"),
88 Self::U64(_) => formatter.write_str("CanonicalValue::U64([REDACTED])"),
89 Self::Decimal(_) => formatter.write_str("CanonicalValue::Decimal([REDACTED])"),
90 Self::Money(_) => formatter.write_str("CanonicalValue::Money([REDACTED])"),
91 Self::String(value) => formatter
92 .debug_tuple("CanonicalValue::String")
93 .field(value)
94 .finish(),
95 Self::Bytes(value) => formatter
96 .debug_tuple("CanonicalValue::Bytes")
97 .field(value)
98 .finish(),
99 Self::Timestamp(_) => formatter.write_str("CanonicalValue::Timestamp([REDACTED])"),
100 Self::Date(_) => formatter.write_str("CanonicalValue::Date([REDACTED])"),
101 Self::Uuid(_) => formatter.write_str("CanonicalValue::Uuid([REDACTED])"),
102 Self::Enum { .. } => formatter.write_str("CanonicalValue::Enum([REDACTED])"),
103 Self::List(values) => formatter
104 .debug_struct("CanonicalValue::List")
105 .field("length", &values.len())
106 .finish(),
107 Self::Record(record) => formatter
108 .debug_struct("CanonicalValue::Record")
109 .field("field_count", &record.len())
110 .finish(),
111 Self::Vector(vector) => formatter
112 .debug_struct("CanonicalValue::Vector")
113 .field("dimension", &vector.dimension())
114 .finish(),
115 }
116 }
117}
118
119impl CanonicalValue {
120 pub fn string(value: impl Into<String>) -> Result<Self, ValueError> {
122 let value = value.into();
123 Ok(Self::String(CanonicalString::new(value)?))
124 }
125
126 pub fn bytes(value: impl Into<Vec<u8>>) -> Result<Self, ValueError> {
128 let value = value.into();
129 Ok(Self::Bytes(CanonicalBytes::new(value)?))
130 }
131
132 pub fn list(values: Vec<Self>) -> Result<Self, ValueError> {
134 Ok(Self::List(CanonicalList::new(values)?))
135 }
136
137 pub fn record(fields: Vec<(FieldId, Self)>) -> Result<Self, ValueError> {
139 Ok(Self::Record(CanonicalRecord::new(fields)?))
140 }
141
142 fn nesting_depth(&self) -> usize {
143 match self {
144 Self::List(list) => list
145 .values()
146 .iter()
147 .map(Self::nesting_depth)
148 .max()
149 .map_or(0, |depth| depth + 1),
150 Self::Record(record) => record
151 .fields()
152 .iter()
153 .map(|(_, value)| value.nesting_depth())
154 .max()
155 .map_or(0, |depth| depth + 1),
156 _ => 0,
157 }
158 }
159}
160
161#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
163pub struct CanonicalString(String);
164crate::inspect_owned_capacity_tuple_v1!(CanonicalString, String);
165
166impl fmt::Debug for CanonicalString {
167 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
168 formatter
169 .debug_struct("CanonicalString")
170 .field("value", &"[REDACTED]")
171 .field("length", &self.len())
172 .finish()
173 }
174}
175
176impl CanonicalString {
177 pub fn new(value: impl Into<String>) -> Result<Self, ValueError> {
179 let value = value.into();
180 if value.len() > MAX_STRING_BYTES {
181 return Err(ValueError::StringTooLong {
182 actual: value.len(),
183 maximum: MAX_STRING_BYTES,
184 });
185 }
186 Ok(Self(value))
187 }
188
189 pub fn as_str(&self) -> &str {
191 &self.0
192 }
193
194 pub fn len(&self) -> usize {
196 self.0.len()
197 }
198
199 pub fn is_empty(&self) -> bool {
201 self.0.is_empty()
202 }
203
204 #[must_use]
206 pub fn into_string(self) -> String {
207 self.0
208 }
209}
210
211#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
213pub struct CanonicalBytes(Vec<u8>);
214crate::inspect_owned_capacity_tuple_v1!(CanonicalBytes, Vec<u8>);
215
216impl fmt::Debug for CanonicalBytes {
217 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
218 formatter
219 .debug_struct("CanonicalBytes")
220 .field("value", &"[REDACTED]")
221 .field("length", &self.len())
222 .finish()
223 }
224}
225
226impl CanonicalBytes {
227 pub fn new(value: impl Into<Vec<u8>>) -> Result<Self, ValueError> {
229 let value = value.into();
230 if value.len() > MAX_BYTES_VALUE_BYTES {
231 return Err(ValueError::BytesTooLong {
232 actual: value.len(),
233 maximum: MAX_BYTES_VALUE_BYTES,
234 });
235 }
236 Ok(Self(value))
237 }
238
239 pub fn as_bytes(&self) -> &[u8] {
241 &self.0
242 }
243
244 pub fn len(&self) -> usize {
246 self.0.len()
247 }
248
249 pub fn is_empty(&self) -> bool {
251 self.0.is_empty()
252 }
253
254 #[must_use]
256 pub fn into_vec(self) -> Vec<u8> {
257 self.0
258 }
259}
260
261#[derive(Clone, Eq, PartialEq)]
263pub struct CanonicalList(Vec<CanonicalValue>);
264crate::inspect_owned_capacity_tuple_v1!(CanonicalList, Vec<CanonicalValue>);
265
266impl fmt::Debug for CanonicalList {
267 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
268 formatter
269 .debug_struct("CanonicalList")
270 .field("length", &self.len())
271 .finish()
272 }
273}
274
275impl CanonicalList {
276 pub fn new(values: Vec<CanonicalValue>) -> Result<Self, ValueError> {
278 if values.len() > MAX_LIST_ENTRIES {
279 return Err(ValueError::TooManyListEntries {
280 actual: values.len(),
281 maximum: MAX_LIST_ENTRIES,
282 });
283 }
284 validate_child_depth(values.iter())?;
285 Ok(Self(values))
286 }
287
288 pub fn values(&self) -> &[CanonicalValue] {
290 &self.0
291 }
292
293 pub fn len(&self) -> usize {
295 self.0.len()
296 }
297
298 pub fn is_empty(&self) -> bool {
300 self.0.is_empty()
301 }
302
303 #[must_use]
305 pub fn into_values(self) -> Vec<CanonicalValue> {
306 self.0
307 }
308}
309
310#[derive(Clone, Eq, PartialEq)]
312pub struct CanonicalRecord {
313 fields: Vec<(FieldId, CanonicalValue)>,
314}
315crate::inspect_owned_capacity_struct_v1!(CanonicalRecord {
316 owned: [fields],
317 inline: []
318});
319
320impl fmt::Debug for CanonicalRecord {
321 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
322 formatter
323 .debug_struct("CanonicalRecord")
324 .field("field_count", &self.len())
325 .finish()
326 }
327}
328
329impl CanonicalRecord {
330 pub fn new(mut fields: Vec<(FieldId, CanonicalValue)>) -> Result<Self, ValueError> {
332 if fields.len() > MAX_RECORD_FIELDS {
333 return Err(ValueError::TooManyRecordFields {
334 actual: fields.len(),
335 maximum: MAX_RECORD_FIELDS,
336 });
337 }
338
339 fields.sort_by_key(|(field_id, _)| field_id.get());
340 if let Some(field_id) = fields
341 .windows(2)
342 .find_map(|pair| (pair[0].0 == pair[1].0).then_some(pair[0].0))
343 {
344 return Err(ValueError::DuplicateRecordField { field_id });
345 }
346 validate_child_depth(fields.iter().map(|(_, value)| value))?;
347
348 Ok(Self { fields })
349 }
350
351 pub(crate) fn from_canonical_fields(fields: Vec<(FieldId, CanonicalValue)>) -> Self {
352 debug_assert!(fields.windows(2).all(|pair| pair[0].0 < pair[1].0));
353 Self { fields }
354 }
355
356 pub fn fields(&self) -> &[(FieldId, CanonicalValue)] {
358 &self.fields
359 }
360
361 pub fn len(&self) -> usize {
363 self.fields.len()
364 }
365
366 pub fn is_empty(&self) -> bool {
368 self.fields.is_empty()
369 }
370
371 #[must_use]
373 pub fn into_fields(self) -> Vec<(FieldId, CanonicalValue)> {
374 self.fields
375 }
376}
377
378#[derive(Clone, Debug, Eq, PartialEq)]
380pub enum ValueError {
381 StringTooLong {
383 actual: usize,
385 maximum: usize,
387 },
388 BytesTooLong {
390 actual: usize,
392 maximum: usize,
394 },
395 TooManyListEntries {
397 actual: usize,
399 maximum: usize,
401 },
402 TooManyRecordFields {
404 actual: usize,
406 maximum: usize,
408 },
409 DuplicateRecordField {
411 field_id: FieldId,
413 },
414 NestingTooDeep {
416 depth: usize,
418 maximum: usize,
420 },
421}
422
423impl fmt::Display for ValueError {
424 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
425 match self {
426 Self::StringTooLong { actual, maximum } => {
427 write!(formatter, "string has {actual} bytes; maximum is {maximum}")
428 }
429 Self::BytesTooLong { actual, maximum } => {
430 write!(
431 formatter,
432 "byte value has {actual} bytes; maximum is {maximum}"
433 )
434 }
435 Self::TooManyListEntries { actual, maximum } => {
436 write!(formatter, "list has {actual} entries; maximum is {maximum}")
437 }
438 Self::TooManyRecordFields { actual, maximum } => {
439 write!(
440 formatter,
441 "record has {actual} fields; maximum is {maximum}"
442 )
443 }
444 Self::DuplicateRecordField { field_id } => {
445 write!(
446 formatter,
447 "record contains duplicate field ID {}",
448 field_id.get()
449 )
450 }
451 Self::NestingTooDeep { depth, maximum } => {
452 write!(formatter, "nesting depth is {depth}; maximum is {maximum}")
453 }
454 }
455 }
456}
457
458impl std::error::Error for ValueError {}
459
460fn validate_child_depth<'a>(
461 children: impl IntoIterator<Item = &'a CanonicalValue>,
462) -> Result<(), ValueError> {
463 let depth = children
464 .into_iter()
465 .map(CanonicalValue::nesting_depth)
466 .max()
467 .map_or(0, |depth| depth + 1);
468 if depth > MAX_NESTING_DEPTH {
469 return Err(ValueError::NestingTooDeep {
470 depth,
471 maximum: MAX_NESTING_DEPTH,
472 });
473 }
474 Ok(())
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 #[test]
482 fn record_constructor_sorts_and_rejects_duplicates() {
483 let record = CanonicalRecord::new(vec![
484 (FieldId::new(9).expect("nonzero"), CanonicalValue::Null),
485 (
486 FieldId::new(2).expect("nonzero"),
487 CanonicalValue::Bool(true),
488 ),
489 ])
490 .expect("valid record");
491 assert_eq!(
492 record
493 .fields()
494 .iter()
495 .map(|(id, _)| id.get())
496 .collect::<Vec<_>>(),
497 [2, 9]
498 );
499
500 assert_eq!(
501 CanonicalRecord::new(vec![
502 (FieldId::new(2).expect("nonzero"), CanonicalValue::Null),
503 (FieldId::new(2).expect("nonzero"), CanonicalValue::Null),
504 ]),
505 Err(ValueError::DuplicateRecordField {
506 field_id: FieldId::new(2).expect("nonzero")
507 })
508 );
509 }
510
511 #[test]
512 fn canonical_debug_output_redacts_exact_values() {
513 let secret = "raw-idempotency-key-canary";
514 let values = [
515 CanonicalValue::string(secret).expect("bounded string"),
516 CanonicalValue::bytes(secret.as_bytes().to_vec()).expect("bounded bytes"),
517 CanonicalValue::I64(4_242_424_242),
518 ];
519
520 for value in values {
521 let output = format!("{value:?}");
522 assert!(!output.contains(secret));
523 assert!(!output.contains("4242424242"));
524 }
525 }
526}