1use std::collections::{HashMap, HashSet};
19
20use facet_value::{VArray, VBytes, VObject, VString, Value};
21use phon_schema::bytes::{
22 Reader, write_bool, write_f32, write_f64, write_i8, write_i16, write_i32, write_i64,
23 write_i128, write_u8, write_u16, write_u32, write_u64, write_u128,
24};
25use phon_schema::{
26 DecodeError, EncodeError, Field, Primitive, Schema, SchemaId, SchemaKind, SchemaRef, Variant,
27 VariantPayload, extended_from_string, extended_to_string, primitive_id, read_value,
28 resolve_ids, write_value,
29};
30
31const MAX_DEPTH: usize = 128;
33
34#[derive(Clone, Debug, PartialEq, Eq)]
40#[non_exhaustive]
41pub enum CompactError {
42 UnknownSchema(SchemaId),
45 BundleSchemaIdMismatch {
47 stated: SchemaId,
48 recomputed: SchemaId,
49 },
50 Unsupported(&'static str),
52 TypeMismatch { expected: &'static str },
54 UnknownVariant(String),
56 BadVariantIndex(u32),
58 GenericArity { params: usize, args: usize },
60 Malformed(&'static str),
63 Incompatible(String),
65 WriterOnlyVariant(u32),
68 Decode(DecodeError),
70 Encode(EncodeError),
72}
73
74impl From<DecodeError> for CompactError {
75 fn from(e: DecodeError) -> Self {
76 CompactError::Decode(e)
77 }
78}
79
80impl From<EncodeError> for CompactError {
81 fn from(e: EncodeError) -> Self {
82 CompactError::Encode(e)
83 }
84}
85
86impl core::fmt::Display for CompactError {
87 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
88 match self {
89 CompactError::UnknownSchema(id) => write!(f, "unknown schema {id}"),
90 CompactError::BundleSchemaIdMismatch { stated, recomputed } => {
91 write!(
92 f,
93 "schema bundle id mismatch: stated {stated}, recomputed {recomputed}"
94 )
95 }
96 CompactError::Unsupported(what) => {
97 write!(f, "compact codec does not support {what} yet")
98 }
99 CompactError::TypeMismatch { expected } => {
100 write!(f, "value does not match schema (expected {expected})")
101 }
102 CompactError::UnknownVariant(name) => write!(f, "unknown enum variant {name:?}"),
103 CompactError::BadVariantIndex(i) => write!(f, "enum variant index {i} out of range"),
104 CompactError::GenericArity { params, args } => {
105 write!(f, "generic expects {params} type arguments, got {args}")
106 }
107 CompactError::Malformed(what) => write!(f, "malformed schema: {what}"),
108 CompactError::Incompatible(why) => write!(f, "incompatible schemas: {why}"),
109 CompactError::WriterOnlyVariant(i) => {
110 write!(
111 f,
112 "received enum variant {i} the reader schema does not have"
113 )
114 }
115 CompactError::Decode(e) => write!(f, "decode: {e}"),
116 CompactError::Encode(e) => write!(f, "encode: {e}"),
117 }
118 }
119}
120
121impl std::error::Error for CompactError {}
122
123type Result<T> = core::result::Result<T, CompactError>;
124
125pub struct Registry {
132 composites: HashMap<SchemaId, Schema>,
133 primitives: HashMap<SchemaId, Primitive>,
134}
135
136impl Registry {
137 #[must_use]
140 pub fn new(schemas: impl IntoIterator<Item = Schema>) -> Self {
141 let primitives = Primitive::ALL
142 .iter()
143 .map(|&p| (primitive_id(p), p))
144 .collect();
145 let composites = schemas.into_iter().map(|s| (s.id, s)).collect();
146 Registry {
147 composites,
148 primitives,
149 }
150 }
151
152 pub fn try_new(schemas: impl IntoIterator<Item = Schema>) -> Result<Self> {
159 let schemas: Vec<_> = schemas.into_iter().collect();
160 validate_bundle(&schemas)?;
161 Ok(Self::new(schemas))
162 }
163
164 fn primitive(&self, id: SchemaId) -> Option<Primitive> {
165 self.primitives.get(&id).copied()
166 }
167
168 fn composite(&self, id: SchemaId) -> Option<&Schema> {
169 self.composites.get(&id)
170 }
171}
172
173fn validate_bundle(schemas: &[Schema]) -> Result<()> {
174 let recomputed = resolve_ids(schemas.to_vec());
175 for (schema, recomputed) in schemas.iter().zip(&recomputed) {
176 if schema.id != recomputed.id {
177 return Err(CompactError::BundleSchemaIdMismatch {
178 stated: schema.id,
179 recomputed: recomputed.id,
180 });
181 }
182 }
183
184 let provided: HashSet<_> = schemas.iter().map(|schema| schema.id).collect();
185 let primitives: HashSet<_> = Primitive::ALL.iter().map(|&p| primitive_id(p)).collect();
186 for schema in schemas {
187 validate_kind_refs(&schema.kind, &provided, &primitives)?;
188 }
189
190 let reg = Registry::new(schemas.to_vec());
191 for schema in schemas {
192 validate_fixed_array_caps(&schema.kind, ®)?;
193 }
194
195 Ok(())
196}
197
198fn validate_kind_refs(
199 kind: &SchemaKind,
200 provided: &HashSet<SchemaId>,
201 primitives: &HashSet<SchemaId>,
202) -> Result<()> {
203 match kind {
204 SchemaKind::Primitive(_) | SchemaKind::Dynamic => Ok(()),
205 SchemaKind::Struct { fields, .. } => fields
206 .iter()
207 .try_for_each(|field| validate_ref(&field.schema, provided, primitives)),
208 SchemaKind::Enum { variants, .. } => variants
209 .iter()
210 .try_for_each(|variant| validate_payload_refs(&variant.payload, provided, primitives)),
211 SchemaKind::Tuple { elements } => elements
212 .iter()
213 .try_for_each(|element| validate_ref(element, provided, primitives)),
214 SchemaKind::List { element }
215 | SchemaKind::Set { element }
216 | SchemaKind::Array { element, .. }
217 | SchemaKind::Tensor { element, .. }
218 | SchemaKind::Option { element }
219 | SchemaKind::Channel { element, .. } => validate_ref(element, provided, primitives),
220 SchemaKind::Map { key, value } => {
221 validate_ref(key, provided, primitives)?;
222 validate_ref(value, provided, primitives)
223 }
224 SchemaKind::External { metadata, .. } => {
225 if let Some(metadata) = metadata {
226 validate_ref(metadata, provided, primitives)?;
227 }
228 Ok(())
229 }
230 }
231}
232
233fn validate_payload_refs(
234 payload: &VariantPayload,
235 provided: &HashSet<SchemaId>,
236 primitives: &HashSet<SchemaId>,
237) -> Result<()> {
238 match payload {
239 VariantPayload::Unit => Ok(()),
240 VariantPayload::Newtype(r) => validate_ref(r, provided, primitives),
241 VariantPayload::Tuple(elements) => elements
242 .iter()
243 .try_for_each(|element| validate_ref(element, provided, primitives)),
244 VariantPayload::Struct(fields) => fields
245 .iter()
246 .try_for_each(|field| validate_ref(&field.schema, provided, primitives)),
247 }
248}
249
250fn validate_ref(
251 r: &SchemaRef,
252 provided: &HashSet<SchemaId>,
253 primitives: &HashSet<SchemaId>,
254) -> Result<()> {
255 match r {
256 SchemaRef::Var { .. } => Ok(()),
257 SchemaRef::Concrete { id, args } => {
258 if !provided.contains(id) && !primitives.contains(id) {
259 return Err(CompactError::UnknownSchema(*id));
260 }
261 args.iter()
262 .try_for_each(|arg| validate_ref(arg, provided, primitives))
263 }
264 }
265}
266
267fn validate_fixed_array_caps(kind: &SchemaKind, reg: &Registry) -> Result<()> {
268 match kind {
269 SchemaKind::Primitive(_) | SchemaKind::Dynamic => Ok(()),
270 SchemaKind::Struct { fields, .. } => fields
271 .iter()
272 .try_for_each(|field| validate_fixed_array_ref(&field.schema)),
273 SchemaKind::Enum { variants, .. } => variants
274 .iter()
275 .try_for_each(|variant| validate_fixed_array_payload(&variant.payload)),
276 SchemaKind::Tuple { elements } => elements.iter().try_for_each(validate_fixed_array_ref),
277 SchemaKind::List { element }
278 | SchemaKind::Set { element }
279 | SchemaKind::Tensor { element, .. }
280 | SchemaKind::Option { element }
281 | SchemaKind::Channel { element, .. } => validate_fixed_array_ref(element),
282 SchemaKind::Map { key, value } => {
283 validate_fixed_array_ref(key)?;
284 validate_fixed_array_ref(value)
285 }
286 SchemaKind::Array {
287 element,
288 dimensions,
289 } => {
290 let count = product(dimensions)?;
291 if min_wire_size_ref(reg, element) == 0
292 && count > phon_schema::bytes::ZST_COUNT_CAP as u64
293 {
294 return Err(CompactError::Decode(DecodeError::LengthTooLarge {
295 count,
296 remaining: phon_schema::bytes::ZST_COUNT_CAP,
297 }));
298 }
299 validate_fixed_array_ref(element)
300 }
301 SchemaKind::External { metadata, .. } => {
302 if let Some(metadata) = metadata {
303 validate_fixed_array_ref(metadata)?;
304 }
305 Ok(())
306 }
307 }
308}
309
310fn validate_fixed_array_payload(payload: &VariantPayload) -> Result<()> {
311 match payload {
312 VariantPayload::Unit => Ok(()),
313 VariantPayload::Newtype(r) => validate_fixed_array_ref(r),
314 VariantPayload::Tuple(elements) => elements.iter().try_for_each(validate_fixed_array_ref),
315 VariantPayload::Struct(fields) => fields
316 .iter()
317 .try_for_each(|field| validate_fixed_array_ref(&field.schema)),
318 }
319}
320
321fn validate_fixed_array_ref(r: &SchemaRef) -> Result<()> {
322 match r {
323 SchemaRef::Var { .. } => Ok(()),
324 SchemaRef::Concrete { args, .. } => args.iter().try_for_each(validate_fixed_array_ref),
325 }
326}
327
328pub(crate) enum Resolved {
330 Primitive(Primitive),
331 Composite(SchemaKind),
332}
333
334pub(crate) fn resolve(reg: &Registry, r: &SchemaRef) -> Result<Resolved> {
338 match r {
339 SchemaRef::Var { .. } => Err(CompactError::Malformed("unbound type variable")),
340 SchemaRef::Concrete { id, args } => {
341 if let Some(p) = reg.primitive(*id) {
342 if !args.is_empty() {
343 return Err(CompactError::Malformed("primitive carrying type arguments"));
344 }
345 Ok(Resolved::Primitive(p))
346 } else if let Some(schema) = reg.composite(*id) {
347 if schema.type_params.len() != args.len() {
348 return Err(CompactError::GenericArity {
349 params: schema.type_params.len(),
350 args: args.len(),
351 });
352 }
353 let kind = if args.is_empty() {
354 schema.kind.clone()
355 } else {
356 substitute_kind(&schema.kind, &schema.type_params, args)
357 };
358 Ok(Resolved::Composite(kind))
359 } else {
360 Err(CompactError::UnknownSchema(*id))
361 }
362 }
363 }
364}
365
366pub(crate) fn alignment(p: Primitive) -> usize {
372 match p {
373 Primitive::U16 | Primitive::I16 => 2,
374 Primitive::U32 | Primitive::I32 | Primitive::F32 | Primitive::Char => 4,
375 Primitive::U64 | Primitive::I64 | Primitive::F64 => 8,
376 Primitive::U128 | Primitive::I128 => 16,
377 _ => 1,
378 }
379}
380
381pub(crate) use phon_schema::bytes::{pad_to, skip_pad};
385
386const MIN_WIRE_DEPTH: usize = 64;
405
406pub(crate) fn min_wire_size_ref(reg: &Registry, rf: &SchemaRef) -> usize {
409 usize::from(!is_zero_sized_ref(reg, rf, 0))
410}
411
412fn is_zero_sized_ref(reg: &Registry, rf: &SchemaRef, depth: usize) -> bool {
413 if depth > MIN_WIRE_DEPTH {
414 return false;
415 }
416 match resolve(reg, rf) {
417 Ok(Resolved::Primitive(p)) => is_zero_sized_primitive(p),
418 Ok(Resolved::Composite(kind)) => is_zero_sized_kind(reg, &kind, depth),
419 Err(_) => false,
420 }
421}
422
423fn is_zero_sized_primitive(p: Primitive) -> bool {
424 matches!(p, Primitive::Unit)
428}
429
430fn is_zero_sized_kind(reg: &Registry, kind: &SchemaKind, depth: usize) -> bool {
431 match kind {
432 SchemaKind::Primitive(p) => is_zero_sized_primitive(*p),
433 SchemaKind::Struct { fields, .. } => fields
435 .iter()
436 .all(|f| is_zero_sized_ref(reg, &f.schema, depth + 1)),
437 SchemaKind::Tuple { elements } => elements
438 .iter()
439 .all(|e| is_zero_sized_ref(reg, e, depth + 1)),
440 SchemaKind::Array { element, .. } => is_zero_sized_ref(reg, element, depth + 1),
442 _ => false,
446 }
447}
448
449pub fn to_bytes(value: &Value, root: SchemaId, registry: &Registry) -> Result<Vec<u8>> {
459 let mut out = Vec::new();
460 encode_ref(value, &SchemaRef::concrete(root), registry, &mut out)?;
461 Ok(out)
462}
463
464pub fn from_bytes(bytes: &[u8], root: SchemaId, registry: &Registry) -> Result<Value> {
469 let mut r = Reader::new(bytes);
470 let v = read_from(&mut r, root, registry)?;
471 if r.remaining() != 0 {
472 return Err(CompactError::Decode(DecodeError::TrailingBytes(
473 r.remaining(),
474 )));
475 }
476 Ok(v)
477}
478
479pub fn read_from(r: &mut Reader<'_>, root: SchemaId, registry: &Registry) -> Result<Value> {
485 decode_ref(r, &SchemaRef::concrete(root), registry, 0)
486}
487
488fn encode_ref(value: &Value, r: &SchemaRef, reg: &Registry, out: &mut Vec<u8>) -> Result<()> {
494 match r {
495 SchemaRef::Var { .. } => Err(CompactError::Malformed("unbound type variable")),
496 SchemaRef::Concrete { id, args } => {
497 if let Some(p) = reg.primitive(*id) {
498 if !args.is_empty() {
499 return Err(CompactError::Malformed("primitive carrying type arguments"));
500 }
501 encode_primitive(value, p, out)
502 } else if let Some(schema) = reg.composite(*id) {
503 if schema.type_params.len() != args.len() {
504 return Err(CompactError::GenericArity {
505 params: schema.type_params.len(),
506 args: args.len(),
507 });
508 }
509 if args.is_empty() {
510 encode_kind(value, &schema.kind, reg, out)
511 } else {
512 let kind = substitute_kind(&schema.kind, &schema.type_params, args);
513 encode_kind(value, &kind, reg, out)
514 }
515 } else {
516 Err(CompactError::UnknownSchema(*id))
517 }
518 }
519 }
520}
521
522fn number(value: &Value) -> Result<&facet_value::VNumber> {
523 value
524 .as_number()
525 .ok_or(CompactError::TypeMismatch { expected: "number" })
526}
527
528fn encode_primitive(value: &Value, p: Primitive, out: &mut Vec<u8>) -> Result<()> {
529 pad_to(out, alignment(p));
530 match p {
531 Primitive::Bool => write_bool(
532 out,
533 value
534 .as_bool()
535 .ok_or(CompactError::TypeMismatch { expected: "bool" })?,
536 ),
537 Primitive::U8 => write_u8(out, number(value)?.to_u64().unwrap_or(0) as u8),
538 Primitive::U16 => write_u16(out, number(value)?.to_u64().unwrap_or(0) as u16),
539 Primitive::U32 => write_u32(out, number(value)?.to_u64().unwrap_or(0) as u32),
540 Primitive::U64 => write_u64(out, number(value)?.to_u64().unwrap_or(0)),
541 Primitive::U128 => write_u128(out, number(value)?.to_u128().unwrap_or(0)),
542 Primitive::I8 => write_i8(out, number(value)?.to_i64().unwrap_or(0) as i8),
543 Primitive::I16 => write_i16(out, number(value)?.to_i64().unwrap_or(0) as i16),
544 Primitive::I32 => write_i32(out, number(value)?.to_i64().unwrap_or(0) as i32),
545 Primitive::I64 => write_i64(out, number(value)?.to_i64().unwrap_or(0)),
546 Primitive::I128 => write_i128(out, number(value)?.to_i128().unwrap_or(0)),
547 Primitive::F32 => write_f32(out, number(value)?.to_f64_lossy() as f32),
548 Primitive::F64 => write_f64(out, number(value)?.to_f64_lossy()),
549 Primitive::Char => write_u32(
550 out,
551 value
552 .as_char()
553 .ok_or(CompactError::TypeMismatch { expected: "char" })? as u32,
554 ),
555 Primitive::String => {
556 let s = value
557 .as_string()
558 .ok_or(CompactError::TypeMismatch { expected: "string" })?;
559 write_u32(out, s.as_str().len() as u32);
560 out.extend_from_slice(s.as_str().as_bytes());
561 }
562 Primitive::Bytes => {
563 let b = value
564 .as_bytes()
565 .ok_or(CompactError::TypeMismatch { expected: "bytes" })?;
566 write_u32(out, b.as_slice().len() as u32);
567 out.extend_from_slice(b.as_slice());
568 }
569 Primitive::Unit => {
570 if !value.is_null() {
571 return Err(CompactError::TypeMismatch { expected: "unit" });
572 }
573 }
574 Primitive::Never => return Err(CompactError::TypeMismatch { expected: "never" }),
575 Primitive::DateTime | Primitive::Uuid | Primitive::QName => {
576 let s = extended_to_string(value, p).map_err(CompactError::Encode)?;
577 write_u32(out, s.len() as u32);
578 out.extend_from_slice(s.as_bytes());
579 }
580 }
581 Ok(())
582}
583
584fn encode_kind(value: &Value, kind: &SchemaKind, reg: &Registry, out: &mut Vec<u8>) -> Result<()> {
586 match kind {
587 SchemaKind::Primitive(p) => encode_primitive(value, *p, out),
588 SchemaKind::Struct { fields, .. } => {
589 let obj = value
590 .as_object()
591 .ok_or(CompactError::TypeMismatch { expected: "object" })?;
592 for field in fields {
593 let fv = obj
594 .get(&VString::new(&field.name))
595 .ok_or(CompactError::TypeMismatch {
596 expected: "struct field",
597 })?;
598 encode_ref(fv, &field.schema, reg, out)?;
599 }
600 Ok(())
601 }
602 SchemaKind::Tuple { elements } => {
603 let arr = value
604 .as_array()
605 .ok_or(CompactError::TypeMismatch { expected: "tuple" })?;
606 if arr.len() != elements.len() {
607 return Err(CompactError::TypeMismatch {
608 expected: "tuple arity",
609 });
610 }
611 for (i, e) in elements.iter().enumerate() {
612 encode_ref(arr.get(i).unwrap(), e, reg, out)?;
613 }
614 Ok(())
615 }
616 SchemaKind::List { element } | SchemaKind::Set { element } => {
617 let arr = value
618 .as_array()
619 .ok_or(CompactError::TypeMismatch { expected: "list" })?;
620 write_u32(out, arr.len() as u32);
621 for i in 0..arr.len() {
622 encode_ref(arr.get(i).unwrap(), element, reg, out)?;
623 }
624 Ok(())
625 }
626 SchemaKind::Array {
627 element,
628 dimensions,
629 } => {
630 let count = product(dimensions)?;
631 let arr = value
632 .as_array()
633 .ok_or(CompactError::TypeMismatch { expected: "array" })?;
634 if arr.len() as u64 != count {
635 return Err(CompactError::TypeMismatch {
636 expected: "array shape",
637 });
638 }
639 for i in 0..arr.len() {
640 encode_ref(arr.get(i).unwrap(), element, reg, out)?;
641 }
642 Ok(())
643 }
644 SchemaKind::Map { key, value: val } => {
645 let obj = value
646 .as_object()
647 .ok_or(CompactError::TypeMismatch { expected: "map" })?;
648 write_u32(out, obj.len() as u32);
649 for (k, v) in obj.iter() {
650 encode_ref(&Value::from(VString::new(k.as_str())), key, reg, out)?;
651 encode_ref(v, val, reg, out)?;
652 }
653 Ok(())
654 }
655 SchemaKind::Option { element } => {
656 if value.is_null() {
657 write_u8(out, 0);
658 } else {
659 write_u8(out, 1);
660 encode_ref(value, element, reg, out)?;
661 }
662 Ok(())
663 }
664 SchemaKind::Enum { variants, .. } => {
665 let obj = value.as_object().ok_or(CompactError::TypeMismatch {
666 expected: "enum object",
667 })?;
668 if obj.len() != 1 {
669 return Err(CompactError::TypeMismatch {
670 expected: "single-variant enum object",
671 });
672 }
673 let (name, payload) = obj.iter().next().unwrap();
674 let variant = variants
675 .iter()
676 .find(|v| v.name == name.as_str())
677 .ok_or_else(|| CompactError::UnknownVariant(name.as_str().to_string()))?;
678 write_u32(out, variant.index);
679 encode_payload(payload, &variant.payload, reg, out)
680 }
681 SchemaKind::Dynamic => {
682 write_value(out, value)?;
683 Ok(())
684 }
685 SchemaKind::Tensor { .. } => Err(CompactError::Unsupported("tensor")),
686 SchemaKind::Channel { .. } => Err(CompactError::Unsupported("channel")),
687 SchemaKind::External { .. } => Err(CompactError::Unsupported("external")),
688 }
689}
690
691fn encode_payload(
692 value: &Value,
693 payload: &VariantPayload,
694 reg: &Registry,
695 out: &mut Vec<u8>,
696) -> Result<()> {
697 match payload {
698 VariantPayload::Unit => Ok(()),
699 VariantPayload::Newtype(r) => encode_ref(value, r, reg, out),
700 VariantPayload::Tuple(refs) => {
701 let arr = value.as_array().ok_or(CompactError::TypeMismatch {
702 expected: "tuple variant",
703 })?;
704 if arr.len() != refs.len() {
705 return Err(CompactError::TypeMismatch {
706 expected: "tuple variant arity",
707 });
708 }
709 for (i, r) in refs.iter().enumerate() {
710 encode_ref(arr.get(i).unwrap(), r, reg, out)?;
711 }
712 Ok(())
713 }
714 VariantPayload::Struct(fields) => {
715 let obj = value.as_object().ok_or(CompactError::TypeMismatch {
716 expected: "struct variant",
717 })?;
718 for field in fields {
719 let fv = obj
720 .get(&VString::new(&field.name))
721 .ok_or(CompactError::TypeMismatch {
722 expected: "struct variant field",
723 })?;
724 encode_ref(fv, &field.schema, reg, out)?;
725 }
726 Ok(())
727 }
728 }
729}
730
731pub(crate) fn product(dimensions: &[u64]) -> Result<u64> {
732 let mut p: u64 = 1;
733 for &d in dimensions {
734 p = p
735 .checked_mul(d)
736 .ok_or(CompactError::Decode(DecodeError::Malformed(
737 "array dimensions overflow",
738 )))?;
739 }
740 Ok(p)
741}
742
743pub(crate) fn check_fixed_count(count: u64, min_wire: usize, remaining: usize) -> Result<()> {
750 let max = remaining
753 .checked_div(min_wire)
754 .unwrap_or(phon_schema::bytes::ZST_COUNT_CAP) as u64;
755 if count > max {
756 return Err(CompactError::Decode(DecodeError::LengthTooLarge {
757 count,
758 remaining,
759 }));
760 }
761 Ok(())
762}
763
764fn substitute_ref(r: &SchemaRef, params: &[String], args: &[SchemaRef]) -> SchemaRef {
774 match r {
775 SchemaRef::Var { name } => params
776 .iter()
777 .position(|p| p == name)
778 .map(|i| args[i].clone())
779 .unwrap_or_else(|| r.clone()),
780 SchemaRef::Concrete { id, args: inner } => SchemaRef::Concrete {
781 id: *id,
782 args: inner
783 .iter()
784 .map(|a| substitute_ref(a, params, args))
785 .collect(),
786 },
787 }
788}
789
790fn substitute_field(f: &Field, params: &[String], args: &[SchemaRef]) -> Field {
791 Field {
792 name: f.name.clone(),
793 schema: substitute_ref(&f.schema, params, args),
794 required: f.required,
795 }
796}
797
798fn substitute_kind(kind: &SchemaKind, params: &[String], args: &[SchemaRef]) -> SchemaKind {
799 match kind {
800 SchemaKind::Primitive(p) => SchemaKind::Primitive(*p),
801 SchemaKind::Dynamic => SchemaKind::Dynamic,
802 SchemaKind::Struct { name, fields } => SchemaKind::Struct {
803 name: name.clone(),
804 fields: fields
805 .iter()
806 .map(|f| substitute_field(f, params, args))
807 .collect(),
808 },
809 SchemaKind::Enum { name, variants } => SchemaKind::Enum {
810 name: name.clone(),
811 variants: variants
812 .iter()
813 .map(|v| Variant {
814 name: v.name.clone(),
815 index: v.index,
816 payload: match &v.payload {
817 VariantPayload::Unit => VariantPayload::Unit,
818 VariantPayload::Newtype(r) => {
819 VariantPayload::Newtype(substitute_ref(r, params, args))
820 }
821 VariantPayload::Tuple(rs) => VariantPayload::Tuple(
822 rs.iter().map(|r| substitute_ref(r, params, args)).collect(),
823 ),
824 VariantPayload::Struct(fs) => VariantPayload::Struct(
825 fs.iter()
826 .map(|f| substitute_field(f, params, args))
827 .collect(),
828 ),
829 },
830 })
831 .collect(),
832 },
833 SchemaKind::Tuple { elements } => SchemaKind::Tuple {
834 elements: elements
835 .iter()
836 .map(|r| substitute_ref(r, params, args))
837 .collect(),
838 },
839 SchemaKind::List { element } => SchemaKind::List {
840 element: substitute_ref(element, params, args),
841 },
842 SchemaKind::Set { element } => SchemaKind::Set {
843 element: substitute_ref(element, params, args),
844 },
845 SchemaKind::Option { element } => SchemaKind::Option {
846 element: substitute_ref(element, params, args),
847 },
848 SchemaKind::Map { key, value } => SchemaKind::Map {
849 key: substitute_ref(key, params, args),
850 value: substitute_ref(value, params, args),
851 },
852 SchemaKind::Array {
853 element,
854 dimensions,
855 } => SchemaKind::Array {
856 element: substitute_ref(element, params, args),
857 dimensions: dimensions.clone(),
858 },
859 SchemaKind::Tensor { element, rank } => SchemaKind::Tensor {
860 element: substitute_ref(element, params, args),
861 rank: *rank,
862 },
863 SchemaKind::Channel { direction, element } => SchemaKind::Channel {
864 direction: *direction,
865 element: substitute_ref(element, params, args),
866 },
867 SchemaKind::External { kind, metadata } => SchemaKind::External {
868 kind: kind.clone(),
869 metadata: metadata.as_ref().map(|r| substitute_ref(r, params, args)),
870 },
871 }
872}
873
874pub(crate) fn decode_ref(
879 r: &mut Reader,
880 rf: &SchemaRef,
881 reg: &Registry,
882 depth: usize,
883) -> Result<Value> {
884 if depth > MAX_DEPTH {
885 return Err(CompactError::Decode(DecodeError::DepthExceeded));
886 }
887 match rf {
888 SchemaRef::Var { .. } => Err(CompactError::Malformed("unbound type variable")),
889 SchemaRef::Concrete { id, args } => {
890 if let Some(p) = reg.primitive(*id) {
891 if !args.is_empty() {
892 return Err(CompactError::Malformed("primitive carrying type arguments"));
893 }
894 decode_primitive(r, p)
895 } else if let Some(schema) = reg.composite(*id) {
896 if schema.type_params.len() != args.len() {
897 return Err(CompactError::GenericArity {
898 params: schema.type_params.len(),
899 args: args.len(),
900 });
901 }
902 if args.is_empty() {
903 decode_kind(r, &schema.kind, reg, depth + 1)
904 } else {
905 let kind = substitute_kind(&schema.kind, &schema.type_params, args);
906 decode_kind(r, &kind, reg, depth + 1)
907 }
908 } else {
909 Err(CompactError::UnknownSchema(*id))
910 }
911 }
912 }
913}
914
915pub(crate) fn decode_primitive(r: &mut Reader, p: Primitive) -> Result<Value> {
916 skip_pad(r, alignment(p))?;
917 Ok(match p {
918 Primitive::Bool => Value::from(r.read_bool()?),
919 Primitive::U8 => Value::from(r.read_u8()?),
920 Primitive::U16 => Value::from(r.read_u16()?),
921 Primitive::U32 => Value::from(r.read_u32()?),
922 Primitive::U64 => Value::from(r.read_u64()?),
923 Primitive::U128 => Value::from(r.read_u128()?),
924 Primitive::I8 => Value::from(r.read_i8()?),
925 Primitive::I16 => Value::from(r.read_i16()?),
926 Primitive::I32 => Value::from(r.read_i32()?),
927 Primitive::I64 => Value::from(r.read_i64()?),
928 Primitive::I128 => Value::from(r.read_i128()?),
929 Primitive::F32 => Value::from(r.read_f32()?),
930 Primitive::F64 => Value::from(r.read_f64()?),
931 Primitive::Char => Value::from(r.read_char()?),
932 Primitive::String => VString::new(r.read_str()?).into(),
933 Primitive::Bytes => VBytes::new(r.read_bytes()?).into(),
934 Primitive::Unit => Value::NULL,
935 Primitive::Never => {
936 return Err(CompactError::Decode(DecodeError::Malformed(
937 "never is uninhabited",
938 )));
939 }
940 Primitive::DateTime | Primitive::Uuid | Primitive::QName => {
941 extended_from_string(r.read_str()?, p).map_err(CompactError::Decode)?
942 }
943 })
944}
945
946fn decode_kind(r: &mut Reader, kind: &SchemaKind, reg: &Registry, depth: usize) -> Result<Value> {
947 match kind {
948 SchemaKind::Primitive(p) => decode_primitive(r, *p),
949 SchemaKind::Struct { fields, .. } => {
950 let mut obj = VObject::new();
951 for field in fields {
952 let fv = decode_ref(r, &field.schema, reg, depth)?;
953 obj.insert(VString::new(&field.name), fv);
954 }
955 Ok(obj.into())
956 }
957 SchemaKind::Tuple { elements } => {
958 let mut arr = VArray::new();
959 for e in elements {
960 arr.push(decode_ref(r, e, reg, depth)?);
961 }
962 Ok(arr.into())
963 }
964 SchemaKind::List { element } => {
965 let n = r.read_len(min_wire_size_ref(reg, element))?;
966 let mut arr = VArray::new();
967 for _ in 0..n {
968 arr.push(decode_ref(r, element, reg, depth)?);
969 }
970 Ok(arr.into())
971 }
972 SchemaKind::Set { element } => {
973 let n = r.read_len(min_wire_size_ref(reg, element))?;
974 let mut arr = VArray::new();
975 let mut seen = std::collections::HashSet::new();
976 for _ in 0..n {
977 let v = decode_ref(r, element, reg, depth)?;
978 if !seen.insert(v.clone()) {
979 return Err(CompactError::Decode(DecodeError::DuplicateElement));
980 }
981 arr.push(v);
982 }
983 Ok(arr.into())
984 }
985 SchemaKind::Array {
986 element,
987 dimensions,
988 } => {
989 let count = product(dimensions)?;
990 check_fixed_count(count, min_wire_size_ref(reg, element), r.remaining())?;
991 let mut arr = VArray::new();
992 for _ in 0..count {
993 arr.push(decode_ref(r, element, reg, depth)?);
994 }
995 Ok(arr.into())
996 }
997 SchemaKind::Map { key, value } => {
998 let n = r.read_len(1)?;
999 let mut obj = VObject::new();
1000 for _ in 0..n {
1001 let k = decode_ref(r, key, reg, depth)?;
1002 let v = decode_ref(r, value, reg, depth)?;
1003 let ks = k
1004 .as_string()
1005 .ok_or(CompactError::Unsupported("map with non-string keys"))?;
1006 if obj.insert(VString::new(ks.as_str()), v).is_some() {
1007 return Err(CompactError::Decode(DecodeError::DuplicateKey));
1008 }
1009 }
1010 Ok(obj.into())
1011 }
1012 SchemaKind::Option { element } => match r.read_u8()? {
1013 0 => Ok(Value::NULL),
1014 1 => decode_ref(r, element, reg, depth),
1015 b => Err(CompactError::Decode(DecodeError::InvalidBool(b))),
1016 },
1017 SchemaKind::Enum { variants, .. } => {
1018 let index = r.read_u32()?;
1019 let variant = variants
1020 .iter()
1021 .find(|v| v.index == index)
1022 .ok_or(CompactError::BadVariantIndex(index))?;
1023 let payload = decode_payload(r, &variant.payload, reg, depth)?;
1024 let mut obj = VObject::new();
1025 obj.insert(VString::new(&variant.name), payload);
1026 Ok(obj.into())
1027 }
1028 SchemaKind::Dynamic => Ok(read_value(r)?),
1029 SchemaKind::Tensor { .. } => Err(CompactError::Unsupported("tensor")),
1030 SchemaKind::Channel { .. } => Err(CompactError::Unsupported("channel")),
1031 SchemaKind::External { .. } => Err(CompactError::Unsupported("external")),
1032 }
1033}
1034
1035fn decode_payload(
1036 r: &mut Reader,
1037 payload: &VariantPayload,
1038 reg: &Registry,
1039 depth: usize,
1040) -> Result<Value> {
1041 match payload {
1042 VariantPayload::Unit => Ok(Value::NULL),
1043 VariantPayload::Newtype(rf) => decode_ref(r, rf, reg, depth),
1044 VariantPayload::Tuple(refs) => {
1045 let mut arr = VArray::new();
1046 for rf in refs {
1047 arr.push(decode_ref(r, rf, reg, depth)?);
1048 }
1049 Ok(arr.into())
1050 }
1051 VariantPayload::Struct(fields) => {
1052 let mut obj = VObject::new();
1053 for field in fields {
1054 let fv = decode_ref(r, &field.schema, reg, depth)?;
1055 obj.insert(VString::new(&field.name), fv);
1056 }
1057 Ok(obj.into())
1058 }
1059 }
1060}
1061
1062#[cfg(test)]
1063mod tests {
1064 use super::*;
1065 use phon_schema::{Field, Schema, SchemaKind, SchemaRef, Variant};
1066
1067 fn prim(p: Primitive) -> SchemaRef {
1068 SchemaRef::concrete(primitive_id(p))
1069 }
1070
1071 fn schema(id: u64, kind: SchemaKind) -> Schema {
1072 Schema {
1073 id: SchemaId(id),
1074 type_params: Vec::new(),
1075 kind,
1076 }
1077 }
1078
1079 fn rt(value: Value, root: SchemaId, reg: &Registry) {
1081 let bytes = to_bytes(&value, root, reg).expect("encode");
1082 let back = from_bytes(&bytes, root, reg).expect("decode");
1083 assert_eq!(value, back);
1084 assert_eq!(to_bytes(&back, root, reg).unwrap(), bytes);
1085 }
1086
1087 #[test]
1088 fn registry_try_new_validates_received_schema_bundles() {
1090 let point = schema(
1091 1,
1092 SchemaKind::Struct {
1093 name: "Point".to_string(),
1094 fields: vec![Field {
1095 name: "x".to_string(),
1096 schema: prim(Primitive::U32),
1097 required: true,
1098 }],
1099 },
1100 );
1101 let resolved = resolve_ids(vec![point]);
1102 Registry::try_new(resolved).expect("resolved bundle should validate");
1103 }
1104
1105 #[test]
1106 fn registry_try_new_rejects_stale_schema_ids() {
1108 let unit = schema(
1109 1,
1110 SchemaKind::Struct {
1111 name: "UnitLike".to_string(),
1112 fields: Vec::new(),
1113 },
1114 );
1115 let mut resolved = resolve_ids(vec![unit]);
1116 resolved[0].id = SchemaId(resolved[0].id.0 ^ 1);
1117
1118 assert!(matches!(
1119 Registry::try_new(resolved),
1120 Err(CompactError::BundleSchemaIdMismatch { stated, recomputed })
1121 if stated != recomputed
1122 ));
1123 }
1124
1125 #[test]
1126 fn registry_try_new_rejects_incomplete_schema_closures() {
1128 let holder = schema(
1129 1,
1130 SchemaKind::Struct {
1131 name: "Holder".to_string(),
1132 fields: vec![Field {
1133 name: "missing".to_string(),
1134 schema: SchemaRef::concrete(SchemaId(0xFEED_FACE_CAFE_BEEF)),
1135 required: true,
1136 }],
1137 },
1138 );
1139 let resolved = resolve_ids(vec![holder]);
1140
1141 assert!(matches!(
1142 Registry::try_new(resolved),
1143 Err(CompactError::UnknownSchema(SchemaId(0xFEED_FACE_CAFE_BEEF)))
1144 ));
1145 }
1146
1147 #[test]
1148 fn registry_try_new_rejects_unbounded_zero_wire_fixed_arrays() {
1150 let array = schema(
1151 1,
1152 SchemaKind::Array {
1153 element: prim(Primitive::Unit),
1154 dimensions: vec![phon_schema::bytes::ZST_COUNT_CAP as u64 + 1],
1155 },
1156 );
1157 let resolved = resolve_ids(vec![array]);
1158
1159 assert!(matches!(
1160 Registry::try_new(resolved),
1161 Err(CompactError::Decode(DecodeError::LengthTooLarge { count, .. }))
1162 if count == phon_schema::bytes::ZST_COUNT_CAP as u64 + 1
1163 ));
1164 }
1165
1166 #[test]
1167 fn compact_values_can_be_decoded_back_to_back() {
1169 let reg = Registry::new([]);
1170 let first_root = primitive_id(Primitive::U8);
1171 let second_root = primitive_id(Primitive::Bool);
1172 let mut bytes = to_bytes(&Value::from(7u8), first_root, ®).unwrap();
1173 bytes.extend(to_bytes(&Value::from(true), second_root, ®).unwrap());
1174
1175 let mut reader = Reader::new(&bytes);
1176 let first = read_from(&mut reader, first_root, ®).unwrap();
1177 assert_eq!(first, Value::from(7u8));
1178 assert_eq!(reader.position(), 1);
1179
1180 let second = read_from(&mut reader, second_root, ®).unwrap();
1181 assert_eq!(second, Value::from(true));
1182 assert_eq!(reader.position(), bytes.len());
1183 assert_eq!(reader.remaining(), 0);
1184 }
1185
1186 #[test]
1187 fn struct_with_alignment() {
1189 let point = schema(
1191 1,
1192 SchemaKind::Struct {
1193 name: "Point".to_string(),
1194 fields: vec![
1195 Field {
1196 name: "x".to_string(),
1197 schema: prim(Primitive::U32),
1198 required: true,
1199 },
1200 Field {
1201 name: "y".to_string(),
1202 schema: prim(Primitive::F64),
1203 required: true,
1204 },
1205 ],
1206 },
1207 );
1208 let reg = Registry::new([point]);
1209 let mut obj = VObject::new();
1210 obj.insert(VString::new("x"), Value::from(7u32));
1211 obj.insert(VString::new("y"), Value::from(2.5f64));
1212 let value: Value = obj.into();
1213
1214 let bytes = to_bytes(&value, SchemaId(1), ®).unwrap();
1215 assert_eq!(bytes.len(), 16);
1217 assert_eq!(&bytes[4..8], &[0, 0, 0, 0]);
1218 rt(value, SchemaId(1), ®);
1219 }
1220
1221 #[test]
1222 fn list_run_is_aligned() {
1224 let list = schema(
1225 1,
1226 SchemaKind::List {
1227 element: prim(Primitive::U64),
1228 },
1229 );
1230 let reg = Registry::new([list]);
1231 let mut arr = VArray::new();
1232 arr.push(Value::from(1u64));
1233 arr.push(Value::from(2u64));
1234 let value: Value = arr.into();
1235 let bytes = to_bytes(&value, SchemaId(1), ®).unwrap();
1236 assert_eq!(bytes.len(), 4 + 4 + 16);
1238 rt(value, SchemaId(1), ®);
1239 }
1240
1241 #[test]
1242 fn enum_tuple_option_array() {
1244 let e = schema(
1246 1,
1247 SchemaKind::Enum {
1248 name: "E".to_string(),
1249 variants: vec![
1250 Variant {
1251 name: "A".to_string(),
1252 index: 0,
1253 payload: VariantPayload::Unit,
1254 },
1255 Variant {
1256 name: "B".to_string(),
1257 index: 1,
1258 payload: VariantPayload::Newtype(prim(Primitive::U32)),
1259 },
1260 Variant {
1261 name: "C".to_string(),
1262 index: 2,
1263 payload: VariantPayload::Tuple(vec![
1264 prim(Primitive::U8),
1265 prim(Primitive::U8),
1266 ]),
1267 },
1268 ],
1269 },
1270 );
1271 let opt = schema(
1272 2,
1273 SchemaKind::Option {
1274 element: SchemaRef::concrete(SchemaId(1)),
1275 },
1276 );
1277 let arr = schema(
1278 3,
1279 SchemaKind::Array {
1280 element: prim(Primitive::U16),
1281 dimensions: vec![3],
1282 },
1283 );
1284 let reg = Registry::new([e.clone(), opt, arr]);
1285
1286 let mut a = VObject::new();
1288 a.insert(VString::new("A"), Value::NULL);
1289 rt(a.into(), SchemaId(1), ®);
1290 let mut b = VObject::new();
1292 b.insert(VString::new("B"), Value::from(42u32));
1293 rt(b.into(), SchemaId(1), ®);
1294 let mut cpay = VArray::new();
1296 cpay.push(Value::from(1u8));
1297 cpay.push(Value::from(2u8));
1298 let mut c = VObject::new();
1299 c.insert(VString::new("C"), Value::from(cpay));
1300 rt(c.into(), SchemaId(1), ®);
1301
1302 let mut some_inner = VObject::new();
1304 some_inner.insert(VString::new("A"), Value::NULL);
1305 rt(some_inner.into(), SchemaId(2), ®);
1306 rt(Value::NULL, SchemaId(2), ®);
1307
1308 let mut av = VArray::new();
1310 av.push(Value::from(10u16));
1311 av.push(Value::from(20u16));
1312 av.push(Value::from(30u16));
1313 rt(av.into(), SchemaId(3), ®);
1314 }
1315
1316 #[test]
1317 fn map_and_set_and_dynamic() {
1318 let map = schema(
1319 1,
1320 SchemaKind::Map {
1321 key: prim(Primitive::String),
1322 value: prim(Primitive::U32),
1323 },
1324 );
1325 let set = schema(
1326 2,
1327 SchemaKind::Set {
1328 element: prim(Primitive::U32),
1329 },
1330 );
1331 let dynamic = schema(3, SchemaKind::Dynamic);
1332 let reg = Registry::new([map, set, dynamic]);
1333
1334 let mut m = VObject::new();
1335 m.insert(VString::new("a"), Value::from(1u32));
1336 m.insert(VString::new("b"), Value::from(2u32));
1337 rt(m.into(), SchemaId(1), ®);
1338
1339 let mut s = VArray::new();
1340 s.push(Value::from(1u32));
1341 s.push(Value::from(2u32));
1342 rt(s.into(), SchemaId(2), ®);
1343
1344 rt(Value::from("hello dynamic"), SchemaId(3), ®);
1346 }
1347
1348 #[test]
1349 fn unknown_schema_and_type_mismatch() {
1351 let reg = Registry::new([]);
1352 assert!(matches!(
1354 to_bytes(&Value::from(1u32), SchemaId(999), ®),
1355 Err(CompactError::UnknownSchema(_))
1356 ));
1357 assert!(matches!(
1359 to_bytes(&Value::from("x"), primitive_id(Primitive::U32), ®),
1360 Err(CompactError::TypeMismatch { .. })
1361 ));
1362 }
1363
1364 #[test]
1365 fn generics_resolve() {
1367 let pair = Schema {
1370 id: SchemaId(10),
1371 type_params: vec!["A".to_string(), "B".to_string()],
1372 kind: SchemaKind::Tuple {
1373 elements: vec![SchemaRef::var("A"), SchemaRef::var("B")],
1374 },
1375 };
1376 let holder = Schema {
1377 id: SchemaId(11),
1378 type_params: vec!["T".to_string()],
1379 kind: SchemaKind::Struct {
1380 name: "Holder".to_string(),
1381 fields: vec![
1382 Field {
1383 name: "pair".to_string(),
1384 schema: SchemaRef::generic(
1385 SchemaId(10),
1386 vec![SchemaRef::var("T"), prim(Primitive::U32)],
1387 ),
1388 required: true,
1389 },
1390 Field {
1391 name: "tag".to_string(),
1392 schema: prim(Primitive::String),
1393 required: true,
1394 },
1395 ],
1396 },
1397 };
1398 let root = schema(
1399 12,
1400 SchemaKind::Struct {
1401 name: "Root".to_string(),
1402 fields: vec![Field {
1403 name: "h".to_string(),
1404 schema: SchemaRef::generic(SchemaId(11), vec![prim(Primitive::U8)]),
1405 required: true,
1406 }],
1407 },
1408 );
1409 let reg = Registry::new([pair, holder, root]);
1410
1411 let mut pair_val = VArray::new();
1412 pair_val.push(Value::from(5u8));
1413 pair_val.push(Value::from(70_000u32));
1414 let mut holder_val = VObject::new();
1415 holder_val.insert(VString::new("pair"), Value::from(pair_val));
1416 holder_val.insert(VString::new("tag"), VString::new("hi"));
1417 let mut root_val = VObject::new();
1418 root_val.insert(VString::new("h"), Value::from(holder_val));
1419
1420 rt(root_val.into(), SchemaId(12), ®);
1421
1422 let bad = schema(
1424 13,
1425 SchemaKind::Struct {
1426 name: "Bad".to_string(),
1427 fields: vec![Field {
1428 name: "h".to_string(),
1429 schema: SchemaRef::concrete(SchemaId(11)), required: true,
1431 }],
1432 },
1433 );
1434 let reg2 = Registry::new([
1435 Schema {
1436 id: SchemaId(11),
1437 type_params: vec!["T".to_string()],
1438 kind: SchemaKind::Option {
1439 element: SchemaRef::var("T"),
1440 },
1441 },
1442 bad,
1443 ]);
1444 let mut bv = VObject::new();
1445 bv.insert(VString::new("h"), Value::NULL);
1446 assert!(matches!(
1447 to_bytes(&bv.into(), SchemaId(13), ®2),
1448 Err(CompactError::GenericArity { .. })
1449 ));
1450 }
1451
1452 #[test]
1460 fn zero_sized_collections_roundtrip() {
1461 let empty = schema(
1462 1,
1463 SchemaKind::Struct {
1464 name: "Z".into(),
1465 fields: vec![],
1466 },
1467 );
1468 let list = schema(
1469 2,
1470 SchemaKind::List {
1471 element: SchemaRef::concrete(SchemaId(1)),
1472 },
1473 );
1474 let set = schema(
1475 3,
1476 SchemaKind::List {
1477 element: prim(Primitive::Unit),
1478 },
1479 );
1480 let array = schema(
1481 4,
1482 SchemaKind::Array {
1483 element: prim(Primitive::Unit),
1484 dimensions: vec![4],
1485 },
1486 );
1487 let reg = Registry::new([empty, list, set, array]);
1488
1489 let mut a = VArray::new();
1491 for _ in 0..5 {
1492 a.push(Value::from(VObject::new()));
1493 }
1494 rt(Value::from(a), SchemaId(2), ®);
1495
1496 let mut u = VArray::new();
1498 for _ in 0..3 {
1499 u.push(Value::NULL);
1500 }
1501 rt(Value::from(u), SchemaId(3), ®);
1502
1503 let mut fa = VArray::new();
1505 for _ in 0..4 {
1506 fa.push(Value::NULL);
1507 }
1508 rt(Value::from(fa), SchemaId(4), ®);
1509 }
1510
1511 #[test]
1512 fn extended_primitives() {
1513 use facet_value::{VDateTime, VQName, VUuid};
1514 let reg = Registry::new([]);
1515 rt(
1516 VUuid::from_u128(0x0123_4567_89ab_cdef_fedc_ba98_7654_3210).into(),
1517 primitive_id(Primitive::Uuid),
1518 ®,
1519 );
1520 rt(
1521 VQName::new(VString::new("http://ns"), VString::new("el")).into(),
1522 primitive_id(Primitive::QName),
1523 ®,
1524 );
1525 rt(
1526 VDateTime::new_offset(2026, 5, 29, 7, 32, 0, 0, 330).into(),
1527 primitive_id(Primitive::DateTime),
1528 ®,
1529 );
1530 rt(
1531 VDateTime::new_local_date(2026, 5, 29).into(),
1532 primitive_id(Primitive::DateTime),
1533 ®,
1534 );
1535 }
1536}