conjure_codegen/
objects.rs1use std::iter;
2
3use crate::context::{BaseModule, BuilderConfig, BuilderItemConfig, Context};
17use crate::types::objects::{FieldDefinition, ObjectDefinition};
18use proc_macro2::TokenStream;
19use quote::quote;
20
21pub fn generate(ctx: &Context, base_module: BaseModule, def: &ObjectDefinition) -> TokenStream {
22 let docs = ctx.docs(def.docs());
23 let name = ctx.type_name(def.type_name().name());
24
25 let mut type_attrs = vec![quote!(#[serde(crate = "conjure_object::serde")])];
26 let mut derives = vec![
27 "Debug",
28 "Clone",
29 "conjure_object::serde::Serialize",
30 "conjure_object::serde::Deserialize",
31 ];
32
33 if def.fields().iter().any(|v| ctx.has_double(v.type_())) {
34 derives.push("conjure_object::private::DeriveWith");
35 type_attrs.push(quote!(#[derive_with(PartialEq, Eq, PartialOrd, Ord, Hash)]));
36 } else {
37 derives.push("PartialEq");
38 derives.push("Eq");
39 derives.push("PartialOrd");
40 derives.push("Ord");
41 derives.push("Hash");
42 }
43
44 if def.fields().iter().all(|v| ctx.is_copy(v.type_())) {
45 derives.push("Copy");
46 }
47
48 let derives = derives.iter().map(|s| s.parse::<TokenStream>().unwrap());
49 type_attrs.insert(0, quote!(#[derive(#(#derives),*)]));
51
52 if ctx.public_fields() {
53 type_attrs.push(quote!(#[non_exhaustive]));
54 }
55
56 let field_attrs = def.fields().iter().map(|s| {
57 let builder_attr = field_builder_attr(ctx, base_module, def, s);
58 let serde_attr = serde_field_attr(ctx, def, s);
59 let educe_attr = if ctx.is_double(s.type_()) {
60 quote! {
61 #[derive_with(with = conjure_object::private::DoubleWrapper)]
62 }
63 } else {
64 quote!()
65 };
66 let (docs, deprecated) = if ctx.public_fields() {
67 (ctx.docs(s.docs()), ctx.deprecated(s.docs()))
68 } else {
69 (quote!(), quote!())
70 };
71
72 quote! {
73 #builder_attr
74 #serde_attr
75 #educe_attr
76 #docs
77 #deprecated
78 }
79 });
80
81 let pub_ = if ctx.public_fields() {
82 quote!(pub)
83 } else {
84 quote!()
85 };
86 let pub_ = iter::repeat(pub_);
87 let fields = def.fields().iter().map(|f| ctx.field_name(f.field_name()));
88 let boxed_types = &def
89 .fields()
90 .iter()
91 .map(|s| ctx.boxed_rust_type(base_module, def.type_name(), s.type_()))
92 .collect::<Vec<_>>();
93
94 let constructor = generate_constructor(ctx, base_module, def);
95
96 let accessors = def.fields().iter().map(|s| {
97 if ctx.public_fields() {
98 return quote!();
99 }
100
101 let docs = ctx.docs(s.docs());
102 let deprecated = ctx.deprecated(s.deprecated());
103 let name = ctx.field_name(s.field_name());
104 let ret_type = ctx.borrowed_rust_type(base_module, def.type_name(), s.type_());
105 let borrow = ctx.borrow_rust_type(quote!(self.#name), s.type_());
106
107 quote!(
108 #docs
109 #deprecated
110 #[inline]
111 pub fn #name(&self) -> #ret_type {
112 #borrow
113 }
114 )
115 });
116
117 quote! {
118 #docs
119 #(#type_attrs)*
120 #[conjure_object::private::staged_builder::staged_builder]
121 #[builder(
122 crate = conjure_object::private::staged_builder,
123 update,
124 inline,
125 )]
126 pub struct #name {
127 #(
128 #field_attrs
129 #pub_ #fields: #boxed_types,
130 )*
131 }
132
133 impl #name {
134 #constructor
135
136 #(#accessors)*
137 }
138 }
139}
140
141fn generate_constructor(
142 ctx: &Context,
143 base_module: BaseModule,
144 def: &ObjectDefinition,
145) -> TokenStream {
146 let required_args = def
147 .fields()
148 .iter()
149 .filter(|f| ctx.is_required(f.type_()))
150 .collect::<Vec<_>>();
151
152 if required_args.len() > 3 {
153 return quote!();
154 }
155
156 let new = if def.fields().iter().any(|f| **f.field_name() == "new") {
157 quote!(new_)
158 } else {
159 quote!(new)
160 };
161
162 let arguments = required_args.iter().map(|f| {
163 let name = ctx.field_name(f.field_name());
164 let ty = match ctx.builder_config(base_module, def.type_name(), f.type_()) {
165 BuilderConfig::Normal => ctx.rust_type(base_module, def.type_name(), f.type_()),
166 BuilderConfig::Into => {
167 let into = ctx.into_ident(def.type_name());
168 let ty = ctx.rust_type(base_module, def.type_name(), f.type_());
169 quote!(impl #into<#ty>)
170 }
171 BuilderConfig::Custom { type_, .. } => type_,
172 BuilderConfig::List { .. } | BuilderConfig::Set { .. } | BuilderConfig::Map { .. } => {
173 unreachable!()
174 }
175 };
176 quote!(#name: #ty)
177 });
178
179 let setters = required_args.iter().map(|f| {
180 let field = ctx.field_name(f.field_name());
181 quote!(.#field(#field))
182 });
183
184 quote! {
185 #[inline]
187 pub fn #new(#(#arguments,)*) -> Self {
188 Self::builder()
189 #(#setters)*
190 .build()
191 }
192 }
193}
194
195fn serde_field_attr(ctx: &Context, def: &ObjectDefinition, field: &FieldDefinition) -> TokenStream {
196 let mut parts = vec![];
197
198 let name = &field.field_name().0;
199 parts.push(quote!(rename = #name));
200
201 if !ctx.serialize_empty_collections() {
202 if let Some(is_empty) = ctx.is_empty_method(def.type_name(), field.type_()) {
203 parts.push(quote!(skip_serializing_if = #is_empty));
204 }
205 }
206
207 if !ctx.is_required(field.type_()) {
208 parts.push(quote!(default));
209 }
210
211 quote!(#[serde(#(#parts),*)])
212}
213
214fn field_builder_attr(
215 ctx: &Context,
216 base_module: BaseModule,
217 def: &ObjectDefinition,
218 field: &FieldDefinition,
219) -> TokenStream {
220 let mut inner = match ctx.builder_config(base_module, def.type_name(), field.type_()) {
221 BuilderConfig::Normal => quote!(),
222 BuilderConfig::Into => quote!(into),
223 BuilderConfig::Custom { type_, convert } => {
224 quote!(custom(type = #type_, convert = #convert))
225 }
226 BuilderConfig::List { item } => {
227 let item = builder_item_attr(item);
228 quote!(list(item(#item)))
229 }
230 BuilderConfig::Set { item } => {
231 let item = builder_item_attr(item);
232 quote!(set(item(#item)))
233 }
234 BuilderConfig::Map { key, value } => {
235 let key = builder_item_attr(key);
236 let value = builder_item_attr(value);
237 quote!(map(key(#key), value(#value)))
238 }
239 };
240
241 if !ctx.is_required(field.type_()) {
243 inner = quote!(default, #inner);
244 }
245
246 if inner.is_empty() {
247 quote!()
248 } else {
249 quote!(#[builder(#inner)])
250 }
251}
252
253fn builder_item_attr(config: BuilderItemConfig) -> TokenStream {
254 match config {
255 BuilderItemConfig::Normal { type_ } => quote!(type = #type_),
256 BuilderItemConfig::Into { type_ } => quote!(type = #type_, into),
257 BuilderItemConfig::Custom { type_, convert } => {
258 quote!(custom(type = #type_, convert = #convert))
259 }
260 }
261}