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 inferrence
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
172impl fmt::Display for Schema {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        if self.name.is_some() && (self.ty.is_struct() || self.ty.is_reference()) {
175            write!(f, "{}", self.name.as_ref().unwrap())
176        } else {
177            write!(f, "{}", self.ty)
178        }
179    }
180}
181
182impl Deref for Schema {
183    type Target = SchemaType;
184
185    fn deref(&self) -> &Self::Target {
186        &self.ty
187    }
188}
189
190impl DerefMut for Schema {
191    fn deref_mut(&mut self) -> &mut Self::Target {
192        &mut self.ty
193    }
194}
195
196impl From<Schema> for SchemaType {
197    fn from(val: Schema) -> Self {
198        val.ty
199    }
200}
201
202impl Schematic for Schema {}
203
204#[cfg(feature = "serde")]
205fn is_false(value: &bool) -> bool {
206    !value
207}
208
209/// Describes the metadata and shape of a field within a struct or enum.
210#[derive(Clone, Debug, Default, PartialEq)]
211#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
212pub struct SchemaField {
213    #[cfg_attr(
214        feature = "serde",
215        serde(default, skip_serializing_if = "Option::is_none")
216    )]
217    pub comment: Option<String>,
218
219    pub schema: Schema,
220
221    #[cfg_attr(
222        feature = "serde",
223        serde(default, skip_serializing_if = "Option::is_none")
224    )]
225    pub deprecated: Option<String>,
226
227    #[cfg_attr(
228        feature = "serde",
229        serde(default, skip_serializing_if = "Option::is_none")
230    )]
231    pub env_var: Option<String>,
232
233    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
234    pub hidden: bool,
235
236    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
237    pub nullable: bool,
238
239    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
240    pub optional: bool,
241
242    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
243    pub read_only: bool,
244
245    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
246    pub write_only: bool,
247}
248
249impl SchemaField {
250    pub fn new(schema: impl Into<Schema>) -> Self {
251        Self {
252            schema: schema.into(),
253            ..Default::default()
254        }
255    }
256}
257
258impl From<SchemaField> for Schema {
259    fn from(val: SchemaField) -> Self {
260        val.schema
261    }
262}
263
264impl From<Schema> for SchemaField {
265    fn from(mut schema: Schema) -> Self {
266        SchemaField {
267            comment: schema.description.take(),
268            schema,
269            ..Default::default()
270        }
271    }
272}
273
274impl Schematic for SchemaField {}