Skip to content

Commit

Permalink
Respect #[serde(transparent)] attribute (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
GREsau committed May 16, 2020
1 parent 509a1c3 commit 5a28cef
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
## In-dev - version TBC
### Added:
- Setting `#[deprecated]` attribute will now cause generated schemas to have the `deprecated` property set to `true`
- Respect #[serde(transparent)] attribute (https://github.com/GREsau/schemars/issues/17)

## [0.7.4] - 2020-05-16
### Added:
Expand Down
9 changes: 9 additions & 0 deletions docs/1.1-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ Setting this on a container will set the `additionalProperties` keyword on gener

Serde docs: [container](https://serde.rs/container-attrs.html#deny_unknown_fields)

<h3 id="transparent">

`#[serde(transparent)]` / `#[schemars(transparent)]`
</h3>

Set on a newtype struct or a braced struct with one field to make the struct's generated schema exactly the same as that of the single field's.

Serde docs: [container](https://serde.rs/container-attrs.html#transparent)

</div>

## Other Attributes
Expand Down
33 changes: 33 additions & 0 deletions schemars/tests/expected/transparent-struct.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "OuterStruct",
"type": "object",
"properties": {
"inner": {
"anyOf": [
{
"$ref": "#/definitions/InnerStruct"
},
{
"type": "null"
}
]
}
},
"definitions": {
"InnerStruct": {
"type": "array",
"items": [
{
"type": "string"
},
{
"type": "integer",
"format": "int32"
}
],
"maxItems": 2,
"minItems": 2
}
}
}
27 changes: 27 additions & 0 deletions schemars/tests/transparent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
mod util;
use schemars::JsonSchema;
use util::*;

#[derive(Debug, JsonSchema)]
pub struct OuterStruct {
inner: TransparentStruct,
}

#[derive(Debug, JsonSchema)]
#[serde(transparent)]
pub struct TransparentStruct {
#[serde(with = "TransparentNewType")]
inner: (),
}

#[derive(Debug, JsonSchema)]
#[schemars(transparent)]
pub struct TransparentNewType(Option<InnerStruct>);

#[derive(Debug, JsonSchema)]
pub struct InnerStruct(String, i32);

#[test]
fn transparent_struct() -> TestResult {
test_default_generated_schema::<OuterStruct>("transparent-struct")
}
10 changes: 10 additions & 0 deletions schemars_derive/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ impl<'a> Container<'a> {
pub fn name(&self) -> String {
self.serde_attrs.name().deserialize_name()
}

pub fn transparent_field(&'a self) -> Option<&'a Field> {
if self.serde_attrs.transparent() {
if let Data::Struct(_, fields) = &self.data {
return Some(&fields[0]);
}
}

None
}
}

impl<'a> Variant<'a> {
Expand Down
1 change: 1 addition & 0 deletions schemars_derive/src/attr/schemars_to_serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ static SERDE_KEYWORDS: &[&str] = &[
"skip_deserializing",
"flatten",
"remote",
"transparent",
// Special cases - `with`/`serialize_with` are passed to serde but not copied from schemars attrs to serde attrs.
// This is because we want to preserve any serde attribute's `serialize_with` value to determine whether the field's
// default value should be serialized. We also check the `with` value on schemars/serde attrs e.g. to support deriving
Expand Down
43 changes: 39 additions & 4 deletions schemars_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,44 @@ fn derive_json_schema(mut input: syn::DeriveInput) -> TokenStream {
Err(e) => return compile_error(&e),
};

let schema_expr = schema_exprs::expr_for_container(&cont);

let type_name = &cont.ident;
let type_params: Vec<_> = cont.generics.type_params().map(|ty| &ty.ident).collect();
let (impl_generics, ty_generics, where_clause) = cont.generics.split_for_impl();

if let Some(transparent_field) = cont.transparent_field() {
let (ty, type_def) = schema_exprs::type_for_schema(transparent_field, 0);
return quote! {
#[automatically_derived]
impl #impl_generics schemars::JsonSchema for #type_name #ty_generics #where_clause {
#type_def

fn is_referenceable() -> bool {
<#ty as schemars::JsonSchema>::is_referenceable()
}

fn schema_name() -> std::string::String {
<#ty as schemars::JsonSchema>::schema_name()
}

fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
<#ty as schemars::JsonSchema>::json_schema(gen)
}

fn json_schema_for_flatten(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
<#ty as schemars::JsonSchema>::json_schema_for_flatten(gen)
}

fn add_schema_as_property(
gen: &mut schemars::gen::SchemaGenerator,
parent: &mut schemars::schema::SchemaObject,
name: String,
metadata: Option<schemars::schema::Metadata>,
required: bool,
) {
<#ty as schemars::JsonSchema>::add_schema_as_property(gen, parent, name, metadata, required)
}
};
};
}

let mut schema_base_name = cont.name();
let schema_is_renamed = *type_name != schema_base_name;
Expand All @@ -46,6 +80,7 @@ fn derive_json_schema(mut input: syn::DeriveInput) -> TokenStream {
}
}

let type_params: Vec<_> = cont.generics.type_params().map(|ty| &ty.ident).collect();
let schema_name = if type_params.is_empty() {
quote! {
#schema_base_name.to_owned()
Expand All @@ -67,7 +102,7 @@ fn derive_json_schema(mut input: syn::DeriveInput) -> TokenStream {
}
};

let (impl_generics, ty_generics, where_clause) = cont.generics.split_for_impl();
let schema_expr = schema_exprs::expr_for_container(&cont);

quote! {
#[automatically_derived]
Expand Down
2 changes: 1 addition & 1 deletion schemars_derive/src/schema_exprs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream {
}
}

fn type_for_schema(field: &Field, local_id: usize) -> (syn::Type, Option<TokenStream>) {
pub fn type_for_schema(field: &Field, local_id: usize) -> (syn::Type, Option<TokenStream>) {
match &field.attrs.with {
None => (field.ty.to_owned(), None),
Some(WithAttr::Type(ty)) => (ty.to_owned(), None),
Expand Down

0 comments on commit 5a28cef

Please sign in to comment.