// Copyright 2017 Serde Developers // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use ast::{Data, Container, Style}; use attr::Identifier; use Ctxt; /// Cross-cutting checks that require looking at more than a single attrs /// object. Simpler checks should happen when parsing and building the attrs. pub fn check(cx: &Ctxt, cont: &Container) { check_getter(cx, cont); check_identifier(cx, cont); check_variant_skip_attrs(cx, cont); } /// Getters are only allowed inside structs (not enums) with the `remote` /// attribute. fn check_getter(cx: &Ctxt, cont: &Container) { match cont.data { Data::Enum(_) => { if cont.data.has_getter() { cx.error("#[serde(getter = \"...\")] is not allowed in an enum"); } } Data::Struct(_, _) => { if cont.data.has_getter() && cont.attrs.remote().is_none() { cx.error( "#[serde(getter = \"...\")] can only be used in structs \ that have #[serde(remote = \"...\")]", ); } } } } /// The `other` attribute must be used at most once and it must be the last /// variant of an enum that has the `field_identifier` attribute. /// /// Inside a `variant_identifier` all variants must be unit variants. Inside a /// `field_identifier` all but possibly one variant must be unit variants. The /// last variant may be a newtype variant which is an implicit "other" case. fn check_identifier(cx: &Ctxt, cont: &Container) { let variants = match cont.data { Data::Enum(ref variants) => variants, Data::Struct(_, _) => { return; } }; for (i, variant) in variants.iter().enumerate() { match ( variant.style, cont.attrs.identifier(), variant.attrs.other(), ) { // The `other` attribute may only be used in a field_identifier. (_, Identifier::Variant, true) | (_, Identifier::No, true) => { cx.error("#[serde(other)] may only be used inside a field_identifier"); } // Variant with `other` attribute must be the last one. (Style::Unit, Identifier::Field, true) => { if i < variants.len() - 1 { cx.error("#[serde(other)] must be the last variant"); } } // Variant with `other` attribute must be a unit variant. (_, Identifier::Field, true) => { cx.error("#[serde(other)] must be on a unit variant"); } // Any sort of variant is allowed if this is not an identifier. (_, Identifier::No, false) => {} // Unit variant without `other` attribute is always fine. (Style::Unit, _, false) => {} // The last field is allowed to be a newtype catch-all. (Style::Newtype, Identifier::Field, false) => { if i < variants.len() - 1 { cx.error(format!("`{}` must be the last variant", variant.ident)); } } (_, Identifier::Field, false) => { cx.error("field_identifier may only contain unit variants"); } (_, Identifier::Variant, false) => { cx.error("variant_identifier may only contain unit variants"); } } } } /// Skip-(de)serializing attributes are not allowed on variants marked /// (de)serialize_with. fn check_variant_skip_attrs(cx: &Ctxt, cont: &Container) { let variants = match cont.data { Data::Enum(ref variants) => variants, Data::Struct(_, _) => { return; } }; for variant in variants.iter() { if variant.attrs.serialize_with().is_some() { if variant.attrs.skip_serializing() { cx.error(format!( "variant `{}` cannot have both #[serde(serialize_with)] and \ #[serde(skip_serializing)]", variant.ident )); } for (i, field) in variant.fields.iter().enumerate() { let ident = field .ident .as_ref() .map_or_else(|| format!("{}", i), |ident| format!("`{}`", ident)); if field.attrs.skip_serializing() { cx.error(format!( "variant `{}` cannot have both #[serde(serialize_with)] and \ a field {} marked with #[serde(skip_serializing)]", variant.ident, ident )); } if field.attrs.skip_serializing_if().is_some() { cx.error(format!( "variant `{}` cannot have both #[serde(serialize_with)] and \ a field {} marked with #[serde(skip_serializing_if)]", variant.ident, ident )); } } } if variant.attrs.deserialize_with().is_some() { if variant.attrs.skip_deserializing() { cx.error(format!( "variant `{}` cannot have both #[serde(deserialize_with)] and \ #[serde(skip_deserializing)]", variant.ident )); } for (i, field) in variant.fields.iter().enumerate() { if field.attrs.skip_deserializing() { let ident = field .ident .as_ref() .map_or_else(|| format!("{}", i), |ident| format!("`{}`", ident)); cx.error(format!( "variant `{}` cannot have both #[serde(deserialize_with)] \ and a field {} marked with #[serde(skip_deserializing)]", variant.ident, ident )); } } } } }