24 releases

0.5.13 Oct 14, 2024
0.5.11 Feb 9, 2024
0.5.9 Nov 28, 2023
0.5.7 Jul 9, 2023
0.2.1 Jul 10, 2021

#106 in Web programming

Download history 480/week @ 2024-07-20 945/week @ 2024-07-27 738/week @ 2024-08-03 597/week @ 2024-08-10 554/week @ 2024-08-17 856/week @ 2024-08-24 800/week @ 2024-08-31 748/week @ 2024-09-07 617/week @ 2024-09-14 406/week @ 2024-09-21 570/week @ 2024-09-28 773/week @ 2024-10-05 778/week @ 2024-10-12 828/week @ 2024-10-19 780/week @ 2024-10-26 962/week @ 2024-11-02

3,447 downloads per month
Used in 9 crates (5 directly)

MIT license

77KB
1K SLoC

Crates.io Build Status docs.rs

typescript-type-def

Generate TypeScript type definitions for Rust types.

This crate allows you to produce a TypeScript module containing type definitions which describe the JSON serialization of Rust types. The intended use is to define TypeScript types for data that is serialized from Rust types as JSON using serde_json so it can be safely used from TypeScript without needing to maintain a parallel set of type definitions.

One example of where this crate is useful is when working on a web application project with a Rust backend and a TypeScript frontend. If the data used to communicate between the two is defined in Rust and uses serde_json to encode/decode it for transmission across the network, you can use this crate to automatically generate a TypeScript definition file for those types in order to use them safely in your frontend code. This process can even be completely automated if you use this crate in a build script for your server to write the definition file to your TypeScript source code directory.

You can also use the Typescript type info generated by this library to write Typescript code that uses these type definitions. Rust types that implement TypeDef have an associated constant TypeDef::INFO which has a method TypeInfo::write_ref_expr that can be used for this purpose.

Features

  • json_value - Adds TypeDef impls for JSON value types from serde_json.

Examples

Simple example:

use serde::Serialize;
use typescript_type_def::{
    write_definition_file,
    DefinitionFileOptions,
    TypeDef,
};

#[derive(Serialize, TypeDef)]
struct Foo {
    a: usize,
    b: String,
}

let ts_module = {
    let mut buf = Vec::new();
    let options = DefinitionFileOptions::default();
    write_definition_file::<_, Foo>(&mut buf, options).unwrap();
    String::from_utf8(buf).unwrap()
};
assert_eq!(
    ts_module,
    r#"// AUTO-GENERATED by typescript-type-def

export default types;
export namespace types{
export type Usize=number;
export type Foo={"a":types.Usize;"b":string;};
}
"#
);

let foo = Foo {
    a: 123,
    b: "hello".to_owned(),
};
let json = serde_json::to_string(&foo).unwrap();
// This JSON matches the TypeScript type definition above
assert_eq!(json, r#"{"a":123,"b":"hello"}"#);

When working with a large codebase consisting of many types, a useful pattern is to declare an "API" type alias which lists all the types you want to make definitions for. For example:

use serde::Serialize;
use typescript_type_def::{write_definition_file, TypeDef};

#[derive(Serialize, TypeDef)]
struct Foo {
    a: String,
}

#[derive(Serialize, TypeDef)]
struct Bar {
    a: String,
}

#[derive(Serialize, TypeDef)]
struct Baz {
    a: Qux,
}

#[derive(Serialize, TypeDef)]
struct Qux {
    a: String,
}

// This type lists all the top-level types we want to make definitions for.
// You don't need to list *every* type in your API here, only ones that
// wouldn't be referenced otherwise. Note that `Qux` is not mentioned, but
// is still emitted because it is a dependency of `Baz`.
type Api = (Foo, Bar, Baz);

let ts_module = {
    let mut buf = Vec::new();
    write_definition_file::<_, Api>(&mut buf, Default::default()).unwrap();
    String::from_utf8(buf).unwrap()
};
assert_eq!(
    ts_module,
    r#"// AUTO-GENERATED by typescript-type-def

export default types;
export namespace types{
export type Foo={"a":string;};
export type Bar={"a":string;};
export type Qux={"a":string;};
export type Baz={"a":types.Qux;};
}
"#
);

Alternatively, you can use write_definition_file_from_type_infos with a list of TypeInfo references created at runtime to create a definition file. For example:

use serde::Serialize;
use typescript_type_def::{write_definition_file_from_type_infos, TypeDef};

#[derive(Serialize, TypeDef)]
struct Foo {
    a: String,
}

#[derive(Serialize, TypeDef)]
struct Bar {
    a: String,
}

#[derive(Serialize, TypeDef)]
struct Baz {
    a: Qux,
}

#[derive(Serialize, TypeDef)]
struct Qux {
    a: String,
}

// This list contains type info for all the top-level types we want to make
// definitions for.
// You don't need to list *every* type in your API here, only ones that
// wouldn't be referenced otherwise. Note that `Qux` is not mentioned, but
// is still emitted because it is a dependency of `Baz`.
let api = vec![
    &Foo::INFO,
    &Bar::INFO,
    &Baz::INFO,
];

let ts_module = {
    let mut buf = Vec::new();
    write_definition_file_from_type_infos(
        &mut buf,
        Default::default(),
        &api,
    )
    .unwrap();
    String::from_utf8(buf).unwrap()
};
assert_eq!(
    ts_module,
    r#"// AUTO-GENERATED by typescript-type-def

export default types;
export namespace types{
export type Foo={"a":string;};
export type Bar={"a":string;};
export type Qux={"a":string;};
export type Baz={"a":types.Qux;};
}
"#
);

Dependencies

~0.6–1.3MB
~28K SLoC