1use std::collections::HashMap;
2
3use facet_core::{Shape, Type, UserType};
4use vox_schema::{
5 FieldSchema, PrimitiveType, Schema, SchemaHash, SchemaKind, SchemaRegistry, TypeRef,
6 VariantPayload, VariantSchema,
7};
8
9use crate::error::{PathSegment, SchemaSide, TranslationError, TranslationErrorKind};
10
11#[derive(Debug)]
17pub enum TranslationPlan {
18 Identity,
21
22 Struct {
24 field_ops: Vec<FieldOp>,
26 nested: HashMap<usize, TranslationPlan>,
28 },
29
30 Enum {
32 variant_map: Vec<Option<usize>>,
34 variant_plans: HashMap<usize, TranslationPlan>,
36 nested: HashMap<usize, TranslationPlan>,
38 },
39
40 Tuple {
42 field_ops: Vec<FieldOp>,
43 nested: HashMap<usize, TranslationPlan>,
44 },
45
46 List { element: Box<TranslationPlan> },
48
49 Option { inner: Box<TranslationPlan> },
51
52 Map {
54 key: Box<TranslationPlan>,
55 value: Box<TranslationPlan>,
56 },
57
58 Array { element: Box<TranslationPlan> },
60
61 Pointer { pointee: Box<TranslationPlan> },
63}
64
65#[derive(Debug)]
66pub enum FieldOp {
67 Read { local_index: usize },
69 Skip { type_ref: TypeRef },
71}
72
73#[derive(Debug)]
75pub struct SchemaSet {
76 pub root: Schema,
77 pub registry: SchemaRegistry,
78}
79
80impl SchemaSet {
81 pub fn from_schemas(schemas: Vec<Schema>) -> Self {
84 let root = schemas.last().cloned().expect("empty schema list");
85 let registry = vox_schema::build_registry(&schemas);
86 SchemaSet { root, registry }
87 }
88
89 pub fn from_root_and_schemas(root: TypeRef, schemas: Vec<Schema>) -> Self {
92 let registry = vox_schema::build_registry(&schemas);
93 let root_kind = root
94 .resolve_kind(®istry)
95 .expect("root schema must be in registry");
96 let root_id = match &root {
97 TypeRef::Concrete { type_id, .. } => *type_id,
98 TypeRef::Var { .. } => unreachable!("root type ref is never a Var"),
99 };
100 let root = Schema {
101 id: root_id,
102 type_params: vec![],
103 kind: root_kind,
104 };
105 SchemaSet { root, registry }
106 }
107}
108
109pub struct PlanInput<'a> {
111 pub remote: &'a SchemaSet,
112 pub local: &'a SchemaSet,
113}
114
115pub fn build_identity_plan(shape: &'static Shape) -> TranslationPlan {
119 match shape.ty {
120 Type::User(UserType::Struct(struct_type)) => {
121 let field_ops = (0..struct_type.fields.len())
122 .map(|i| FieldOp::Read { local_index: i })
123 .collect();
124 TranslationPlan::Struct {
125 field_ops,
126 nested: HashMap::new(),
127 }
128 }
129 Type::User(UserType::Enum(enum_type)) => {
130 let variant_map = (0..enum_type.variants.len()).map(Some).collect();
131 let mut variant_plans = HashMap::new();
132 for (i, variant) in enum_type.variants.iter().enumerate() {
133 let field_ops = (0..variant.data.fields.len())
134 .map(|j| FieldOp::Read { local_index: j })
135 .collect();
136 variant_plans.insert(
137 i,
138 TranslationPlan::Struct {
139 field_ops,
140 nested: HashMap::new(),
141 },
142 );
143 }
144 TranslationPlan::Enum {
145 variant_map,
146 variant_plans,
147 nested: HashMap::new(),
148 }
149 }
150 _ => TranslationPlan::Identity,
151 }
152}
153
154pub fn build_plan(input: &PlanInput) -> Result<TranslationPlan, TranslationError> {
166 let remote = &input.remote.root;
167 let local = &input.local.root;
168
169 if let (Some(remote_name), Some(local_name)) = (remote.name(), local.name())
171 && remote_name != local_name
172 {
173 return Err(TranslationError::new(TranslationErrorKind::NameMismatch {
174 remote: remote.clone(),
175 local: local.clone(),
176 remote_rust: crate::error::format_schema_rust(remote, &input.remote.registry),
177 local_rust: crate::error::format_schema_rust(local, &input.local.registry),
178 }));
179 }
180
181 if is_byte_buffer_kind(&remote.kind, &input.remote.registry)
182 && is_byte_buffer_kind(&local.kind, &input.local.registry)
183 {
184 return Ok(TranslationPlan::Identity);
185 }
186
187 match (&remote.kind, &local.kind) {
188 (
189 SchemaKind::Struct {
190 fields: remote_fields,
191 ..
192 },
193 SchemaKind::Struct {
194 fields: local_fields,
195 ..
196 },
197 ) => build_struct_plan(remote_fields, local_fields, remote, local, input),
198 (
199 SchemaKind::Enum {
200 variants: remote_variants,
201 ..
202 },
203 SchemaKind::Enum {
204 variants: local_variants,
205 ..
206 },
207 ) => build_enum_plan(remote_variants, local_variants, remote, local, input),
208 (
209 SchemaKind::Tuple {
210 elements: remote_elements,
211 },
212 SchemaKind::Tuple {
213 elements: local_elements,
214 },
215 ) => build_tuple_plan(remote_elements, local_elements, remote, local, input),
216 (
218 SchemaKind::List {
219 element: remote_elem,
220 },
221 SchemaKind::List {
222 element: local_elem,
223 },
224 ) => {
225 let element_plan = nested_plan(remote_elem, local_elem, input)?;
226 Ok(TranslationPlan::List {
227 element: Box::new(element_plan.unwrap_or(TranslationPlan::Identity)),
228 })
229 }
230 (
231 SchemaKind::Option {
232 element: remote_elem,
233 },
234 SchemaKind::Option {
235 element: local_elem,
236 },
237 ) => {
238 let inner_plan = nested_plan(remote_elem, local_elem, input)?;
239 Ok(TranslationPlan::Option {
240 inner: Box::new(inner_plan.unwrap_or(TranslationPlan::Identity)),
241 })
242 }
243 (
244 SchemaKind::Map {
245 key: remote_key,
246 value: remote_val,
247 },
248 SchemaKind::Map {
249 key: local_key,
250 value: local_val,
251 },
252 ) => {
253 let key_plan = nested_plan(remote_key, local_key, input)?;
254 let val_plan = nested_plan(remote_val, local_val, input)?;
255 Ok(TranslationPlan::Map {
256 key: Box::new(key_plan.unwrap_or(TranslationPlan::Identity)),
257 value: Box::new(val_plan.unwrap_or(TranslationPlan::Identity)),
258 })
259 }
260 (
261 SchemaKind::Array {
262 element: remote_elem,
263 ..
264 },
265 SchemaKind::Array {
266 element: local_elem,
267 ..
268 },
269 ) => {
270 let element_plan = nested_plan(remote_elem, local_elem, input)?;
271 Ok(TranslationPlan::Array {
272 element: Box::new(element_plan.unwrap_or(TranslationPlan::Identity)),
273 })
274 }
275 (
276 SchemaKind::Channel {
277 direction: remote_direction,
278 ..
279 },
280 SchemaKind::Channel {
281 direction: local_direction,
282 ..
283 },
284 ) if remote_direction == local_direction => Ok(TranslationPlan::Identity),
285 (
286 SchemaKind::Primitive {
287 primitive_type: remote_primitive,
288 },
289 SchemaKind::Primitive {
290 primitive_type: local_primitive,
291 },
292 ) if remote_primitive == local_primitive => Ok(TranslationPlan::Identity),
293 _ => Err(TranslationError::new(TranslationErrorKind::KindMismatch {
295 remote: remote.clone(),
296 local: local.clone(),
297 remote_rust: crate::error::format_schema_rust(remote, &input.remote.registry),
298 local_rust: crate::error::format_schema_rust(local, &input.local.registry),
299 })),
300 }
301}
302
303fn nested_plan(
306 remote_type_ref: &TypeRef,
307 local_type_ref: &TypeRef,
308 input: &PlanInput,
309) -> Result<Option<TranslationPlan>, TranslationError> {
310 let resolve_schema = |type_ref: &TypeRef, registry: &SchemaRegistry, side: SchemaSide| {
311 let type_id = match type_ref {
312 TypeRef::Concrete { type_id, .. } => *type_id,
313 TypeRef::Var { name } => {
314 return Err(TranslationError::new(TranslationErrorKind::UnresolvedVar {
315 name: format!("{name:?}"),
316 side,
317 }));
318 }
319 };
320 let kind = type_ref.resolve_kind(registry).ok_or_else(|| {
321 TranslationError::new(TranslationErrorKind::SchemaNotFound { type_id, side })
322 })?;
323 let base = registry.get(&type_id).ok_or_else(|| {
324 TranslationError::new(TranslationErrorKind::SchemaNotFound { type_id, side })
325 })?;
326 Ok(Schema {
327 id: base.id,
328 type_params: vec![],
329 kind,
330 })
331 };
332
333 let remote_schema =
334 resolve_schema(remote_type_ref, &input.remote.registry, SchemaSide::Remote)?;
335 let local_schema = resolve_schema(local_type_ref, &input.local.registry, SchemaSide::Local)?;
336
337 let sub_input = PlanInput {
338 remote: &SchemaSet {
339 root: remote_schema,
340 registry: input.remote.registry.clone(),
341 },
342 local: &SchemaSet {
343 root: local_schema,
344 registry: input.local.registry.clone(),
345 },
346 };
347 build_plan(&sub_input).map(Some)
348}
349
350fn is_byte_buffer_kind(kind: &SchemaKind, registry: &SchemaRegistry) -> bool {
351 match kind {
352 SchemaKind::Primitive {
353 primitive_type: PrimitiveType::Bytes,
354 } => true,
355 SchemaKind::List { element } => matches!(
356 element.resolve_kind(registry),
357 Some(SchemaKind::Primitive {
358 primitive_type: PrimitiveType::U8,
359 })
360 ),
361 _ => false,
362 }
363}
364
365fn build_struct_plan(
366 remote_fields: &[FieldSchema],
367 local_fields: &[FieldSchema],
368 remote_schema: &Schema,
369 _local_schema: &Schema,
370 input: &PlanInput,
371) -> Result<TranslationPlan, TranslationError> {
372 let mut field_ops = Vec::with_capacity(remote_fields.len());
373 let mut nested = HashMap::new();
374 let mut matched_local = vec![false; local_fields.len()];
375
376 for remote_field in remote_fields {
377 if let Some((local_idx, local_field)) = local_fields
378 .iter()
379 .enumerate()
380 .find(|(_, f)| f.name == remote_field.name)
381 {
382 matched_local[local_idx] = true;
383 field_ops.push(FieldOp::Read {
384 local_index: local_idx,
385 });
386
387 let nested_plan = nested_plan(&remote_field.type_ref, &local_field.type_ref, input)
389 .map_err(|e| e.with_path_prefix(PathSegment::Field(remote_field.name.clone())))?;
390 if let Some(plan) = nested_plan {
391 nested.insert(local_idx, plan);
392 }
393 } else {
394 field_ops.push(FieldOp::Skip {
395 type_ref: remote_field.type_ref.clone(),
396 });
397 }
398 }
399
400 for (i, matched) in matched_local.iter().enumerate() {
402 if !matched && local_fields[i].required {
403 return Err(TranslationError::new(
404 TranslationErrorKind::MissingRequiredField {
405 field: local_fields[i].clone(),
406 remote_struct: remote_schema.clone(),
407 },
408 ));
409 }
410 }
411
412 Ok(TranslationPlan::Struct { field_ops, nested })
413}
414
415fn build_tuple_plan(
416 remote_elements: &[TypeRef<SchemaHash>],
417 local_elements: &[TypeRef<SchemaHash>],
418 remote_schema: &Schema,
419 local_schema: &Schema,
420 input: &PlanInput,
421) -> Result<TranslationPlan, TranslationError> {
422 if remote_elements.len() != local_elements.len() {
423 return Err(TranslationError::new(
424 TranslationErrorKind::TupleLengthMismatch {
425 remote: remote_schema.clone(),
426 local: local_schema.clone(),
427 remote_rust: crate::error::format_schema_rust(
428 remote_schema,
429 &input.remote.registry,
430 ),
431 local_rust: crate::error::format_schema_rust(local_schema, &input.local.registry),
432 remote_len: remote_elements.len(),
433 local_len: local_elements.len(),
434 },
435 ));
436 }
437
438 let mut field_ops = Vec::with_capacity(remote_elements.len());
439 let mut nested = HashMap::new();
440
441 for (i, (remote_elem, local_elem)) in remote_elements
442 .iter()
443 .zip(local_elements.iter())
444 .enumerate()
445 {
446 field_ops.push(FieldOp::Read { local_index: i });
447
448 let nested_plan = nested_plan(remote_elem, local_elem, input)
449 .map_err(|e| e.with_path_prefix(PathSegment::Index(i)))?;
450 if let Some(plan) = nested_plan {
451 nested.insert(i, plan);
452 }
453 }
454
455 Ok(TranslationPlan::Tuple { field_ops, nested })
456}
457
458fn build_enum_plan(
462 remote_variants: &[VariantSchema],
463 local_variants: &[VariantSchema],
464 _remote_schema: &Schema,
465 _local_schema: &Schema,
466 input: &PlanInput,
467) -> Result<TranslationPlan, TranslationError> {
468 let mut variant_map = Vec::with_capacity(remote_variants.len());
469 let mut variant_plans = HashMap::new();
470 let mut nested = HashMap::new();
471
472 for (remote_idx, remote_variant) in remote_variants.iter().enumerate() {
473 if let Some((local_idx, local_variant)) = local_variants
474 .iter()
475 .enumerate()
476 .find(|(_, v)| v.name == remote_variant.name)
477 {
478 variant_map.push(Some(local_idx));
479
480 match (&remote_variant.payload, &local_variant.payload) {
481 (
483 VariantPayload::Struct {
484 fields: remote_fields,
485 },
486 VariantPayload::Struct {
487 fields: local_fields,
488 },
489 ) => {
490 let variant_field_ops: Vec<FieldOp> = remote_fields
491 .iter()
492 .map(|rf| {
493 if let Some((local_field_idx, _)) = local_fields
494 .iter()
495 .enumerate()
496 .find(|(_, f)| f.name == rf.name)
497 {
498 FieldOp::Read {
499 local_index: local_field_idx,
500 }
501 } else {
502 FieldOp::Skip {
503 type_ref: rf.type_ref.clone(),
504 }
505 }
506 })
507 .collect();
508 variant_plans.insert(
509 remote_idx,
510 TranslationPlan::Struct {
511 field_ops: variant_field_ops,
512 nested: HashMap::new(),
513 },
514 );
515 }
516 (
518 VariantPayload::Newtype {
519 type_ref: remote_inner_ref,
520 },
521 VariantPayload::Newtype {
522 type_ref: local_inner_ref,
523 },
524 ) => {
525 let inner_plan = nested_plan(remote_inner_ref, local_inner_ref, input)
526 .map_err(|e| {
527 e.with_path_prefix(PathSegment::Variant(remote_variant.name.clone()))
528 })?;
529 if let Some(plan) = inner_plan {
530 nested.insert(local_idx, plan);
531 }
532 }
533 (
535 VariantPayload::Tuple {
536 types: remote_types,
537 },
538 VariantPayload::Tuple { types: local_types },
539 ) => {
540 let tuple_plan = build_tuple_plan(
541 remote_types,
542 local_types,
543 _remote_schema,
544 _local_schema,
545 input,
546 )
547 .map_err(|e| {
548 e.with_path_prefix(PathSegment::Variant(remote_variant.name.clone()))
549 })?;
550 variant_plans.insert(remote_idx, tuple_plan);
551 }
552 (VariantPayload::Unit, VariantPayload::Unit) => {}
553 _ => {
555 return Err(TranslationError::new(
556 TranslationErrorKind::IncompatibleVariantPayload {
557 remote_variant: remote_variant.clone(),
558 local_variant: local_variant.clone(),
559 },
560 )
561 .with_path_prefix(PathSegment::Variant(remote_variant.name.clone())));
562 }
563 }
564 } else {
565 variant_map.push(None);
567 }
568 }
569
570 Ok(TranslationPlan::Enum {
571 variant_map,
572 variant_plans,
573 nested,
574 })
575}