vld/combinators/
discriminated_union.rs1use serde_json::Value;
2
3use crate::error::{value_type_name, IssueCode, PathSegment, VldError};
4use crate::schema::DynSchema;
5use crate::schema::VldSchema;
6
7struct Variant {
9 discriminator_value: Value,
10 schema: Box<dyn DynSchema + Send + Sync>,
11}
12
13pub struct ZDiscriminatedUnion {
27 discriminator: String,
28 variants: Vec<Variant>,
29}
30
31impl ZDiscriminatedUnion {
32 pub fn new(discriminator: impl Into<String>) -> Self {
33 Self {
34 discriminator: discriminator.into(),
35 variants: vec![],
36 }
37 }
38
39 pub fn variant<S: DynSchema + Send + Sync + 'static>(
41 mut self,
42 value: impl Into<Value>,
43 schema: S,
44 ) -> Self {
45 self.variants.push(Variant {
46 discriminator_value: value.into(),
47 schema: Box::new(schema),
48 });
49 self
50 }
51
52 pub fn discriminator(&self) -> &str {
54 &self.discriminator
55 }
56
57 pub fn variants(&self) -> impl Iterator<Item = (&Value, &dyn DynSchema)> {
59 self.variants
60 .iter()
61 .map(|v| (&v.discriminator_value, v.schema.as_ref() as &dyn DynSchema))
62 }
63
64 pub fn variant_str<S: DynSchema + Send + Sync + 'static>(self, value: &str, schema: S) -> Self {
66 self.variant(Value::String(value.to_string()), schema)
67 }
68}
69
70impl VldSchema for ZDiscriminatedUnion {
71 type Output = Value;
72
73 fn parse_value(&self, value: &Value) -> Result<Value, VldError> {
74 let obj = value.as_object().ok_or_else(|| {
75 VldError::from_code_with_value(
76 IssueCode::InvalidType {
77 expected: "object".to_string(),
78 received: value_type_name(value),
79 },
80 value,
81 )
82 })?;
83 let at_disc = |e: VldError| e.with_prefix(PathSegment::Field(self.discriminator.clone()));
84
85 let disc_value = obj
86 .get(&self.discriminator)
87 .ok_or_else(|| at_disc(VldError::from_code(IssueCode::MissingField)))?;
88
89 for variant in &self.variants {
90 if crate::primitives::json_eq(disc_value, &variant.discriminator_value) {
91 return variant.schema.dyn_parse(value);
92 }
93 }
94
95 let options = self
96 .variants
97 .iter()
98 .map(|v| v.discriminator_value.clone())
99 .collect();
100 Err(at_disc(VldError::from_code_with_value(
101 IssueCode::InvalidDiscriminator { options },
102 disc_value,
103 )))
104 }
105}