openapi_trait_shared/codegen/
compositions.rs1use heck::ToPascalCase;
21use openapiv3::{Discriminator, ReferenceOr, Schema, SchemaKind, Type};
22use proc_macro2::TokenStream;
23use quote::{format_ident, quote};
24
25use super::schemas::{doc_attr, object_field_tokens};
26use super::types::{ref_to_ident, schema_to_rust_type_ctx};
27
28#[must_use]
33pub fn generate_one_of(
34 name: &str,
35 variants: &[ReferenceOr<Schema>],
36 discriminator: Option<&Discriminator>,
37 description: Option<&String>,
38 inline_types: &mut Vec<TokenStream>,
39) -> TokenStream {
40 generate_enum(name, variants, discriminator, description, inline_types)
41}
42
43#[must_use]
49pub fn generate_any_of(
50 name: &str,
51 variants: &[ReferenceOr<Schema>],
52 description: Option<&String>,
53 inline_types: &mut Vec<TokenStream>,
54) -> TokenStream {
55 generate_enum(name, variants, None, description, inline_types)
56}
57
58#[must_use]
65pub fn generate_all_of(
66 name: &str,
67 variants: &[ReferenceOr<Schema>],
68 description: Option<&String>,
69 inline_types: &mut Vec<TokenStream>,
70) -> TokenStream {
71 let ident = format_ident!("{}", name.to_pascal_case());
72 let doc = doc_attr(&description.cloned());
73
74 let mut fields: Vec<TokenStream> = Vec::new();
75 let mut ref_field_counter = 0usize;
76
77 for branch in variants {
78 match branch {
79 ReferenceOr::Reference { reference } => {
80 ref_field_counter += 1;
81 let field_ident = format_ident!("inner_{}", ref_field_counter);
82 let ty = ref_to_ident(reference);
83 fields.push(quote! {
84 #[serde(flatten)]
85 pub #field_ident: #ty,
86 });
87 }
88 ReferenceOr::Item(schema) => {
89 if let SchemaKind::Type(Type::Object(obj)) = &schema.schema_kind {
90 for (prop_name, prop_ref) in &obj.properties {
91 let is_required = obj.required.iter().any(|r| r == prop_name);
92 fields.push(object_field_tokens(
93 prop_name,
94 &prop_ref.clone().unbox(),
95 is_required,
96 name,
97 inline_types,
98 ));
99 }
100 } else {
101 ref_field_counter += 1;
104 let field_ident = format_ident!("inner_{}", ref_field_counter);
105 let parent = format!("{name}Inner{ref_field_counter}");
106 let ty = schema_to_rust_type_ctx(
107 &ReferenceOr::Item(schema.clone()),
108 true,
109 Some(&parent),
110 inline_types,
111 );
112 fields.push(quote! {
113 #[serde(flatten)]
114 pub #field_ident: #ty,
115 });
116 }
117 }
118 }
119 }
120
121 quote! {
122 #doc
123 #[derive(
124 ::core::fmt::Debug,
125 ::core::clone::Clone,
126 ::serde::Serialize,
127 ::serde::Deserialize,
128 )]
129 pub struct #ident {
130 #(#fields)*
131 }
132 }
133}
134
135fn generate_enum(
137 name: &str,
138 variants: &[ReferenceOr<Schema>],
139 discriminator: Option<&Discriminator>,
140 description: Option<&String>,
141 inline_types: &mut Vec<TokenStream>,
142) -> TokenStream {
143 let ident = format_ident!("{}", name.to_pascal_case());
144 let doc = doc_attr(&description.cloned());
145
146 let serde_attr = discriminator.map_or_else(
147 || quote! { #[serde(untagged)] },
148 |d| {
149 let tag = &d.property_name;
150 quote! { #[serde(tag = #tag)] }
151 },
152 );
153
154 let variant_tokens: Vec<TokenStream> = variants
155 .iter()
156 .enumerate()
157 .map(|(idx, branch)| build_enum_variant(name, idx, branch, discriminator, inline_types))
158 .collect();
159
160 quote! {
161 #doc
162 #[derive(
163 ::core::fmt::Debug,
164 ::core::clone::Clone,
165 ::serde::Serialize,
166 ::serde::Deserialize,
167 )]
168 #serde_attr
169 pub enum #ident {
170 #(#variant_tokens,)*
171 }
172 }
173}
174
175fn build_enum_variant(
177 parent: &str,
178 idx: usize,
179 branch: &ReferenceOr<Schema>,
180 discriminator: Option<&Discriminator>,
181 inline_types: &mut Vec<TokenStream>,
182) -> TokenStream {
183 match branch {
184 ReferenceOr::Reference { reference } => {
185 let target_name = reference.rsplit('/').next().unwrap_or(reference);
186 let variant_ident = format_ident!("{}", target_name.to_pascal_case());
187 let ty = ref_to_ident(reference);
188 let rename_attr = discriminator
192 .and_then(|d| discriminator_key_for_ref(d, reference))
193 .map_or_else(|| quote! {}, |k| quote! { #[serde(rename = #k)] });
194 quote! {
195 #rename_attr
196 #variant_ident(#ty)
197 }
198 }
199 ReferenceOr::Item(schema) => {
200 let variant_ident = format_ident!("Variant{}", idx + 1);
201 let parent_for_synth = format!("{parent}Variant{}", idx + 1);
202 let ty = schema_to_rust_type_ctx(
203 &ReferenceOr::Item(schema.clone()),
204 true,
205 Some(&parent_for_synth),
206 inline_types,
207 );
208 quote! {
209 #variant_ident(#ty)
210 }
211 }
212 }
213}
214
215fn discriminator_key_for_ref(d: &Discriminator, reference: &str) -> Option<String> {
219 let bare = reference.rsplit('/').next().unwrap_or(reference);
220 d.mapping
221 .iter()
222 .find(|(_, v)| *v == reference || *v == bare)
223 .map(|(k, _)| k.clone())
224}