diff --git a/schemars/tests/expected/property-name-struct.json b/schemars/tests/expected/property-name-struct.json new file mode 100644 index 0000000..d5eba68 --- /dev/null +++ b/schemars/tests/expected/property-name-struct.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MyStruct", + "type": "object", + "properties": { + "camelCase": { + "type": "integer" + }, + "new_name_1": { + "type": "integer" + }, + "new_name_2": { + "type": "integer" + } + } +} \ No newline at end of file diff --git a/schemars/tests/expected/naming-custom.json b/schemars/tests/expected/schema-name-custom.json similarity index 100% rename from schemars/tests/expected/naming-custom.json rename to schemars/tests/expected/schema-name-custom.json diff --git a/schemars/tests/expected/naming-default.json b/schemars/tests/expected/schema-name-default.json similarity index 100% rename from schemars/tests/expected/naming-default.json rename to schemars/tests/expected/schema-name-default.json diff --git a/schemars/tests/flatten.rs b/schemars/tests/flatten.rs index d154582..a3cabb0 100644 --- a/schemars/tests/flatten.rs +++ b/schemars/tests/flatten.rs @@ -12,7 +12,7 @@ struct Flat { } #[derive(Debug, MakeSchema)] -#[serde(rename = "Flat")] +#[schemars(rename = "Flat")] struct Deep1 { foo: f32, #[serde(flatten)] diff --git a/schemars/tests/property_name.rs b/schemars/tests/property_name.rs new file mode 100644 index 0000000..46577ff --- /dev/null +++ b/schemars/tests/property_name.rs @@ -0,0 +1,19 @@ +mod util; +use schemars::MakeSchema; +use util::*; + +#[derive(Debug, MakeSchema)] +#[serde(rename_all = "camelCase")] +struct MyStruct { + camel_case: i32, + #[serde(rename = "new_name_1")] + old_name_1: i32, + #[serde(rename = "ignored")] + #[schemars(rename = "new_name_2")] + old_name_2: i32, +} + +#[test] +fn set_struct_property_names() -> TestResult { + test_default_generated_schema::("property-name-struct") +} \ No newline at end of file diff --git a/schemars/tests/naming.rs b/schemars/tests/schema_name.rs similarity index 52% rename from schemars/tests/naming.rs rename to schemars/tests/schema_name.rs index 7f7b1bf..decf334 100644 --- a/schemars/tests/naming.rs +++ b/schemars/tests/schema_name.rs @@ -4,11 +4,11 @@ use util::*; #[derive(Debug, MakeSchema)] struct MyStruct { - t: T, - u: U, - v: V, - w: W, - inner: MySimpleStruct, + t: T, + u: U, + v: V, + w: W, + inner: MySimpleStruct, } #[derive(Debug, MakeSchema)] @@ -16,24 +16,25 @@ struct MySimpleStruct {} #[test] fn default_name_multiple_type_params() -> TestResult { - test_default_generated_schema::>>("naming-default") + test_default_generated_schema::>>("schema-name-default") } #[derive(Debug, MakeSchema)] #[serde(rename = "a-new-name-{W}-{T}-{T}")] struct MyRenamedStruct { - t: T, - u: U, - v: V, - w: W, - inner: MySimpleRenamedStruct, + t: T, + u: U, + v: V, + w: W, + inner: MySimpleRenamedStruct, } #[derive(Debug, MakeSchema)] -#[serde(rename = "another-new-name")] +#[serde(rename = "this-attribute-is-ignored")] +#[schemars(rename = "another-new-name")] struct MySimpleRenamedStruct {} #[test] fn overriden_with_rename_multiple_type_params() -> TestResult { - test_default_generated_schema::>>("naming-custom") + test_default_generated_schema::>>("schema-name-custom") } diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml index bc3ee46..cb57ab4 100644 --- a/schemars_derive/Cargo.toml +++ b/schemars_derive/Cargo.toml @@ -12,5 +12,5 @@ proc-macro = true [dependencies] proc-macro2 = "0.4" quote = "0.6.13" -syn = { version = "0.15.22", features = ["visit"] } # do we need visit? +syn = "0.15.22" serde_derive_internals = "0.24.1" diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index d2c4729..3f6f134 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -5,19 +5,21 @@ extern crate syn; extern crate proc_macro; +mod preprocess; + use proc_macro2::{Span, TokenStream}; use serde_derive_internals::ast::{Container, Data, Field, Style, Variant}; use serde_derive_internals::attr::{self, EnumTag}; use serde_derive_internals::{Ctxt, Derive}; use syn::spanned::Spanned; -use syn::{DeriveInput, GenericParam, Generics}; +use syn::DeriveInput; #[proc_macro_derive(MakeSchema, attributes(schemars, serde))] pub fn derive_make_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let mut input = parse_macro_input!(input as DeriveInput); - // TODO is mutating the input really the best way to do this? - add_trait_bounds(&mut input.generics); + preprocess::add_trait_bounds(&mut input.generics); + preprocess::rename_schemars_attrs(&mut input); let ctxt = Ctxt::new(); let cont = Container::from_ast(&ctxt, &input, Derive::Deserialize); @@ -75,14 +77,6 @@ pub fn derive_make_schema(input: proc_macro::TokenStream) -> proc_macro::TokenSt proc_macro::TokenStream::from(impl_block) } -fn add_trait_bounds(generics: &mut Generics) { - for param in &mut generics.params { - if let GenericParam::Type(ref mut type_param) = *param { - type_param.bounds.push(parse_quote!(schemars::MakeSchema)); - } - } -} - fn wrap_schema_fields(schema_contents: TokenStream) -> TokenStream { quote! { Ok(schemars::schema::Schema::Object( diff --git a/schemars_derive/src/preprocess.rs b/schemars_derive/src/preprocess.rs new file mode 100644 index 0000000..6e63cd7 --- /dev/null +++ b/schemars_derive/src/preprocess.rs @@ -0,0 +1,57 @@ +use proc_macro2::Span; +use syn::{Attribute, Data, DeriveInput, Field, GenericParam, Generics, Ident, Variant}; + +pub fn add_trait_bounds(generics: &mut Generics) { + for param in &mut generics.params { + if let GenericParam::Type(ref mut type_param) = *param { + type_param.bounds.push(parse_quote!(schemars::MakeSchema)); + } + } +} + +// If a struct/variant/field has any #[schemars] attributes, then rename them +// to #[serde] so that serde_derive_internals will parse them for us. +pub fn rename_schemars_attrs(input: &mut DeriveInput) { + rename_attrs(input.attrs.iter_mut()); + match input.data { + Data::Struct(ref mut s) => rename_field_attrs(s.fields.iter_mut()), + Data::Enum(ref mut e) => rename_variant_attrs(e.variants.iter_mut()), + Data::Union(ref mut u) => rename_field_attrs(u.fields.named.iter_mut()), + }; +} + +fn rename_variant_attrs<'a>(variants: impl Iterator) { + for v in variants { + rename_attrs(v.attrs.iter_mut()); + rename_field_attrs(v.fields.iter_mut()); + } +} + +fn rename_field_attrs<'a>(fields: impl Iterator) { + for f in fields { + rename_attrs(f.attrs.iter_mut()); + } +} + +fn rename_attrs<'a>(attrs: impl Iterator) { + let (schemars_attrs, others): (Vec<_>, Vec<_>) = + attrs.partition(|a| a.path.is_ident("schemars")); + + if !schemars_attrs.is_empty() { + for attr in schemars_attrs { + let schemars_ident = attr.path.segments.pop().unwrap().into_value().ident; + attr.path + .segments + .push(Ident::new("serde", schemars_ident.span()).into()); + } + // Give any other attributes a new name so that serde doesn't process them + // and complain about duplicate attributes. + // TODO we shouldn't need to remove all attributes - it should be possible + // to just remove duplicated parts of the attributes. + for attr in others { + attr.path + .segments + .push(Ident::new("dummy", Span::call_site()).into()); + } + } +}