schema2struct/
try_from_impls.rs

1use proc_macro_error::{abort, OptionExt};
2use syn::spanned::Spanned as _;
3
4use crate::models::{Formats, JsonSchema, JsonSchemaKeywords, JsonSchemaTypes, JsonSchemaValues};
5
6// ----
7impl TryFrom<syn::Ident> for JsonSchemaTypes {
8    type Error = syn::Error;
9
10    fn try_from(value: syn::Ident) -> std::result::Result<Self, Self::Error> {
11        match value.to_string().as_str() {
12            "array" => Ok(Self::Array),
13            "object" => Ok(Self::Object),
14            "string" => Ok(Self::String),
15            "number" => Ok(Self::Number),
16            _ => Err(syn::Error::new(value.span(), "Unknown type")),
17        }
18    }
19}
20// ----
21
22// ----
23impl TryFrom<syn::Expr> for JsonSchemaValues {
24    type Error = syn::Error;
25
26    fn try_from(value: syn::Expr) -> std::result::Result<Self, Self::Error> {
27        match value {
28            syn::Expr::Path(path) if path.path.segments.len() == 1 => {
29                let ident = path
30                    .path
31                    .segments
32                    .first()
33                    .expect("We already checked the length")
34                    .clone()
35                    .ident;
36
37                Ok(JsonSchemaValues::Ident(ident))
38            }
39
40            syn::Expr::Lit(literal) => match literal.lit {
41                syn::Lit::Str(s) => Ok(JsonSchemaValues::Str(s.value())),
42                syn::Lit::Int(int) => Ok(JsonSchemaValues::Number(
43                    int.base10_parse().unwrap_or_default(),
44                )),
45                syn::Lit::Bool(b) => Ok(JsonSchemaValues::Bool(b.value)),
46                syn::Lit::Char(ch) => Ok(JsonSchemaValues::Char(ch.value())),
47                _ => Err(syn::Error::new(literal.span(), "invalid literal")),
48            },
49            syn::Expr::Array(array) => {
50                let mut elements = vec![];
51                for element in array.elems {
52                    elements.push(JsonSchemaValues::try_from(element)?);
53                }
54
55                Ok(JsonSchemaValues::Array(elements))
56            }
57
58            _ => Err(syn::Error::new(value.span(), "Unsupported expression type")),
59        }
60    }
61}
62// ----
63
64// ----
65impl TryFrom<syn::Ident> for JsonSchemaKeywords {
66    type Error = syn::Error;
67
68    fn try_from(value: syn::Ident) -> std::result::Result<Self, Self::Error> {
69        match value.to_string().as_str() {
70            "type" => Ok(JsonSchemaKeywords::Type),
71            "title" => Ok(JsonSchemaKeywords::Title),
72            "required" => Ok(JsonSchemaKeywords::Required),
73            "description" => Ok(JsonSchemaKeywords::Description),
74            "items" => Ok(JsonSchemaKeywords::Items),
75            "properties" => Ok(JsonSchemaKeywords::Properties),
76            "default" => Ok(JsonSchemaKeywords::Default),
77            "examples" => Ok(JsonSchemaKeywords::Examples),
78            "enum" => Ok(JsonSchemaKeywords::Enum),
79            "const" => Ok(JsonSchemaKeywords::Const),
80            "min_length" => Ok(JsonSchemaKeywords::MinLength),
81            "max_length" => Ok(JsonSchemaKeywords::MaxLenght),
82            "pattern" => Ok(JsonSchemaKeywords::Pattern),
83            "format" => Ok(JsonSchemaKeywords::Format),
84            "minimum" => Ok(JsonSchemaKeywords::Minimum),
85            "maximum" => Ok(JsonSchemaKeywords::Maximum),
86            "max_items" => Ok(JsonSchemaKeywords::MaxItems),
87            "min_items" => Ok(JsonSchemaKeywords::MinItems),
88            "unique_items" => Ok(JsonSchemaKeywords::UniqueItems),
89            "contains" => Ok(JsonSchemaKeywords::Contains),
90            "struct" => Ok(JsonSchemaKeywords::Struct),
91            _ => Err(syn::Error::new(value.span(), "Unknown keyword")),
92        }
93    }
94}
95// ----
96
97// ----
98
99impl TryFrom<syn::Ident> for Formats {
100    type Error = syn::Error;
101
102    fn try_from(value: syn::Ident) -> Result<Self, Self::Error> {
103        match value.to_string().as_str() {
104            "date" => Ok(Formats::Date),
105            "time" => Ok(Formats::Time),
106            "date-time" => Ok(Formats::DateTime),
107            "email" => Ok(Formats::Email),
108            "hostname" => Ok(Formats::Hostname),
109            "ipv4" => Ok(Formats::Ipv4),
110            "ipv6" => Ok(Formats::Ipv6),
111            "uri" => Ok(Formats::Uri),
112            _ => {
113             Err(syn::Error::new(
114                    value.span(),
115                    "unsupported format, avaliables are: `data`, `time`, `date-time`, `email`, `hostname`, `ipv4`, `ipv6`, `uri`",
116                ))
117            }
118        }
119    }
120}
121
122// ----
123
124// ---
125impl TryFrom<(syn::Ident, syn::Expr)> for JsonSchema {
126    type Error = syn::Error;
127
128    fn try_from(value: (syn::Ident, syn::Expr)) -> std::result::Result<Self, Self::Error> {
129        let key = value.0;
130        let value = value.1;
131        let value_span = value.span();
132
133        let schema_key = JsonSchemaKeywords::try_from(key)?;
134        let schema_value = JsonSchemaValues::try_from(value)?;
135
136        let mut schema = Self::default();
137
138        match schema_key {
139            JsonSchemaKeywords::Type => match schema_value {
140                JsonSchemaValues::Ident(ident) => schema.ty = JsonSchemaTypes::try_from(ident)?,
141                _ => return Err(syn::Error::new(value_span, "Invalid type")),
142            },
143
144            JsonSchemaKeywords::Struct => match schema_value {
145                JsonSchemaValues::Ident(ident) => schema.struct_name = Some(ident.to_string()),
146                _ => return Err(syn::Error::new(value_span, "only idents are allowed")),
147            },
148
149            JsonSchemaKeywords::UniqueItems => match schema_value {
150                JsonSchemaValues::Bool(b) => schema.unique_items = Some(b),
151                _ => return Err(syn::Error::new(value_span, "only boolean is allowed")),
152            },
153
154            JsonSchemaKeywords::MinItems => match schema_value {
155                JsonSchemaValues::Number(num) => schema.min_items = Some(num as usize),
156                _ => return Err(syn::Error::new(value_span, "only number is allowed")),
157            },
158
159            JsonSchemaKeywords::MaxItems => match schema_value {
160                JsonSchemaValues::Number(num) => schema.max_items = Some(num as usize),
161                _ => return Err(syn::Error::new(value_span, "only number is allowed")),
162            },
163
164            JsonSchemaKeywords::Minimum => match schema_value {
165                JsonSchemaValues::Number(num) => schema.minimum = Some(num as usize),
166                _ => return Err(syn::Error::new(value_span, "only number is allowed")),
167            },
168            JsonSchemaKeywords::Maximum => match schema_value {
169                JsonSchemaValues::Number(num) => schema.maximum = Some(num as usize),
170                _ => return Err(syn::Error::new(value_span, "only number is allowed")),
171            },
172
173            JsonSchemaKeywords::MinLength => match schema_value {
174                JsonSchemaValues::Number(num) => schema.min_lenght = Some(num as usize),
175                _ => return Err(syn::Error::new(value_span, "only number is allowed")),
176            },
177
178            JsonSchemaKeywords::MaxLenght => match schema_value {
179                JsonSchemaValues::Number(num) => schema.max_lenght = Some(num as usize),
180                _ => return Err(syn::Error::new(value_span, "only number is allowed")),
181            },
182
183            JsonSchemaKeywords::Pattern => match schema_value {
184                JsonSchemaValues::Str(s) => schema.pattern = Some(s),
185                _ => return Err(syn::Error::new(value_span, "only string is allowed")),
186            },
187
188            JsonSchemaKeywords::Format => match schema_value {
189                JsonSchemaValues::Ident(ident) => {
190                    let format = Formats::try_from(ident)?;
191
192                    schema.format = Some(format);
193                }
194                _ => return Err(syn::Error::new(value_span, "only idents are supported")),
195            },
196            JsonSchemaKeywords::Examples => match schema_value {
197                JsonSchemaValues::Array(examples) => {
198                    for example in examples.iter() {
199                        if !matches!(example, JsonSchemaValues::Str(_)) {
200                            return Err(syn::Error::new(
201                                value_span,
202                                "examples should all be string",
203                            ));
204                        }
205                    }
206
207                    let examples = examples
208                        .iter()
209                        .map(|value| {
210                            value
211                                .get_str()
212                                .cloned()
213                                .expect_or_abort("couldn't get the strings from the examples array")
214                        })
215                        .collect();
216
217                    schema.examples = Some(examples);
218                }
219                _ => {
220                    return Err(syn::Error::new(
221                        value_span,
222                        "examples should be inside of an array",
223                    ))
224                }
225            },
226
227            JsonSchemaKeywords::Enum => match schema_value {
228                JsonSchemaValues::Array(enum_values) => {
229                    for value in enum_values.iter() {
230                        if let JsonSchemaValues::Ident(ident) = value {
231                            return Err(syn::Error::new(
232                                ident.span(),
233                                "enum should contain values, not idents",
234                            ));
235                        }
236                    }
237
238                    schema.enum_values = Some(enum_values)
239                }
240
241                _ => {
242                    return Err(syn::Error::new(
243                        value_span,
244                        "enum should be inside of an array",
245                    ))
246                }
247            },
248
249            JsonSchemaKeywords::Const => match schema_value {
250                JsonSchemaValues::Ident(ident) => {
251                    return Err(syn::Error::new(
252                        ident.span(),
253                        "const value can't be an ident",
254                    ))
255                }
256                JsonSchemaValues::Array(_) => {
257                    return Err(syn::Error::new(value_span, "const value can't be an array"))
258                }
259                value => schema.const_value = Some(value),
260            },
261
262            JsonSchemaKeywords::Default => match schema_value {
263                JsonSchemaValues::Ident(ident) => {
264                    return Err(syn::Error::new(
265                        ident.span(),
266                        "default value can't be an ident",
267                    ))
268                }
269                value => schema.default = Some(value),
270            },
271
272            JsonSchemaKeywords::Title => match schema_value {
273                JsonSchemaValues::Str(s) => {
274                    schema.title = Some(s);
275                }
276                _ => return Err(syn::Error::new(value_span, "title must be a string")),
277            },
278            JsonSchemaKeywords::Description => match schema_value {
279                JsonSchemaValues::Str(s) => schema.description = Some(s),
280                _ => return Err(syn::Error::new(value_span, "description must be a string")),
281            },
282
283            JsonSchemaKeywords::Required => match schema_value {
284                JsonSchemaValues::Array(array) => {
285                    let are_all_str = array.iter().all(|v| matches!(v, JsonSchemaValues::Str(_)));
286
287                    if !are_all_str {
288                        abort!(value_span, "the array must be all string");
289                    }
290
291                    let mut collected_items = vec![];
292
293                    for item in array {
294                        match item {
295                            JsonSchemaValues::Str(s) => collected_items.push(s),
296                            _ => {
297                                abort!(value_span, "the array must be all string");
298                            }
299                        }
300                    }
301
302                    schema.required = Some(collected_items);
303                }
304                _ => {
305                    abort!(value_span, "the `required` field must be an array");
306                }
307            },
308
309            JsonSchemaKeywords::Properties => unreachable!("it's already handled at parsing"),
310            JsonSchemaKeywords::Items => unreachable!("it's already handled at parsing"),
311            JsonSchemaKeywords::Contains => unreachable!("it's already handled at parsing"),
312        }
313
314        Ok(schema)
315    }
316}