utoipa_gen/component/features/
validation.rs1use std::str::FromStr;
2
3use proc_macro2::{Ident, Literal, Span, TokenStream, TokenTree};
4use quote::{quote, ToTokens};
5use syn::parse::ParseStream;
6use syn::LitStr;
7
8use crate::{parse_utils, Diagnostics};
9
10use super::validators::Validator;
11use super::{impl_feature, Feature, Parse, Validate};
12
13#[inline]
14fn from_str<T: FromStr>(number: &str, span: Span) -> syn::Result<T>
15where
16 <T as std::str::FromStr>::Err: std::fmt::Display,
17{
18 T::from_str(number).map_err(|error| syn::Error::new(span, error))
19}
20
21#[derive(Clone)]
22#[cfg_attr(feature = "debug", derive(Debug))]
23pub struct NumberValue {
24 minus: bool,
25 pub lit: Literal,
26}
27
28impl NumberValue {
29 pub fn try_from_str<T>(&self) -> syn::Result<T>
30 where
31 T: FromStr,
32 <T as std::str::FromStr>::Err: std::fmt::Display,
33 {
34 let number = if self.minus {
35 format!("-{}", &self.lit)
36 } else {
37 self.lit.to_string()
38 };
39
40 let parsed = from_str::<T>(&number, self.lit.span())?;
41 Ok(parsed)
42 }
43}
44
45impl syn::parse::Parse for NumberValue {
46 fn parse(input: ParseStream) -> syn::Result<Self> {
47 let mut minus = false;
48 let result = input.step(|cursor| {
49 let mut rest = *cursor;
50
51 while let Some((tt, next)) = rest.token_tree() {
52 match &tt {
53 TokenTree::Punct(punct) if punct.as_char() == '-' => {
54 minus = true;
55 }
56 TokenTree::Literal(lit) => return Ok((lit.clone(), next)),
57 _ => (),
58 }
59 rest = next;
60 }
61 Err(cursor.error("no `literal` value found after this point"))
62 })?;
63
64 Ok(Self { minus, lit: result })
65 }
66}
67
68impl ToTokens for NumberValue {
69 fn to_tokens(&self, tokens: &mut TokenStream) {
70 let punct = if self.minus { Some(quote! {-}) } else { None };
71 let lit = &self.lit;
72
73 tokens.extend(quote! {
74 #punct #lit
75 })
76 }
77}
78
79#[inline]
80fn parse_next_number_value(input: ParseStream) -> syn::Result<NumberValue> {
81 use syn::parse::Parse;
82 parse_utils::parse_next(input, || NumberValue::parse(input))
83}
84
85impl_feature! {
86 #[cfg_attr(feature = "debug", derive(Debug))]
87 #[derive(Clone)]
88 pub struct MultipleOf(pub(super) NumberValue, Ident);
89}
90
91impl Validate for MultipleOf {
92 fn validate(&self, validator: impl Validator) -> Option<Diagnostics> {
93 match validator.is_valid() {
94 Err(error) => Some(Diagnostics::with_span(self.1.span(), format!( "`multiple_of` error: {}", error))
95 .help("See more details: `http://json-schema.org/draft/2020-12/json-schema-validation.html#name-multipleof`")),
96 _ => None
97 }
98 }
99}
100
101impl Parse for MultipleOf {
102 fn parse(input: ParseStream, ident: Ident) -> syn::Result<Self> {
103 parse_next_number_value(input).map(|number| Self(number, ident))
104 }
105}
106
107impl ToTokens for MultipleOf {
108 fn to_tokens(&self, tokens: &mut TokenStream) {
109 self.0.to_tokens(tokens);
110 }
111}
112
113impl From<MultipleOf> for Feature {
114 fn from(value: MultipleOf) -> Self {
115 Feature::MultipleOf(value)
116 }
117}
118
119impl_feature! {
120 #[cfg_attr(feature = "debug", derive(Debug))]
121 #[derive(Clone)]
122 pub struct Maximum(pub(super) NumberValue, Ident);
123}
124
125impl Validate for Maximum {
126 fn validate(&self, validator: impl Validator) -> Option<Diagnostics> {
127 match validator.is_valid() {
128 Err(error) => Some(Diagnostics::with_span(self.1.span(), format!("`maximum` error: {}", error))
129 .help("See more details: `http://json-schema.org/draft/2020-12/json-schema-validation.html#name-maximum`")),
130 _ => None,
131 }
132 }
133}
134
135impl Parse for Maximum {
136 fn parse(input: ParseStream, ident: Ident) -> syn::Result<Self>
137 where
138 Self: Sized,
139 {
140 parse_next_number_value(input).map(|number| Self(number, ident))
141 }
142}
143
144impl ToTokens for Maximum {
145 fn to_tokens(&self, tokens: &mut TokenStream) {
146 self.0.to_tokens(tokens);
147 }
148}
149
150impl From<Maximum> for Feature {
151 fn from(value: Maximum) -> Self {
152 Feature::Maximum(value)
153 }
154}
155
156impl_feature! {
157 #[cfg_attr(feature = "debug", derive(Debug))]
158 #[derive(Clone)]
159 pub struct Minimum(NumberValue, Ident);
160}
161
162impl Minimum {
163 pub fn new(value: f64, span: Span) -> Self {
164 Self(
165 NumberValue {
166 minus: value < 0.0,
167 lit: Literal::f64_suffixed(value),
168 },
169 Ident::new("empty", span),
170 )
171 }
172}
173
174impl Validate for Minimum {
175 fn validate(&self, validator: impl Validator) -> Option<Diagnostics> {
176 match validator.is_valid() {
177 Err(error) => Some(
178 Diagnostics::with_span(self.1.span(), format!("`minimum` error: {}", error))
179 .help("See more details: `http://json-schema.org/draft/2020-12/json-schema-validation.html#name-minimum`")
180 ),
181 _ => None,
182 }
183 }
184}
185
186impl Parse for Minimum {
187 fn parse(input: ParseStream, ident: Ident) -> syn::Result<Self>
188 where
189 Self: Sized,
190 {
191 parse_next_number_value(input).map(|number| Self(number, ident))
192 }
193}
194
195impl ToTokens for Minimum {
196 fn to_tokens(&self, tokens: &mut TokenStream) {
197 self.0.to_tokens(tokens);
198 }
199}
200
201impl From<Minimum> for Feature {
202 fn from(value: Minimum) -> Self {
203 Feature::Minimum(value)
204 }
205}
206
207impl_feature! {
208 #[cfg_attr(feature = "debug", derive(Debug))]
209 #[derive(Clone)]
210 pub struct ExclusiveMaximum(NumberValue, Ident);
211}
212
213impl Validate for ExclusiveMaximum {
214 fn validate(&self, validator: impl Validator) -> Option<Diagnostics> {
215 match validator.is_valid() {
216 Err(error) => Some(Diagnostics::with_span(self.1.span(), format!("`exclusive_maximum` error: {}", error))
217 .help("See more details: `http://json-schema.org/draft/2020-12/json-schema-validation.html#name-exclusivemaximum`")),
218 _ => None,
219 }
220 }
221}
222
223impl Parse for ExclusiveMaximum {
224 fn parse(input: ParseStream, ident: Ident) -> syn::Result<Self>
225 where
226 Self: Sized,
227 {
228 parse_next_number_value(input).map(|number| Self(number, ident))
229 }
230}
231
232impl ToTokens for ExclusiveMaximum {
233 fn to_tokens(&self, tokens: &mut TokenStream) {
234 self.0.to_tokens(tokens);
235 }
236}
237
238impl From<ExclusiveMaximum> for Feature {
239 fn from(value: ExclusiveMaximum) -> Self {
240 Feature::ExclusiveMaximum(value)
241 }
242}
243
244impl_feature! {
245 #[cfg_attr(feature = "debug", derive(Debug))]
246 #[derive(Clone)]
247 pub struct ExclusiveMinimum(NumberValue, Ident);
248}
249
250impl Validate for ExclusiveMinimum {
251 fn validate(&self, validator: impl Validator) -> Option<Diagnostics> {
252 match validator.is_valid() {
253 Err(error) => Some(Diagnostics::with_span(self.1.span(), format!("`exclusive_minimum` error: {}", error))
254 .help("See more details: `http://json-schema.org/draft/2020-12/json-schema-validation.html#name-exclusiveminimum`")),
255 _ => None,
256 }
257 }
258}
259
260impl Parse for ExclusiveMinimum {
261 fn parse(input: ParseStream, ident: Ident) -> syn::Result<Self>
262 where
263 Self: Sized,
264 {
265 parse_next_number_value(input).map(|number| Self(number, ident))
266 }
267}
268
269impl ToTokens for ExclusiveMinimum {
270 fn to_tokens(&self, tokens: &mut TokenStream) {
271 self.0.to_tokens(tokens);
272 }
273}
274
275impl From<ExclusiveMinimum> for Feature {
276 fn from(value: ExclusiveMinimum) -> Self {
277 Feature::ExclusiveMinimum(value)
278 }
279}
280
281impl_feature! {
282 #[cfg_attr(feature = "debug", derive(Debug))]
283 #[derive(Clone)]
284 pub struct MaxLength(pub(super) NumberValue, Ident);
285}
286
287impl Validate for MaxLength {
288 fn validate(&self, validator: impl Validator) -> Option<Diagnostics> {
289 match validator.is_valid() {
290 Err(error) => Some(Diagnostics::with_span(self.1.span(), format!("`max_length` error: {}", error))
291 .help("See more details: `http://json-schema.org/draft/2020-12/json-schema-validation.html#name-maxlength`")),
292 _ => None,
293 }
294 }
295}
296
297impl Parse for MaxLength {
298 fn parse(input: ParseStream, ident: Ident) -> syn::Result<Self>
299 where
300 Self: Sized,
301 {
302 parse_next_number_value(input).map(|number| Self(number, ident))
303 }
304}
305
306impl ToTokens for MaxLength {
307 fn to_tokens(&self, tokens: &mut TokenStream) {
308 self.0.to_tokens(tokens);
309 }
310}
311
312impl From<MaxLength> for Feature {
313 fn from(value: MaxLength) -> Self {
314 Feature::MaxLength(value)
315 }
316}
317
318impl_feature! {
319 #[cfg_attr(feature = "debug", derive(Debug))]
320 #[derive(Clone)]
321 pub struct MinLength(pub(super) NumberValue, Ident);
322}
323
324impl Validate for MinLength {
325 fn validate(&self, validator: impl Validator) -> Option<Diagnostics> {
326 match validator.is_valid() {
327 Err(error) => Some(Diagnostics::with_span(self.1.span(), format!("`min_length` error: {}", error))
328 .help("See more details: `http://json-schema.org/draft/2020-12/json-schema-validation.html#name-minlength`")),
329 _ => None,
330 }
331 }
332}
333
334impl Parse for MinLength {
335 fn parse(input: ParseStream, ident: Ident) -> syn::Result<Self>
336 where
337 Self: Sized,
338 {
339 parse_next_number_value(input).map(|number| Self(number, ident))
340 }
341}
342
343impl ToTokens for MinLength {
344 fn to_tokens(&self, tokens: &mut TokenStream) {
345 self.0.to_tokens(tokens);
346 }
347}
348
349impl From<MinLength> for Feature {
350 fn from(value: MinLength) -> Self {
351 Feature::MinLength(value)
352 }
353}
354
355impl_feature! {
356 #[cfg_attr(feature = "debug", derive(Debug))]
357 #[derive(Clone)]
358 pub struct Pattern(String, Ident);
359}
360
361impl Validate for Pattern {
362 fn validate(&self, validator: impl Validator) -> Option<Diagnostics> {
363 match validator.is_valid() {
364 Err(error) => Some(Diagnostics::with_span(self.1.span(), format!("`pattern` error: {}", error))
365 .help("See more details: `http://json-schema.org/draft/2020-12/json-schema-validation.html#name-pattern`")
366 ),
367 _ => None,
368 }
369 }
370}
371
372impl Parse for Pattern {
373 fn parse(input: ParseStream, ident: Ident) -> syn::Result<Self>
374 where
375 Self: Sized,
376 {
377 parse_utils::parse_next(input, || input.parse::<LitStr>())
378 .map(|pattern| Self(pattern.value(), ident))
379 }
380}
381
382impl ToTokens for Pattern {
383 fn to_tokens(&self, tokens: &mut TokenStream) {
384 self.0.to_tokens(tokens);
385 }
386}
387
388impl From<Pattern> for Feature {
389 fn from(value: Pattern) -> Self {
390 Feature::Pattern(value)
391 }
392}
393
394impl_feature! {
395 #[cfg_attr(feature = "debug", derive(Debug))]
396 #[derive(Clone)]
397 pub struct MaxItems(pub(super) NumberValue, Ident);
398}
399
400impl Validate for MaxItems {
401 fn validate(&self, validator: impl Validator) -> Option<Diagnostics> {
402 match validator.is_valid() {
403 Err(error) => Some(Diagnostics::with_span(self.1.span(), format!("`max_items` error: {}", error))
404 .help("See more details: `http://json-schema.org/draft/2020-12/json-schema-validation.html#name-maxitems")),
405 _ => None,
406 }
407 }
408}
409
410impl Parse for MaxItems {
411 fn parse(input: ParseStream, ident: Ident) -> syn::Result<Self>
412 where
413 Self: Sized,
414 {
415 parse_next_number_value(input).map(|number| Self(number, ident))
416 }
417}
418
419impl ToTokens for MaxItems {
420 fn to_tokens(&self, tokens: &mut TokenStream) {
421 self.0.to_tokens(tokens);
422 }
423}
424
425impl From<MaxItems> for Feature {
426 fn from(value: MaxItems) -> Self {
427 Feature::MaxItems(value)
428 }
429}
430
431impl_feature! {
432 #[cfg_attr(feature = "debug", derive(Debug))]
433 #[derive(Clone)]
434 pub struct MinItems(pub(super) NumberValue, Ident);
435}
436
437impl Validate for MinItems {
438 fn validate(&self, validator: impl Validator) -> Option<Diagnostics> {
439 match validator.is_valid() {
440 Err(error) => Some(Diagnostics::with_span(self.1.span(), format!("`min_items` error: {}", error))
441 .help("See more details: `http://json-schema.org/draft/2020-12/json-schema-validation.html#name-minitems")),
442 _ => None,
443 }
444 }
445}
446
447impl Parse for MinItems {
448 fn parse(input: ParseStream, ident: Ident) -> syn::Result<Self>
449 where
450 Self: Sized,
451 {
452 parse_next_number_value(input).map(|number| Self(number, ident))
453 }
454}
455
456impl ToTokens for MinItems {
457 fn to_tokens(&self, tokens: &mut TokenStream) {
458 self.0.to_tokens(tokens);
459 }
460}
461
462impl From<MinItems> for Feature {
463 fn from(value: MinItems) -> Self {
464 Feature::MinItems(value)
465 }
466}
467
468impl_feature! {
469 #[cfg_attr(feature = "debug", derive(Debug))]
470 #[derive(Clone)]
471 pub struct MaxProperties(NumberValue, ());
472}
473
474impl Parse for MaxProperties {
475 fn parse(input: ParseStream, _ident: Ident) -> syn::Result<Self>
476 where
477 Self: Sized,
478 {
479 parse_next_number_value(input).map(|number| Self(number, ()))
480 }
481}
482
483impl ToTokens for MaxProperties {
484 fn to_tokens(&self, tokens: &mut TokenStream) {
485 self.0.to_tokens(tokens);
486 }
487}
488
489impl From<MaxProperties> for Feature {
490 fn from(value: MaxProperties) -> Self {
491 Feature::MaxProperties(value)
492 }
493}
494
495impl_feature! {
496 #[cfg_attr(feature = "debug", derive(Debug))]
497 #[derive(Clone)]
498 pub struct MinProperties(NumberValue, ());
499}
500
501impl Parse for MinProperties {
502 fn parse(input: ParseStream, _ident: Ident) -> syn::Result<Self>
503 where
504 Self: Sized,
505 {
506 parse_next_number_value(input).map(|number| Self(number, ()))
507 }
508}
509
510impl ToTokens for MinProperties {
511 fn to_tokens(&self, tokens: &mut TokenStream) {
512 self.0.to_tokens(tokens);
513 }
514}
515
516impl From<MinProperties> for Feature {
517 fn from(value: MinProperties) -> Self {
518 Feature::MinProperties(value)
519 }
520}