1use proc_macro_error::{abort, OptionExt};
2use syn::spanned::Spanned as _;
3
4use crate::models::{Formats, JsonSchema, JsonSchemaKeywords, JsonSchemaTypes, JsonSchemaValues};
5
6impl 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}
20impl 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}
62impl 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}
95impl 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
122impl 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}