openapi_trait_shared/codegen/
types.rs1use heck::ToPascalCase;
2use openapiv3::{
3 AdditionalProperties, IntegerFormat, NumberFormat, ObjectType, ReferenceOr, Schema, SchemaKind,
4 StringFormat, Type,
5};
6use proc_macro2::TokenStream;
7use quote::{format_ident, quote};
8
9#[must_use]
18pub fn schema_to_rust_type(ref_or: &ReferenceOr<Schema>, required: bool) -> TokenStream {
19 let mut sink: Vec<TokenStream> = Vec::new();
20 schema_to_rust_type_ctx(ref_or, required, None, &mut sink)
21 }
23
24#[must_use]
31pub fn schema_to_rust_type_ctx(
32 ref_or: &ReferenceOr<Schema>,
33 required: bool,
34 parent_name: Option<&str>,
35 inline_types: &mut Vec<TokenStream>,
36) -> TokenStream {
37 let inner = ref_or_to_inner_type_ctx(ref_or, parent_name, inline_types);
38 if required {
39 inner
40 } else {
41 quote! { ::core::option::Option<#inner> }
42 }
43}
44
45fn ref_or_to_inner_type_ctx(
48 ref_or: &ReferenceOr<Schema>,
49 parent_name: Option<&str>,
50 inline_types: &mut Vec<TokenStream>,
51) -> TokenStream {
52 match ref_or {
53 ReferenceOr::Reference { reference } => ref_to_ident(reference),
54 ReferenceOr::Item(schema) => schema_kind_to_type(schema, parent_name, inline_types),
55 }
56}
57
58#[must_use]
59pub fn ref_to_ident(reference: &str) -> TokenStream {
60 let name = reference.rsplit('/').next().unwrap_or(reference);
62 let ident = format_ident!("{}", name.to_pascal_case());
63 quote! { #ident }
64}
65
66fn schema_kind_to_type(
69 schema: &Schema,
70 parent_name: Option<&str>,
71 inline_types: &mut Vec<TokenStream>,
72) -> TokenStream {
73 match &schema.schema_kind {
74 SchemaKind::Type(Type::Object(obj)) => {
75 object_schema_to_type(schema, obj, parent_name, inline_types)
76 }
77 SchemaKind::Type(t) => primitive_type_to_rust(t, parent_name, inline_types),
78 SchemaKind::OneOf { one_of } => {
79 synthesize_inline_composition(parent_name, inline_types, |name, sink| {
80 super::compositions::generate_one_of(
81 name,
82 one_of,
83 schema.schema_data.discriminator.as_ref(),
84 schema.schema_data.description.as_ref(),
85 sink,
86 )
87 })
88 }
89 SchemaKind::AnyOf { any_of } => {
90 synthesize_inline_composition(parent_name, inline_types, |name, sink| {
91 super::compositions::generate_any_of(
92 name,
93 any_of,
94 schema.schema_data.description.as_ref(),
95 sink,
96 )
97 })
98 }
99 SchemaKind::AllOf { all_of } => {
100 synthesize_inline_composition(parent_name, inline_types, |name, sink| {
101 super::compositions::generate_all_of(
102 name,
103 all_of,
104 schema.schema_data.description.as_ref(),
105 sink,
106 )
107 })
108 }
109 SchemaKind::Not { .. } | SchemaKind::Any(_) => {
110 quote! { ::serde_json::Value }
112 }
113 }
114}
115
116fn synthesize_inline_composition(
120 parent_name: Option<&str>,
121 inline_types: &mut Vec<TokenStream>,
122 generate: impl FnOnce(&str, &mut Vec<TokenStream>) -> TokenStream,
123) -> TokenStream {
124 parent_name.map_or_else(
125 || quote! { ::serde_json::Value },
126 |name| {
127 let tokens = generate(name, inline_types);
128 inline_types.push(tokens);
129 let ident = format_ident!("{}", name.to_pascal_case());
130 quote! { #ident }
131 },
132 )
133}
134
135fn primitive_type_to_rust(
137 t: &Type,
138 parent_name: Option<&str>,
139 inline_types: &mut Vec<TokenStream>,
140) -> TokenStream {
141 match t {
142 Type::Integer(i) => {
143 if i.format == openapiv3::VariantOrUnknownOrEmpty::Item(IntegerFormat::Int32) {
144 quote! { i32 }
145 } else {
146 quote! { i64 }
147 }
148 }
149 Type::Number(n) => {
150 if n.format == openapiv3::VariantOrUnknownOrEmpty::Item(NumberFormat::Float) {
151 quote! { f32 }
152 } else {
153 quote! { f64 }
154 }
155 }
156 Type::String(s) => {
157 if s.enumeration.is_empty() {
160 if matches!(
161 &s.format,
162 openapiv3::VariantOrUnknownOrEmpty::Item(StringFormat::Binary)
163 ) {
164 quote! { ::std::vec::Vec<u8> }
165 } else {
166 quote! { ::std::string::String }
167 }
168 } else {
169 quote! { ::std::string::String }
170 }
171 }
172 Type::Boolean(_) => quote! { bool },
173 Type::Array(a) => {
174 let item_ty = a.items.as_ref().map_or_else(
175 || quote! { ::serde_json::Value },
176 |items| ref_or_to_inner_type_ctx(&items.clone().unbox(), parent_name, inline_types),
177 );
178 quote! { ::std::vec::Vec<#item_ty> }
179 }
180 Type::Object(_) => quote! { ::serde_json::Value },
183 }
184}
185
186fn object_schema_to_type(
196 schema: &Schema,
197 obj: &ObjectType,
198 parent_name: Option<&str>,
199 inline_types: &mut Vec<TokenStream>,
200) -> TokenStream {
201 if !obj.properties.is_empty() {
202 return synthesize_inline_composition(parent_name, inline_types, |name, sink| {
203 super::schemas::generate_object_struct(name, schema, obj, sink)
204 });
205 }
206 if let Some(ap) = &obj.additional_properties {
207 if let Some(value_ty) = additional_properties_value_type(ap, parent_name, inline_types) {
208 return quote! {
209 ::std::collections::HashMap<::std::string::String, #value_ty>
210 };
211 }
212 }
213 quote! { ::serde_json::Value }
214}
215
216#[must_use]
224pub fn additional_properties_value_type(
225 ap: &AdditionalProperties,
226 parent_name: Option<&str>,
227 inline_types: &mut Vec<TokenStream>,
228) -> Option<TokenStream> {
229 match ap {
230 AdditionalProperties::Any(false) => None,
231 AdditionalProperties::Any(true) => Some(quote! { ::serde_json::Value }),
232 AdditionalProperties::Schema(schema) => {
233 Some(ref_or_to_inner_type_ctx(schema, parent_name, inline_types))
234 }
235 }
236}
237
238#[must_use]
240pub fn is_string_enum(schema: &Schema) -> bool {
241 if let SchemaKind::Type(Type::String(s)) = &schema.schema_kind {
242 !s.enumeration.is_empty()
243 } else {
244 false
245 }
246}
247
248#[must_use]
250pub fn string_enum_values(schema: &Schema) -> Vec<String> {
251 if let SchemaKind::Type(Type::String(s)) = &schema.schema_kind {
252 s.enumeration.iter().filter_map(Clone::clone).collect()
253 } else {
254 vec![]
255 }
256}