conjure-codegen 5.11.0

Rust code generation for Conjure definitions
Documentation
use std::iter;

// Copyright 2021 Palantir Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::context::{BaseModule, BuilderConfig, BuilderItemConfig, Context};
use crate::types::objects::{FieldDefinition, ObjectDefinition};
use proc_macro2::TokenStream;
use quote::quote;

pub fn generate(ctx: &Context, base_module: BaseModule, def: &ObjectDefinition) -> TokenStream {
    let docs = ctx.docs(def.docs());
    let name = ctx.type_name(def.type_name().name());

    let mut type_attrs = vec![quote!(#[serde(crate = "conjure_object::serde")])];
    let mut derives = vec![
        "Debug",
        "Clone",
        "conjure_object::serde::Serialize",
        "conjure_object::serde::Deserialize",
    ];

    if def.fields().iter().any(|v| ctx.has_double(v.type_())) {
        derives.push("conjure_object::private::DeriveWith");
        type_attrs.push(quote!(#[derive_with(PartialEq, Eq, PartialOrd, Ord, Hash)]));
    } else {
        derives.push("PartialEq");
        derives.push("Eq");
        derives.push("PartialOrd");
        derives.push("Ord");
        derives.push("Hash");
    }

    if def.fields().iter().all(|v| ctx.is_copy(v.type_())) {
        derives.push("Copy");
    }

    let derives = derives.iter().map(|s| s.parse::<TokenStream>().unwrap());
    // The derive attr has to be before the derive_with attr, so insert rather than push
    type_attrs.insert(0, quote!(#[derive(#(#derives),*)]));

    if ctx.public_fields() {
        type_attrs.push(quote!(#[non_exhaustive]));
    }

    let field_attrs = def.fields().iter().map(|s| {
        let builder_attr = field_builder_attr(ctx, base_module, def, s);
        let serde_attr = serde_field_attr(ctx, def, s);
        let educe_attr = if ctx.is_double(s.type_()) {
            quote! {
                #[derive_with(with = conjure_object::private::DoubleWrapper)]
            }
        } else {
            quote!()
        };
        let (docs, deprecated) = if ctx.public_fields() {
            (ctx.docs(s.docs()), ctx.deprecated(s.deprecated()))
        } else {
            (quote!(), quote!())
        };

        quote! {
            #builder_attr
            #serde_attr
            #educe_attr
            #docs
            #deprecated
        }
    });

    let pub_ = if ctx.public_fields() {
        quote!(pub)
    } else {
        quote!()
    };
    let pub_ = iter::repeat(pub_);
    let fields = def.fields().iter().map(|f| ctx.field_name(f.field_name()));
    let boxed_types = &def
        .fields()
        .iter()
        .map(|s| ctx.boxed_rust_type(base_module, def.type_name(), s.type_()))
        .collect::<Vec<_>>();

    let constructor = generate_constructor(ctx, base_module, def);

    let accessors = def.fields().iter().map(|s| {
        if ctx.public_fields() {
            return quote!();
        }

        let docs = ctx.docs(s.docs());
        let deprecated = ctx.deprecated(s.deprecated());
        let name = ctx.field_name(s.field_name());
        let ret_type = ctx.borrowed_rust_type(base_module, def.type_name(), s.type_());
        let borrow = ctx.borrow_rust_type(quote!(self.#name), s.type_());

        quote!(
            #docs
            #deprecated
            #[inline]
            pub fn #name(&self) -> #ret_type {
                #borrow
            }
        )
    });

    quote! {
        #docs
        #(#type_attrs)*
        #[conjure_object::private::staged_builder::staged_builder]
        #[builder(
            crate = conjure_object::private::staged_builder,
            update,
            inline,
        )]
        pub struct #name {
            #(
                #field_attrs
                #pub_ #fields: #boxed_types,
            )*
        }

        impl #name {
            #constructor

            #(#accessors)*
        }
    }
}

fn generate_constructor(
    ctx: &Context,
    base_module: BaseModule,
    def: &ObjectDefinition,
) -> TokenStream {
    let required_args = def
        .fields()
        .iter()
        .filter(|f| ctx.is_required(f.type_()))
        .collect::<Vec<_>>();

    if required_args.len() > 3 {
        return quote!();
    }

    let new = if def.fields().iter().any(|f| **f.field_name() == "new") {
        quote!(new_)
    } else {
        quote!(new)
    };

    let arguments = required_args.iter().map(|f| {
        let name = ctx.field_name(f.field_name());
        let ty = match ctx.builder_config(base_module, def.type_name(), f.type_()) {
            BuilderConfig::Normal => ctx.rust_type(base_module, def.type_name(), f.type_()),
            BuilderConfig::Into => {
                let into = ctx.into_ident(def.type_name());
                let ty = ctx.rust_type(base_module, def.type_name(), f.type_());
                quote!(impl #into<#ty>)
            }
            BuilderConfig::Custom { type_, .. } => type_,
            BuilderConfig::List { .. } | BuilderConfig::Set { .. } | BuilderConfig::Map { .. } => {
                unreachable!()
            }
        };
        quote!(#name: #ty)
    });

    let setters = required_args.iter().map(|f| {
        let field = ctx.field_name(f.field_name());
        quote!(.#field(#field))
    });

    quote! {
        /// Constructs a new instance of the type.
        #[inline]
        pub fn #new(#(#arguments,)*) -> Self {
            Self::builder()
                #(#setters)*
                .build()
        }
    }
}

fn serde_field_attr(ctx: &Context, def: &ObjectDefinition, field: &FieldDefinition) -> TokenStream {
    let mut parts = vec![];

    let name = &field.field_name().0;
    parts.push(quote!(rename = #name));

    if !ctx.serialize_empty_collections() {
        if let Some(is_empty) = ctx.is_empty_method(def.type_name(), field.type_()) {
            parts.push(quote!(skip_serializing_if = #is_empty));
        }
    }

    if !ctx.is_required(field.type_()) {
        parts.push(quote!(default));
    }

    quote!(#[serde(#(#parts),*)])
}

fn field_builder_attr(
    ctx: &Context,
    base_module: BaseModule,
    def: &ObjectDefinition,
    field: &FieldDefinition,
) -> TokenStream {
    let mut inner = match ctx.builder_config(base_module, def.type_name(), field.type_()) {
        BuilderConfig::Normal => quote!(),
        BuilderConfig::Into => quote!(into),
        BuilderConfig::Custom { type_, convert } => {
            quote!(custom(type = #type_, convert = #convert))
        }
        BuilderConfig::List { item } => {
            let item = builder_item_attr(item);
            quote!(list(item(#item)))
        }
        BuilderConfig::Set { item } => {
            let item = builder_item_attr(item);
            quote!(set(item(#item)))
        }
        BuilderConfig::Map { key, value } => {
            let key = builder_item_attr(key);
            let value = builder_item_attr(value);
            quote!(map(key(#key), value(#value)))
        }
    };

    // FIXME this is unnecessary for lists, sets, maps
    if !ctx.is_required(field.type_()) {
        inner = quote!(default, #inner);
    }

    if inner.is_empty() {
        quote!()
    } else {
        quote!(#[builder(#inner)])
    }
}

fn builder_item_attr(config: BuilderItemConfig) -> TokenStream {
    match config {
        BuilderItemConfig::Normal { type_ } => quote!(type = #type_),
        BuilderItemConfig::Into { type_ } => quote!(type = #type_, into),
        BuilderItemConfig::Custom { type_, convert } => {
            quote!(custom(type = #type_, convert = #convert))
        }
    }
}