Skip to main content

openapi_trait_shared/codegen/
types.rs

1use openapiv3::{IntegerFormat, NumberFormat, ReferenceOr, Schema, SchemaKind, StringFormat, Type};
2use proc_macro2::TokenStream;
3use quote::quote;
4
5/// Map an `OpenAPI` `Schema` (or `$ref`) to a Rust type `TokenStream`.
6///
7/// `required` controls whether the result is wrapped in `Option<T>`.
8#[must_use]
9pub fn schema_to_rust_type(ref_or: &ReferenceOr<Schema>, required: bool) -> TokenStream {
10    let inner = ref_or_to_inner_type(ref_or);
11    if required {
12        inner
13    } else {
14        quote! { ::core::option::Option<#inner> }
15    }
16}
17
18/// Convert a reference-or-schema to a Rust type token stream.
19fn ref_or_to_inner_type(ref_or: &ReferenceOr<Schema>) -> TokenStream {
20    match ref_or {
21        ReferenceOr::Reference { reference } => ref_to_ident(reference),
22        ReferenceOr::Item(schema) => schema_kind_to_type(&schema.schema_kind),
23    }
24}
25
26#[must_use]
27pub fn ref_to_ident(reference: &str) -> TokenStream {
28    // "#/components/schemas/Foo" -> Foo
29    let name = reference.rsplit('/').next().unwrap_or(reference);
30    let ident = quote::format_ident!("{}", name);
31    quote! { #ident }
32}
33
34/// Convert a schema kind to a Rust type token stream.
35fn schema_kind_to_type(kind: &SchemaKind) -> TokenStream {
36    match kind {
37        SchemaKind::Type(t) => primitive_type_to_rust(t),
38        // For compositions, fall back to serde_json::Value
39        SchemaKind::OneOf { .. }
40        | SchemaKind::AllOf { .. }
41        | SchemaKind::AnyOf { .. }
42        | SchemaKind::Not { .. }
43        | SchemaKind::Any(_) => {
44            quote! { ::serde_json::Value }
45        }
46    }
47}
48
49/// Convert a primitive `OpenAPI` type to a Rust type token stream.
50fn primitive_type_to_rust(t: &Type) -> TokenStream {
51    match t {
52        Type::Integer(i) => {
53            if i.format == openapiv3::VariantOrUnknownOrEmpty::Item(IntegerFormat::Int32) {
54                quote! { i32 }
55            } else {
56                quote! { i64 }
57            }
58        }
59        Type::Number(n) => {
60            if n.format == openapiv3::VariantOrUnknownOrEmpty::Item(NumberFormat::Float) {
61                quote! { f32 }
62            } else {
63                quote! { f64 }
64            }
65        }
66        Type::String(s) => {
67            // For string enums, the caller handles the dedicated enum type;
68            // here we just return String as a fallback.
69            if s.enumeration.is_empty() {
70                if matches!(
71                    &s.format,
72                    openapiv3::VariantOrUnknownOrEmpty::Item(StringFormat::Binary)
73                ) {
74                    quote! { ::std::vec::Vec<u8> }
75                } else {
76                    quote! { ::std::string::String }
77                }
78            } else {
79                quote! { ::std::string::String }
80            }
81        }
82        Type::Boolean(_) => quote! { bool },
83        Type::Array(a) => {
84            let item_ty = a.items.as_ref().map_or_else(
85                || quote! { ::serde_json::Value },
86                |items| ref_or_to_inner_type(&items.clone().unbox()),
87            );
88            quote! { ::std::vec::Vec<#item_ty> }
89        }
90        Type::Object(_) => quote! { ::serde_json::Value },
91    }
92}
93
94/// Returns true when the schema is a string with enumeration values.
95#[must_use]
96pub fn is_string_enum(schema: &Schema) -> bool {
97    if let SchemaKind::Type(Type::String(s)) = &schema.schema_kind {
98        !s.enumeration.is_empty()
99    } else {
100        false
101    }
102}
103
104/// Extract enum values from a string schema (skipping None entries).
105#[must_use]
106pub fn string_enum_values(schema: &Schema) -> Vec<String> {
107    if let SchemaKind::Type(Type::String(s)) = &schema.schema_kind {
108        s.enumeration.iter().filter_map(Clone::clone).collect()
109    } else {
110        vec![]
111    }
112}