Skip to content

Commit

Permalink
feat(serde_with_macros): proper handling of cfg_attr and schemars (
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasbb authored Oct 1, 2024
2 parents 2c68754 + 2d70b70 commit 67326f8
Show file tree
Hide file tree
Showing 4 changed files with 332 additions and 152 deletions.
16 changes: 16 additions & 0 deletions serde_with/tests/schemars_0_8.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ fn schemars_basic() {
expected.assert_eq(&schema);
}

#[test]
fn schemars_other_cfg_attrs() {
#[serde_as]
#[derive(JsonSchema, Serialize)]
struct Test {
#[serde_as(as = "DisplayFromStr")]
#[cfg_attr(any(), arbitrary("some" |weird| syntax::<bool, 2>()))]
#[cfg_attr(any(), schemars(with = "i32"))]
custom: i32,
}

check_matches_schema::<Test>(&json!({
"custom": "23",
}));
}

#[test]
fn schemars_custom_with() {
#[serde_as]
Expand Down
139 changes: 139 additions & 0 deletions serde_with_macros/src/lazy_bool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use core::{
mem,
ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not},
};

/// Not-yet evaluated boolean value.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum LazyBool<T> {
/// Like `false`.
///
/// This is this type’s default.
#[default]
False,

/// Like `true`.
True,

/// Not-yet decided.
Lazy(T),
}

impl<T> From<bool> for LazyBool<T> {
fn from(value: bool) -> Self {
match value {
false => Self::False,
true => Self::True,
}
}
}

/// Helper to implement various binary operations on [`LazyBool`].
macro_rules! impl_op {
(
<
$trait:ident::$method:ident,
$assign_trait:ident::$assign_method:ident
>($matching:pat_param) {
$($pattern:pat => $body:expr),+ $(,)?
}
$(where $($bound:tt)+)?
) => {
impl<L, R, T> $trait<LazyBool<R>> for LazyBool<L>
where
L: $trait<R, Output = T>,
LazyBool<L>: Into<LazyBool<T>>,
LazyBool<R>: Into<LazyBool<T>>,
$($($bound)+)?
{
type Output = LazyBool<T>;

fn $method(self, rhs: LazyBool<R>) -> Self::Output {
match (self, rhs) {
(LazyBool::Lazy(lhs), LazyBool::Lazy(rhs)) => LazyBool::Lazy(lhs.$method(rhs)),
($matching, rhs) => rhs.into(),
(lhs, $matching) => lhs.into(),
$($pattern => $body),+
}
}
}

impl<'a, L, R, T> $trait<&'a LazyBool<R>> for LazyBool<L>
where
L: $trait<&'a R, Output = T>,
LazyBool<L>: Into<LazyBool<T>>,
LazyBool<R>: Into<LazyBool<T>> + Clone,
$($($bound)+)?
{
type Output = LazyBool<T>;

fn $method(self, rhs: &'a LazyBool<R>) -> Self::Output {
match (self, rhs) {
(LazyBool::Lazy(lhs), LazyBool::Lazy(rhs)) => LazyBool::Lazy(lhs.$method(rhs)),
($matching, rhs) => rhs.clone().into(),
(lhs, $matching) => lhs.into(),
$($pattern => $body),+
}
}
}

impl<'a, L, R, T> $trait<LazyBool<R>> for &'a LazyBool<L>
where
LazyBool<R>: $trait<&'a LazyBool<L>, Output = LazyBool<T>>,
{
type Output = LazyBool<T>;

fn $method(self, rhs: LazyBool<R>) -> Self::Output {
rhs.$method(self)
}
}

impl<L, R> $assign_trait<LazyBool<R>> for LazyBool<L>
where
LazyBool<L>: $trait<LazyBool<R>, Output = LazyBool<L>>,
{
fn $assign_method(&mut self, rhs: LazyBool<R>) {
let lhs = mem::take(self);
*self = lhs.$method(rhs);
}
}
};
}

