Skip to main content

schematic_types/
schema.rs

1use crate::*;
2use std::fmt;
3use std::ops::{Deref, DerefMut};
4
5/// Describes the metadata and shape of a type.
6#[derive(Clone, Debug, Default, PartialEq)]
7#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
8pub struct Schema {
9    #[cfg_attr(
10        feature = "serde",
11        serde(default, skip_serializing_if = "Option::is_none")
12    )]
13    pub deprecated: Option<String>,
14
15    #[cfg_attr(
16        feature = "serde",
17        serde(default, skip_serializing_if = "Option::is_none")
18    )]
19    pub description: Option<String>,
20
21    #[cfg_attr(
22        feature = "serde",
23        serde(default, skip_serializing_if = "Option::is_none")
24    )]
25    pub name: Option<String>,
26
27    #[cfg_attr(feature = "serde", serde(default, skip_serializing))]
28    pub nullable: bool,
29
30    pub ty: SchemaType,
31}
32
33impl Schema {
34    /// Create a schema with the provided type.
35    pub fn new(ty: impl Into<SchemaType>) -> Self {
36        Self {
37            ty: ty.into(),
38            ..Default::default()
39        }
40    }
41
42    /// Create an array schema.
43    pub fn array(value: ArrayType) -> Self {
44        Self::new(SchemaType::Array(Box::new(value)))
45    }
46
47    /// Create a boolean schema.
48    pub fn boolean(value: BooleanType) -> Self {
49        Self::new(SchemaType::Boolean(Box::new(value)))
50    }
51
52    /// Create an enum schema.
53    pub fn enumerable(value: EnumType) -> Self {
54        Self::new(SchemaType::Enum(Box::new(value)))
55    }
56
57    /// Create a float schema.
58    pub fn float(value: FloatType) -> Self {
59        Self::new(SchemaType::Float(Box::new(value)))
60    }
61
62    /// Create an integer schema.
63    pub fn integer(value: IntegerType) -> Self {
64        Self::new(SchemaType::Integer(Box::new(value)))
65    }
66
67    /// Create a literal schema.
68    pub fn literal(value: LiteralType) -> Self {
69        Self::new(SchemaType::Literal(Box::new(value)))
70    }
71
72    /// Create a literal schema with the provided value.
73    pub fn literal_value(value: LiteralValue) -> Self {
74        Self::new(SchemaType::Literal(Box::new(LiteralType::new(value))))
75    }
76
77    /// Create an object schema.
78    pub fn object(value: ObjectType) -> Self {
79        Self::new(SchemaType::Object(Box::new(value)))
80    }
81
82    /// Create a string schema.
83    pub fn string(value: StringType) -> Self {
84        Self::new(SchemaType::String(Box::new(value)))
85    }
86
87    /// Create a struct schema.
88    pub fn structure(value: StructType) -> Self {
89        Self::new(SchemaType::Struct(Box::new(value)))
90    }
91
92    /// Create a tuple schema.
93    pub fn tuple(value: TupleType) -> Self {
94        Self::new(SchemaType::Tuple(Box::new(value)))
95    }
96
97    /// Create a union schema.
98    pub fn union(value: UnionType) -> Self {
99        Self::new(SchemaType::Union(Box::new(value)))
100    }
101
102    /// Create a null schema.
103    pub fn null() -> Self {
104        Self::new(SchemaType::Null)
105    }
106
107    /// Create an unknown schema.
108    pub fn unknown() -> Self {
109        Self::new(SchemaType::Unknown)
110    }
111
112    /// Convert the current schema to a nullable type. If already nullable,
113    /// do nothing, otherwise convert to a union.
114    pub fn nullify(&mut self) {
115        if self.nullable {
116            // May already be a null union through inference
117            return;
118        }
119
120        self.nullable = true;
121
122        if let SchemaType::Union(inner) = &mut self.ty {
123            // If the union has an explicit name, then we can assume it's a distinct
124            // type, so we shouldn't add null to it and alter the intended type.
125            if self.name.is_none() {
126                if !inner.variants_types.iter().any(|t| t.is_null()) {
127                    inner.variants_types.push(Box::new(Schema::null()));
128                }
129
130                return;
131            }
132        }
133
134        // Convert to a nullable union
135        let mut new_schema = Schema::new(std::mem::replace(&mut self.ty, SchemaType::Unknown));
136        new_schema.name = self.name.take();
137        new_schema.description.clone_from(&self.description);
138        new_schema.deprecated.clone_from(&self.deprecated);
139
140        self.ty = SchemaType::Union(Box::new(UnionType::new_any([new_schema, Schema::null()])));
141    }
142
143    /// Mark the inner schema type as partial. Only structs and unions can be marked partial,
144    /// but arrays and objects will also be recursively set to update the inner type.
145    pub fn partialize(&mut self) {
146        match &mut self.ty {
147            SchemaType::Array(inner) => inner.items_type.partialize(),
148            SchemaType::Object(inner) => inner.value_type.partialize(),
149            SchemaType::Struct(inner) => {
150                inner.partial = true;
151            }
152            SchemaType::Union(inner) => {
153                inner.partial = true;
154
155                // This is to handle things wrapped in `Option`, is it correct?
156                // Not sure of a better way to do this at the moment...
157                let is_nullable = inner.variants_types.iter().any(|t| t.ty.is_null());
158
159                if is_nullable {
160                    for item in inner.variants_types.iter_mut() {
161                        if !item.is_null() {
162                            item.partialize();
163                        }
164                    }
165                }
166            }
167            _ => {}
168        };
169    }
170
171    /// Mark this schema as deprecated.
172    pub fn set_deprecated(&mut self, value: impl AsRef<str>) {
173        self.deprecated = Some(value.as_ref().to_owned());
174    }
175
176    /// Add a description for this schema.
177    pub fn set_description(&mut self, value: impl AsRef<str>) {
178        self.description = Some(value.as_ref().to_owned());
179    }
180
181    /// Add a name for this schema.
182    pub fn set_name(&mut self, value: impl AsRef<str>) {
183        let name = value.as_ref();
184
185        self.name = Some(name.to_owned());
186    }
187
188    /// Set the type of schema.
189    pub fn set_type(&mut self, value: SchemaType) {
190        self.ty = value;
191    }
192
193    /// Return a non-null schema if available. If a null type,
194    /// returns `None`. If a union type, returns the first non-null
195    /// type or `None`. Otherwise, returns the current type.
196    pub fn get_nonnull_schema(&self) -> Option<&Schema> {
197        match &self.ty {
198            SchemaType::Null => None,
199            SchemaType::Union(inner) => {
200                for ty in &inner.variants_types {
201                    if ty.is_null() {
202                        continue;
203                    }
204
205                    return Some(ty);
206                }
207
208                None
209            }
210            _ => Some(self),
211        }
212    }
213}
214
215impl fmt::Display for Schema {
216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217        if self.name.is_some() && (self.ty.is_struct() || self.ty.is_reference()) {
218            write!(f, "{}", self.name.as_ref().unwrap())
219        } else {
220            write!(f, "{}", self.ty)
221        }
222    }
223}
224
225impl Deref for Schema {
226    type Target = SchemaType;
227
228    fn deref(&self) -> &Self::Target {
229        &self.ty
230    }
231}
232
233impl DerefMut for Schema {
234    fn deref_mut(&mut self) -> &mut Self::Target {
235        &mut self.ty
236    }
237}
238
239impl From<Schema> for SchemaType {
240    fn from(val: Schema) -> Self {
241        val.ty
242    }
243}
244
245impl Schematic for Schema {}
246
247#[cfg(feature = "serde")]
248fn is_false(value: &bool) -> bool {
249    !value
250}
251
252/// Describes the metadata and shape of a field within a struct or enum.
253#[derive(Clone, Debug, Default, PartialEq)]
254#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
255pub struct SchemaField {
256    #[cfg_attr(
257        feature = "serde",
258        serde(default, skip_serializing_if = "Vec::is_empty")
259    )]
260    pub aliases: Vec<String>,
261
262    #[cfg_attr(
263        feature = "serde",
264        serde(default, skip_serializing_if = "Option::is_none")
265    )]
266    pub comment: Option<String>,
267
268    pub schema: Schema,
269
270    #[cfg_attr(
271        feature = "serde",
272        serde(default, skip_serializing_if = "Option::is_none")
273    )]
274    pub deprecated: Option<String>,
275
276    #[cfg_attr(
277        feature = "serde",
278        serde(default, skip_serializing_if = "Option::is_none")
279    )]
280    pub env_var: Option<String>,
281
282    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
283    pub flatten: bool,
284
285    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
286    pub hidden: bool,
287
288    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
289    pub nullable: bool,
290
291    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
292    pub optional: bool,
293
294    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
295    pub read_only: bool,
296
297    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
298    pub write_only: bool,
299}
300
301impl SchemaField {
302    pub fn new(schema: impl Into<Schema>) -> Self {
303        Self {
304            schema: schema.into(),
305            ..Default::default()
306        }
307    }
308}
309
310impl From<SchemaField> for Schema {
311    fn from(val: SchemaField) -> Self {
312        val.schema
313    }
314}
315
316impl From<Schema> for SchemaField {
317    fn from(mut schema: Schema) -> Self {
318        SchemaField {
319            comment: schema.description.take(),
320            schema,
321            ..Default::default()
322        }
323    }
324}
325
326impl Schematic for SchemaField {}