utoipa_gen/
schema_type.rs

1use proc_macro2::TokenStream;
2use quote::{quote, ToTokens};
3use syn::spanned::Spanned;
4use syn::{parse::Parse, Error, Ident, LitStr, Path};
5
6use crate::{Diagnostics, ToTokensDiagnostics};
7
8/// Represents data type of [`Schema`].
9#[cfg_attr(feature = "debug", derive(Debug))]
10#[allow(dead_code)]
11pub enum SchemaTypeInner {
12    /// Generic schema type allows "properties" with custom types
13    Object,
14    /// Indicates string type of content.
15    String,
16    /// Indicates integer type of content.    
17    Integer,
18    /// Indicates floating point number type of content.
19    Number,
20    /// Indicates boolean type of content.
21    Boolean,
22    /// Indicates array type of content.
23    Array,
24    /// Null type. Used together with other type to indicate nullable values.
25    Null,
26}
27
28impl ToTokens for SchemaTypeInner {
29    fn to_tokens(&self, tokens: &mut TokenStream) {
30        let ty = match self {
31            Self::Object => quote! { utoipa::openapi::schema::Type::Object },
32            Self::String => quote! { utoipa::openapi::schema::Type::String },
33            Self::Integer => quote! { utoipa::openapi::schema::Type::Integer },
34            Self::Number => quote! { utoipa::openapi::schema::Type::Number },
35            Self::Boolean => quote! { utoipa::openapi::schema::Type::Boolean },
36            Self::Array => quote! { utoipa::openapi::schema::Type::Array },
37            Self::Null => quote! { utoipa::openapi::schema::Type::Null },
38        };
39        tokens.extend(ty)
40    }
41}
42
43/// Tokenizes OpenAPI data type correctly according to the Rust type
44#[cfg_attr(feature = "debug", derive(Debug))]
45pub struct SchemaType<'a> {
46    pub path: std::borrow::Cow<'a, syn::Path>,
47    pub nullable: bool,
48}
49
50impl SchemaType<'_> {
51    fn last_segment_to_string(&self) -> String {
52        self.path
53            .segments
54            .last()
55            .expect("Expected at least one segment is_integer")
56            .ident
57            .to_string()
58    }
59
60    pub fn is_value(&self) -> bool {
61        matches!(&*self.last_segment_to_string(), "Value")
62    }
63
64    /// Check whether type is known to be primitive in which case returns true.
65    pub fn is_primitive(&self) -> bool {
66        let SchemaType { path, .. } = self;
67        let last_segment = match path.segments.last() {
68            Some(segment) => segment,
69            None => return false,
70        };
71        let name = &*last_segment.ident.to_string();
72
73        #[cfg(not(any(
74            feature = "chrono",
75            feature = "decimal",
76            feature = "decimal_float",
77            feature = "rocket_extras",
78            feature = "uuid",
79            feature = "ulid",
80            feature = "url",
81            feature = "time",
82        )))]
83        {
84            is_primitive(name)
85        }
86
87        #[cfg(any(
88            feature = "chrono",
89            feature = "decimal",
90            feature = "decimal_float",
91            feature = "rocket_extras",
92            feature = "uuid",
93            feature = "ulid",
94            feature = "url",
95            feature = "time",
96        ))]
97        {
98            let mut primitive = is_primitive(name);
99
100            #[cfg(feature = "chrono")]
101            if !primitive {
102                primitive = is_primitive_chrono(name);
103            }
104
105            #[cfg(any(feature = "decimal", feature = "decimal_float"))]
106            if !primitive {
107                primitive = is_primitive_rust_decimal(name);
108            }
109
110            #[cfg(feature = "rocket_extras")]
111            if !primitive {
112                primitive = matches!(name, "PathBuf");
113            }
114
115            #[cfg(feature = "uuid")]
116            if !primitive {
117                primitive = matches!(name, "Uuid");
118            }
119
120            #[cfg(feature = "ulid")]
121            if !primitive {
122                primitive = matches!(name, "Ulid");
123            }
124
125            #[cfg(feature = "url")]
126            if !primitive {
127                primitive = matches!(name, "Url");
128            }
129
130            #[cfg(feature = "time")]
131            if !primitive {
132                primitive = matches!(
133                    name,
134                    "Date" | "PrimitiveDateTime" | "OffsetDateTime" | "Duration"
135                );
136            }
137
138            #[cfg(feature = "jiff_0_2")]
139            if !primitive {
140                primitive = matches!(name, "Zoned" | "Date");
141            }
142
143            primitive
144        }
145    }
146
147    pub fn is_integer(&self) -> bool {
148        matches!(
149            &*self.last_segment_to_string(),
150            "i8" | "i16"
151                | "i32"
152                | "i64"
153                | "i128"
154                | "isize"
155                | "u8"
156                | "u16"
157                | "u32"
158                | "u64"
159                | "u128"
160                | "usize"
161        )
162    }
163
164    pub fn is_unsigned_integer(&self) -> bool {
165        matches!(
166            &*self.last_segment_to_string(),
167            "u8" | "u16" | "u32" | "u64" | "u128" | "usize"
168        )
169    }
170
171    pub fn is_number(&self) -> bool {
172        match &*self.last_segment_to_string() {
173            "f32" | "f64" => true,
174            _ if self.is_integer() => true,
175            _ => false,
176        }
177    }
178
179    pub fn is_string(&self) -> bool {
180        matches!(&*self.last_segment_to_string(), "str" | "String")
181    }
182
183    pub fn is_byte(&self) -> bool {
184        matches!(&*self.last_segment_to_string(), "u8")
185    }
186}
187
188#[inline]
189fn is_primitive(name: &str) -> bool {
190    matches!(
191        name,
192        "String"
193            | "str"
194            | "char"
195            | "bool"
196            | "usize"
197            | "u8"
198            | "u16"
199            | "u32"
200            | "u64"
201            | "u128"
202            | "isize"
203            | "i8"
204            | "i16"
205            | "i32"
206            | "i64"
207            | "i128"
208            | "f32"
209            | "f64"
210    )
211}
212
213#[inline]
214#[cfg(feature = "chrono")]
215fn is_primitive_chrono(name: &str) -> bool {
216    matches!(
217        name,
218        "DateTime" | "Date" | "NaiveDate" | "NaiveTime" | "Duration" | "NaiveDateTime"
219    )
220}
221
222#[inline]
223#[cfg(any(feature = "decimal", feature = "decimal_float"))]
224fn is_primitive_rust_decimal(name: &str) -> bool {
225    matches!(name, "Decimal")
226}
227
228impl ToTokensDiagnostics for SchemaType<'_> {
229    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) -> Result<(), Diagnostics> {
230        let last_segment = self.path.segments.last().ok_or_else(|| {
231            Diagnostics::with_span(
232                self.path.span(),
233                "schema type should have at least one segment in the path",
234            )
235        })?;
236        let name = &*last_segment.ident.to_string();
237
238        fn schema_type_tokens(
239            tokens: &mut TokenStream,
240            schema_type: SchemaTypeInner,
241            nullable: bool,
242        ) {
243            if nullable {
244                tokens.extend(quote! {
245                    {
246                        use std::iter::FromIterator;
247                        utoipa::openapi::schema::SchemaType::from_iter([
248                            #schema_type,
249                            utoipa::openapi::schema::Type::Null
250                        ])
251                    }
252                })
253            } else {
254                tokens.extend(quote! { utoipa::openapi::schema::SchemaType::new(#schema_type)});
255            }
256        }
257
258        match name {
259            "String" | "str" | "char" => {
260                schema_type_tokens(tokens, SchemaTypeInner::String, self.nullable)
261            }
262
263            "bool" => schema_type_tokens(tokens, SchemaTypeInner::Boolean, self.nullable),
264
265            "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64"
266            | "u128" | "usize" => {
267                schema_type_tokens(tokens, SchemaTypeInner::Integer, self.nullable)
268            }
269            "f32" | "f64" => schema_type_tokens(tokens, SchemaTypeInner::Number, self.nullable),
270
271            #[cfg(feature = "chrono")]
272            "DateTime" | "NaiveDateTime" | "NaiveDate" | "NaiveTime" => {
273                schema_type_tokens(tokens, SchemaTypeInner::String, self.nullable)
274            }
275
276            #[cfg(any(feature = "chrono", feature = "time", feature = "jiff_0_2"))]
277            "Date" | "Duration" => {
278                schema_type_tokens(tokens, SchemaTypeInner::String, self.nullable)
279            }
280
281            #[cfg(feature = "decimal")]
282            "Decimal" => schema_type_tokens(tokens, SchemaTypeInner::String, self.nullable),
283
284            #[cfg(feature = "decimal_float")]
285            "Decimal" => schema_type_tokens(tokens, SchemaTypeInner::Number, self.nullable),
286
287            #[cfg(feature = "rocket_extras")]
288            "PathBuf" => schema_type_tokens(tokens, SchemaTypeInner::String, self.nullable),
289
290            #[cfg(feature = "uuid")]
291            "Uuid" => schema_type_tokens(tokens, SchemaTypeInner::String, self.nullable),
292
293            #[cfg(feature = "ulid")]
294            "Ulid" => schema_type_tokens(tokens, SchemaTypeInner::String, self.nullable),
295
296            #[cfg(feature = "url")]
297            "Url" => schema_type_tokens(tokens, SchemaTypeInner::String, self.nullable),
298
299            #[cfg(feature = "time")]
300            "PrimitiveDateTime" | "OffsetDateTime" => {
301                schema_type_tokens(tokens, SchemaTypeInner::String, self.nullable)
302            }
303            #[cfg(feature = "jiff_0_2")]
304            "Zoned" => schema_type_tokens(tokens, SchemaTypeInner::String, self.nullable),
305            _ => schema_type_tokens(tokens, SchemaTypeInner::Object, self.nullable),
306        };
307
308        Ok(())
309    }
310}
311
312/// [`Parse`] and [`ToTokens`] implementation for [`utoipa::openapi::schema::SchemaFormat`].
313#[derive(Clone)]
314#[cfg_attr(feature = "debug", derive(Debug))]
315pub enum KnownFormat {
316    #[cfg(feature = "non_strict_integers")]
317    Int8,
318    #[cfg(feature = "non_strict_integers")]
319    Int16,
320    Int32,
321    Int64,
322    #[cfg(feature = "non_strict_integers")]
323    UInt8,
324    #[cfg(feature = "non_strict_integers")]
325    UInt16,
326    #[cfg(feature = "non_strict_integers")]
327    UInt32,
328    #[cfg(feature = "non_strict_integers")]
329    UInt64,
330    Float,
331    Double,
332    Byte,
333    Binary,
334    Date,
335    DateTime,
336    Duration,
337    Password,
338    #[cfg(feature = "uuid")]
339    Uuid,
340    #[cfg(feature = "ulid")]
341    Ulid,
342    #[cfg(feature = "url")]
343    Uri,
344    #[cfg(feature = "url")]
345    UriReference,
346    #[cfg(feature = "url")]
347    Iri,
348    #[cfg(feature = "url")]
349    IriReference,
350    Email,
351    IdnEmail,
352    Hostname,
353    IdnHostname,
354    Ipv4,
355    Ipv6,
356    UriTemplate,
357    JsonPointer,
358    RelativeJsonPointer,
359    Regex,
360    /// Custom format is reserved only for manual entry.
361    Custom(String),
362    /// This is not tokenized, but is present for purpose of having some format in
363    /// case we do not know the format. E.g. We cannot determine the format based on type path.
364    #[allow(unused)]
365    Unknown,
366}
367
368impl KnownFormat {
369    pub fn from_path(path: &syn::Path) -> Result<Self, Diagnostics> {
370        let last_segment = path.segments.last().ok_or_else(|| {
371            Diagnostics::with_span(
372                path.span(),
373                "type should have at least one segment in the path",
374            )
375        })?;
376        let name = &*last_segment.ident.to_string();
377
378        let variant = match name {
379            #[cfg(feature = "non_strict_integers")]
380            "i8" => Self::Int8,
381            #[cfg(feature = "non_strict_integers")]
382            "u8" => Self::UInt8,
383            #[cfg(feature = "non_strict_integers")]
384            "i16" => Self::Int16,
385            #[cfg(feature = "non_strict_integers")]
386            "u16" => Self::UInt16,
387            #[cfg(feature = "non_strict_integers")]
388            "u32" => Self::UInt32,
389            #[cfg(feature = "non_strict_integers")]
390            "u64" => Self::UInt64,
391
392            #[cfg(not(feature = "non_strict_integers"))]
393            "i8" | "i16" | "u8" | "u16" | "u32" => Self::Int32,
394
395            #[cfg(not(feature = "non_strict_integers"))]
396            "u64" => Self::Int64,
397
398            "i32" => Self::Int32,
399            "i64" => Self::Int64,
400            "f32" => Self::Float,
401            "f64" => Self::Double,
402
403            #[cfg(feature = "chrono")]
404            "NaiveDate" => Self::Date,
405
406            #[cfg(feature = "chrono")]
407            "DateTime" | "NaiveDateTime" => Self::DateTime,
408
409            #[cfg(any(feature = "chrono", feature = "time", feature = "jiff_0_2"))]
410            "Date" => Self::Date,
411
412            #[cfg(feature = "decimal_float")]
413            "Decimal" => Self::Double,
414
415            #[cfg(feature = "uuid")]
416            "Uuid" => Self::Uuid,
417
418            #[cfg(feature = "ulid")]
419            "Ulid" => Self::Ulid,
420
421            #[cfg(feature = "url")]
422            "Url" => Self::Uri,
423
424            #[cfg(feature = "time")]
425            "PrimitiveDateTime" | "OffsetDateTime" => Self::DateTime,
426
427            #[cfg(feature = "jiff_0_2")]
428            "Zoned" => Self::DateTime,
429            _ => Self::Unknown,
430        };
431
432        Ok(variant)
433    }
434
435    pub fn is_known_format(&self) -> bool {
436        !matches!(self, Self::Unknown)
437    }
438
439    fn get_allowed_formats() -> String {
440        let default_formats = [
441            "Int32",
442            "Int64",
443            "Float",
444            "Double",
445            "Byte",
446            "Binary",
447            "Date",
448            "DateTime",
449            "Duration",
450            "Password",
451            #[cfg(feature = "uuid")]
452            "Uuid",
453            #[cfg(feature = "ulid")]
454            "Ulid",
455            #[cfg(feature = "url")]
456            "Uri",
457            #[cfg(feature = "url")]
458            "UriReference",
459            #[cfg(feature = "url")]
460            "Iri",
461            #[cfg(feature = "url")]
462            "IriReference",
463            "Email",
464            "IdnEmail",
465            "Hostname",
466            "IdnHostname",
467            "Ipv4",
468            "Ipv6",
469            "UriTemplate",
470            "JsonPointer",
471            "RelativeJsonPointer",
472            "Regex",
473        ];
474        #[cfg(feature = "non_strict_integers")]
475        let non_strict_integer_formats = [
476            "Int8", "Int16", "Int32", "Int64", "UInt8", "UInt16", "UInt32", "UInt64",
477        ];
478
479        #[cfg(feature = "non_strict_integers")]
480        let formats = {
481            let mut formats = default_formats
482                .into_iter()
483                .chain(non_strict_integer_formats)
484                .collect::<Vec<_>>();
485            formats.sort_unstable();
486            formats.join(", ")
487        };
488        #[cfg(not(feature = "non_strict_integers"))]
489        let formats = {
490            let formats = default_formats.into_iter().collect::<Vec<_>>();
491            formats.join(", ")
492        };
493
494        formats
495    }
496}
497
498impl Parse for KnownFormat {
499    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
500        let formats = KnownFormat::get_allowed_formats();
501
502        let lookahead = input.lookahead1();
503        if lookahead.peek(Ident) {
504            let format = input.parse::<Ident>()?;
505            let name = &*format.to_string();
506
507            match name {
508                #[cfg(feature = "non_strict_integers")]
509                "Int8" => Ok(Self::Int8),
510                #[cfg(feature = "non_strict_integers")]
511                "Int16" => Ok(Self::Int16),
512                "Int32" => Ok(Self::Int32),
513                "Int64" => Ok(Self::Int64),
514                #[cfg(feature = "non_strict_integers")]
515                "UInt8" => Ok(Self::UInt8),
516                #[cfg(feature = "non_strict_integers")]
517                "UInt16" => Ok(Self::UInt16),
518                #[cfg(feature = "non_strict_integers")]
519                "UInt32" => Ok(Self::UInt32),
520                #[cfg(feature = "non_strict_integers")]
521                "UInt64" => Ok(Self::UInt64),
522                "Float" => Ok(Self::Float),
523                "Double" => Ok(Self::Double),
524                "Byte" => Ok(Self::Byte),
525                "Binary" => Ok(Self::Binary),
526                "Date" => Ok(Self::Date),
527                "DateTime" => Ok(Self::DateTime),
528                "Duration" => Ok(Self::Duration),
529                "Password" => Ok(Self::Password),
530                #[cfg(feature = "uuid")]
531                "Uuid" => Ok(Self::Uuid),
532                #[cfg(feature = "ulid")]
533                "Ulid" => Ok(Self::Ulid),
534                #[cfg(feature = "url")]
535                "Uri" => Ok(Self::Uri),
536                #[cfg(feature = "url")]
537                "UriReference" => Ok(Self::UriReference),
538                #[cfg(feature = "url")]
539                "Iri" => Ok(Self::Iri),
540                #[cfg(feature = "url")]
541                "IriReference" => Ok(Self::IriReference),
542                "Email" => Ok(Self::Email),
543                "IdnEmail" => Ok(Self::IdnEmail),
544                "Hostname" => Ok(Self::Hostname),
545                "IdnHostname" => Ok(Self::IdnHostname),
546                "Ipv4" => Ok(Self::Ipv4),
547                "Ipv6" => Ok(Self::Ipv6),
548                "UriTemplate" => Ok(Self::UriTemplate),
549                "JsonPointer" => Ok(Self::JsonPointer),
550                "RelativeJsonPointer" => Ok(Self::RelativeJsonPointer),
551                "Regex" => Ok(Self::Regex),
552                _ => Err(Error::new(
553                    format.span(),
554                    format!("unexpected format: {name}, expected one of: {formats}"),
555                )),
556            }
557        } else if lookahead.peek(LitStr) {
558            let value = input.parse::<LitStr>()?.value();
559            Ok(Self::Custom(value))
560        } else {
561            Err(lookahead.error())
562        }
563    }
564}
565
566impl ToTokens for KnownFormat {
567    fn to_tokens(&self, tokens: &mut TokenStream) {
568        match self {
569            #[cfg(feature = "non_strict_integers")]
570            Self::Int8 => tokens.extend(quote! {utoipa::openapi::schema::SchemaFormat::KnownFormat(utoipa::openapi::schema::KnownFormat::Int8)}),
571            #[cfg(feature = "non_strict_integers")]
572            Self::Int16 => tokens.extend(quote! {utoipa::openapi::schema::SchemaFormat::KnownFormat(utoipa::openapi::schema::KnownFormat::Int16)}),
573            Self::Int32 => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
574                utoipa::openapi::schema::KnownFormat::Int32
575            ))),
576            Self::Int64 => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
577                utoipa::openapi::schema::KnownFormat::Int64
578            ))),
579            #[cfg(feature = "non_strict_integers")]
580            Self::UInt8 => tokens.extend(quote! {utoipa::openapi::schema::SchemaFormat::KnownFormat(utoipa::openapi::schema::KnownFormat::UInt8)}),
581            #[cfg(feature = "non_strict_integers")]
582            Self::UInt16 => tokens.extend(quote! {utoipa::openapi::schema::SchemaFormat::KnownFormat(utoipa::openapi::schema::KnownFormat::UInt16)}),
583            #[cfg(feature = "non_strict_integers")]
584            Self::UInt32 => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
585                utoipa::openapi::schema::KnownFormat::UInt32
586            ))),
587            #[cfg(feature = "non_strict_integers")]
588            Self::UInt64 => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
589                utoipa::openapi::schema::KnownFormat::UInt64
590            ))),
591            Self::Float => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
592                utoipa::openapi::schema::KnownFormat::Float
593            ))),
594            Self::Double => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
595                utoipa::openapi::schema::KnownFormat::Double
596            ))),
597            Self::Byte => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
598                utoipa::openapi::schema::KnownFormat::Byte
599            ))),
600            Self::Binary => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
601                utoipa::openapi::schema::KnownFormat::Binary
602            ))),
603            Self::Date => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
604                utoipa::openapi::schema::KnownFormat::Date
605            ))),
606            Self::DateTime => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
607                utoipa::openapi::schema::KnownFormat::DateTime
608            ))),
609            Self::Duration => tokens.extend(quote! {utoipa::openapi::schema::SchemaFormat::KnownFormat(
610                utoipa::openapi::schema::KnownFormat::Duration
611            ) }),
612            Self::Password => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
613                utoipa::openapi::schema::KnownFormat::Password
614            ))),
615            #[cfg(feature = "uuid")]
616            Self::Uuid => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
617                utoipa::openapi::schema::KnownFormat::Uuid
618            ))),
619            #[cfg(feature = "ulid")]
620            Self::Ulid => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
621                utoipa::openapi::schema::KnownFormat::Ulid
622            ))),
623            #[cfg(feature = "url")]
624            Self::Uri => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
625                utoipa::openapi::schema::KnownFormat::Uri
626            ))),
627            #[cfg(feature = "url")]
628            Self::UriReference => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
629                utoipa::openapi::schema::KnownFormat::UriReference
630            ))),
631            #[cfg(feature = "url")]
632            Self::Iri => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
633                utoipa::openapi::schema::KnownFormat::Iri
634            ))),
635            #[cfg(feature = "url")]
636            Self::IriReference => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
637                utoipa::openapi::schema::KnownFormat::IriReference
638            ))),
639            Self::Email => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
640                utoipa::openapi::schema::KnownFormat::Email
641            ))),
642            Self::IdnEmail => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
643                utoipa::openapi::schema::KnownFormat::IdnEmail
644            ))),
645            Self::Hostname => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
646                utoipa::openapi::schema::KnownFormat::Hostname
647            ))),
648            Self::IdnHostname => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
649                utoipa::openapi::schema::KnownFormat::IdnHostname
650            ))),
651            Self::Ipv4 => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
652                utoipa::openapi::schema::KnownFormat::Ipv4
653            ))),
654            Self::Ipv6 => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
655                utoipa::openapi::schema::KnownFormat::Ipv6
656            ))),
657            Self::UriTemplate => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
658                utoipa::openapi::schema::KnownFormat::UriTemplate
659            ))),
660            Self::JsonPointer => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
661                utoipa::openapi::schema::KnownFormat::JsonPointer
662            ))),
663            Self::RelativeJsonPointer => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
664                utoipa::openapi::schema::KnownFormat::RelativeJsonPointer
665            ))),
666            Self::Regex => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
667                utoipa::openapi::schema::KnownFormat::Regex
668            ))),
669            Self::Custom(value) => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::Custom(
670                String::from(#value)
671            ))),
672            Self::Unknown => (), // unknown we just skip it
673        };
674    }
675}
676
677#[cfg_attr(feature = "debug", derive(Debug))]
678pub struct PrimitiveType {
679    pub ty: syn::Type,
680}
681
682impl PrimitiveType {
683    pub fn new(path: &Path) -> Option<PrimitiveType> {
684        let last_segment = path.segments.last().unwrap_or_else(|| {
685            panic!(
686                "Path for DefaultType must have at least one segment: `{path}`",
687                path = path.to_token_stream()
688            )
689        });
690
691        let name = &*last_segment.ident.to_string();
692
693        let ty: syn::Type = match name {
694            "String" | "str" | "char" => syn::parse_quote!(#path),
695
696            "bool" => syn::parse_quote!(#path),
697
698            "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64"
699            | "u128" | "usize" => syn::parse_quote!(#path),
700            "f32" | "f64" => syn::parse_quote!(#path),
701
702            #[cfg(feature = "chrono")]
703            "DateTime" | "NaiveDateTime" | "NaiveDate" | "NaiveTime" => {
704                syn::parse_quote!(String)
705            }
706
707            #[cfg(any(feature = "chrono", feature = "time", feature = "jiff_0_2"))]
708            "Date" => {
709                syn::parse_quote!(String)
710            }
711
712            #[cfg(any(feature = "chrono", feature = "time"))]
713            "Duration" => {
714                syn::parse_quote!(String)
715            }
716
717            #[cfg(feature = "decimal")]
718            "Decimal" => {
719                syn::parse_quote!(String)
720            }
721
722            #[cfg(feature = "decimal_float")]
723            "Decimal" => {
724                syn::parse_quote!(f64)
725            }
726
727            #[cfg(feature = "rocket_extras")]
728            "PathBuf" => {
729                syn::parse_quote!(String)
730            }
731
732            #[cfg(feature = "uuid")]
733            "Uuid" => {
734                syn::parse_quote!(String)
735            }
736
737            #[cfg(feature = "ulid")]
738            "Ulid" => {
739                syn::parse_quote!(String)
740            }
741
742            #[cfg(feature = "url")]
743            "Url" => {
744                syn::parse_quote!(String)
745            }
746
747            #[cfg(feature = "time")]
748            "PrimitiveDateTime" | "OffsetDateTime" => {
749                syn::parse_quote!(String)
750            }
751
752            #[cfg(feature = "jiff_0_2")]
753            "Zoned" => {
754                syn::parse_quote!(String)
755            }
756            _ => {
757                // not a primitive type
758                return None;
759            }
760        };
761
762        Some(Self { ty })
763    }
764}