Skip to content

Commit

Permalink
new: Support unnamed structs (newtypes). (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
milesj committed May 20, 2024
1 parent 7016a11 commit 6b24627
Show file tree
Hide file tree
Showing 16 changed files with 436 additions and 119 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

#### 🚀 Updates

- Added support for tuple based structs (newtypes).

## 0.16.0

#### 💥 Breaking
Expand Down
48 changes: 24 additions & 24 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ proc-macro = true
[dependencies]
convert_case = "0.6.0"
darling = "0.20.9"
proc-macro2 = "1.0.82"
proc-macro2 = "1.0.83"
quote = "1.0.36"
syn = { version = "2.0.63", features = ["full"] }
syn = { version = "2.0.65", features = ["full"] }

[features]
default = []
Expand Down
31 changes: 31 additions & 0 deletions crates/macros/src/common/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ use syn::Fields;

pub enum Container<'l> {
NamedStruct { fields: Vec<Field<'l>> },
UnnamedStruct { fields: Vec<Field<'l>> },
Enum { variants: Vec<Variant<'l>> },
}

impl<'l> Container<'l> {
pub fn has_nested(&self) -> bool {
match self {
Self::NamedStruct { fields, .. } => fields.iter().any(|v| v.is_nested()),
Self::UnnamedStruct { fields, .. } => fields.iter().any(|v| v.is_nested()),
Self::Enum { variants } => variants.iter().any(|v| v.is_nested()),
}
}
Expand Down Expand Up @@ -46,6 +48,35 @@ impl<'l> Container<'l> {
]))
}
}
Self::UnnamedStruct { fields, .. } => {
let schema_types = fields
.iter()
.filter_map(|f| {
if f.is_excluded() {
None
} else {
Some(f.generate_schema_type())
}
})
.collect::<Vec<_>>();

if fields.len() == 1 {
let single_type = &schema_types[0];

quote! {
let mut schema = #single_type;
#description
schema
}
} else {
quote! {
#description
schema.tuple(TupleType::new([
#(#schema_types),*
]))
}
}
}
Self::Enum { variants } => {
let is_all_unit_enum = variants
.iter()
Expand Down
72 changes: 47 additions & 25 deletions crates/macros/src/common/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ pub struct Field<'l> {
pub serde_args: FieldSerdeArgs,
pub attrs: Vec<&'l Attribute>,
pub casing_format: String,
pub name: &'l Ident,
pub name: Option<&'l Ident>, // Named
pub index: usize, // Unnamed
pub value: &'l Type,
pub value_type: FieldValue<'l>,
pub env_prefix: Option<String>,
Expand All @@ -64,7 +65,8 @@ impl<'l> Field<'l> {
let serde_args = FieldSerdeArgs::from_attributes(&field.attrs).unwrap_or_default();

let field = Field {
name: field.ident.as_ref().unwrap(),
name: field.ident.as_ref(),
index: 0,
attrs: extract_common_attrs(&field.attrs),
casing_format: String::new(),
value: &field.ty,
Expand Down Expand Up @@ -119,15 +121,23 @@ impl<'l> Field<'l> {
self.args.skip || self.serde_args.skip
}

pub fn get_name_raw(&self) -> &Ident {
self.name.as_ref().expect("Missing name for field")
}

pub fn get_name(&self, casing_format: Option<&str>) -> String {
let Some(name) = &self.name else {
return String::new();
};

if let Some(local) = &self.args.rename {
local.to_owned()
} else if let Some(serde) = &self.serde_args.rename {
serde.to_owned()
} else if let Some(format) = casing_format {
format_case(format, &self.name.to_string(), false)
format_case(format, &name.to_string(), false)
} else {
self.name.to_string()
name.to_string()
}
}

Expand Down Expand Up @@ -191,7 +201,6 @@ impl<'l> Field<'l> {
}

pub fn generate_schema_type(&self) -> TokenStream {
let name = self.get_name(Some(&self.casing_format));
let hidden = map_bool_field_quote("hidden", self.is_skipped());
let nullable = map_bool_field_quote("nullable", self.is_optional());
let description = map_option_field_quote("description", extract_comment(&self.attrs));
Expand Down Expand Up @@ -229,34 +238,41 @@ impl<'l> Field<'l> {
inner_schema = quote! { schema.infer_with_default::<#value>(#lit_value) };
}

if description.is_none()
let value = if description.is_none()
&& deprecated.is_none()
&& env_var.is_none()
&& hidden.is_none()
&& nullable.is_none()
{
return quote! {
(#name.into(), #inner_schema)
};
}
inner_schema
} else {
quote! {
{
let mut field = #inner_schema;
#description
#deprecated
#env_var
#hidden
#nullable
field
}
}
};

quote! {
(#name.into(), {
let mut field = #inner_schema;
#description
#deprecated
#env_var
#hidden
#nullable
field
})
if self.name.is_some() {
let name = self.get_name(Some(&self.casing_format));

quote! {
(#name.into(), #value)
}
} else {
value
}
}
}

impl<'l> ToTokens for Field<'l> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = self.name;
let value = &self.value_type;

// Gather all attributes
Expand All @@ -270,9 +286,15 @@ impl<'l> ToTokens for Field<'l> {
attrs.push(quote! { #attr });
}

tokens.extend(quote! {
#(#attrs)*
pub #name: #value,
});
if let Some(name) = &self.name {
tokens.extend(quote! {
#(#attrs)*
pub #name: #value,
});
} else {
tokens.extend(quote! {
pub #value,
});
}
}
}
Loading

0 comments on commit 6b24627

Please sign in to comment.