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}
228
229#[derive(Debug, Clone, Copy)]
231pub enum SchemaSide {
232 Remote,
233 Local,
234}
235
236impl fmt::Display for SchemaSide {
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 match self {
239 SchemaSide::Remote => write!(f, "remote"),
240 SchemaSide::Local => write!(f, "local"),
241 }
242 }
243}
244
245impl TranslationError {
246 pub fn new(kind: TranslationErrorKind) -> Self {
247 Self {
248 path: SchemaPath::new(),
249 kind: Box::new(kind),
250 }
251 }
252
253 pub fn with_path_prefix(mut self, segment: PathSegment) -> Self {
255 self.path.push_front(segment);
256 self
257 }
258}
259
260pub(crate) fn format_schema_rust(schema: &Schema, registry: &SchemaRegistry) -> String {
261 match &schema.kind {
262 SchemaKind::Struct { name, .. } | SchemaKind::Enum { name, .. } => name.clone(),
263 kind => format_schema_kind_rust(kind, registry),
264 }
265}
266
267pub(crate) fn format_type_ref_rust(type_ref: &TypeRef, registry: &SchemaRegistry) -> String {
268 match type_ref {
269 TypeRef::Var { name } => name.as_str().to_string(),
270 TypeRef::Concrete { type_id, args } => {
271 let Some(schema) = registry.get(type_id) else {
272 return format!("<missing:{type_id:?}>");
273 };
274 match &schema.kind {
275 SchemaKind::Struct { name, .. } | SchemaKind::Enum { name, .. } => {
276 if args.is_empty() {
277 name.clone()
278 } else {
279 let args = args
280 .iter()
281 .map(|arg| format_type_ref_rust(arg, registry))
282 .collect::<Vec<_>>()
283 .join(", ");
284 format!("{name}<{args}>")
285 }
286 }
287 kind => format_schema_kind_rust(kind, registry),
288 }
289 }
290 }
291}
292
293fn format_schema_kind_rust(kind: &SchemaKind, registry: &SchemaRegistry) -> String {
294 match kind {
295 SchemaKind::Struct { name, .. } | SchemaKind::Enum { name, .. } => name.clone(),
296 SchemaKind::Tuple { elements } => {
297 let elements = elements
298 .iter()
299 .map(|element| format_type_ref_rust(element, registry))
300 .collect::<Vec<_>>();
301 match elements.len() {
302 0 => "()".to_string(),
303 1 => format!("({},)", elements[0]),
304 _ => format!("({})", elements.join(", ")),
305 }
306 }
307 SchemaKind::List { element } => format!("Vec<{}>", format_type_ref_rust(element, registry)),
308 SchemaKind::Map { key, value } => format!(
309 "HashMap<{}, {}>",
310 format_type_ref_rust(key, registry),
311 format_type_ref_rust(value, registry)
312 ),
313 SchemaKind::Array { element, length } => {
314 format!("[{}; {length}]", format_type_ref_rust(element, registry))
315 }
316 SchemaKind::Option { element } => {
317 format!("Option<{}>", format_type_ref_rust(element, registry))
318 }
319 SchemaKind::Channel { direction, element } => format!(
320 "{}<{}>",
321 match direction {
322 ChannelDirection::Tx => "Tx",
323 ChannelDirection::Rx => "Rx",
324 },
325 format_type_ref_rust(element, registry)
326 ),
327 SchemaKind::Primitive { primitive_type } => match primitive_type {
328 PrimitiveType::Bool => "bool".to_string(),
329 PrimitiveType::U8 => "u8".to_string(),
330 PrimitiveType::U16 => "u16".to_string(),
331 PrimitiveType::U32 => "u32".to_string(),
332 PrimitiveType::U64 => "u64".to_string(),
333 PrimitiveType::U128 => "u128".to_string(),
334 PrimitiveType::I8 => "i8".to_string(),
335 PrimitiveType::I16 => "i16".to_string(),
336 PrimitiveType::I32 => "i32".to_string(),
337 PrimitiveType::I64 => "i64".to_string(),
338 PrimitiveType::I128 => "i128".to_string(),
339 PrimitiveType::F32 => "f32".to_string(),
340 PrimitiveType::F64 => "f64".to_string(),
341 PrimitiveType::Char => "char".to_string(),
342 PrimitiveType::String => "String".to_string(),
343 PrimitiveType::Unit => "()".to_string(),
344 PrimitiveType::Never => "never".to_string(),
345 PrimitiveType::Bytes => "Vec<u8>".to_string(),
346 PrimitiveType::Payload => "Payload".to_string(),
347 },
348 }
349}
350
351impl fmt::Display for TranslationError {
352 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353 if !self.path.segments.is_empty() {
354 write!(f, "at {}: ", self.path)?;
355 }
356
357 match &*self.kind {
358 TranslationErrorKind::NameMismatch {
359 remote,
360 local,
361 remote_rust,
362 local_rust,
363 } => {
364 write!(
365 f,
366 "type name mismatch: remote is `{remote_rust}`, local is `{local_rust}` (remote name `{}`, local name `{}`)",
367 remote.name().unwrap_or("<anonymous>"),
368 local.name().unwrap_or("<anonymous>"),
369 )
370 }
371 TranslationErrorKind::KindMismatch {
372 remote_rust,
373 local_rust,
374 ..
375 } => {
376 write!(
377 f,
378 "structural mismatch: remote is `{remote_rust}`, local is `{local_rust}`",
379 )
380 }
381 TranslationErrorKind::MissingRequiredField {
382 field,
383 remote_struct,
384 } => {
385 write!(
386 f,
387 "required field '{}' (type {:?}) missing from remote '{}'",
388 field.name,
389 field.type_ref,
390 format_schema_rust(remote_struct, &SchemaRegistry::new()),
391 )?;
392 if let SchemaKind::Struct { fields, .. } = &remote_struct.kind {
393 write!(f, " (remote has fields: ")?;
394 for (i, rf) in fields.iter().enumerate() {
395 if i > 0 {
396 write!(f, ", ")?;
397 }
398 write!(f, "{}", rf.name)?;
399 }
400 write!(f, ")")?;
401 }
402 Ok(())
403 }
404 TranslationErrorKind::FieldTypeMismatch {
405 field_name,
406 remote_field_type,
407 local_field_type,
408 source,
409 } => {
410 write!(
411 f,
412 "field '{field_name}' type mismatch: remote is '{}', local is '{}': {source}",
413 format_schema_rust(remote_field_type, &SchemaRegistry::new()),
414 format_schema_rust(local_field_type, &SchemaRegistry::new()),
415 )
416 }
417 TranslationErrorKind::IncompatibleVariantPayload {
418 remote_variant,
419 local_variant,
420 } => {
421 write!(
422 f,
423 "variant '{}' payload mismatch: remote is {}, local is {}",
424 remote_variant.name,
425 variant_payload_str(&remote_variant.payload),
426 variant_payload_str(&local_variant.payload),
427 )
428 }
429 TranslationErrorKind::SchemaNotFound { type_id, side } => {
430 write!(f, "{side} schema not found for type ID {type_id:?}")
431 }
432 TranslationErrorKind::TupleLengthMismatch {
433 remote_rust,
434 local_rust,
435 remote_len,
436 local_len,
437 ..
438 } => {
439 write!(
440 f,
441 "tuple length mismatch: remote `{remote_rust}` has {remote_len} elements, local `{local_rust}` has {local_len} elements",
442 )
443 }
444 TranslationErrorKind::UnresolvedVar { name, side } => {
445 write!(
446 f,
447 "unresolved type variable {name} on {side} side — Var substitution failed"
448 )
449 }
450 }
451 }
452}
453
454fn variant_payload_str(payload: &vox_schema::VariantPayload) -> &'static str {
455 match payload {
456 vox_schema::VariantPayload::Unit => "unit",
457 vox_schema::VariantPayload::Newtype { .. } => "newtype",
458 vox_schema::VariantPayload::Tuple { .. } => "tuple",
459 vox_schema::VariantPayload::Struct { .. } => "struct",
460 }
461}
462
463impl std::error::Error for TranslationError {}