Repository: Aleph-Alpha/ts-rs
Pull request #77 (2022-01-19 13:49:03)

Add support for serde(tag=...) on structs
Serde supports this (see https://github.com/serde-rs/serde/pull/1448).  Though serde's documentation is missing it at the moment.
It is very useful for bringing things to typescript.



Patch:
diff --git a/macros/src/attr/struct.rs b/macros/src/attr/struct.rs
index 32c82de..5130de9 100644
--- a/macros/src/attr/struct.rs
+++ b/macros/src/attr/struct.rs
@@ -13,6 +13,7 @@ pub struct StructAttr {
     pub rename: Option<String>,
     pub export_to: Option<String>,
     pub export: bool,
+    pub tag: Option<String>,
 }
 
 #[cfg(feature = "serde-compat")]
@@ -35,12 +36,14 @@ impl StructAttr {
             rename,
             export,
             export_to,
+            tag,
         }: StructAttr,
     ) {
         self.rename = self.rename.take().or(rename);
         self.rename_all = self.rename_all.take().or(rename_all);
         self.export_to = self.export_to.take().or(export_to);
         self.export = self.export || export;
+        self.tag = self.tag.take().or(tag);
     }
 }
 
@@ -58,5 +61,6 @@ impl_parse! {
     SerdeStructAttr(input, out) {
         "rename" => out.0.rename = Some(parse_assign_str(input)?),
         "rename_all" => out.0.rename_all = Some(parse_assign_str(input).and_then(Inflection::try_from)?),
+        "tag" => out.0.tag = Some(parse_assign_str(input)?),
     }
 }
diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs
index d56eeb8..751ea91 100644
--- a/macros/src/types/named.rs
+++ b/macros/src/types/named.rs
@@ -18,6 +18,13 @@ pub(crate) fn named(
 ) -> Result<DerivedTS> {
     let mut formatted_fields = vec![];
     let mut dependencies = Dependencies::default();
+    if let Some(tag) = &attr.tag {
+        let formatted = format!("{}: \"{}\",", tag, name);
+        formatted_fields.push(quote! {
+            #formatted.to_string()
+        });
+    }
+
     for field in &fields.named {
         format_field(
             &mut formatted_fields,
diff --git a/macros/src/types/newtype.rs b/macros/src/types/newtype.rs
index 66e0552..9ebd3e6 100644
--- a/macros/src/types/newtype.rs
+++ b/macros/src/types/newtype.rs
@@ -17,6 +17,9 @@ pub(crate) fn newtype(
     if attr.rename_all.is_some() {
         syn_err!("`rename_all` is not applicable to newtype structs");
     }
+    if attr.tag.is_some() {
+        syn_err!("`tag` is not applicable to newtype structs");
+    }
     let inner = fields.unnamed.first().unwrap();
     let FieldAttr {
         type_override,
diff --git a/macros/src/types/tuple.rs b/macros/src/types/tuple.rs
index 320dfba..ac763af 100644
--- a/macros/src/types/tuple.rs
+++ b/macros/src/types/tuple.rs
@@ -18,6 +18,9 @@ pub(crate) fn tuple(
     if attr.rename_all.is_some() {
         syn_err!("`rename_all` is not applicable to tuple structs");
     }
+    if attr.tag.is_some() {
+        syn_err!("`tag` is not applicable to tuple structs");
+    }
 
     let mut formatted_fields = Vec::new();
     let mut dependencies = Dependencies::default();
diff --git a/macros/src/types/unit.rs b/macros/src/types/unit.rs
index 9a436e6..9068600 100644
--- a/macros/src/types/unit.rs
+++ b/macros/src/types/unit.rs
@@ -8,6 +8,10 @@ pub(crate) fn unit(attr: &StructAttr, name: &str) -> Result<DerivedTS> {
         syn_err!("`rename_all` is not applicable to unit structs");
     }
 
+    if attr.tag.is_some() {
+        syn_err!("`tag` is not applicable to unit structs");
+    }
+
     Ok(DerivedTS {
         inline: quote!("null".to_owned()),
         decl: quote!(format!("type {} = null;", #name)),


Test patch:
diff --git a/ts-rs/tests/struct_tag.rs b/ts-rs/tests/struct_tag.rs
new file mode 100644
index 0000000..e978259
--- /dev/null
+++ b/ts-rs/tests/struct_tag.rs
@@ -0,0 +1,16 @@
+#![allow(dead_code)]
+
+use ts_rs::TS;
+use serde::Serialize;
+
+#[derive(TS, Serialize)]
+#[serde(tag="type")]
+struct TaggedType {
+    a: i32,
+    b: i32,
+}
+
+#[test]
+fn test() {
+    assert_eq!(TaggedType::inline(), "{ type: \"TaggedType\", a: number, b: number, }")
+}