diff --git a/README.md b/README.md index ebd9811..db92fe9 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ impl From for Foo!["0.2.0"] { } // an enumeration of all versions of `Foo` is accessed using the `obake::AnyVersion` type -// alias. +// alias let versioned_example: obake::AnyVersion = (Foo { bar: 42 }).into(); // this enumeration implements `Into`, where `Foo` is the latest declared @@ -78,6 +78,9 @@ assert_eq!(example, Foo { bar: 42 }); - `#[obake(inherit)]`: allows nesting of versioned data-structures. - `#[obake(derive(...))]`: allows derive attributes to be applied to generated enums. +- `#[obake(serde(...))]`: allows [`serde`](https://serde.rs) attributes to be applied to + generated `enum`s. + - Note: requires the feature `serde`. ## Limitations diff --git a/obake/Cargo.toml b/obake/Cargo.toml index 27ee5f7..73f03d8 100644 --- a/obake/Cargo.toml +++ b/obake/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "obake" authors = ["Nathan Corbyn "] -version = "1.0.3" +version = "1.0.4" edition = "2018" license = "MIT OR Apache-2.0" description = "Versioned data-structures for Rust" @@ -21,3 +21,8 @@ obake_macros = { path = "../obake_macros", version = "1.0" } [dev-dependencies] trybuild = "1.0" +obake_macros = { path = "../obake_macros", version = "1.0", features = ["serde"] } + +[features] +default = [] +serde = ["obake_macros/serde"] diff --git a/obake/src/lib.rs b/obake/src/lib.rs index 49dc8e5..de67f64 100644 --- a/obake/src/lib.rs +++ b/obake/src/lib.rs @@ -38,7 +38,7 @@ //! } //! //! // an enumeration of all versions of `Foo` is accessed using the `obake::AnyVersion` type -//! // alias. +//! // alias //! let versioned_example: obake::AnyVersion = (Foo { bar: 42 }).into(); //! //! // this enumeration implements `Into`, where `Foo` is the latest declared @@ -52,6 +52,9 @@ //! //! - `#[obake(inherit)]`: allows nesting of versioned data-structures. //! - `#[obake(derive(...))]`: allows derive attributes to be applied to generated `enum`s. +//! - `#[obake(serde(...))]`: allows [`serde`](https://serde.rs) attributes to be applied to +//! generated `enum`s. +//! - Note: requires the feature `serde`. //! //! ## Limitations //! @@ -80,9 +83,14 @@ /// attributes are treated as a disjunctively). /// - `#[obake(derive(...))]` - Apply a derive to the version-tagged enum generated for the /// data-structre. +/// - `#[obake(serde(...))]` - Apply a [serde] attribute to the version-tagged enum generated +/// for the data-structre. +/// - Note: requires the feature `serde`. /// - `#[obake(inherit)]` - Marks a field as having an inherited version (i.e., given a field of /// type `Bar`, when marked with `inherit`, this field will be expanded to a field of type /// `Bar![{version}]` in every version). +/// +/// [serde]: https://serde.rs // TODO(@doctorn) document generated types and trait implementations pub use obake_macros::versioned; diff --git a/obake/tests/ui/bad_helpers.rs b/obake/tests/ui/bad_helpers.rs index b23f15b..2de15a1 100644 --- a/obake/tests/ui/bad_helpers.rs +++ b/obake/tests/ui/bad_helpers.rs @@ -56,4 +56,29 @@ mod derives { } } +mod serdes { + #[obake::versioned] + #[obake(version("0.1.0"))] + struct Foo { + #[obake(serde(skip_serializing))] + field_0: u32, + } + + #[obake::versioned] + #[obake(version("0.1.0"))] + enum Bar { + #[obake(serde(skip_serializing))] + X, + } + + #[obake::versioned] + #[obake(version("0.1.0"))] + enum Baz { + X { + #[obake(serde(skip_serializing))] + field_0: u32, + }, + } +} + fn main() {} diff --git a/obake/tests/ui/bad_helpers.stderr b/obake/tests/ui/bad_helpers.stderr index 109c82a..cc3af03 100644 --- a/obake/tests/ui/bad_helpers.stderr +++ b/obake/tests/ui/bad_helpers.stderr @@ -45,3 +45,21 @@ error: `#[obake(derive(...))]` not valid in this context | 53 | #[obake(derive(Clone))] | ^^^^^^ + +error: `#[obake(serde(...))]` not valid in this context + --> $DIR/bad_helpers.rs:63:17 + | +63 | #[obake(serde(skip_serializing))] + | ^^^^^ + +error: `#[obake(serde(...))]` not valid in this context + --> $DIR/bad_helpers.rs:70:17 + | +70 | #[obake(serde(skip_serializing))] + | ^^^^^ + +error: `#[obake(serde(...))]` not valid in this context + --> $DIR/bad_helpers.rs:78:21 + | +78 | #[obake(serde(skip_serializing))] + | ^^^^^ diff --git a/obake_macros/Cargo.toml b/obake_macros/Cargo.toml index 9612b35..146a858 100644 --- a/obake_macros/Cargo.toml +++ b/obake_macros/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "obake_macros" authors = ["Nathan Corbyn "] -version = "1.0.3" +version = "1.0.4" edition = "2018" license = "MIT OR Apache-2.0" description = "Macros for versioned data-structures" @@ -22,3 +22,7 @@ proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0", features = ["full"] } semver = "1.0" + +[features] +default = [] +serde = [] diff --git a/obake_macros/src/expand.rs b/obake_macros/src/expand.rs index bc55f65..76fe737 100644 --- a/obake_macros/src/expand.rs +++ b/obake_macros/src/expand.rs @@ -59,6 +59,14 @@ impl VersionedField { )); } + #[cfg(feature = "serde")] + if let Some(serde) = self.attrs.serdes().next() { + return Err(syn::Error::new( + serde.span, + "`#[obake(serde(...))]` not valid in this context", + )); + } + let mut reqs: Vec<_> = self.attrs.cfgs().map(|attr| attr.req.clone()).collect(); // If we have no `#[obake(cfg(...))]` attributes, default to `#[obake(cfg("*"))]` @@ -122,13 +130,21 @@ impl VersionedVariant { )); } - if let Some(inherit) = self.attrs.derives().next() { + if let Some(derive) = self.attrs.derives().next() { return Err(syn::Error::new( - inherit.span, + derive.span, "`#[obake(derive(...))]` not valid in this context", )); } + #[cfg(feature = "serde")] + if let Some(serde) = self.attrs.serdes().next() { + return Err(syn::Error::new( + serde.span, + "`#[obake(serde(...))]` not valid in this context", + )); + } + let mut reqs: Vec<_> = self.attrs.cfgs().map(|attr| attr.req.clone()).collect(); // If we have no `#[obake(cfg(...))]` attributes, default to `#[obake(cfg("*"))]` @@ -303,6 +319,11 @@ impl VersionedItem { let tokens = &attr.tokens; quote!(#[derive(#tokens)]) }); + #[cfg(feature = "serde")] + let derives = derives.chain(self.attrs.serdes().map(|attr| { + let tokens = &attr.tokens; + quote!(#[serde(#tokens)]) + })); quote! { #[doc(hidden)] diff --git a/obake_macros/src/internal.rs b/obake_macros/src/internal.rs index 4545e5f..ac197b1 100644 --- a/obake_macros/src/internal.rs +++ b/obake_macros/src/internal.rs @@ -48,12 +48,21 @@ pub struct DeriveAttr { pub tokens: TokenStream2, } +#[cfg(feature = "serde")] +#[derive(Clone)] +pub struct SerdeAttr { + pub span: Span, + pub tokens: TokenStream2, +} + #[derive(Clone)] pub enum ObakeAttribute { Version(VersionAttr), Cfg(CfgAttr), Inherit(InheritAttr), Derive(DeriveAttr), + #[cfg(feature = "serde")] + Serde(SerdeAttr), } #[derive(Clone)] @@ -108,6 +117,15 @@ impl ObakeAttribute { _ => None, } } + + #[cfg(feature = "serde")] + pub fn serde(&self) -> Option<&SerdeAttr> { + #![allow(clippy::match_wildcard_for_single_variants)] + match &self { + ObakeAttribute::Serde(serde) => Some(serde), + _ => None, + } + } } impl VersionedAttribute { @@ -149,6 +167,11 @@ impl VersionedAttributes { self.obake().filter_map(ObakeAttribute::derive) } + #[cfg(feature = "serde")] + pub fn serdes(&self) -> impl Iterator + '_ { + self.obake().filter_map(ObakeAttribute::serde) + } + pub fn attrs(&self) -> impl Iterator + '_ { self.attrs.iter().filter_map(VersionedAttribute::attr) } diff --git a/obake_macros/src/parse.rs b/obake_macros/src/parse.rs index f08dea7..747a62b 100644 --- a/obake_macros/src/parse.rs +++ b/obake_macros/src/parse.rs @@ -53,6 +53,15 @@ impl Parse for ObakeAttribute { tokens: content.parse()?, }) } + #[cfg(feature = "serde")] + _ if ident == "serde" => { + let content; + parenthesized!(content in input); + Self::Serde(SerdeAttr { + span: ident.span(), + tokens: content.parse()?, + }) + } _ => { return Err(syn::Error::new( ident.span(),