1use std::fmt;
2
3use vox_schema::{
4 ChannelDirection, FieldSchema, PrimitiveType, Schema, SchemaHash, SchemaKind, SchemaRegistry,
5 TypeRef, VariantSchema,
6};
7
8#[derive(Debug)]
9pub enum SerializeError {
10 UnsupportedType(String),
11 ReflectError(String),
12}
13
14impl fmt::Display for SerializeError {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 match self {
17 Self::UnsupportedType(ty) => write!(f, "unsupported type: {ty}"),
18 Self::ReflectError(msg) => write!(f, "reflect error: {msg}"),
19 }
20 }
21}
22
23impl std::error::Error for SerializeError {}
24
25#[derive(Debug)]
26pub enum DeserializeError {
27 UnexpectedEof {
28 pos: usize,
29 },
30 VarintOverflow {
31 pos: usize,
32 },
33 InvalidBool {
34 pos: usize,
35 got: u8,
36 },
37 InvalidUtf8 {
38 pos: usize,
39 },
40 InvalidOptionTag {
41 pos: usize,
42 got: u8,
43 },
44 InvalidEnumDiscriminant {
45 pos: usize,
46 index: u64,
47 variant_count: usize,
48 },
49 UnsupportedType(String),
50 ReflectError(String),
51 UnknownVariant {
52 remote_index: usize,
53 },
54 TrailingBytes {
55 pos: usize,
56 len: usize,
57 },
58 Custom(String),
59 Protocol(String),
62}
63
64impl fmt::Display for DeserializeError {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 Self::UnexpectedEof { pos } => write!(f, "unexpected EOF at byte {pos}"),
68 Self::VarintOverflow { pos } => write!(f, "varint overflow at byte {pos}"),
69 Self::InvalidBool { pos, got } => write!(f, "invalid bool 0x{got:02x} at byte {pos}"),
70 Self::InvalidUtf8 { pos } => write!(f, "invalid UTF-8 at byte {pos}"),
71 Self::InvalidOptionTag { pos, got } => {
72 write!(f, "invalid option tag 0x{got:02x} at byte {pos}")
73 }
74 Self::InvalidEnumDiscriminant {
75 pos,
76 index,
77 variant_count,
78 } => {
79 write!(
80 f,
81 "enum discriminant {index} out of range (0..{variant_count}) at byte {pos}"
82 )
83 }
84 Self::UnsupportedType(ty) => write!(f, "unsupported type: {ty}"),
85 Self::ReflectError(msg) => write!(f, "reflect error: {msg}"),
86 Self::UnknownVariant { remote_index } => {
87 write!(f, "unknown remote enum variant index {remote_index}")
88 }
89 Self::TrailingBytes { pos, len } => {
90 write!(
91 f,
92 "trailing bytes: {remaining} at byte {pos}",
93 remaining = len - pos
94 )
95 }
96 Self::Custom(msg) => write!(f, "{msg}"),
97 Self::Protocol(msg) => write!(f, "protocol error: {msg}"),
98 }
99 }
100}
101
102impl DeserializeError {
103 pub fn protocol(msg: &str) -> Self {
104 Self::Protocol(msg.to_string())
105 }
106}
107
108impl std::error::Error for DeserializeError {}
109
110#[derive(Debug, Clone, Default)]
114pub struct SchemaPath {
115 segments: Vec<PathSegment>,
116}
117
118#[derive(Debug, Clone)]
120pub enum PathSegment {
121 Field(String),
123 Variant(String),
125 Index(usize),
127}
128
129impl SchemaPath {
130 pub fn new() -> Self {
131 Self {
132 segments: Vec::new(),
133 }
134 }
135
136 pub fn push_front(&mut self, segment: PathSegment) {
138 self.segments.insert(0, segment);
139 }
140
141 pub fn with_front(mut self, segment: PathSegment) -> Self {
142 self.push_front(segment);
143 self
144 }
145}
146
147impl fmt::Display for SchemaPath {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 for segment in &self.segments {
150 match segment {
151 PathSegment::Field(name) => write!(f, ".{name}")?,
152 PathSegment::Variant(name) => write!(f, "::{name}")?,
153 PathSegment::Index(i) => write!(f, ".{i}")?,
154 }
155 }
156 Ok(())
157 }
158}
159
160#[derive(Debug)]
163pub struct TranslationError {
164 pub path: SchemaPath,
166 pub kind: Box<TranslationErrorKind>,
168}
169
170#[derive(Debug)]
171pub enum TranslationErrorKind {
172 NameMismatch {
174 remote: Schema,
175 local: Schema,
176 remote_rust: String,
177 local_rust: String,
178 },
179 KindMismatch {
181 remote: Schema,
182 local: Schema,
183 remote_rust: String,
184 local_rust: String,
185 },
186 MissingRequiredField {
189 field: FieldSchema,
191 remote_struct: Schema,
193 },
194 FieldTypeMismatch {
197 field_name: String,
198 remote_field_type: Schema,
199 local_field_type: Schema,
200 source: Box<TranslationError>,
202 },
203 IncompatibleVariantPayload {
205 remote_variant: VariantSchema,
206 local_variant: VariantSchema,
207 },
208 SchemaNotFound {
210 type_id: SchemaHash,
211 side: SchemaSide,
213 },
214 TupleLengthMismatch {
216 remote: Schema,
217 local: Schema,
218 remote_rust: String,
219 local_rust: String,
220 remote_len: usize,
221 local_len: usize,
222 },
223 UnresolvedVar { name: String, side: SchemaSide },
227 RecursiveTypeMismatch {
234 remote: Schema,
235 local: Schema,
236 remote_rust: String,
237 local_rust: String,
238 },
239}
240
241#[derive(Debug, Clone, Copy)]
243pub enum SchemaSide {
244 Remote,
245 Local,
246}
247
248impl fmt::Display for SchemaSide {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 match self {
251 SchemaSide::Remote => write!(f, "remote"),
252 SchemaSide::Local => write!(f, "local"),
253 }
254 }
255}
256
257impl TranslationError {
258 pub fn new(kind: TranslationErrorKind) -> Self {
259 Self {
260 path: SchemaPath::new(),
261 kind: Box::new(kind),
262 }
263 }
264
265 pub fn with_path_prefix(mut self, segment: PathSegment) -> Self {
267 self.path.push_front(segment);
268 self
269 }
270}
271
272pub(crate) fn format_schema_rust(schema: &Schema, registry: &SchemaRegistry) -> String {
273 match &schema.kind {
274 SchemaKind::Struct { name, .. } | SchemaKind::Enum { name, .. } => name.clone(),
275 kind => format_schema_kind_rust(kind, registry),
276 }
277}
278
279pub(crate) fn format_type_ref_rust(type_ref: &TypeRef, registry: &SchemaRegistry) -> String {
280 match type_ref {
281 TypeRef::Var { name } => name.as_str().to_string(),
282 TypeRef::Concrete { type_id, args } => {
283 let Some(schema) = registry.get(type_id) else {
284 return format!("<missing:{type_id:?}>");
285 };
286 match &schema.kind {
287 SchemaKind::Struct { name, .. } | SchemaKind::Enum { name, .. } => {
288 if args.is_empty() {
289 name.clone()
290 } else {
291 let args = args
292 .iter()
293 .map(|arg| format_type_ref_rust(arg, registry))
294 .collect::<Vec<_>>()
295 .join(", ");
296 format!("{name}<{args}>")
297 }
298 }
299 kind => format_schema_kind_rust(kind, registry),
300 }
301 }
302 }
303}
304
305fn format_schema_kind_rust(kind: &SchemaKind, registry: &SchemaRegistry) -> String {
306 match kind {
307 SchemaKind::Struct { name, .. } | SchemaKind::Enum { name, .. } => name.clone(),
308 SchemaKind::Tuple { elements } => {
309 let elements = elements
310 .iter()
311 .map(|element| format_type_ref_rust(element, registry))
312 .collect::<Vec<_>>();
313 match elements.len() {
314 0 => "()".to_string(),
315 1 => format!("({},)", elements[0]),
316 _ => format!("({})", elements.join(", ")),
317 }
318 }
319 SchemaKind::List { element } => format!("Vec<{}>", format_type_ref_rust(element, registry)),
320 SchemaKind::Map { key, value } => format!(
321 "HashMap<{}, {}>",
322 format_type_ref_rust(key, registry),
323 format_type_ref_rust(value, registry)
324 ),
325 SchemaKind::Array { element, length } => {
326 format!("[{}; {length}]", format_type_ref_rust(element, registry))
327 }
328 SchemaKind::Option { element } => {
329 format!("Option<{}>", format_type_ref_rust(element, registry))
330 }
331 SchemaKind::Channel { direction, element } => format!(
332 "{}<{}>",
333 match direction {
334 ChannelDirection::Tx => "Tx",
335 ChannelDirection::Rx => "Rx",
336 },
337 format_type_ref_rust(element, registry)
338 ),
339 SchemaKind::Primitive { primitive_type } => match primitive_type {
340 PrimitiveType::Bool => "bool".to_string(),
341 PrimitiveType::U8 => "u8".to_string(),
342 PrimitiveType::U16 => "u16".to_string(),
343 PrimitiveType::U32 => "u32".to_string(),
344 PrimitiveType::U64 => "u64".to_string(),
345 PrimitiveType::U128 => "u128".to_string(),
346 PrimitiveType::I8 => "i8".to_string(),
347 PrimitiveType::I16 => "i16".to_string(),
348 PrimitiveType::I32 => "i32".to_string(),
349 PrimitiveType::I64 => "i64".to_string(),
350 PrimitiveType::I128 => "i128".to_string(),
351 PrimitiveType::F32 => "f32".to_string(),
352 PrimitiveType::F64 => "f64".to_string(),
353 PrimitiveType::Char => "char".to_string(),
354 PrimitiveType::String => "String".to_string(),
355 PrimitiveType::Unit => "()".to_string(),
356 PrimitiveType::Never => "never".to_string(),
357 PrimitiveType::Bytes => "Vec<u8>".to_string(),
358 PrimitiveType::Payload => "Payload".to_string(),
359 },
360 }
361}
362
363impl fmt::Display for TranslationError {
364 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365 if !self.path.segments.is_empty() {
366 write!(f, "at {}: ", self.path)?;
367 }
368
369 match &*self.kind {
370 TranslationErrorKind::NameMismatch {
371 remote,
372 local,
373 remote_rust,
374 local_rust,
375 } => {
376 write!(
377 f,
378 "type name mismatch: remote is `{remote_rust}`, local is `{local_rust}` (remote name `{}`, local name `{}`)",
379 remote.name().unwrap_or("<anonymous>"),
380 local.name().unwrap_or("<anonymous>"),
381 )
382 }
383 TranslationErrorKind::KindMismatch {
384 remote_rust,
385 local_rust,
386 ..
387 } => {
388 write!(
389 f,
390 "structural mismatch: remote is `{remote_rust}`, local is `{local_rust}`",
391 )
392 }
393 TranslationErrorKind::MissingRequiredField {
394 field,
395 remote_struct,
396 } => {
397 write!(
398 f,
399 "required field '{}' (type {:?}) missing from remote '{}'",
400 field.name,
401 field.type_ref,
402 format_schema_rust(remote_struct, &SchemaRegistry::new()),
403 )?;
404 if let SchemaKind::Struct { fields, .. } = &remote_struct.kind {
405 write!(f, " (remote has fields: ")?;
406 for (i, rf) in fields.iter().enumerate() {
407 if i > 0 {
408 write!(f, ", ")?;
409 }
410 write!(f, "{}", rf.name)?;
411 }
412 write!(f, ")")?;
413 }
414 Ok(())
415 }
416 TranslationErrorKind::FieldTypeMismatch {
417 field_name,
418 remote_field_type,
419 local_field_type,
420 source,
421 } => {
422 write!(
423 f,
424 "field '{field_name}' type mismatch: remote is '{}', local is '{}': {source}",
425 format_schema_rust(remote_field_type, &SchemaRegistry::new()),
426 format_schema_rust(local_field_type, &SchemaRegistry::new()),
427 )
428 }
429 TranslationErrorKind::IncompatibleVariantPayload {
430 remote_variant,
431 local_variant,
432 } => {
433 write!(
434 f,
435 "variant '{}' payload mismatch: remote is {}, local is {}",
436 remote_variant.name,
437 variant_payload_str(&remote_variant.payload),
438 variant_payload_str(&local_variant.payload),
439 )
440 }
441 TranslationErrorKind::SchemaNotFound { type_id, side } => {
442 write!(f, "{side} schema not found for type ID {type_id:?}")
443 }
444 TranslationErrorKind::TupleLengthMismatch {
445 remote_rust,
446 local_rust,
447 remote_len,
448 local_len,
449 ..
450 } => {
451 write!(
452 f,
453 "tuple length mismatch: remote `{remote_rust}` has {remote_len} elements, local `{local_rust}` has {local_len} elements",
454 )
455 }
456 TranslationErrorKind::UnresolvedVar { name, side } => {
457 write!(
458 f,
459 "unresolved type variable {name} on {side} side — Var substitution failed"
460 )
461 }
462 TranslationErrorKind::RecursiveTypeMismatch {
463 remote_rust,
464 local_rust,
465 ..
466 } => {
467 write!(
468 f,
469 "recursive type mismatch: cycle reaches `{remote_rust}` (remote) paired with `{local_rust}` (local), and the two are not structurally identical — only self-recursion with matching schemas is supported today",
470 )
471 }
472 }
473 }
474}
475
476fn variant_payload_str(payload: &vox_schema::VariantPayload) -> &'static str {
477 match payload {
478 vox_schema::VariantPayload::Unit => "unit",
479 vox_schema::VariantPayload::Newtype { .. } => "newtype",
480 vox_schema::VariantPayload::Tuple { .. } => "tuple",
481 vox_schema::VariantPayload::Struct { .. } => "struct",
482 }
483}
484
485impl std::error::Error for TranslationError {}