Repository: Aleph-Alpha/ts-rs
Pull request #227 (2024-02-08 13:46:28)

Implement `#[ts(untagged)]`, `#[ts(tag = "...")]` and `#[ts(tag = "...", content = "...")]`
The docs for the `TS` trait mention `#[ts(untagged)]`, `#[ts(tag = "...")]` and `#[ts(tag = "...", content = "...")]`, but they weren't actually parsed by our macros, this should fix it.

After merging this, #226 will need to be updated to also implement `#[ts(untagged)]` for enum variants


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4be78a9..0a381c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@
 - Add `semver-impl` cargo feature with support for the *semver* crate ([#176](https://github.com/Aleph-Alpha/ts-rs/pull/176))
 - Support `HashMap` with custom hashers ([#173](https://github.com/Aleph-Alpha/ts-rs/pull/173))
 - Add `import-esm` cargo feature to import files with a `.js` extension ([#192](https://github.com/Aleph-Alpha/ts-rs/pull/192))
+- Implement `#[ts(...)]` equivalents for `#[serde(tag = "...")]`, `#[serde(tag = "...", content = "...")]` and `#[serde(untagged)]` ([#227](https://github.com/Aleph-Alpha/ts-rs/pull/227))
 
 ### Fixes
 - `rename_all` with `camelCase` produces wrong names if fields were already in camelCase ([#198](https://github.com/Aleph-Alpha/ts-rs/pull/198))
diff --git a/macros/src/attr/enum.rs b/macros/src/attr/enum.rs
index 0f71919..fb8ad5e 100644
--- a/macros/src/attr/enum.rs
+++ b/macros/src/attr/enum.rs
@@ -76,7 +76,10 @@ impl_parse! {
         "rename" => out.rename = Some(parse_assign_str(input)?),
         "rename_all" => out.rename_all = Some(parse_assign_inflection(input)?),
         "export_to" => out.export_to = Some(parse_assign_str(input)?),
-        "export" => out.export = true
+        "export" => out.export = true,
+        "tag" => out.tag = Some(parse_assign_str(input)?),
+        "content" => out.content = Some(parse_assign_str(input)?),
+        "untagged" => out.untagged = true
     }
 }
 


Test patch:
diff --git a/ts-rs/tests/enum_flattening.rs b/ts-rs/tests/enum_flattening.rs
index 5d4f9f5..07a7af6 100644
--- a/ts-rs/tests/enum_flattening.rs
+++ b/ts-rs/tests/enum_flattening.rs
@@ -1,3 +1,5 @@
+#![allow(dead_code)]
+
 #[cfg(feature = "serde-compat")]
 use serde::Serialize;
 use ts_rs::TS;
@@ -31,19 +33,22 @@ fn externally_tagged() {
 }
 
 #[test]
-#[cfg(feature = "serde-compat")]
 fn adjacently_tagged() {
-    #[derive(Serialize, TS)]
+    #[cfg_attr(feature = "serde-compat", derive(Serialize))]
+    #[derive(TS)]
     struct Foo {
         one: i32,
-        #[serde(flatten)]
+        #[cfg_attr(feature = "serde-compat", serde(flatten))]
+        #[cfg_attr(not(feature = "serde-compat"), ts(flatten))]
         baz: Bar,
         qux: Option<String>,
     }
 
-    #[derive(Serialize, TS)]
+    #[cfg_attr(feature = "serde-compat", derive(Serialize))]
+    #[derive(TS)]
     #[allow(dead_code)]
-    #[serde(tag = "type", content = "stuff")]
+    #[cfg_attr(feature = "serde-compat", serde(tag = "type", content = "stuff"))]
+    #[cfg_attr(not(feature = "serde-compat"), ts(tag = "type", content = "stuff"))]
     enum Bar {
         Baz { a: i32, a2: String },
         Biz { b: bool },
@@ -57,19 +62,22 @@ fn adjacently_tagged() {
 }
 
 #[test]
-#[cfg(feature = "serde-compat")]
 fn internally_tagged() {
-    #[derive(Serialize, TS)]
+    #[cfg_attr(feature = "serde-compat", derive(Serialize))]
+    #[derive(TS)]
     struct Foo {
         qux: Option<String>,
 
-        #[serde(flatten)]
+        #[cfg_attr(feature = "serde-compat", serde(flatten))]
+        #[cfg_attr(not(feature = "serde-compat"), ts(flatten))]
         baz: Bar,
     }
 
-    #[derive(Serialize, TS)]
+    #[cfg_attr(feature = "serde-compat", derive(Serialize))]
+    #[derive(TS)]
     #[allow(dead_code)]
-    #[serde(tag = "type")]
+    #[cfg_attr(feature = "serde-compat", serde(tag = "type"))]
+    #[cfg_attr(not(feature = "serde-compat"), ts(tag = "type"))]
     enum Bar {
         Baz { a: i32, a2: String },
         Biz { b: bool },
@@ -83,17 +91,19 @@ fn internally_tagged() {
 }
 
 #[test]
-#[cfg(feature = "serde-compat")]
 fn untagged() {
-    #[derive(Serialize, TS)]
+    #[cfg_attr(feature = "serde-compat", derive(Serialize))]
+    #[derive(TS)]
     struct Foo {
-        #[serde(flatten)]
+        #[cfg_attr(feature = "serde-compat", serde(flatten))]
+        #[cfg_attr(not(feature = "serde-compat"), ts(flatten))]
         baz: Bar,
     }
 
-    #[derive(Serialize, TS)]
-    #[allow(dead_code)]
-    #[serde(untagged)]
+    #[derive(TS)]
+    #[cfg_attr(feature = "serde-compat", derive(Serialize))]
+    #[cfg_attr(feature = "serde-compat", serde(untagged))]
+    #[cfg_attr(not(feature = "serde-compat"), ts(untagged))]
     enum Bar {
         Baz { a: i32, a2: String },
         Biz { b: bool },
diff --git a/ts-rs/tests/enum_struct_rename_all.rs b/ts-rs/tests/enum_struct_rename_all.rs
index d681d33..85f1821 100644
--- a/ts-rs/tests/enum_struct_rename_all.rs
+++ b/ts-rs/tests/enum_struct_rename_all.rs
@@ -1,8 +1,10 @@
-use serde::{Deserialize, Serialize};
+#[cfg(feature = "serde-compat")]
+use serde::Serialize;
 use ts_rs::TS;
 
-#[derive(Serialize, Deserialize, TS, Clone)]
+#[derive(TS)]
 #[ts(export)]
+#[cfg_attr(feature = "serde-compat", derive(Serialize))]
 #[cfg_attr(feature = "serde-compat", serde(rename_all = "camelCase"))]
 #[cfg_attr(not(feature = "serde-compat"), ts(rename_all = "camelCase"))]
 pub enum TaskStatus {
diff --git a/ts-rs/tests/enum_variant_annotation.rs b/ts-rs/tests/enum_variant_annotation.rs
index a87570f..e1306cb 100644
--- a/ts-rs/tests/enum_variant_annotation.rs
+++ b/ts-rs/tests/enum_variant_annotation.rs
@@ -1,9 +1,11 @@
 #![allow(dead_code)]
 
-use serde::{Deserialize, Serialize};
+#[cfg(feature = "serde-compat")]
+use serde::Serialize;
 use ts_rs::TS;
 
-#[derive(Serialize, Deserialize, TS)]
+#[cfg_attr(feature = "serde-compat", derive(Serialize))]
+#[derive(TS)]
 #[cfg_attr(feature = "serde-compat", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
 #[cfg_attr(not(feature = "serde-compat"), ts(rename_all = "SCREAMING_SNAKE_CASE"))]
 #[ts(export)]
@@ -28,7 +30,8 @@ fn test_enum_variant_rename_all() {
     );
 }
 
-#[derive(Serialize, Deserialize, TS)]
+#[cfg_attr(feature = "serde-compat", derive(Serialize))]
+#[derive(TS)]
 #[ts(export)]
 enum B {
     #[cfg_attr(feature = "serde-compat", serde(rename = "SnakeMessage"))]
@@ -53,22 +56,23 @@ fn test_enum_variant_rename() {
     );
 }
 
-#[cfg(feature = "serde")]
-#[derive(Serialize, Deserialize, TS)]
-#[serde(tag = "kind")]
+#[cfg_attr(feature = "serde-compat", derive(Serialize))]
+#[derive(TS)]
+#[cfg_attr(feature = "serde-compat", serde(tag = "kind"))]
+#[cfg_attr(not(feature = "serde-compat"), ts(tag = "kind"))]
 #[ts(export)]
 pub enum C {
-    #[serde(rename = "SQUARE_THING")]
+    #[cfg_attr(feature = "serde-compat", serde(rename = "SQUARE_THING"))]
+    #[cfg_attr(not(feature = "serde-compat"), ts(rename = "SQUARE_THING"))]
     SquareThing {
         name: String,
         // ...
     },
 }
 
-#[cfg(feature = "serde")]
 #[test]
 fn test_enum_variant_with_tag() {
-    assert_eq!(C::inline(), "{ kind: \"SQUARE_THING\", name: string, }");
+    assert_eq!(C::inline(), r#"{ "kind": "SQUARE_THING", name: string, }"#);
 }
 
 #[cfg(feature = "serde-compat")]