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, model_derives, 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 models: &std::collections::BTreeSet<String>,
40) -> TokenStream {
41 generate_enum(
42 name,
43 variants,
44 discriminator,
45 description,
46 inline_types,
47 models,
48 )
49}
50
51#[must_use]
57pub fn generate_any_of(
58 name: &str,
59 variants: &[ReferenceOr<Schema>],
60 description: Option<&String>,
61 inline_types: &mut Vec<TokenStream>,
62 models: &std::collections::BTreeSet<String>,
63) -> TokenStream {
64 generate_enum(name, variants, None, description, inline_types, models)
65}
66
67#[must_use]
74pub fn generate_all_of(
75 name: &str,
76 variants: &[ReferenceOr<Schema>],
77 description: Option<&String>,
78 inline_types: &mut Vec<TokenStream>,
79 models: &std::collections::BTreeSet<String>,
80) -> TokenStream {
81 let ident = format_ident!("{}", name.to_pascal_case());
82 let doc = doc_attr(&description.cloned());
83
84 let mut fields: Vec<TokenStream> = Vec::new();
85 let mut ref_field_counter = 0usize;
86
87 for branch in variants {
88 match branch {
89 ReferenceOr::Reference { reference } => {
90 ref_field_counter += 1;
91 let field_ident = format_ident!("inner_{}", ref_field_counter);
92 let ty = ref_to_ident(reference);
93 fields.push(quote! {
94 #[serde(flatten)]
95 pub #field_ident: #ty,
96 });
97 }
98 ReferenceOr::Item(schema) => {
99 if let SchemaKind::Type(Type::Object(obj)) = &schema.schema_kind {
100 for (prop_name, prop_ref) in &obj.properties {
101 let is_required = obj.required.iter().any(|r| r == prop_name);
102 fields.push(object_field_tokens(
103 prop_name,
104 &prop_ref.clone().unbox(),
105 is_required,
106 name,
107 inline_types,
108 models,
109 ));
110 }
111 } else {
112 ref_field_counter += 1;
115 let field_ident = format_ident!("inner_{}", ref_field_counter);
116 let parent = format!("{name}Inner{ref_field_counter}");
117 let ty = schema_to_rust_type_ctx(
118 &ReferenceOr::Item(schema.clone()),
119 true,
120 Some(&parent),
121 inline_types,
122 models,
123 );
124 fields.push(quote! {
125 #[serde(flatten)]
126 pub #field_ident: #ty,
127 });
128 }
129 }
130 }
131 }
132
133 let derives = model_derives();
134 quote! {
135 #doc
136 #derives
137 pub struct #ident {
138 #(#fields)*
139 }
140 }
141}
142
143fn generate_enum(
145 name: &str,
146 variants: &[ReferenceOr<Schema>],
147 discriminator: Option<&Discriminator>,
148 description: Option<&String>,
149 inline_types: &mut Vec<TokenStream>,
150 models: &std::collections::BTreeSet<String>,
151) -> TokenStream {
152 let ident = format_ident!("{}", name.to_pascal_case());
153 let doc = doc_attr(&description.cloned());
154
155 let serde_attr = discriminator.map_or_else(
156 || quote! { #[serde(untagged)] },
157 |d| {
158 let tag = &d.property_name;
159 quote! { #[serde(tag = #tag)] }
160 },
161 );
162
163 let variant_tokens: Vec<TokenStream> = variants
164 .iter()
165 .enumerate()
166 .map(|(idx, branch)| {
167 build_enum_variant(name, idx, branch, discriminator, inline_types, models)
168 })
169 .collect();
170
171 let derives = model_derives();
172 quote! {
173 #doc
174 #derives
175 #serde_attr
176 pub enum #ident {
177 #(#variant_tokens,)*
178 }
179 }
180}
181
182fn build_enum_variant(
184 parent: &str,
185 idx: usize,
186 branch: &ReferenceOr<Schema>,
187 discriminator: Option<&Discriminator>,
188 inline_types: &mut Vec<TokenStream>,
189 models: &std::collections::BTreeSet<String>,
190) -> TokenStream {
191 match branch {
192 ReferenceOr::Reference { reference } => {
193 let target_name = reference.rsplit('/').next().unwrap_or(reference);
194 let variant_ident = format_ident!("{}", target_name.to_pascal_case());
195 let ty = ref_to_ident(reference);
196 let rename_attr = discriminator
200 .and_then(|d| discriminator_key_for_ref(d, reference))
201 .map_or_else(|| quote! {}, |k| quote! { #[serde(rename = #k)] });
202 quote! {
203 #rename_attr
204 #variant_ident(#ty)
205 }
206 }
207 ReferenceOr::Item(schema) => {
208 let variant_ident = format_ident!("Variant{}", idx + 1);
209 let parent_for_synth = format!("{parent}Variant{}", idx + 1);
210 let ty = schema_to_rust_type_ctx(
211 &ReferenceOr::Item(schema.clone()),
212 true,
213 Some(&parent_for_synth),
214 inline_types,
215 models,
216 );
217 quote! {
218 #variant_ident(#ty)
219 }
220 }
221 }
222}
223
224fn discriminator_key_for_ref(d: &Discriminator, reference: &str) -> Option<String> {
228 let bare = reference.rsplit('/').next().unwrap_or(reference);
229 d.mapping
230 .iter()
231 .find(|(_, v)| *v == reference || *v == bare)
232 .map(|(k, _)| k.clone())
233}