Skip to content

Commit

Permalink
Support #[serde(with = "...")] for struct fields (#280)
Browse files Browse the repository at this point in the history
  • Loading branch information
escritorio-gustavo committed Apr 9, 2024
1 parent 1079b99 commit ff93760
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 0 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 @@

### Breaking

- `#[serde(with = "...")]` requires the use of `#[ts(as = "...")]` or `#[ts(type = "...")]` ([#280](https://github.com/Aleph-Alpha/ts-rs/pull/280))
- Fix incompatibility with serde for `snake_case`, `kebab-case` and `SCREAMING_SNAKE_CASE` ([#298](https://github.com/Aleph-Alpha/ts-rs/pull/298))
- `#[ts(rename_all = "...")]` no longer accepts variations in the string's casing, dashes and underscores to make behavior consistent with serde ([#298](https://github.com/Aleph-Alpha/ts-rs/pull/298))

Expand Down
17 changes: 17 additions & 0 deletions macros/src/attr/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ pub struct FieldAttr {
pub optional: Optional,
pub flatten: bool,
pub docs: String,

#[cfg(feature = "serde-compat")]
pub using_serde_with: bool,
}

/// Indicates whether the field is marked with `#[ts(optional)]`.
Expand Down Expand Up @@ -55,6 +58,8 @@ impl Attr for FieldAttr {
nullable: self.optional.nullable || other.optional.nullable,
},
flatten: self.flatten || other.flatten,
#[cfg(feature = "serde-compat")]
using_serde_with: self.using_serde_with || other.using_serde_with,

// We can't emit TSDoc for a flattened field
// and we cant make this invalid in assert_validity because
Expand All @@ -68,6 +73,14 @@ impl Attr for FieldAttr {
}

fn assert_validity(&self, field: &Self::Item) -> Result<()> {
#[cfg(feature = "serde-compat")]
if self.using_serde_with && !(self.type_as.is_some() || self.type_override.is_some()) {
syn_err_spanned!(
field;
r#"using `#[serde(with = "...")]` requires the use of `#[ts(as = "...")]` or `#[ts(type = "...")]`"#
)
}

if self.type_override.is_some() {
if self.type_as.is_some() {
syn_err_spanned!(field; "`type` is not compatible with `as`")
Expand Down Expand Up @@ -183,5 +196,9 @@ impl_parse! {
parse_assign_str(input)?;
}
},
"with" => {
parse_assign_str(input)?;
out.0.using_serde_with = true;
},
}
}
3 changes: 3 additions & 0 deletions macros/src/types/named.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ fn format_field(
optional,
flatten,
docs,

#[cfg(feature = "serde-compat")]
using_serde_with: _,
} = field_attr;

if skip {
Expand Down
1 change: 1 addition & 0 deletions macros/src/types/newtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub(crate) fn newtype(attr: &StructAttr, name: &str, fields: &FieldsUnnamed) ->
type_override,
inline,
skip,
docs: _,
..
} = field_attr;

Expand Down
3 changes: 3 additions & 0 deletions macros/src/types/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ fn format_field(
optional: _,
flatten: _,
docs: _,

#[cfg(feature = "serde-compat")]
using_serde_with: _,
} = field_attr;

if skip {
Expand Down
66 changes: 66 additions & 0 deletions ts-rs/tests/serde_with.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#![allow(unused, dead_code, clippy::disallowed_names)]

use serde::{Deserialize, Serialize};
use ts_rs::TS;

#[derive(Serialize, Deserialize, TS)]
struct Foo {
a: i32,
}

#[derive(Serialize, Deserialize, TS)]
struct Bar {
a: i32,
}

mod deser {
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use super::Foo;

pub fn serialize<S: Serializer>(foo: &Foo, serializer: S) -> Result<S::Ok, S::Error> {
foo.serialize(serializer)
}

pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Foo, D::Error> {
Foo::deserialize(deserializer)
}
}

// This test should pass when serde-compat is disabled,
// otherwise, it should fail to compile
#[test]
#[cfg(not(feature = "serde-compat"))]
fn no_serde_compat() {
#[derive(Serialize, Deserialize, TS)]
struct Baz {
#[serde(with = "deser")]
a: Foo,
}

assert_eq!(Baz::inline(), "{ a: Foo, }")
}

#[test]
fn serde_compat_as() {
#[derive(Serialize, Deserialize, TS)]
struct Baz {
#[serde(with = "deser")]
#[ts(as = "Bar")]
a: Foo,
}

assert_eq!(Baz::inline(), "{ a: Bar, }")
}

#[test]
fn serde_compat_type() {
#[derive(Serialize, Deserialize, TS)]
struct Baz {
#[serde(with = "deser")]
#[ts(type = "{ a: number }")]
a: Foo,
}

assert_eq!(Baz::inline(), "{ a: { a: number }, }")
}

0 comments on commit ff93760

Please sign in to comment.