, at your
35 | // option. This file may not be copied, modified, or distributed
36 | // except according to those terms.
37 | //
38 | // Adapted from rustc's path_relative_from
39 | // https://github.com/rust-lang/rust/blob/e1d0de82cc40b666b88d4a6d2c9dcbc81d7ed27f/src/librustc_back/rpath.rs#L116-L158
40 | pub(super) fn diff_paths(path: P, base: B) -> Result
41 | where
42 | P: AsRef,
43 | B: AsRef,
44 | {
45 | let path = absolute(path)?;
46 | let base = absolute(base)?;
47 |
48 | let mut ita = path.components();
49 | let mut itb = base.components();
50 | let mut comps: Vec = vec![];
51 |
52 | loop {
53 | match (ita.next(), itb.next()) {
54 | (Some(C::ParentDir | C::CurDir), _) | (_, Some(C::ParentDir | C::CurDir)) => {
55 | unreachable!(
56 | "The paths have been cleaned, no no '.' or '..' components are present"
57 | )
58 | }
59 | (None, None) => break,
60 | (Some(a), None) => {
61 | comps.push(a);
62 | comps.extend(ita.by_ref());
63 | break;
64 | }
65 | (None, _) => comps.push(C::ParentDir),
66 | (Some(a), Some(b)) if comps.is_empty() && a == b => (),
67 | (Some(a), Some(_)) => {
68 | comps.push(C::ParentDir);
69 | for _ in itb {
70 | comps.push(C::ParentDir);
71 | }
72 | comps.push(a);
73 | comps.extend(ita.by_ref());
74 | break;
75 | }
76 | }
77 | }
78 |
79 | Ok(comps.iter().map(|c| c.as_os_str()).collect())
80 | }
81 |
--------------------------------------------------------------------------------
/ts-rs/src/serde_json.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use super::{impl_primitives, impl_shadow, TS};
4 |
5 | #[derive(TS)]
6 | #[ts(
7 | crate = "crate",
8 | rename = "JsonValue",
9 | untagged,
10 | export_to = "serde_json/"
11 | )]
12 | pub enum TsJsonValue {
13 | Number(i32),
14 | String(String),
15 | Boolean(bool),
16 | Array(Vec),
17 | Object(HashMap),
18 | Null(()),
19 | }
20 |
21 | impl_shadow!(as TsJsonValue: impl TS for serde_json::Value);
22 | impl_primitives!(serde_json::Number => "number");
23 | impl_shadow!(as HashMap: impl TS for serde_json::Map);
24 |
--------------------------------------------------------------------------------
/ts-rs/src/tokio.rs:
--------------------------------------------------------------------------------
1 | use tokio::sync::{Mutex, OnceCell, RwLock};
2 |
3 | use super::{impl_wrapper, TypeVisitor, TS};
4 |
5 | impl_wrapper!(impl TS for Mutex);
6 | impl_wrapper!(impl TS for OnceCell);
7 | impl_wrapper!(impl TS for RwLock);
8 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/arrays.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 |
3 | use ts_rs::TS;
4 |
5 | #[derive(TS)]
6 | #[ts(export, export_to = "arrays/")]
7 | struct Interface {
8 | a: [i32; 4],
9 | }
10 |
11 | #[test]
12 | fn free() {
13 | assert_eq!(<[String; 4]>::inline(), "[string, string, string, string]")
14 | }
15 |
16 | #[test]
17 | fn interface() {
18 | assert_eq!(
19 | Interface::inline(),
20 | "{ a: [number, number, number, number], }"
21 | )
22 | }
23 |
24 | #[test]
25 | fn newtype() {
26 | #[derive(TS)]
27 | struct Newtype(#[allow(dead_code)] [i32; 4]);
28 |
29 | assert_eq!(Newtype::inline(), "[number, number, number, number]")
30 | }
31 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/bound.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 |
3 | use ts_rs::TS;
4 |
5 | trait Driver {
6 | type Info;
7 | }
8 |
9 | struct TsDriver;
10 |
11 | #[derive(TS)]
12 | struct TsInfo;
13 |
14 | impl Driver for TsDriver {
15 | type Info = TsInfo;
16 | }
17 |
18 | #[derive(TS)]
19 | #[ts(export, export_to = "bound/")]
20 | #[ts(concrete(D = TsDriver))]
21 | struct Inner {
22 | info: D::Info,
23 | }
24 |
25 | #[derive(TS)]
26 | #[ts(export, export_to = "bound/")]
27 | #[ts(concrete(D = TsDriver), bound = "D::Info: TS")]
28 | struct Outer {
29 | inner: Inner,
30 | }
31 |
32 | #[test]
33 | fn test_bound() {
34 | assert_eq!(Outer::::decl(), "type Outer = { inner: Inner, };");
35 | assert_eq!(Inner::::decl(), "type Inner = { info: TsInfo, };");
36 | }
37 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/bson.rs:
--------------------------------------------------------------------------------
1 | #![cfg(feature = "bson-uuid-impl")]
2 |
3 | use bson::{oid::ObjectId, Uuid};
4 | use ts_rs::TS;
5 |
6 | #[derive(TS)]
7 | #[ts(export, export_to = "bson/")]
8 | struct User {
9 | _id: ObjectId,
10 | _uuid: Uuid,
11 | }
12 |
13 | #[test]
14 | fn bson() {
15 | assert_eq!(User::decl(), "type User = { _id: string, _uuid: string, };")
16 | }
17 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/chrono.rs:
--------------------------------------------------------------------------------
1 | #![allow(deprecated, dead_code)]
2 | #![cfg(feature = "chrono-impl")]
3 |
4 | use chrono::{
5 | Date, DateTime, Duration, FixedOffset, Local, Month, NaiveDate, NaiveDateTime, NaiveTime, Utc,
6 | Weekday,
7 | };
8 | use ts_rs::TS;
9 |
10 | #[derive(TS)]
11 | #[ts(export, export_to = "chrono/")]
12 | struct Chrono {
13 | date: (NaiveDate, Date, Date, Date),
14 | time: NaiveTime,
15 | date_time: (
16 | NaiveDateTime,
17 | DateTime,
18 | DateTime,
19 | DateTime,
20 | ),
21 | duration: Duration,
22 | month: Month,
23 | weekday: Weekday,
24 | }
25 |
26 | #[test]
27 | fn chrono() {
28 | assert_eq!(
29 | Chrono::decl(),
30 | "type Chrono = { date: [string, string, string, string], time: string, date_time: [string, string, string, string], duration: string, month: string, weekday: string, };"
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/complex_flattened_type.rs:
--------------------------------------------------------------------------------
1 | use ts_rs::TS;
2 |
3 | /// Defines the type of input and its intial fields
4 | #[derive(TS)]
5 | #[ts(tag = "input_type")]
6 | pub enum InputType {
7 | Text,
8 | Expression,
9 | Number {
10 | min: Option,
11 | max: Option,
12 | },
13 | Dropdown {
14 | options: Vec<(String, String)>,
15 | },
16 | }
17 |
18 | #[derive(TS)]
19 | #[ts(tag = "type")]
20 | pub enum InputFieldElement {
21 | Label {
22 | text: String,
23 | },
24 | Input {
25 | #[ts(flatten)]
26 | input: InputType,
27 | name: Option,
28 | placeholder: Option,
29 | default: Option,
30 | },
31 | }
32 |
33 | #[derive(TS)]
34 | #[ts(export, export_to = "complex_flattened_type/")]
35 | pub struct InputField {
36 | #[ts(flatten)]
37 | r#type: InputFieldElement,
38 | }
39 |
40 | #[test]
41 | fn complex_flattened_type() {
42 | assert_eq!(
43 | InputFieldElement::decl(),
44 | r#"type InputFieldElement = { "type": "Label", text: string, } | { "type": "Input", name: string | null, placeholder: string | null, default: string | null, } & ({ "input_type": "Text" } | { "input_type": "Expression" } | { "input_type": "Number", min: number | null, max: number | null, } | { "input_type": "Dropdown", options: Array<[string, string]>, });"#
45 | );
46 | assert_eq!(
47 | InputField::decl(),
48 | r#"type InputField = { "type": "Label", text: string, } | { "type": "Input", name: string | null, placeholder: string | null, default: string | null, } & ({ "input_type": "Text" } | { "input_type": "Expression" } | { "input_type": "Number", min: number | null, max: number | null, } | { "input_type": "Dropdown", options: Array<[string, string]>, });"#
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/concrete_generic.rs:
--------------------------------------------------------------------------------
1 | #![allow(unused)]
2 |
3 | mod issue_261 {
4 | use ts_rs::TS;
5 |
6 | trait Driver {
7 | type Info;
8 | }
9 |
10 | struct TsDriver;
11 | impl Driver for TsDriver {
12 | type Info = String;
13 | }
14 |
15 | #[derive(TS)]
16 | #[ts(export, export_to = "concrete_generic/issue_261/")]
17 | struct OtherInfo {
18 | x: i32,
19 | }
20 |
21 | #[derive(TS)]
22 | #[ts(export, export_to = "concrete_generic/issue_261/")]
23 | struct OtherDriver;
24 | impl Driver for OtherDriver {
25 | type Info = OtherInfo;
26 | }
27 |
28 | #[derive(TS)]
29 | #[ts(export, export_to = "concrete_generic/issue_261/", concrete(T = TsDriver))]
30 | struct Consumer1 {
31 | info: T::Info,
32 | }
33 |
34 | #[derive(TS)]
35 | #[ts(export, export_to = "concrete_generic/issue_261/", concrete(T = OtherDriver))]
36 | struct Consumer2 {
37 | info: T::Info,
38 | driver: T,
39 | }
40 |
41 | #[test]
42 | fn concrete_generic_param() {
43 | assert_eq!(
44 | Consumer1::::decl(),
45 | "type Consumer1 = { info: string, };"
46 | );
47 | // `decl` must use the concrete generic, no matter what we pass in
48 | assert_eq!(
49 | Consumer1::::decl(),
50 | Consumer1::::decl()
51 | );
52 |
53 | assert_eq!(
54 | Consumer2::::decl_concrete(),
55 | "type Consumer2 = { info: OtherInfo, driver: OtherDriver, };"
56 | );
57 | }
58 | }
59 |
60 | mod simple {
61 | use ts_rs::TS;
62 |
63 | #[derive(TS)]
64 | #[ts(export, export_to = "concrete_generic/simple/")]
65 | #[ts(concrete(T = i32))]
66 | struct Simple {
67 | t: T,
68 | }
69 |
70 | #[derive(TS)]
71 | #[ts(export, export_to = "concrete_generic/simple/")]
72 | struct Tuple {
73 | f: Option,
74 | }
75 |
76 | #[derive(TS)]
77 | #[ts(export, export_to = "concrete_generic/simple/")]
78 | #[ts(concrete(T = i32))]
79 | struct WithOption {
80 | opt: Option,
81 | }
82 |
83 | #[test]
84 | fn simple() {
85 | assert_eq!(Simple::::decl(), "type Simple = { t: number, };");
86 | assert_eq!(
87 | WithOption::::decl(),
88 | "type WithOption = { opt: number | null, };"
89 | );
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/enum_flattening.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 |
3 | #[cfg(feature = "serde-compat")]
4 | use serde::Serialize;
5 | use ts_rs::TS;
6 |
7 | #[derive(TS)]
8 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
9 | #[ts(export, export_to = "enum_flattening/externally_tagged/")]
10 | struct FooExternally {
11 | qux: i32,
12 | #[cfg_attr(feature = "serde-compat", serde(flatten))]
13 | #[cfg_attr(not(feature = "serde-compat"), ts(flatten))]
14 | baz: BarExternally,
15 | biz: Option,
16 | }
17 |
18 | #[derive(TS)]
19 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
20 | #[ts(export, export_to = "enum_flattening/externally_tagged/")]
21 | enum BarExternally {
22 | Baz { a: i32, a2: String },
23 | Biz { b: bool },
24 | Buz { c: String, d: Option },
25 | }
26 |
27 | #[derive(TS)]
28 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
29 | #[ts(export, export_to = "enum_flattening/externally_tagged/")]
30 | struct NestedExternally {
31 | #[cfg_attr(feature = "serde-compat", serde(flatten))]
32 | #[cfg_attr(not(feature = "serde-compat"), ts(flatten))]
33 | a: FooExternally,
34 | u: u32,
35 | }
36 |
37 | #[test]
38 | fn externally_tagged() {
39 | assert_eq!(
40 | FooExternally::inline(),
41 | r#"{ qux: number, biz: string | null, } & ({ "Baz": { a: number, a2: string, } } | { "Biz": { b: boolean, } } | { "Buz": { c: string, d: number | null, } })"#
42 | );
43 | assert_eq!(
44 | NestedExternally::inline(),
45 | r#"{ u: number, qux: number, biz: string | null, } & ({ "Baz": { a: number, a2: string, } } | { "Biz": { b: boolean, } } | { "Buz": { c: string, d: number | null, } })"#
46 | );
47 | }
48 |
49 | #[derive(TS)]
50 | #[ts(export, export_to = "enum_flattening/adjacently_tagged/")]
51 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
52 | struct FooAdjecently {
53 | one: i32,
54 | #[cfg_attr(feature = "serde-compat", serde(flatten))]
55 | #[cfg_attr(not(feature = "serde-compat"), ts(flatten))]
56 | baz: BarAdjecently,
57 | qux: Option,
58 | }
59 |
60 | #[derive(TS)]
61 | #[ts(export, export_to = "enum_flattening/adjacently_tagged/")]
62 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
63 | #[cfg_attr(feature = "serde-compat", serde(tag = "type", content = "stuff"))]
64 | #[cfg_attr(not(feature = "serde-compat"), ts(tag = "type", content = "stuff"))]
65 | enum BarAdjecently {
66 | Baz {
67 | a: i32,
68 | a2: String,
69 | },
70 | Biz {
71 | b: bool,
72 | },
73 |
74 | #[cfg_attr(feature = "serde-compat", serde(untagged))]
75 | #[cfg_attr(not(feature = "serde-compat"), ts(untagged))]
76 | Buz {
77 | c: String,
78 | d: Option,
79 | },
80 | }
81 |
82 | #[derive(TS)]
83 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
84 | struct NestedAdjecently {
85 | #[cfg_attr(feature = "serde-compat", serde(flatten))]
86 | #[cfg_attr(not(feature = "serde-compat"), ts(flatten))]
87 | a: FooAdjecently,
88 | u: u32,
89 | }
90 |
91 | #[test]
92 | fn adjacently_tagged() {
93 | assert_eq!(
94 | FooAdjecently::inline(),
95 | r#"{ one: number, qux: string | null, } & ({ "type": "Baz", "stuff": { a: number, a2: string, } } | { "type": "Biz", "stuff": { b: boolean, } } | { c: string, d: number | null, })"#
96 | );
97 | assert_eq!(
98 | NestedAdjecently::inline(),
99 | r#"{ u: number, one: number, qux: string | null, } & ({ "type": "Baz", "stuff": { a: number, a2: string, } } | { "type": "Biz", "stuff": { b: boolean, } } | { c: string, d: number | null, })"#
100 | );
101 | }
102 |
103 | #[derive(TS)]
104 | #[ts(export, export_to = "enum_flattening/internally_tagged/")]
105 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
106 | struct FooInternally {
107 | qux: Option,
108 |
109 | #[cfg_attr(feature = "serde-compat", serde(flatten))]
110 | #[cfg_attr(not(feature = "serde-compat"), ts(flatten))]
111 | baz: BarInternally,
112 | }
113 |
114 | #[derive(TS)]
115 | #[ts(export, export_to = "enum_flattening/internally_tagged/")]
116 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
117 | #[cfg_attr(feature = "serde-compat", serde(tag = "type"))]
118 | #[cfg_attr(not(feature = "serde-compat"), ts(tag = "type"))]
119 | enum BarInternally {
120 | Baz { a: i32, a2: String },
121 | Biz { b: bool },
122 | Buz { c: String, d: Option },
123 | }
124 |
125 | #[derive(TS)]
126 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
127 | struct NestedInternally {
128 | #[cfg_attr(feature = "serde-compat", serde(flatten))]
129 | #[cfg_attr(not(feature = "serde-compat"), ts(flatten))]
130 | a: FooInternally,
131 | u: u32,
132 | }
133 |
134 | #[test]
135 | fn internally_tagged() {
136 | assert_eq!(
137 | FooInternally::inline(),
138 | r#"{ qux: string | null, } & ({ "type": "Baz", a: number, a2: string, } | { "type": "Biz", b: boolean, } | { "type": "Buz", c: string, d: number | null, })"#
139 | );
140 | assert_eq!(
141 | NestedInternally::inline(),
142 | r#"{ u: number, qux: string | null, } & ({ "type": "Baz", a: number, a2: string, } | { "type": "Biz", b: boolean, } | { "type": "Buz", c: string, d: number | null, })"#
143 | );
144 | }
145 |
146 | #[derive(TS)]
147 | #[ts(export, export_to = "enum_flattening/untagged/")]
148 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
149 | struct FooUntagged {
150 | one: u32,
151 | #[cfg_attr(feature = "serde-compat", serde(flatten))]
152 | #[cfg_attr(not(feature = "serde-compat"), ts(flatten))]
153 | baz: BarUntagged,
154 | }
155 |
156 | #[derive(TS)]
157 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
158 | struct NestedUntagged {
159 | #[cfg_attr(feature = "serde-compat", serde(flatten))]
160 | #[cfg_attr(not(feature = "serde-compat"), ts(flatten))]
161 | a: FooUntagged,
162 | u: u32,
163 | }
164 |
165 | #[derive(TS)]
166 | #[ts(export, export_to = "enum_flattening/untagged/")]
167 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
168 | #[cfg_attr(feature = "serde-compat", serde(untagged))]
169 | #[cfg_attr(not(feature = "serde-compat"), ts(untagged))]
170 | enum BarUntagged {
171 | Baz { a: i32, a2: String },
172 | Biz { b: bool },
173 | Buz { c: String },
174 | }
175 |
176 | #[test]
177 | fn untagged() {
178 | assert_eq!(
179 | FooUntagged::inline(),
180 | r#"{ one: number, } & ({ a: number, a2: string, } | { b: boolean, } | { c: string, })"#
181 | );
182 | assert_eq!(
183 | NestedUntagged::inline(),
184 | r#"{ u: number, one: number, } & ({ a: number, a2: string, } | { b: boolean, } | { c: string, })"#
185 | );
186 | }
187 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/enum_struct_rename_all.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "serde-compat")]
2 | use serde::Serialize;
3 | use ts_rs::TS;
4 |
5 | #[derive(TS)]
6 | #[ts(export, export_to = "enum_struct_rename_all/")]
7 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
8 | #[cfg_attr(feature = "serde-compat", serde(rename_all = "camelCase"))]
9 | #[cfg_attr(not(feature = "serde-compat"), ts(rename_all = "camelCase"))]
10 | pub enum TaskStatus {
11 | #[cfg_attr(feature = "serde-compat", serde(rename_all = "camelCase"))]
12 | #[cfg_attr(not(feature = "serde-compat"), ts(rename_all = "camelCase"))]
13 | Running { started_time: String },
14 |
15 | #[cfg_attr(feature = "serde-compat", serde(rename_all = "camelCase"))]
16 | #[cfg_attr(not(feature = "serde-compat"), ts(rename_all = "camelCase"))]
17 | Terminated {
18 | status: i32,
19 | stdout: String,
20 | stderr: String,
21 | },
22 | }
23 |
24 | #[test]
25 | pub fn enum_struct_rename_all() {
26 | assert_eq!(
27 | TaskStatus::inline(),
28 | r#"{ "running": { startedTime: string, } } | { "terminated": { status: number, stdout: string, stderr: string, } }"#
29 | )
30 | }
31 |
32 | #[derive(TS, Clone)]
33 | #[ts(export, export_to = "enum_struct_rename_all/")]
34 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
35 | #[cfg_attr(feature = "serde-compat", serde(rename_all_fields = "kebab-case"))]
36 | #[cfg_attr(not(feature = "serde-compat"), ts(rename_all_fields = "kebab-case"))]
37 | pub enum TaskStatus2 {
38 | Running {
39 | started_time: String,
40 | },
41 |
42 | Terminated {
43 | status: i32,
44 | stdout: String,
45 | stderr: String,
46 | },
47 |
48 | A(i32),
49 | B(i32, i32),
50 | C,
51 | }
52 |
53 | #[test]
54 | pub fn enum_struct_rename_all_fields() {
55 | assert_eq!(
56 | TaskStatus2::inline(),
57 | r#"{ "Running": { "started-time": string, } } | { "Terminated": { status: number, stdout: string, stderr: string, } } | { "A": number } | { "B": [number, number] } | "C""#
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/enum_variant_annotation.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 |
3 | #[cfg(feature = "serde-compat")]
4 | use serde::Serialize;
5 | use ts_rs::TS;
6 |
7 | #[derive(TS)]
8 | #[ts(export, export_to = "enum_variant_anotation/")]
9 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
10 | #[cfg_attr(feature = "serde-compat", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
11 | #[cfg_attr(not(feature = "serde-compat"), ts(rename_all = "SCREAMING_SNAKE_CASE"))]
12 | enum A {
13 | MessageOne {
14 | sender_id: String,
15 | number_of_snakes: u64,
16 | },
17 | #[cfg_attr(feature = "serde-compat", serde(rename_all = "camelCase"))]
18 | #[cfg_attr(not(feature = "serde-compat"), ts(rename_all = "camelCase"))]
19 | MessageTwo {
20 | sender_id: String,
21 | number_of_camels: u64,
22 | },
23 | }
24 |
25 | #[test]
26 | fn test_enum_variant_rename_all() {
27 | assert_eq!(
28 | A::inline(),
29 | r#"{ "MESSAGE_ONE": { sender_id: string, number_of_snakes: bigint, } } | { "MESSAGE_TWO": { senderId: string, numberOfCamels: bigint, } }"#,
30 | );
31 | }
32 |
33 | #[derive(TS)]
34 | #[ts(export, export_to = "enum_variant_anotation/")]
35 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
36 | enum B {
37 | #[cfg_attr(feature = "serde-compat", serde(rename = "SnakeMessage"))]
38 | #[cfg_attr(not(feature = "serde-compat"), ts(rename = "SnakeMessage"))]
39 | MessageOne {
40 | sender_id: String,
41 | number_of_snakes: u64,
42 | },
43 | #[cfg_attr(feature = "serde-compat", serde(rename = "CamelMessage"))]
44 | #[cfg_attr(not(feature = "serde-compat"), ts(rename = "CamelMessage"))]
45 | MessageTwo {
46 | sender_id: String,
47 | number_of_camels: u64,
48 | },
49 | }
50 |
51 | #[test]
52 | fn test_enum_variant_rename() {
53 | assert_eq!(
54 | B::inline(),
55 | r#"{ "SnakeMessage": { sender_id: string, number_of_snakes: bigint, } } | { "CamelMessage": { sender_id: string, number_of_camels: bigint, } }"#,
56 | );
57 | }
58 |
59 | #[derive(TS)]
60 | #[ts(export, export_to = "enum_variant_anotation/")]
61 | #[cfg_attr(feature = "serde-compat", derive(Serialize))]
62 | #[cfg_attr(feature = "serde-compat", serde(tag = "kind"))]
63 | #[cfg_attr(not(feature = "serde-compat"), ts(tag = "kind"))]
64 | pub enum C {
65 | #[cfg_attr(feature = "serde-compat", serde(rename = "SQUARE_THING"))]
66 | #[cfg_attr(not(feature = "serde-compat"), ts(rename = "SQUARE_THING"))]
67 | SquareThing {
68 | name: String,
69 | // ...
70 | },
71 | }
72 |
73 | #[test]
74 | fn test_enum_variant_with_tag() {
75 | assert_eq!(C::inline(), r#"{ "kind": "SQUARE_THING", name: string, }"#);
76 | }
77 |
78 | #[cfg(feature = "serde-compat")]
79 | #[test]
80 | fn test_tag_and_content_quoted() {
81 | #[derive(Serialize, TS)]
82 | #[serde(tag = "kebab-cased-tag", content = "whitespace in content")]
83 | enum E {
84 | V { f: String },
85 | }
86 | assert_eq!(
87 | E::inline(),
88 | r#"{ "kebab-cased-tag": "V", "whitespace in content": { f: string, } }"#
89 | )
90 | }
91 |
92 | #[cfg(feature = "serde-compat")]
93 | #[test]
94 | fn test_variant_quoted() {
95 | #[derive(Serialize, TS)]
96 | #[serde(rename_all = "kebab-case")]
97 | enum E {
98 | VariantName { f: String },
99 | }
100 | assert_eq!(E::inline(), r#"{ "variant-name": { f: string, } }"#)
101 | }
102 |
103 | #[derive(TS)]
104 | #[ts(export, export_to = "enum_variant_anotation/")]
105 | enum D {
106 | Foo {},
107 | }
108 |
109 | #[derive(TS)]
110 | #[ts(export, export_to = "enum_variant_anotation/", tag = "type")]
111 | enum E {
112 | Foo {},
113 | Bar {},
114 | Biz { x: i32 },
115 | }
116 |
117 | #[test]
118 | fn test_empty_struct_variant_with_tag() {
119 | assert_eq!(
120 | E::inline(),
121 | r#"{ "type": "Foo", } | { "type": "Bar", } | { "type": "Biz", x: number, }"#
122 | )
123 | }
124 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/export_manually.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 |
3 | use std::{concat, fs};
4 |
5 | use ts_rs::TS;
6 |
7 | #[derive(TS)]
8 | #[ts(export_to = "export_manually/UserFile.ts")]
9 | struct User {
10 | name: String,
11 | age: i32,
12 | active: bool,
13 | }
14 |
15 | #[derive(TS)]
16 | #[ts(export_to = "export_manually/dir/")]
17 | struct UserDir {
18 | name: String,
19 | age: i32,
20 | active: bool,
21 | }
22 |
23 | #[test]
24 | fn export_manually() {
25 | User::export().unwrap();
26 |
27 | let expected_content = if cfg!(feature = "format") {
28 | concat!(
29 | "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n\n",
30 | "export type User = { name: string; age: number; active: boolean };\n",
31 | )
32 | } else {
33 | concat!(
34 | "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n",
35 | "\nexport type User = { name: string, age: number, active: boolean, };",
36 | "\n",
37 | )
38 | };
39 |
40 | let actual_content = fs::read_to_string(User::default_output_path().unwrap()).unwrap();
41 |
42 | assert_eq!(actual_content, expected_content);
43 | }
44 |
45 | #[test]
46 | fn export_manually_dir() {
47 | UserDir::export().unwrap();
48 |
49 | let expected_content = if cfg!(feature = "format") {
50 | concat!(
51 | "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n\n",
52 | "export type UserDir = { name: string; age: number; active: boolean };\n",
53 | )
54 | } else {
55 | concat!(
56 | "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n",
57 | "\nexport type UserDir = { name: string, age: number, active: boolean, };",
58 | "\n",
59 | )
60 | };
61 |
62 | let actual_content = fs::read_to_string(UserDir::default_output_path().unwrap()).unwrap();
63 |
64 | assert_eq!(actual_content, expected_content);
65 | }
66 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/export_to.rs:
--------------------------------------------------------------------------------
1 | use std::path::Path;
2 |
3 | use ts_rs::TS;
4 |
5 | #[derive(TS)]
6 | #[ts(export, export_to = "export_to/with_str_to_file.ts")]
7 | struct WithStrToFile;
8 |
9 | #[derive(TS)]
10 | #[ts(export, export_to = "export_to/")]
11 | struct WithStrToDir;
12 |
13 | // --
14 |
15 | #[derive(TS)]
16 | #[ts(export, export_to = &"export_to/with_str_ref_to_file.ts")]
17 | struct WithStrRefToFile;
18 |
19 | #[derive(TS)]
20 | #[ts(export, export_to = &"export_to/")]
21 | struct WithStrRefToDir;
22 |
23 | // --
24 |
25 | #[derive(TS)]
26 | #[ts(export, export_to = format!("export_to/with_string_to_file.ts"))]
27 | struct WithStringToFile;
28 |
29 | #[derive(TS)]
30 | #[ts(export, export_to = format!("export_to/"))]
31 | struct WithStringToDir;
32 |
33 | // --
34 |
35 | #[derive(TS)]
36 | #[ts(export, export_to = &format!("export_to/with_string_ref_to_file.ts"))]
37 | struct WithStringRefToFile;
38 |
39 | #[derive(TS)]
40 | #[ts(export, export_to = &format!("export_to/"))]
41 | struct WithStringRefToDir;
42 |
43 | // --
44 |
45 | #[derive(TS)]
46 | #[ts(export, export_to = {
47 | let dir = WithStrToFile::default_output_path().unwrap();
48 | let dir = dir.parent().unwrap();
49 | let file = dir.join("to_absolute_file_path.ts");
50 | let file = std::path::absolute(file).unwrap();
51 | file.display().to_string()
52 | })]
53 | struct ToAbsoluteFilePath(WithStrToDir, WithStrToFile);
54 |
55 | #[derive(TS)]
56 | #[ts(export, export_to = {
57 | let dir = WithStrToFile::default_output_path().unwrap();
58 | let dir = dir.parent().unwrap();
59 | let dir = std::path::absolute(dir).unwrap();
60 | let dir = dir.display();
61 | format!("{dir}/")
62 | })]
63 | struct ToAbsoluteDirPath(WithStrToDir, WithStrToFile, ToAbsoluteFilePath);
64 |
65 | // --
66 |
67 | #[test]
68 | #[cfg(test)]
69 | fn check_export_complete() {
70 | export_bindings_withstrtofile();
71 | export_bindings_withstrtodir();
72 | export_bindings_withstrreftofile();
73 | export_bindings_withstrreftodir();
74 | export_bindings_withstringtofile();
75 | export_bindings_withstringtodir();
76 | export_bindings_withstringreftofile();
77 | export_bindings_withstringreftodir();
78 | export_bindings_toabsolutefilepath();
79 | export_bindings_toabsolutedirpath();
80 |
81 | let files = [
82 | "with_str_to_file.ts",
83 | "WithStrToDir.ts",
84 | "with_str_ref_to_file.ts",
85 | "WithStrRefToDir.ts",
86 | "with_string_to_file.ts",
87 | "WithStringToDir.ts",
88 | "with_string_ref_to_file.ts",
89 | "WithStringRefToDir.ts",
90 | "to_absolute_file_path.ts",
91 | "ToAbsoluteDirPath.ts",
92 | ];
93 |
94 | let dir = std::env::var("TS_RS_EXPORT_DIR").unwrap_or_else(|_| "./bindings".to_owned());
95 | let dir = Path::new(&dir).join("export_to");
96 |
97 | files
98 | .iter()
99 | .map(|file| dir.join(file))
100 | .for_each(|file| assert!(file.is_file(), "{file:?}"));
101 | }
102 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/field_rename.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 |
3 | use ts_rs::TS;
4 |
5 | #[derive(TS)]
6 | #[cfg_attr(feature = "serde-compat", derive(serde::Serialize, serde::Deserialize))]
7 | struct Rename {
8 | #[cfg_attr(
9 | feature = "serde-compat",
10 | serde(rename = "c", skip_serializing_if = "String::is_empty")
11 | )]
12 | a: String,
13 | #[ts(rename = "bb")]
14 | b: i32,
15 | }
16 |
17 | #[test]
18 | fn test() {
19 | if (cfg!(feature = "serde-compat")) {
20 | assert_eq!(Rename::inline(), "{ c: string, bb: number, }")
21 | } else {
22 | assert_eq!(Rename::inline(), "{ a: string, bb: number, }")
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/flatten.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 |
3 | use std::collections::HashMap;
4 |
5 | use ts_rs::TS;
6 |
7 | #[derive(TS)]
8 | #[ts(export, export_to = "flatten/")]
9 | struct A {
10 | a: i32,
11 | b: i32,
12 | #[ts(flatten)]
13 | c: HashMap,
14 | }
15 |
16 | #[derive(TS)]
17 | #[ts(export, export_to = "flatten/")]
18 | struct B {
19 | #[ts(flatten)]
20 | a: A,
21 | c: i32,
22 | }
23 |
24 | #[derive(TS)]
25 | #[ts(export, export_to = "flatten/")]
26 | struct C {
27 | #[ts(inline)]
28 | b: B,
29 | d: i32,
30 | }
31 |
32 | #[test]
33 | fn test_def() {
34 | assert_eq!(
35 | C::inline(),
36 | "{ b: { c: number, a: number, b: number, } & ({ [key in string]?: number }), d: number, }"
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/generic_fields.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code, clippy::box_collection)]
2 |
3 | use std::borrow::Cow;
4 |
5 | use ts_rs::TS;
6 |
7 | #[derive(TS)]
8 | #[ts(export, export_to = "generic_fields/")]
9 | struct Newtype(Vec>);
10 |
11 | #[test]
12 | fn newtype() {
13 | assert_eq!(Newtype::inline(), "Array");
14 | }
15 |
16 | #[derive(TS)]
17 | #[ts(export, export_to = "generic_fields/")]
18 | struct NewtypeNested(Vec>);
19 |
20 | #[test]
21 | fn newtype_nested() {
22 | assert_eq!(NewtypeNested::inline(), "Array>");
23 | }
24 |
25 | #[test]
26 | fn alias() {
27 | type Alias = Vec;
28 | assert_eq!(Alias::inline(), "Array");
29 | }
30 |
31 | #[test]
32 | fn alias_nested() {
33 | type Alias = Vec>;
34 | assert_eq!(Alias::inline(), "Array>");
35 | }
36 |
37 | #[derive(TS)]
38 | #[ts(export, export_to = "generic_fields/")]
39 | struct Struct {
40 | a: Box>,
41 | b: (Vec, Vec),
42 | c: [Vec; 3],
43 | }
44 |
45 | #[test]
46 | fn named() {
47 | assert_eq!(
48 | Struct::inline(),
49 | "{ a: Array, b: [Array, Array], c: [Array, Array, Array], }"
50 | );
51 | }
52 |
53 | #[derive(TS)]
54 | #[ts(export, export_to = "generic_fields/")]
55 | struct StructNested {
56 | a: Vec>,
57 | b: (Vec>, Vec>),
58 | c: [Vec>; 3],
59 | }
60 |
61 | #[test]
62 | fn named_nested() {
63 | assert_eq!(StructNested::inline(), "{ a: Array>, b: [Array>, Array>], c: [Array>, Array>, Array>], }");
64 | }
65 |
66 | #[derive(TS)]
67 | #[ts(export, export_to = "generic_fields/")]
68 | struct Tuple(Vec, (Vec, Vec), [Vec; 3]);
69 |
70 | #[test]
71 | fn tuple() {
72 | assert_eq!(
73 | Tuple::inline(),
74 | "[Array, [Array, Array], [Array, Array, Array]]"
75 | );
76 | }
77 |
78 | #[derive(TS)]
79 | #[ts(export, export_to = "generic_fields/")]
80 | struct TupleNested(
81 | Vec>,
82 | (Vec>, Vec>),
83 | [Vec>; 3],
84 | );
85 |
86 | #[test]
87 | fn tuple_nested() {
88 | assert_eq!(
89 | TupleNested::inline(),
90 | "[Array>, [Array>, Array>], [Array>, Array>, Array>]]"
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/generic_without_import.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 |
3 | #[derive(ts_rs::TS)]
4 | struct Test {
5 | field: T,
6 | }
7 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/generics_flatten.rs:
--------------------------------------------------------------------------------
1 | use ts_rs_macros::TS;
2 |
3 | // https://github.com/Aleph-Alpha/ts-rs/issues/335
4 | #[derive(TS)]
5 | #[ts(export, export_to = "generics/flatten/")]
6 | struct Item {
7 | id: String,
8 | #[ts(flatten)]
9 | inner: D,
10 | }
11 |
12 | #[derive(TS)]
13 | #[ts(export, export_to = "generics/flatten/")]
14 | struct TwoParameters {
15 | id: String,
16 | #[ts(flatten)]
17 | a: A,
18 | #[ts(flatten)]
19 | b: B,
20 | ab: (A, B),
21 | }
22 |
23 | #[derive(TS)]
24 | #[ts(export, export_to = "generics/flatten/")]
25 | enum Enum {
26 | A {
27 | #[ts(flatten)]
28 | a: A,
29 | },
30 | B {
31 | #[ts(flatten)]
32 | b: B,
33 | },
34 | AB(A, B),
35 | }
36 |
37 | #[test]
38 | fn flattened_generic_parameters() {
39 | use ts_rs::TS;
40 |
41 | #[derive(TS)]
42 | struct Inner {
43 | x: i32,
44 | }
45 |
46 | assert_eq!(Item::<()>::decl(), "type Item = { id: string, } & D;");
47 | assert_eq!(
48 | TwoParameters::<(), ()>::decl(),
49 | "type TwoParameters = { id: string, ab: [A, B], } & A & B;"
50 | );
51 | assert_eq!(
52 | Enum::<(), ()>::decl(),
53 | "type Enum = { \"A\": A } | { \"B\": B } | { \"AB\": [A, B] };"
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/hashmap.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 |
3 | use std::collections::{BTreeMap, HashMap, HashSet};
4 |
5 | use ts_rs::TS;
6 |
7 | #[derive(TS)]
8 | #[ts(export, export_to = "hashmap/")]
9 | struct Hashes {
10 | map: HashMap,
11 | set: HashSet,
12 | }
13 |
14 | #[test]
15 | fn hashmap() {
16 | assert_eq!(
17 | Hashes::decl(),
18 | "type Hashes = { map: { [key in string]?: string }, set: Array, };"
19 | )
20 | }
21 |
22 | struct CustomHasher {}
23 |
24 | type CustomHashMap = HashMap;
25 | type CustomHashSet = HashSet;
26 |
27 | #[derive(TS)]
28 | #[ts(export, export_to = "hashmap/")]
29 | struct HashesHasher {
30 | map: CustomHashMap,
31 | set: CustomHashSet,
32 | }
33 |
34 | #[test]
35 | fn hashmap_with_custom_hasher() {
36 | assert_eq!(
37 | HashesHasher::decl(),
38 | "type HashesHasher = { map: { [key in string]?: string }, set: Array, };"
39 | )
40 | }
41 |
42 | #[derive(TS, Eq, PartialEq, Hash)]
43 | #[ts(export, export_to = "hashmap/")]
44 | struct CustomKey(String);
45 |
46 | #[derive(TS)]
47 | #[ts(export, export_to = "hashmap/")]
48 | struct CustomValue;
49 |
50 | #[derive(TS)]
51 | #[ts(export, export_to = "hashmap/")]
52 | struct HashMapWithCustomTypes {
53 | map: HashMap,
54 | }
55 |
56 | #[derive(TS)]
57 | #[ts(export, export_to = "hashmap/")]
58 | struct BTreeMapWithCustomTypes {
59 | map: BTreeMap,
60 | }
61 |
62 | #[test]
63 | fn with_custom_types() {
64 | assert_eq!(
65 | HashMapWithCustomTypes::inline(),
66 | BTreeMapWithCustomTypes::inline()
67 | );
68 | assert_eq!(
69 | HashMapWithCustomTypes::decl(),
70 | "type HashMapWithCustomTypes = { map: { [key in CustomKey]?: CustomValue }, };"
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/hashset.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 |
3 | use std::collections::{BTreeSet, HashSet};
4 |
5 | use ts_rs::TS;
6 |
7 | #[derive(TS, Eq, PartialEq, Hash)]
8 | #[ts(export, export_to = "hashset/")]
9 | struct CustomValue;
10 |
11 | #[derive(TS)]
12 | #[ts(export, export_to = "hashset/")]
13 | struct HashSetWithCustomType {
14 | set: HashSet,
15 | }
16 |
17 | #[derive(TS)]
18 | #[ts(export, export_to = "hashset/")]
19 | struct BTreeSetWithCustomType {
20 | set: BTreeSet,
21 | }
22 |
23 | #[test]
24 | fn with_custom_types() {
25 | assert_eq!(
26 | HashSetWithCustomType::inline(),
27 | BTreeSetWithCustomType::inline()
28 | );
29 | assert_eq!(
30 | HashSetWithCustomType::decl(),
31 | "type HashSetWithCustomType = { set: Array, };"
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/ts-rs/tests/integration/impl_primitive.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "bigdecimal-impl")]
2 | #[test]
3 | fn impl_primitive_bigdecimal() {
4 | assert_eq!(
5 | ::name(),
6 | ::name()
7 | );
8 | assert_eq!(
9 | ::inline(),
10 | ::inline()
11 | )
12 | }
13 |
14 | #[cfg(feature = "smol_str-impl")]
15 | #[test]
16 | fn impl_primitive_smolstr() {
17 | assert_eq!(
18 | ::name(),
19 | ::name()
20 | );
21 | assert_eq!(
22 | ::inline(),
23 | ::inline()
24 | )
25 | }
26 |
27 | #[cfg(feature = "uuid-impl")]
28 | #[test]
29 | fn impl_primitive_uuid() {
30 | assert_eq!(
31 | ::name(),
32 | ::name()
33 | );
34 | assert_eq!(
35 | ::inline(),
36 | ::inline()
37 | )
38 | }
39 |
40 | #[cfg(feature = "url-impl")]
41 | #[test]
42 | fn impl_primitive_url() {
43 | assert_eq!(
44 | ::name(),
45 | ::name()
46 | );
47 | assert_eq!(
48 | ::inline(),
49 | ::inline()
50 | )
51 | }
52 |
53 | #[cfg(feature = "ordered-float-impl")]
54 | #[test]
55 | fn impl_primitive_order_float() {
56 | assert_eq!(
57 | as ts_rs::TS>::name(),
58 | ::name()
59 | );
60 | assert_eq!(
61 | as ts_rs::TS>::inline(),
62 | ::inline()
63 | );
64 | assert_eq!(
65 | as ts_rs::TS>::name(),
66 | ::name()
67 | );
68 | assert_eq!(
69 | as ts_rs::TS>::inline(),
70 | ::inline()
71 | )
72 | }
73 |
74 | #[cfg(feature = "bson-uuid-impl")]
75 | #[test]
76 | fn impl_primitive_bson_uuid() {
77 | assert_eq!(
78 | ::name(),
79 | ::name()
80 | );
81 | assert_eq!(
82 | ::inline(),
83 | ::inline()
84 | );
85 | assert_eq!(
86 | ::name(),
87 |