Skip to content

Deriving the TS trait

escritorio-gustavo edited this page Feb 21, 2024 · 26 revisions

#[derive(TS)]

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

#[ts(...)] attributes

Container attributes

These are attributes that can be used both with structs and enums

#[ts(export)]

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)

#[ts(export_to = "...")]

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.

Note that if you use #[ts(export_to = "...")] with this config, the path given to TS_RS_EXPORT_DIR will be concatenated with the path given to export_to as if they were strings, so using ../foo/bar/ in export_to will cause problems, due to a currently unresolved issue with parent components in the middle of paths.

#[ts(rename = "...")]

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, };

#[ts(rename_all = "...")]

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, };

Struct field attributes

#[ts(inline)]

#[ts(flatten)]

#[ts(as = "...")]

#[ts(type = "...")]

#[ts(optional)]

Adds the optional modifier (?) to the TS type, allowing a field to be undefined or missing

#[derive(TS)]
#[ts(export)]
struct Foo {
    #[ts(optional)]
    bar: u32,
}

Generates

export type Foo = { bar?: number, };

#[ts(optional = nullable)]

Adds both the optional modifier and | null to the type

#[derive(TS)]
#[ts(export)]
struct Foo {
    #[ts(optional = nullable)]
    bar: u32,
}

Generates

export type Foo = { bar?: number | null, };

#[ts(skip)]

Avoids generating TS definitions to a field or variant

#[derive(TS)]
#[ts(export)]
struct Foo {
    #[ts(skip)]
    bar: u32,
    baz: String,
}

Generates

export type Foo = { baz: string, };

#[ts(rename = "...")]

Enum attibutes

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, } };

#[ts(tag = "...")]

Generates TS types for a serde internally tagged enum (TS calls these "discriminated unions")

#[derive(TS)]
#[ts(tag = "type")]
enum Message {
    Request { id: String, method: String, params: HashMap<String, String> },
    Response { id: String, status: u8 },
}

Geneates

export type Message = { "type": "Request", method: string, params: Record<string, string>, } | { "type": "Response", status: number, };

#[ts(tag = "...", content = "...")]

Generates TS types for a serde adjacently tagged enum

#[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, };

#[ts(untagged)]

Generates TS types for a serde untagged enum

#[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, };

#[ts(rename_all_fields)]

Equivalent to adding #[ts(rename_all = "..."] to every variant.

Accepted values are lowercase, snake_case, kebab-case, UPPERCASE, camelCase, PascalCase and SCREAMING_SNAKE_CASE.

Enum variant attributes

#[ts(inline)]

#[ts(skip)]

#[ts(rename = "...")]

#[ts(rename_all = "...")]

Clone this wiki locally