impl_op! { <BitAnd::bitand, BitAndAssign::bitand_assign>(LazyBool::True){ _ => LazyBool::False } }
impl_op! { <BitOr::bitor, BitOrAssign::bitor_assign>(LazyBool::False) { _ => LazyBool::True } }
impl_op! {
<BitXor::bitxor, BitXorAssign::bitxor_assign>(LazyBool::False) {
(LazyBool::True, rhs) => (!rhs).into(),
(lhs, LazyBool::True) => (!lhs).into(),
}
where
LazyBool<L>: Not<Output = LazyBool<L>>,
LazyBool<R>: Not<Output = LazyBool<R>>,
}

impl<T> Not for LazyBool<T>
where
T: Not<Output = T>,
{
type Output = Self;

fn not(self) -> Self::Output {
match self {
Self::False => Self::True,
Self::True => Self::False,
Self::Lazy(this) => Self::Lazy(!this),
}
}
}

impl<T> Not for &LazyBool<T>
where
LazyBool<T>: Not<Output = LazyBool<T>> + Clone,
{
type Output = LazyBool<T>;

fn not(self) -> Self::Output {
!self.clone()
}
}
62 changes: 42 additions & 20 deletions serde_with_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@
//! [`serde_with`]: https://crates.io/crates/serde_with/
mod apply;
mod lazy_bool;
mod utils;

use crate::utils::{split_with_de_lifetime, DeriveOptions, IteratorExt as _, SchemaFieldConfig};
use crate::utils::{
split_with_de_lifetime, DeriveOptions, IteratorExt as _, SchemaFieldCondition,
SchemaFieldConfig,
};
use darling::{
ast::NestedMeta,
util::{Flag, Override},
Expand Down Expand Up @@ -619,10 +623,9 @@ pub fn serde_as(args: TokenStream, input: TokenStream) -> TokenStream {
.unwrap_or_else(|| syn::parse_quote!(::serde_with));

let schemars_config = match container_options.enable_schemars_support {
_ if cfg!(not(feature = "schemars_0_8")) => SchemaFieldConfig::Disabled,
Some(true) => SchemaFieldConfig::Unconditional,
Some(false) => SchemaFieldConfig::Disabled,
None => utils::has_derive_jsonschema(input.clone()),
_ if cfg!(not(feature = "schemars_0_8")) => SchemaFieldConfig::False,
Some(condition) => condition.into(),
None => utils::has_derive_jsonschema(input.clone()).unwrap_or_default(),
};

// Convert any error message into a nice compiler error
Expand Down Expand Up @@ -772,21 +775,40 @@ fn serde_as_add_attr_to_field(
let attr = parse_quote!(#[serde(with = #attr_inner_tokens)]);
field.attrs.push(attr);

if let Some(cfg) = schemars_config.cfg_expr() {
let with_cfg = utils::schemars_with_attr_if(
&field.attrs,
&["with", "serialize_with", "deserialize_with", "schema_with"],
)?;
let attr_inner_tokens =
quote!(#serde_with_crate_path::Schema::<#type_original, #replacement_type>)
.to_string();
let attr = parse_quote! {
#[cfg_attr(
all(#cfg, not(#with_cfg)),
schemars(with = #attr_inner_tokens))
]
};
field.attrs.push(attr);
match schemars_config {
SchemaFieldConfig::False => {}
lhs => {
let rhs = utils::schemars_with_attr_if(
&field.attrs,
&["with", "serialize_with", "deserialize_with", "schema_with"],
)?;

match lhs & !rhs {
SchemaFieldConfig::False => {}
condition => {
let attr_inner_tokens = quote! {
#serde_with_crate_path::Schema::<#type_original, #replacement_type>
};
let attr_inner_tokens = attr_inner_tokens.to_string();
let attr = match condition {
SchemaFieldConfig::False => unreachable!(),
SchemaFieldConfig::True => {
parse_quote! { #[schemars(with = #attr_inner_tokens)] }
}
SchemaFieldConfig::Lazy(SchemaFieldCondition(condition)) => {
parse_quote! {
#[cfg_attr(
#condition,
schemars(with = #attr_inner_tokens))
]
}
}
};

field.attrs.push(attr);
}
}
}
}
}
if let Some(type_) = &serde_as_options.deserialize_as {
Expand Down
Loading

0 comments on commit 67326f8

Please sign in to comment.