use std::fmt;
use crate::limits::{
MAX_BYTES_VALUE_BYTES, MAX_LIST_ENTRIES, MAX_NESTING_DEPTH, MAX_RECORD_FIELDS, MAX_STRING_BYTES,
};
use crate::{CanonicalVector, Date, Decimal, EnumTypeId, EnumVariantId, FieldId, Money, Timestamp};
#[derive(Clone, Eq, PartialEq)]
pub enum CanonicalValue {
Null,
Bool(bool),
I64(i64),
U64(u64),
Decimal(Decimal),
Money(Money),
String(CanonicalString),
Bytes(CanonicalBytes),
Timestamp(Timestamp),
Date(Date),
Uuid([u8; 16]),
Enum {
type_id: EnumTypeId,
variant_id: EnumVariantId,
},
List(CanonicalList),
Record(CanonicalRecord),
Vector(CanonicalVector),
}
impl crate::InspectOwnedCapacityV1 for CanonicalValue {
fn inspect_owned_capacity(
&self,
inspection: &mut crate::OwnedCapacityInspectionV1,
) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
match self {
Self::String(value) => inspection.visit(value),
Self::Bytes(value) => inspection.visit(value),
Self::List(value) => inspection.visit(value),
Self::Record(value) => inspection.visit(value),
Self::Vector(value) => inspection.visit(value),
Self::Null
| Self::Bool(_)
| Self::I64(_)
| Self::U64(_)
| Self::Decimal(_)
| Self::Money(_)
| Self::Timestamp(_)
| Self::Date(_)
| Self::Uuid(_)
| Self::Enum {
type_id: _,
variant_id: _,
} => Ok(()),
}
}
}
impl fmt::Debug for CanonicalValue {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Null => formatter.write_str("CanonicalValue::Null"),
Self::Bool(_) => formatter.write_str("CanonicalValue::Bool([REDACTED])"),
Self::I64(_) => formatter.write_str("CanonicalValue::I64([REDACTED])"),
Self::U64(_) => formatter.write_str("CanonicalValue::U64([REDACTED])"),
Self::Decimal(_) => formatter.write_str("CanonicalValue::Decimal([REDACTED])"),
Self::Money(_) => formatter.write_str("CanonicalValue::Money([REDACTED])"),
Self::String(value) => formatter
.debug_tuple("CanonicalValue::String")
.field(value)
.finish(),
Self::Bytes(value) => formatter
.debug_tuple("CanonicalValue::Bytes")
.field(value)
.finish(),
Self::Timestamp(_) => formatter.write_str("CanonicalValue::Timestamp([REDACTED])"),
Self::Date(_) => formatter.write_str("CanonicalValue::Date([REDACTED])"),
Self::Uuid(_) => formatter.write_str("CanonicalValue::Uuid([REDACTED])"),
Self::Enum { .. } => formatter.write_str("CanonicalValue::Enum([REDACTED])"),
Self::List(values) => formatter
.debug_struct("CanonicalValue::List")
.field("length", &values.len())
.finish(),
Self::Record(record) => formatter
.debug_struct("CanonicalValue::Record")
.field("field_count", &record.len())
.finish(),
Self::Vector(vector) => formatter
.debug_struct("CanonicalValue::Vector")
.field("dimension", &vector.dimension())
.finish(),
}
}
}
impl CanonicalValue {
pub fn string(value: impl Into<String>) -> Result<Self, ValueError> {
let value = value.into();
Ok(Self::String(CanonicalString::new(value)?))
}
pub fn bytes(value: impl Into<Vec<u8>>) -> Result<Self, ValueError> {
let value = value.into();
Ok(Self::Bytes(CanonicalBytes::new(value)?))
}
pub fn list(values: Vec<Self>) -> Result<Self, ValueError> {
Ok(Self::List(CanonicalList::new(values)?))
}
pub fn record(fields: Vec<(FieldId, Self)>) -> Result<Self, ValueError> {
Ok(Self::Record(CanonicalRecord::new(fields)?))
}
fn nesting_depth(&self) -> usize {
match self {
Self::List(list) => list
.values()
.iter()
.map(Self::nesting_depth)
.max()
.map_or(0, |depth| depth + 1),
Self::Record(record) => record
.fields()
.iter()
.map(|(_, value)| value.nesting_depth())
.max()
.map_or(0, |depth| depth + 1),
_ => 0,
}
}
}
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CanonicalString(String);
crate::inspect_owned_capacity_tuple_v1!(CanonicalString, String);
impl fmt::Debug for CanonicalString {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CanonicalString")
.field("value", &"[REDACTED]")
.field("length", &self.len())
.finish()
}
}
impl CanonicalString {
pub fn new(value: impl Into<String>) -> Result<Self, ValueError> {
let value = value.into();
if value.len() > MAX_STRING_BYTES {
return Err(ValueError::StringTooLong {
actual: value.len(),
maximum: MAX_STRING_BYTES,
});
}
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CanonicalBytes(Vec<u8>);
crate::inspect_owned_capacity_tuple_v1!(CanonicalBytes, Vec<u8>);
impl fmt::Debug for CanonicalBytes {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CanonicalBytes")
.field("value", &"[REDACTED]")
.field("length", &self.len())
.finish()
}
}
impl CanonicalBytes {
pub fn new(value: impl Into<Vec<u8>>) -> Result<Self, ValueError> {
let value = value.into();
if value.len() > MAX_BYTES_VALUE_BYTES {
return Err(ValueError::BytesTooLong {
actual: value.len(),
maximum: MAX_BYTES_VALUE_BYTES,
});
}
Ok(Self(value))
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn into_vec(self) -> Vec<u8> {
self.0
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct CanonicalList(Vec<CanonicalValue>);
crate::inspect_owned_capacity_tuple_v1!(CanonicalList, Vec<CanonicalValue>);
impl fmt::Debug for CanonicalList {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CanonicalList")
.field("length", &self.len())
.finish()
}
}
impl CanonicalList {
pub fn new(values: Vec<CanonicalValue>) -> Result<Self, ValueError> {
if values.len() > MAX_LIST_ENTRIES {
return Err(ValueError::TooManyListEntries {
actual: values.len(),
maximum: MAX_LIST_ENTRIES,
});
}
validate_child_depth(values.iter())?;
Ok(Self(values))
}
pub fn values(&self) -> &[CanonicalValue] {
&self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn into_values(self) -> Vec<CanonicalValue> {
self.0
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct CanonicalRecord {
fields: Vec<(FieldId, CanonicalValue)>,
}
crate::inspect_owned_capacity_struct_v1!(CanonicalRecord {
owned: [fields],
inline: []
});
impl fmt::Debug for CanonicalRecord {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CanonicalRecord")
.field("field_count", &self.len())
.finish()
}
}
impl CanonicalRecord {
pub fn new(mut fields: Vec<(FieldId, CanonicalValue)>) -> Result<Self, ValueError> {
if fields.len() > MAX_RECORD_FIELDS {
return Err(ValueError::TooManyRecordFields {
actual: fields.len(),
maximum: MAX_RECORD_FIELDS,
});
}
fields.sort_by_key(|(field_id, _)| field_id.get());
if let Some(field_id) = fields
.windows(2)
.find_map(|pair| (pair[0].0 == pair[1].0).then_some(pair[0].0))
{
return Err(ValueError::DuplicateRecordField { field_id });
}
validate_child_depth(fields.iter().map(|(_, value)| value))?;
Ok(Self { fields })
}
pub(crate) fn from_canonical_fields(fields: Vec<(FieldId, CanonicalValue)>) -> Self {
debug_assert!(fields.windows(2).all(|pair| pair[0].0 < pair[1].0));
Self { fields }
}
pub fn fields(&self) -> &[(FieldId, CanonicalValue)] {
&self.fields
}
pub fn len(&self) -> usize {
self.fields.len()
}
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
#[must_use]
pub fn into_fields(self) -> Vec<(FieldId, CanonicalValue)> {
self.fields
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ValueError {
StringTooLong {
actual: usize,
maximum: usize,
},
BytesTooLong {
actual: usize,
maximum: usize,
},
TooManyListEntries {
actual: usize,
maximum: usize,
},
TooManyRecordFields {
actual: usize,
maximum: usize,
},
DuplicateRecordField {
field_id: FieldId,
},
NestingTooDeep {
depth: usize,
maximum: usize,
},
}
impl fmt::Display for ValueError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::StringTooLong { actual, maximum } => {
write!(formatter, "string has {actual} bytes; maximum is {maximum}")
}
Self::BytesTooLong { actual, maximum } => {
write!(
formatter,
"byte value has {actual} bytes; maximum is {maximum}"
)
}
Self::TooManyListEntries { actual, maximum } => {
write!(formatter, "list has {actual} entries; maximum is {maximum}")
}
Self::TooManyRecordFields { actual, maximum } => {
write!(
formatter,
"record has {actual} fields; maximum is {maximum}"
)
}
Self::DuplicateRecordField { field_id } => {
write!(
formatter,
"record contains duplicate field ID {}",
field_id.get()
)
}
Self::NestingTooDeep { depth, maximum } => {
write!(formatter, "nesting depth is {depth}; maximum is {maximum}")
}
}
}
}
impl std::error::Error for ValueError {}
fn validate_child_depth<'a>(
children: impl IntoIterator<Item = &'a CanonicalValue>,
) -> Result<(), ValueError> {
let depth = children
.into_iter()
.map(CanonicalValue::nesting_depth)
.max()
.map_or(0, |depth| depth + 1);
if depth > MAX_NESTING_DEPTH {
return Err(ValueError::NestingTooDeep {
depth,
maximum: MAX_NESTING_DEPTH,
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_constructor_sorts_and_rejects_duplicates() {
let record = CanonicalRecord::new(vec![
(FieldId::new(9).expect("nonzero"), CanonicalValue::Null),
(
FieldId::new(2).expect("nonzero"),
CanonicalValue::Bool(true),
),
])
.expect("valid record");
assert_eq!(
record
.fields()
.iter()
.map(|(id, _)| id.get())
.collect::<Vec<_>>(),
[2, 9]
);
assert_eq!(
CanonicalRecord::new(vec![
(FieldId::new(2).expect("nonzero"), CanonicalValue::Null),
(FieldId::new(2).expect("nonzero"), CanonicalValue::Null),
]),
Err(ValueError::DuplicateRecordField {
field_id: FieldId::new(2).expect("nonzero")
})
);
}
#[test]
fn canonical_debug_output_redacts_exact_values() {
let secret = "raw-idempotency-key-canary";
let values = [
CanonicalValue::string(secret).expect("bounded string"),
CanonicalValue::bytes(secret.as_bytes().to_vec()).expect("bounded bytes"),
CanonicalValue::I64(4_242_424_242),
];
for value in values {
let output = format!("{value:?}");
assert!(!output.contains(secret));
assert!(!output.contains("4242424242"));
}
}
}