-
Notifications
You must be signed in to change notification settings - Fork 150
Deriving the TS trait
The TS trait can be easily derived through its #[derive(TS)] macro,
which will automatically handle mapping a Rust type into TypeScript type
definitions.
The #[derive(TS)] macro provides an attribute helper macro called #[ts(...)]
which can help you control how the types will be generated
These are attributes that can be used both with structs and enums
This attribute causes the generation of a test which will create a ts file
containing the TypeScript declaration for your type, as well as any types
it depends on (as long as they implement TS)
Allows you to change where your TypeScript file will be generated.
The default is ./bindings/TypeName.ts, this path is relative to Cargo.toml.
Usage:
#[derive(ts_rs::TS)]
#[ts(export)]
struct MyStruct {
foo: String
}This must be either:
- An absolute path
- A path relative to your crate's
Cargo.toml
The given path will be treated as a directory if it ends with a /
character, in which case a file called TypeName.ts will be created
within the given directory, otherwise the path will be treated as a
file (even without an extension).
Usage:
#[derive(ts_rs::TS)]
#[ts(export, export_to = "../ts_project/bindings/")] // Note that #[ts(export)] is still required
struct MyStruct {
foo: String
}If you see yourself using #[ts_export = "..."] with the same directory
for a lot (or even all) your types, there is a more convenient way to do
this.
Create a directory at the root of your project (the directory that contains
Cargo.toml) called .cargo. Inside the .cargo directory, create a file
called config.toml and type the following:
[env]
TS_RS_EXPORT_DIR = { value = "...", relative = true }Where value is a path to a directory (i.e. it must end with a slash /)
relative to your Cargo.toml file.
Now, using #[ts(export)] without #[ts(export_to = "...")] will result
in exporting to the directory defined in your config.toml file and if you
use #[ts(export_to = "...")], its path will be relative to that directory.
Changes the name of your type's TypeScript representation.
If the feature flag serde-compat is enabled (default), using
#[serde(rename = "...")] will have the same effect.
Usage:
#[derive(ts_rs::TS)]
#[ts(export, rename = "MyType")]
struct MyStruct {
foo: String
}Generates:
export type MyType = { foo: string, };Renames all the fields in your struct or variants in your enum to use a given inflection.
Accepted values are lowercase, snake_case, kebab-case, UPPERCASE,
camelCase, PascalCase and SCREAMING_SNAKE_CASE.
If the feature flag serde-compat is enabled (default), using
#[serde(rename_all = "...")] will have the same effect.
Usage:
#[derive(ts_rs::TS)]
#[ts(export, rename_all = "camelCase")]
struct MyStruct {
foo_bar: String
}Generates:
export type MyStruct = { fooBar: string, };Add the struct's name (or value of #[ts(rename = "...")]) as a field with the given key.
Usage:
#[derive(ts_rs::TS)]
#[ts(export, tag = "hello")]
struct MyStruct {
foo_bar: String
}Generates:
export type MyStruct = { "hello": "MyStruct", fooBar: string, };Inlines the type of this field, replacing its name with its definition.
Usage:
#[derive(TS)]
struct Pagination {
limit: u32,
offset: u32,
total: u32,
}
#[derive(TS)]
struct Users {
users: Vec<HashMap<String, String>>,
#[ts(inline)]
pagination: Pagination,
}Generates:
export type Users = {
users: Array<Record<string, string>>;
pagination: {
limit: number;
offset: number;
total: number;
};
};The #[ts(flatten)] attribute inlines keys from a field into
the parent struct.
Any struct, enum or map may be flattened, but beware that you should not flatten an externally tagged enum that contains non-skipped unit variants, like the following:
#[derive(TS)]
// The absence of #[ts(tag = "...")], #[ts(tag = "...", content = "...")] or #[ts(untagged)]
// means this enum is externally tagged
enum MyEnum {
Foo, // Unit variant, #[ts(skip)] not used
Bar(u32)
}
#[derive(TS)]
struct MyStruct {
biz: i32,
#[ts(flatten)] // You should not do this
qux: MyEnum
}Why? Well, this generates the following TS code:
export type MyStruct = { biz: number, } & ("Foo" | { "Bar": number })
So in case of the Foo variant, your type will be { biz: number } & 'Foo',
which doesn't make any sense.
Also note that this is valid (though heavily discouraged) if the flattened enum is the only field in your struct, because
#[derive(TS)]
struct MyStruct {
#[ts(flatten)] // You should still not do this, even though the TS is valid
qux: MyEnum
}Generates:
export type MyStruct = "Foo" | { "Bar": number }Which is valid TS, but serde will still fail to (de)serialize it
If the feature flag serde-compat is enabled (default), using
#[serde(flatten)] will have the same effect.
Usage:
#[derive(TS)]
struct Pagination {
limit: u32,
offset: u32,
total: u32,
}
#[derive(TS)]
struct Users {
users: Vec<HashMap<String, String>>,
#[ts(flatten)]
pagination: Pagination,
}Generates:
export type Users = {
users: Array<Record<string, string>>;
limit: number;
offset: number;
total: number;
};Say you are using a crate called foo that has the
following struct:
// foo/lib.rs
pub Foo {
bar: u32
}In your code, you wish to have something like:
#[derive(TS)]
struct MyCoolStruct {
my_field: foo::Foo, // Compiler error: foo::Foo doesn't implement `TS`
}When you are using a type from an external crate that
does not implement TS, it becomes impossible to use
this type normally as the type for a struct field, since
you cannot implement it yourself due to the orphan rule.
#[ts(as = "...")] helps you solve that problem.
You will need to create a duplicate of foo::Foo with
the same fields, which derives TS, then use
#[ts(as = "...")] to point to that struct:
#[derive(TS)]
pub FooDef {
bar: u32,
}
#[derive(TS)]
struct MyCoolStruct {
#[ts(as = "FooDef")]
my_field: foo::Foo,
}Now, to generate the TypeScript code, ts_rs will
use FooDef instead of Foo, avoiding the compiler
error.
Manually override the type emitted in the TypeScript code. This is generally not recommended unless you have no other option.
This also avoids the issue that #[ts(as = "...")]
solves, but at the cost of handwriting the TS type in a
string, potentially introducing TS syntax errors.
// foo/lib.rs
pub Foo {
bar: u32
}#[derive(TS)]
struct MyCoolStruct {
#[ts(type = "{ bar: number }")]
my_field: foo::Foo,
#[ts(type = "string")]
other_field: u32,
#[ts(type = "{ bar; number }")]
// ^ This should be a colon
uh_oh: foo::Foo,
}This will generate:
export type MyCoolStruct = {
my_field: { bar: number };
other_field: string;
uh_oh: { bar; number };
// ^ The contents of `#[ts(type = "...")]` are copied
// verbatim, so the syntax error is carried over
};May be applied on a struct field of type Option<T>. By default, such a field would turn into t: T | null.
If #[ts(optional)] is present, t?: T is generated instead.
Usage:
#[derive(TS)]
#[ts(export)]
struct Foo {
#[ts(optional)]
bar: Option<u32>,
}Generates
export type Foo = { bar?: number, };May be applied on a struct field of type Option<T>. By default, such a field would turn into t: T | null.
If #[ts(optional = nullable)] is present, t?: T | null is generated.
Usage:
#[derive(TS)]
#[ts(export)]
struct Foo {
#[ts(optional = nullable)]
bar: Option<u32>,
}Generates
export type Foo = { bar?: number | null, };Avoids generating TS definitions for a field.
If the feature flag serde-compat is enabled (default), using
#[serde(skip)] will have the same effect.
Usage:
#[derive(TS)]
#[ts(export)]
struct Foo {
#[ts(skip)]
bar: u32,
baz: String,
}Generates
export type Foo = { baz: string, };Changes the name of the field in the type's TS representation.
If the feature flag serde-compat is enabled (default), using
#[serde(rename = "...")] will have the same effect.
Usage:
#[derive(TS)]
#[ts(export)]
struct Foo {
#[ts(rename = "biz")]
bar: u32,
}Generates
export type Foo = { biz: number, };By default, enum type definitions will match serde's externally tagged enums, which means
#[derive(TS)]
#[ts(export)]
enum Message {
Request { id: String, method: String, params: HashMap<String, String> },
Response { id: String, status: u8 },
}Generates:
// Note: this code snipped has been formatted manually to facilitate readability
// The exported code is not guaranteed to match this formatting, even with the
// "format" feature flag
export type Message =
| { "Request": { id: string, method: string, params: Record<string, string> } }
| { "Response": { id: string, status: number } };This behavior can be changed with the attributes #[ts(tag = "...")],
#[ts(tag = "...", content = "...")] and #[ts(untagged)]. Like other
attributes, when the serde-compat feature is enabled, using the serde
version of these will also apply the behavior described below.
Generates TS types for a serde internally tagged enum (TS calls these "discriminated unions").
Beware that this type of enum may not contain tuple variants, as that will generate invalid
TypeScript, as well as cause a runtime panic! when using serde.
Newtype variants also have the same issue, unless they are a new type over a struct with named
fields.
If the feature flag serde-compat is enabled (default), using
#[serde(tag = "...")] will have the same effect.
Usage:
#[derive(TS)]
#[ts(tag = "type")]
enum Message {
Request { id: String, method: String, params: HashMap<String, String> },
Response { id: String, status: u8 },
}Geneates
// Note: this code snipped has been formatted manually to facilitate readability
// The exported code is not guaranteed to match this formatting, even with the
// "format" feature flag
export type Message =
| { "type": "Request", method: string, params: Record<string, string> }
| { "type": "Response", status: number };Generates TS types for a serde adjacently tagged enum.
If the feature flag serde-compat is enabled (default), using
#[serde(tag = "...", content = "...")] will have the same effect.
Usage:
#[derive(TS)]
#[ts(tag = "t", content = "c")]
enum Block {
Para(Vec<String>),
Str(String),
}Geneates
export type Block = { "t": "Para", "c": Array<string>, } | { "t": "Str", "c": string, };Generates TS types for a serde untagged enum.
If the feature flag serde-compat is enabled (default), using
#[serde(untagged)] will have the same effect.
Usage:
#[derive(TS)]
#[ts(untagged)]
enum Message {
Request { id: String, method: String, params: HashMap<String, String> },
Response { id: String, status: u8 },
}Geneates
export type Message = { id: string, method: string, params: Record<string, string>, } | { id: string, status: number, };Equivalent to adding #[ts(rename_all = "..."]
to every variant.
Accepted values are lowercase, snake_case, kebab-case, UPPERCASE,
camelCase, PascalCase and SCREAMING_SNAKE_CASE.
If the feature flag serde-compat is enabled (default), using
#[serde(rename_all_fields = "...")] will have the same effect.
Usage:
#[derive(TS)]
#[ts(export, rename_all_fields = "UPPERCASE")]
enum Message {
Request { id: String, method: String, params: HashMap<String, String> },
Response { id: String, status: u8 },
}Generates:
// Note: this code snipped has been formatted manually to facilitate readability
// The exported code is not guaranteed to match this formatting, even with the
// "format" feature flag
export type Message =
| { "Request": { ID: string, METHOD: string, PARAMS: Record<string, string> } }
| { "Response": { ID: string, STATUS: number } };Avoids generating TS definitions for a variant.
If the feature flag serde-compat is enabled (default), using
#[serde(skip)] will have the same effect.
Usage:
#[derive(TS)]
#[ts(export)]
enum Message {
#[ts(skip)]
Request { id: String, method: String, params: HashMap<String, String> },
Response { id: String, status: u8 },
}Generates:
// Note: this code snipped has been formatted manually to facilitate readability
// The exported code is not guaranteed to match this formatting, even with the
// "format" feature flag
export type Message = { "Response": { id: string, status: number } };Changes the name of a variant's tag (has no effect on untagged enums or variants).
If the feature flag serde-compat is enabled (default), using
#[serde(rename = "...")] will have the same effect.
Usage:
#[derive(TS)]
#[ts(export)]
enum Message {
#[ts(rename = "req")]
Request { id: String, method: String, params: HashMap<String, String> },
#[ts(rename = "res")]
Response { id: String, status: u8 },
}Generates:
// Note: this code snipped has been formatted manually to facilitate readability
// The exported code is not guaranteed to match this formatting, even with the
// "format" feature flag
export type Message =
| { "req": { id: string, method: string, params: Record<string, string> } }
| { "res": { id: string, status: number } };In an enum's struct variant, renames all the fields of the variant to use a given inflection.
Accepted values are lowercase, snake_case, kebab-case, UPPERCASE,
camelCase, PascalCase and SCREAMING_SNAKE_CASE.
If the feature flag serde-compat is enabled (default), using
#[serde(rename_all = "...")] will have the same effect.
Usage:
#[derive(TS)]
#[ts(export)]
enum Message {
#[ts(rename_all = "UPPERCASE")]
Request { id: String, method: String, params: HashMap<String, String> },
Response { id: String, status: u8 },
}Generates:
// Note: this code snipped has been formatted manually to facilitate readability
// The exported code is not guaranteed to match this formatting, even with the
// "format" feature flag
export type Message =
| { "Request": { ID: string, METHOD: string, PARAMS: Record<string, string> } }
| { "Response": { id: string, status: number } };Allows specific enum variants to be treated as untagged, regardless
of the enums #[ts(tag = "...")] or #[ts(tag = "...", content = "...")]
attribute.
If the feature flag serde-compat is enabled (default), using
#[serde(untagged)] will have the same effect.
Usage:
#[derive(TS)]
#[ts(export)]
enum Message {
Request { id: String, method: String, params: HashMap<String, String> },
#[ts(untagged)]
Response { id: String, status: u8 },
}Generates:
// Note: this code snipped has been formatted manually to facilitate readability
// The exported code is not guaranteed to match this formatting, even with the
// "format" feature flag
export type Message =
| { "Request": { id: string, method: string, params: Record<string, string> } }
| { id: string, status: number };