Repository: 1Password/typeshare (rust)

Issue:
`Cow` is not handled correctly
With the following code:
```rust
use std::borrow::Cow;

use typeshare::typeshare;

#[typeshare]
pub struct Foo<'a> {
    value: Cow<'a, str>,
}
```
Since serde (de)serializes `Cow<str>` as a string, I expected Typeshare to output:
```ts
export interface Foo {
	value: string;
}
```
Instead, it outputs:
```ts
export interface Foo {
	value: Cow<string>;
}
```

## Steps to reproduce

```console
$ git clone https://git.jtai.ca/repro/typeshare-1.git
$ cd typeshare-1
$ typeshare . --lang=typescript --output-file=out.ts
```

Pull request:
Support smart pointers
Serde supports transparently (de)serializing many Rust std smart pointers, such as Cow, Rc, Arc, Cell, and others. This expands our automatic Box<T> to inner T mapping with these additional types.

Note: Looking for reviews from language pros to validate output. View the snapshot tests for details.

Resolves #133 

Patch:
diff --git a/core/src/parser.rs b/core/src/parser.rs
index 54b21d3..efc7b50 100644
--- a/core/src/parser.rs
+++ b/core/src/parser.rs
@@ -620,7 +620,7 @@ fn get_decorators(attrs: &[syn::Attribute]) -> HashMap<SupportedLanguage, Vec<St
         let decorators: Vec<String> = value.split(',').map(|s| s.trim().to_string()).collect();
 
         // lastly, get the entry in the hashmap output and extend the value, or insert what we have already found
-        let decs = out.entry(SupportedLanguage::Swift).or_insert_with(Vec::new);
+        let decs = out.entry(SupportedLanguage::Swift).or_default();
         decs.extend(decorators);
         // Sorting so all the added decorators will be after the normal ([`String`], `Codable`) in alphabetical order
         decs.sort_unstable();
