Skip to main content

conjure_codegen/
objects.rs

1use std::iter;
2
3// Copyright 2021 Palantir Technologies, Inc.
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16use 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    // The derive attr has to be before the derive_with attr, so insert rather than push
50    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        /// Constructs a new instance of the type.
186        #[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    // FIXME this is unnecessary for lists, sets, maps
242    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}