predawn-macro 0.9.0

Macros for predawn
Documentation
use from_attr::{AttrsValue, FromAttr};
use proc_macro2::TokenStream;
use quote::quote;
use quote_use::quote_use;
use syn::{DeriveInput, Field};

use crate::{schema_attr::SchemaAttr, serde_attr::SerdeAttr, util};

pub(crate) fn generate(input: DeriveInput) -> syn::Result<TokenStream> {
    let DeriveInput {
        ident,
        generics,
        data,
        ..
    } = input;

    let named = util::extract_named_struct_fields(data, "ToParameters")?;

    let fields_len = named.len();
    let mut push_params = Vec::new();
    let mut errors = Vec::new();

    named
        .into_iter()
        .for_each(|field| match generate_single_field(field) {
            Ok(push_param) => {
                push_params.push(push_param);
            }
            Err(e) => errors.push(e),
        });

    if let Some(e) = errors.into_iter().reduce(|mut a, b| {
        a.combine(b);
        a
    }) {
        return Err(e);
    }

    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    let expand = quote_use! {
        # use std::vec::Vec;
        # use std::collections::BTreeMap;
        # use predawn::openapi::{ParameterData, Schema};
        # use predawn::ToParameters;

        impl #impl_generics ToParameters for #ident #ty_generics #where_clause {
            fn parameters(schemas: &mut BTreeMap<String, Schema>, schemas_in_progress: &mut Vec<String>) -> Vec<ParameterData> {
                let mut params = Vec::with_capacity(#fields_len);
                #(#push_params)*
                params
            }
        }
    };

    Ok(expand)
}

fn generate_single_field(field: Field) -> syn::Result<TokenStream> {
    let Field {
        attrs, ident, ty, ..
    } = field;

    let SerdeAttr {
        rename: serde_rename,
        flatten: serde_flatten,
        default: serde_default,
    } = SerdeAttr::new(&attrs);

    let SchemaAttr {
        rename: schema_rename,
        flatten: schema_flatten,
        default: schema_default,
    } = match SchemaAttr::from_attributes(&attrs) {
        Ok(Some(AttrsValue {
            value: field_attr, ..
        })) => field_attr,
        Ok(None) => Default::default(),
        Err(AttrsValue { value: e, .. }) => return Err(e),
    };

    if serde_flatten || schema_flatten {
        return Ok(quote_use! {
            # use predawn::ToParameters;

            params.extend(<#ty as ToParameters>::parameters(schemas));
        });
    }

    let ident = schema_rename.unwrap_or_else(|| {
        serde_rename.unwrap_or_else(|| {
            ident
                .expect("unreachable: named field must have an identifier")
                .to_string()
        })
    });

    let default_expr = util::generate_default_expr(&ty, serde_default, schema_default)?;
    let add_default = util::generate_add_default_to_schema(&ty, default_expr);

    let description = util::extract_description(&attrs);
    let description = if description.is_empty() {
        quote! { None }
    } else {
        let description = util::generate_string_expr(&description);
        quote! { Some(#description) }
    };

    let generate_schema = if add_default.is_empty() {
        quote_use! {
            # use predawn::ToSchema;

            <#ty as ToSchema>::schema_ref(schemas, schemas_in_progress)
        }
    } else {
        quote_use! {
            # use predawn::ToSchema;
            # use predawn::openapi::ReferenceOr;

            {
                let mut schema = <#ty as ToSchema>::schema(schemas, schemas_in_progress);
                #add_default
                ReferenceOr::Item(schema)
            }
        }
    };

    let required = if add_default.is_empty() {
        quote! { <#ty as ::predawn::ToSchema>::REQUIRED }
    } else {
        quote! { false }
    };

    let expand = quote_use! {
        # use core::default::Default;
        # use std::string::ToString;
        # use predawn::ToSchema;
        # use predawn::openapi::{ParameterData, ParameterSchemaOrContent};

        let schema = #generate_schema;

        let param = ParameterData {
            name: ToString::to_string(#ident),
            description: #description,
            required: #required,
            deprecated: Default::default(),
            format: ParameterSchemaOrContent::Schema(schema),
            example: Default::default(),
            examples: Default::default(),
            explode: Default::default(),
            extensions: Default::default(),
        };
        params.push(param);
    };

    Ok(expand)
}