@@ -710,7 +710,7 @@ fn is_skipped(attrs: &[syn::Attribute]) -> bool {
     attrs.iter().any(|attr| {
         get_serde_meta_items(attr)
             .into_iter()
-            .chain(get_typeshare_meta_items(attr).into_iter())
+            .chain(get_typeshare_meta_items(attr))
             .any(|arg| match arg {
                 NestedMeta::Meta(Meta::Path(path)) => {
                     if let Some(ident) = path.get_ident() {
diff --git a/core/src/rust_types.rs b/core/src/rust_types.rs
index cf8aa2e..84dfe87 100644
--- a/core/src/rust_types.rs
+++ b/core/src/rust_types.rs
@@ -244,9 +244,10 @@ impl TryFrom<&syn::Type> for RustType {
                         ))
                     }
                     "str" | "String" => Self::Special(SpecialRustType::String),
-                    // Since we do not need to box types in other languages, we treat this type
-                    // as its inner type.
-                    "Box" => parameters.into_iter().next().unwrap(),
+                    // These smart pointers can be treated as their inner type since serde can handle it
+                    // See impls of serde::Deserialize
+                    "Box" | "Weak" | "Arc" | "Rc" | "Cow" | "ArcWeak" | "RcWeak" | "Cell"
+                    | "Mutex" | "RefCell" | "RwLock" => parameters.into_iter().next().unwrap(),
                     "bool" => Self::Special(SpecialRustType::Bool),
                     "char" => Self::Special(SpecialRustType::Char),
                     "u8" => Self::Special(SpecialRustType::U8),


Test patch:
diff --git a/core/data/tests/boxed_value/input.rs b/core/data/tests/boxed_value/input.rs
deleted file mode 100644
index c34186c..0000000
--- a/core/data/tests/boxed_value/input.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-/// This is a comment.
-#[typeshare]
-#[serde(tag = "type", content = "content")]
-pub enum Colors {
-    Red,
-    Blue,
-    Green(Box<String>),
-}
diff --git a/core/data/tests/boxed_value/output.go b/core/data/tests/boxed_value/output.go
deleted file mode 100644
index 88da33f..0000000
--- a/core/data/tests/boxed_value/output.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package proto
-
-import "encoding/json"
-
-// This is a comment.
-type ColorsTypes string
-const (
-	ColorsTypeVariantRed ColorsTypes = "Red"
-	ColorsTypeVariantBlue ColorsTypes = "Blue"
-	ColorsTypeVariantGreen ColorsTypes = "Green"
-)
-type Colors struct{ 
-	Type ColorsTypes `json:"type"`
-	content interface{}
-}
-
-func (c *Colors) UnmarshalJSON(data []byte) error {
-	var enum struct {
-		Tag    ColorsTypes   `json:"type"`
-		Content json.RawMessage `json:"content"`
-	}
-	if err := json.Unmarshal(data, &enum); err != nil {
-		return err
-	}
-
-	c.Type = enum.Tag
-	switch c.Type {
-	case ColorsTypeVariantRed:
-		return nil
-	case ColorsTypeVariantBlue:
-		return nil
-	case ColorsTypeVariantGreen:
-		var res string
-		c.content = &res
-
-	}
-	if err := json.Unmarshal(enum.Content, &c.content); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (c Colors) MarshalJSON() ([]byte, error) {
-    var enum struct {
-		Tag    ColorsTypes   `json:"type"`
-		Content interface{} `json:"content,omitempty"`
-    }
-    enum.Tag = c.Type
-    enum.Content = c.content
-    return json.Marshal(enum)
-}
-
-func (c Colors) Green() string {
-	res, _ := c.content.(*string)
-	return *res
-}
-
-func NewColorsTypeVariantRed() Colors {
-    return Colors{
-        Type: ColorsTypeVariantRed,
-    }
-}
-func NewColorsTypeVariantBlue() Colors {
-    return Colors{
-        Type: ColorsTypeVariantBlue,
-    }
-}
-func NewColorsTypeVariantGreen(content string) Colors {
-    return Colors{
-        Type: ColorsTypeVariantGreen,
-        content: &content,
-    }
-}
-
diff --git a/core/data/tests/boxed_value/output.kt b/core/data/tests/boxed_value/output.kt
deleted file mode 100644
index 058cfc7..0000000
--- a/core/data/tests/boxed_value/output.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-@file:NoLiveLiterals
-
-package com.agilebits.onepassword
-
-import androidx.compose.runtime.NoLiveLiterals
-import kotlinx.serialization.*
-
-/// This is a comment.
-@Serializable
-sealed class Colors {
-	@Serializable
-	@SerialName("Red")
-	object Red: Colors()
-	@Serializable
-	@SerialName("Blue")
-	object Blue: Colors()
-	@Serializable
-	@SerialName("Green")
-	data class Green(val content: String): Colors()
-}
-
diff --git a/core/data/tests/boxed_value/output.scala b/core/data/tests/boxed_value/output.scala
deleted file mode 100644
index f254eeb..0000000
--- a/core/data/tests/boxed_value/output.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.agilebits
-
-package onepassword {
-
-// This is a comment.
-sealed trait Colors {
-	def serialName: String
-}
-object Colors {
-	case object Red extends Colors {
-		val serialName: String = "Red"
-	}
-	case object Blue extends Colors {
-		val serialName: String = "Blue"
-	}
-	case class Green(content: String) extends Colors {
-		val serialName: String = "Green"
-	}
-}
-
-}
diff --git a/core/data/tests/boxed_value/output.swift b/core/data/tests/boxed_value/output.swift
deleted file mode 100644
index fdb7b2c..0000000
--- a/core/data/tests/boxed_value/output.swift
+++ /dev/null
@@ -1,51 +0,0 @@
-import Foundation
-
-/// This is a comment.
-public enum Colors: Codable {
-	case red
-	case blue
-	case green(String)
-
-	enum CodingKeys: String, CodingKey, Codable {
-		case red = "Red",
-			blue = "Blue",
-			green = "Green"
-	}
-
-	private enum ContainerCodingKeys: String, CodingKey {
-		case type, content
-	}
-
-	public init(from decoder: Decoder) throws {
-		let container = try decoder.container(keyedBy: ContainerCodingKeys.self)
-		if let type = try? container.decode(CodingKeys.self, forKey: .type) {
-			switch type {
-			case .red:
-				self = .red
-				return
-			case .blue:
-				self = .blue
-				return
-			case .green:
-				if let content = try? container.decode(String.self, forKey: .content) {
-					self = .green(content)
-					return
-				}
-			}
-		}
-		throw DecodingError.typeMismatch(Colors.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Colors"))
-	}
-
-	public func encode(to encoder: Encoder) throws {
-		var container = encoder.container(keyedBy: ContainerCodingKeys.self)
-		switch self {
-		case .red:
-			try container.encode(CodingKeys.red, forKey: .type)
-		case .blue:
-			try container.encode(CodingKeys.blue, forKey: .type)
-		case .green(let content):
-			try container.encode(CodingKeys.green, forKey: .type)
-			try container.encode(content, forKey: .content)
-		}
-	}
-}
diff --git a/core/data/tests/boxed_value/output.ts b/core/data/tests/boxed_value/output.ts
deleted file mode 100644
index 7c8a223..0000000
--- a/core/data/tests/boxed_value/output.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-/** This is a comment. */
-export type Colors = 
-	| { type: "Red", content?: undefined }
-	| { type: "Blue", content?: undefined }
-	| { type: "Green", content: string };
-
diff --git a/core/data/tests/smart_pointers/input.rs b/core/data/tests/smart_pointers/input.rs
new file mode 100644
index 0000000..9ca2e00
--- /dev/null
+++ b/core/data/tests/smart_pointers/input.rs
@@ -0,0 +1,56 @@
+/// This is a comment.
+#[typeshare]
+#[serde(tag = "type", content = "content")]
+pub enum BoxyColors {
+    Red,
+    Blue,
+    Green(Box<String>),
+}
+
+/// This is a comment.
+#[typeshare]
+#[serde(tag = "type", content = "content")]
+pub struct ArcyColors {
+    pub red: Weak<u8>,
+    pub blue: ArcWeak<String>,
+    pub green: Arc<Vec<String>>,
+}
+
+/// This is a comment.
+#[typeshare]
+#[serde(tag = "type", content = "content")]
+pub struct MutexyColors {
+    pub blue: Mutex<Vec<String>>,
+    pub green: Mutex<String>,
+}
+
+/// This is a comment.
+#[typeshare]
+#[serde(tag = "type", content = "content")]
+pub struct RcyColors {
+    pub red: RcWeak<String>,
+    pub blue: Rc<Vec<String>>,
+    pub green: Rc<String>,
+}
+
+/// This is a comment.
+#[typeshare]
+#[serde(tag = "type", content = "content")]
+pub struct CellyColors {
+    pub red: Cell<String>,
+    pub blue: RefCell<Vec<String>>,
+}
+
+/// This is a comment.
+#[typeshare]
+#[serde(tag = "type", content = "content")]
+pub struct LockyColors {
+    pub red: RwLock<String>,
+}
+
+/// This is a comment.
+#[typeshare]
+#[serde(tag = "type", content = "content")]
+pub struct CowyColors<'a> {
+    pub lifetime: Cow<'a, str>,
+}
diff --git a/core/data/tests/smart_pointers/output.go b/core/data/tests/smart_pointers/output.go
new file mode 100644
index 0000000..1416d1b
--- /dev/null
+++ b/core/data/tests/smart_pointers/output.go
@@ -0,0 +1,105 @@
+package proto
+
+import "encoding/json"
+
+// This is a comment.
+type ArcyColors struct {
+	Red int `json:"red"`
+	Blue string `json:"blue"`
+	Green []string `json:"green"`
+}
+// This is a comment.
+type MutexyColors struct {
+	Blue []string `json:"blue"`
+	Green string `json:"green"`
+}
+// This is a comment.
+type RcyColors struct {
+	Red string `json:"red"`
+	Blue []string `json:"blue"`
+	Green string `json:"green"`
+}
+// This is a comment.
+type CellyColors struct {
+	Red string `json:"red"`
+	Blue []string `json:"blue"`
+}
+// This is a comment.
+type LockyColors struct {
+	Red string `json:"red"`
+}
+// This is a comment.
+type CowyColors struct {
+	Lifetime string `json:"lifetime"`
+}
+// This is a comment.
+type BoxyColorsTypes string
+const (
+	BoxyColorsTypeVariantRed BoxyColorsTypes = "Red"
+	BoxyColorsTypeVariantBlue BoxyColorsTypes = "Blue"
+	BoxyColorsTypeVariantGreen BoxyColorsTypes = "Green"
+)
+type BoxyColors struct{ 
+	Type BoxyColorsTypes `json:"type"`
+	content interface{}
+}
+
+func (b *BoxyColors) UnmarshalJSON(data []byte) error {
+	var enum struct {
+		Tag    BoxyColorsTypes   `json:"type"`
+		Content json.RawMessage `json:"content"`
+	}
+	if err := json.Unmarshal(data, &enum); err != nil {
+		return err
+	}
+
+	b.Type = enum.Tag
+	switch b.Type {
+	case BoxyColorsTypeVariantRed:
+		return nil
+	case BoxyColorsTypeVariantBlue:
+		return nil
+	case BoxyColorsTypeVariantGreen:
+		var res string
+		b.content = &res
+
+	}
+	if err := json.Unmarshal(enum.Content, &b.content); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (b BoxyColors) MarshalJSON() ([]byte, error) {
+    var enum struct {
+		Tag    BoxyColorsTypes   `json:"type"`
+		Content interface{} `json:"content,omitempty"`
+    }
+    enum.Tag = b.Type
+    enum.Content = b.content
+    return json.Marshal(enum)
+}
+
+func (b BoxyColors) Green() string {
+	res, _ := b.content.(*string)
+	return *res
+}
+
+func NewBoxyColorsTypeVariantRed() BoxyColors {
+    return BoxyColors{
+        Type: BoxyColorsTypeVariantRed,
+    }
+}
+func NewBoxyColorsTypeVariantBlue() BoxyColors {
+    return BoxyColors{
+        Type: BoxyColorsTypeVariantBlue,
+    }
+}
+func NewBoxyColorsTypeVariantGreen(content string) BoxyColors {
+    return BoxyColors{
+        Type: BoxyColorsTypeVariantGreen,
+        content: &content,
+    }
+}
+
diff --git a/core/data/tests/smart_pointers/output.kt b/core/data/tests/smart_pointers/output.kt
new file mode 100644
index 0000000..2378eb5
--- /dev/null
+++ b/core/data/tests/smart_pointers/output.kt
@@ -0,0 +1,63 @@
+@file:NoLiveLiterals
+
+package com.agilebits.onepassword
+
+import androidx.compose.runtime.NoLiveLiterals
+import kotlinx.serialization.*
+
+/// This is a comment.
+@Serializable
+data class ArcyColors (
+	val red: UByte,
+	val blue: String,
+	val green: List<String>
+)
+
+/// This is a comment.
+@Serializable
+data class MutexyColors (
+	val blue: List<String>,
+	val green: String
+)
+
+/// This is a comment.
+@Serializable
+data class RcyColors (
+	val red: String,
+	val blue: List<String>,
+	val green: String
+)
+
+/// This is a comment.
+@Serializable
+data class CellyColors (
+	val red: String,
+	val blue: List<String>
+)
+
+/// This is a comment.
+@Serializable
+data class LockyColors (
+	val red: String
+)
+
+/// This is a comment.
+@Serializable
+data class CowyColors (
+	val lifetime: String
+)
+
+/// This is a comment.
+@Serializable
+sealed class BoxyColors {
+	@Serializable
+	@SerialName("Red")
+	object Red: BoxyColors()
+	@Serializable
+	@SerialName("Blue")
+	object Blue: BoxyColors()
+	@Serializable
+	@SerialName("Green")
+	data class Green(val content: String): BoxyColors()
+}
+
diff --git a/core/data/tests/smart_pointers/output.scala b/core/data/tests/smart_pointers/output.scala
new file mode 100644
index 0000000..e035f1f
--- /dev/null
+++ b/core/data/tests/smart_pointers/output.scala
@@ -0,0 +1,65 @@
+package com.agilebits
+
+package object onepassword {
+
+type UByte = Byte
+type UShort = Short
+type UInt = Int
+type ULong = Int
+
+}
+package onepassword {
+
+// This is a comment.
+case class ArcyColors (
+	red: UByte,
+	blue: String,
+	green: Vector[String]
+)
+
+// This is a comment.
+case class MutexyColors (
+	blue: Vector[String],
+	green: String
+)
+
+// This is a comment.
+case class RcyColors (
+	red: String,
+	blue: Vector[String],
+	green: String
+)
+
+// This is a comment.
+case class CellyColors (
+	red: String,
+	blue: Vector[String]
+)
+
+// This is a comment.
+case class LockyColors (
+	red: String
+)
+
+// This is a comment.
+case class CowyColors (
+	lifetime: String
+)
+
+// This is a comment.
+sealed trait BoxyColors {
+	def serialName: String
+}
+object BoxyColors {
+	case object Red extends BoxyColors {
+		val serialName: String = "Red"
+	}
+	case object Blue extends BoxyColors {
+		val serialName: String = "Blue"
+	}
+	case class Green(content: String) extends BoxyColors {
+		val serialName: String = "Green"
+	}
+}
+
+}
diff --git a/core/data/tests/smart_pointers/output.swift b/core/data/tests/smart_pointers/output.swift
new file mode 100644
index 0000000..d9c58bf
--- /dev/null
+++ b/core/data/tests/smart_pointers/output.swift
@@ -0,0 +1,117 @@
+import Foundation
+
+/// This is a comment.
+public struct ArcyColors: Codable {
+	public let red: UInt8
+	public let blue: String
+	public let green: [String]
+
+	public init(red: UInt8, blue: String, green: [String]) {
+		self.red = red
+		self.blue = blue
+		self.green = green
+	}
+}
+
+/// This is a comment.
+public struct MutexyColors: Codable {
+	public let blue: [String]
+	public let green: String
+
+	public init(blue: [String], green: String) {
+		self.blue = blue
+		self.green = green
+	}
+}
+
+/// This is a comment.
+public struct RcyColors: Codable {
+	public let red: String
+	public let blue: [String]
+	public let green: String
+
+	public init(red: String, blue: [String], green: String) {
+		self.red = red
+		self.blue = blue
+		self.green = green
+	}
+}
+
+/// This is a comment.
+public struct CellyColors: Codable {
+	public let red: String
+	public let blue: [String]
+
+	public init(red: String, blue: [String]) {
+		self.red = red
+		self.blue = blue
+	}
+}
+
+/// This is a comment.
+public struct LockyColors: Codable {
+	public let red: String
+
+	public init(red: String) {
+		self.red = red
+	}
+}
+
+/// This is a comment.
+public struct CowyColors: Codable {
+	public let lifetime: String
+
+	public init(lifetime: String) {
+		self.lifetime = lifetime
+	}
+}
+
+/// This is a comment.
+public enum BoxyColors: Codable {
+	case red
+	case blue
+	case green(String)
+
+	enum CodingKeys: String, CodingKey, Codable {
+		case red = "Red",
+			blue = "Blue",
+			green = "Green"
+	}
+
+	private enum ContainerCodingKeys: String, CodingKey {
+		case type, content
+	}
+
+	public init(from decoder: Decoder) throws {
+		let container = try decoder.container(keyedBy: ContainerCodingKeys.self)
+		if let type = try? container.decode(CodingKeys.self, forKey: .type) {
+			switch type {
+			case .red:
+				self = .red
+				return
+			case .blue:
+				self = .blue
+				return
+			case .green:
+				if let content = try? container.decode(String.self, forKey: .content) {
+					self = .green(content)
+					return
+				}
+			}
+		}
+		throw DecodingError.typeMismatch(BoxyColors.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for BoxyColors"))
+	}
+
+	public func encode(to encoder: Encoder) throws {
+		var container = encoder.container(keyedBy: ContainerCodingKeys.self)
+		switch self {
+		case .red:
+			try container.encode(CodingKeys.red, forKey: .type)
+		case .blue:
+			try container.encode(CodingKeys.blue, forKey: .type)
+		case .green(let content):
+			try container.encode(CodingKeys.green, forKey: .type)
+			try container.encode(content, forKey: .content)
+		}
+	}
+}
diff --git a/core/data/tests/smart_pointers/output.ts b/core/data/tests/smart_pointers/output.ts
new file mode 100644
index 0000000..b062e58
--- /dev/null
+++ b/core/data/tests/smart_pointers/output.ts
@@ -0,0 +1,42 @@
+/** This is a comment. */
+export interface ArcyColors {
+	red: number;
+	blue: string;
+	green: string[];
+}
+
+/** This is a comment. */
+export interface MutexyColors {
+	blue: string[];
+	green: string;
+}
+
+/** This is a comment. */
+export interface RcyColors {
+	red: string;
+	blue: string[];
+	green: string;
+}
+
+/** This is a comment. */
+export interface CellyColors {
+	red: string;
+	blue: string[];
+}
+
+/** This is a comment. */
+export interface LockyColors {
+	red: string;
+}
+
+/** This is a comment. */
+export interface CowyColors {
+	lifetime: string;
+}
+
+/** This is a comment. */
+export type BoxyColors = 
+	| { type: "Red", content?: undefined }
+	| { type: "Blue", content?: undefined }
+	| { type: "Green", content: string };
+
diff --git a/core/tests/snapshot_tests.rs b/core/tests/snapshot_tests.rs
index 8220fde..62c50c1 100644
--- a/core/tests/snapshot_tests.rs
+++ b/core/tests/snapshot_tests.rs
@@ -545,7 +545,7 @@ tests! {
     serialize_field_as: [kotlin, swift, typescript, scala,  go];
     serialize_type_alias: [kotlin, swift, typescript, scala,  go];
     serialize_anonymous_field_as: [kotlin, swift, typescript, scala,  go];
-    boxed_value: [kotlin, swift, typescript, scala,  go];
+    smart_pointers: [kotlin, swift, typescript, scala, go];
     recursive_enum_decorator: [kotlin, swift, typescript, scala,  go];
 
     uppercase_go_acronyms: [