ts_rs/
lib.rs

1//! <h1 align="center" style="padding-top: 0; margin-top: 0;">
2//! <img width="150px" src="https://raw.githubusercontent.com/Aleph-Alpha/ts-rs/main/logo.png" alt="logo">
3//! <br/>
4//! ts-rs
5//! </h1>
6//! <p align="center">
7//! Generate typescript type declarations from rust types
8//! </p>
9//!
10//! <div align="center">
11//! <!-- Github Actions -->
12//! <img src="https://img.shields.io/github/actions/workflow/status/Aleph-Alpha/ts-rs/test.yml?branch=main" alt="actions status" />
13//! <a href="https://crates.io/crates/ts-rs">
14//! <img src="https://img.shields.io/crates/v/ts-rs.svg?style=flat-square"
15//! alt="Crates.io version" />
16//! </a>
17//! <a href="https://docs.rs/ts-rs">
18//! <img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
19//! alt="docs.rs docs" />
20//! </a>
21//! <a href="https://crates.io/crates/ts-rs">
22//! <img src="https://img.shields.io/crates/d/ts-rs.svg?style=flat-square"
23//! alt="Download" />
24//! </a>
25//! </div>
26//!
27//! ## Why?
28//! When building a web application in rust, data structures have to be shared between backend and frontend.
29//! Using this library, you can easily generate TypeScript bindings to your rust structs & enums so that you can keep your
30//! types in one place.
31//!
32//! ts-rs might also come in handy when working with webassembly.
33//!
34//! ## How?
35//! ts-rs exposes a single trait, `TS`. Using a derive macro, you can implement this interface for your types.
36//! Then, you can use this trait to obtain the TypeScript bindings.
37//! We recommend doing this in your tests.
38//! [See the example](https://github.com/Aleph-Alpha/ts-rs/blob/main/example/src/lib.rs) and [the docs](https://docs.rs/ts-rs/latest/ts_rs/).
39//!
40//! ## Get started
41//! ```toml
42//! [dependencies]
43//! ts-rs = "10.1"
44//! ```
45//!
46//! ```rust
47//! use ts_rs::TS;
48//!
49//! #[derive(TS)]
50//! #[ts(export)]
51//! struct User {
52//!     user_id: i32,
53//!     first_name: String,
54//!     last_name: String,
55//! }
56//! ```
57//!
58//! When running `cargo test` or `cargo test export_bindings`, the TypeScript bindings will be exported to the file `bindings/User.ts`
59//! and will contain the following code:
60//!
61//! ```ts
62//! export type User = { user_id: number, first_name: string, last_name: string, };
63//! ```
64//!
65//! ## Features
66//! - generate type declarations from rust structs
67//! - generate union declarations from rust enums
68//! - inline types
69//! - flatten structs/types
70//! - generate necessary imports when exporting to multiple files
71//! - serde compatibility
72//! - generic types
73//! - support for ESM imports
74//!
75//! ## cargo features
76//! | **Feature**        | **Description**                                                                                                                                                                                           |
77//! |:-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
78//! | serde-compat       | **Enabled by default** <br/>See the *"serde compatibility"* section below for more information.                                                                                                           |
79//! | format             | Enables formatting of the generated TypeScript bindings. <br/>Currently, this unfortunately adds quite a few dependencies.                                                                                |
80//! | no-serde-warnings  | By default, warnings are printed during build if unsupported serde attributes are encountered. <br/>Enabling this feature silences these warnings.                                                        |
81//! | import-esm         | When enabled,`import` statements in the generated file will have the `.js` extension in the end of the path to conform to the ES Modules spec. <br/> Example: `import { MyStruct } from "./my_struct.js"` |
82//! | serde-json-impl    | Implement `TS` for types from *serde_json*                                                                                                                                                                |
83//! | chrono-impl        | Implement `TS` for types from *chrono*                                                                                                                                                                    |
84//! | bigdecimal-impl    | Implement `TS` for types from *bigdecimal*                                                                                                                                                                |
85//! | url-impl           | Implement `TS` for types from *url*                                                                                                                                                                       |
86//! | uuid-impl          | Implement `TS` for types from *uuid*                                                                                                                                                                      |
87//! | bson-uuid-impl     | Implement `TS` for *bson::oid::ObjectId* and *bson::uuid*                                                                                                                                                 |
88//! | bytes-impl         | Implement `TS` for types from *bytes*                                                                                                                                                                     |
89//! | indexmap-impl      | Implement `TS` for types from *indexmap*                                                                                                                                                                  |
90//! | ordered-float-impl | Implement `TS` for types from *ordered_float*                                                                                                                                                             |
91//! | heapless-impl      | Implement `TS` for types from *heapless*                                                                                                                                                                  |
92//! | semver-impl        | Implement `TS` for types from *semver*                                                                                                                                                                    |
93//! | smol_str-impl      | Implement `TS` for types from *smol_str*                                                                                                                                                                    |
94//! | tokio-impl         | Implement `TS` for types from *tokio*                                                                                                                                                                    |
95//!
96//! <br/>
97//!
98//! If there's a type you're dealing with which doesn't implement `TS`, use either
99//! `#[ts(as = "..")]` or `#[ts(type = "..")]`, or open a PR.
100//!
101//! ## `serde` compatability
102//! With the `serde-compat` feature (enabled by default), serde attributes can be parsed for enums and structs.
103//! Supported serde attributes:
104//! - `rename`
105//! - `rename-all`
106//! - `rename-all-fields`
107//! - `tag`
108//! - `content`
109//! - `untagged`
110//! - `skip`
111//! - `flatten`
112//! - `default`
113//!
114//! Note: `skip_serializing` and `skip_deserializing` are ignored. If you wish to exclude a field
115//! from the generated type, but cannot use `#[serde(skip)]`, use `#[ts(skip)]` instead.
116//!
117//! When ts-rs encounters an unsupported serde attribute, a warning is emitted, unless the feature `no-serde-warnings` is enabled.
118//!
119//! ## Contributing
120//! Contributions are always welcome!
121//! Feel free to open an issue, discuss using GitHub discussions or open a PR.
122//! [See CONTRIBUTING.md](https://github.com/Aleph-Alpha/ts-rs/blob/main/CONTRIBUTING.md)
123//!
124//! ## MSRV
125//! The Minimum Supported Rust Version for this crate is 1.63.0
126
127use std::{
128    any::TypeId,
129    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
130    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
131    num::{
132        NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
133        NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize,
134    },
135    ops::{Range, RangeInclusive},
136    path::{Path, PathBuf},
137};
138
139pub use ts_rs_macros::TS;
140
141pub use crate::export::ExportError;
142
143#[cfg(feature = "chrono-impl")]
144mod chrono;
145mod export;
146#[cfg(feature = "serde-json-impl")]
147mod serde_json;
148#[cfg(feature = "tokio-impl")]
149mod tokio;
150
151/// A type which can be represented in TypeScript.  
152/// Most of the time, you'd want to derive this trait instead of implementing it manually.  
153/// ts-rs comes with implementations for all primitives, most collections, tuples,
154/// arrays and containers.
155///
156/// ### exporting
157/// Because Rusts procedural macros are evaluated before other compilation steps, TypeScript
158/// bindings __cannot__ be exported during compile time.
159///
160/// Bindings can be exported within a test, which ts-rs generates for you by adding `#[ts(export)]`
161/// to a type you wish to export to a file.  
162/// When `cargo test` is run, all types annotated with `#[ts(export)]` and all of their
163/// dependencies will be written to `TS_RS_EXPORT_DIR`, or `./bindings` by default.
164///
165/// For each individual type, path and filename within the output directory can be changed using
166/// `#[ts(export_to = "...")]`. By default, the filename will be derived from the name of the type.
167///
168/// If, for some reason, you need to do this during runtime or cannot use `#[ts(export)]`, bindings
169/// can be exported manually:
170///
171/// | Function              | Includes Dependencies | To                 |
172/// |-----------------------|-----------------------|--------------------|
173/// | [`TS::export`]        | ❌                    | `TS_RS_EXPORT_DIR` |
174/// | [`TS::export_all`]    | ✔️                    | `TS_RS_EXPORT_DIR` |
175/// | [`TS::export_all_to`] | ✔️                    | _custom_           |
176///
177/// ### serde compatibility
178/// By default, the feature `serde-compat` is enabled.
179/// ts-rs then parses serde attributes and adjusts the generated typescript bindings accordingly.
180/// Not all serde attributes are supported yet - if you use an unsupported attribute, you'll see a
181/// warning.
182///
183/// ### container attributes
184/// attributes applicable for both structs and enums
185///
186/// - **`#[ts(crate = "..")]`**
187///   Generates code which references the module passed to it instead of defaulting to `::ts_rs`
188///   This is useful for cases where you have to re-export the crate.
189///
190/// - **`#[ts(export)]`**  
191///   Generates a test which will export the type, by default to `bindings/<name>.ts` when running
192///   `cargo test`. The default base directory can be overridden with the `TS_RS_EXPORT_DIR` environment variable.
193///   Adding the variable to a project's [config.toml](https://doc.rust-lang.org/cargo/reference/config.html#env) can
194///   make it easier to manage.
195///   ```toml
196///   # <project-root>/.cargo/config.toml
197///   [env]
198///   TS_RS_EXPORT_DIR = { value = "<OVERRIDE_DIR>", relative = true }
199///   ```
200///   <br/>
201///
202/// - **`#[ts(export_to = "..")]`**  
203///   Specifies where the type should be exported to. Defaults to `<name>.ts`.  
204///   The path given to the `export_to` attribute is relative to the `TS_RS_EXPORT_DIR` environment variable,
205///   or, if `TS_RS_EXPORT_DIR` is not set, to `./bindings`  
206///   If the provided path ends in a trailing `/`, it is interpreted as a directory.   
207///   Note that you need to add the `export` attribute as well, in order to generate a test which exports the type.
208///   <br/><br/>
209///
210/// - **`#[ts(as = "..")]`**  
211///   Overrides the type used in Typescript, using the provided Rust type instead.
212///   This is useful when you have a custom serializer and deserializer and don't want to implement `TS` manually
213///   <br/><br/>
214///
215/// - **`#[ts(type = "..")]`**  
216///   Overrides the type used in TypeScript.  
217///   This is useful when you have a custom serializer and deserializer and don't want to implement `TS` manually
218///   <br/><br/>
219///
220/// - **`#[ts(rename = "..")]`**  
221///   Sets the typescript name of the generated type
222///   <br/><br/>
223///
224/// - **`#[ts(rename_all = "..")]`**  
225///   Rename all fields/variants of the type.  
226///   Valid values are `lowercase`, `UPPERCASE`, `camelCase`, `snake_case`, `PascalCase`, `SCREAMING_SNAKE_CASE`, "kebab-case" and "SCREAMING-KEBAB-CASE"
227///   <br/><br/>
228///
229/// - **`#[ts(concrete(..)]`**  
230///   Disables one ore more generic type parameters by specifying a concrete type for them.  
231///   The resulting TypeScript definition will not be generic over these parameters and will use the
232///   provided type instead.  
233///   This is especially useful for generic types containing associated types. Since TypeScript does
234///   not have an equivalent construct to associated types, we cannot generate a generic definition
235///   for them. Using `#[ts(concrete(..)]`, we can however generate a non-generic definition.
236///   Example:
237///   ```
238///   # use ts_rs::TS;
239///   ##[derive(TS)]
240///   ##[ts(concrete(I = std::vec::IntoIter<String>))]
241///   struct SearchResult<I: Iterator>(Vec<I::Item>);
242///   // will always generate `type SearchResult = Array<String>`.
243///   ```
244///   <br/><br/>
245///
246/// - **`#[ts(bound)]`**
247///   Override the bounds generated on the `TS` implementation for this type. This is useful in
248///   combination with `#[ts(concrete)]`, when the type's generic parameters aren't directly used
249///   in a field or variant.
250///
251///   Example:
252///   ```
253///   # use ts_rs::TS;
254///
255///   trait Container {
256///       type Value: TS;
257///   }
258///
259///   struct MyContainer;
260///
261///   ##[derive(TS)]
262///   struct MyValue;
263///
264///   impl Container for MyContainer {
265///       type Value = MyValue;
266///   }
267///
268///   ##[derive(TS)]
269///   ##[ts(export, concrete(C = MyContainer))]
270///   struct Inner<C: Container> {
271///       value: C::Value,
272///   }
273///
274///   ##[derive(TS)]
275///   // Without `#[ts(bound)]`, `#[derive(TS)]` would generate an unnecessary
276///   // `C: TS` bound
277///   ##[ts(export, concrete(C = MyContainer), bound = "C::Value: TS")]
278///   struct Outer<C: Container> {
279///       inner: Inner<C>,
280///   }
281///   ```
282///   <br/><br/>
283///
284/// ### struct attributes
285/// - **`#[ts(tag = "..")]`**  
286///   Include the structs name (or value of `#[ts(rename = "..")]`) as a field with the given key.
287///   <br/><br/>
288///
289/// ### struct field attributes
290///
291/// - **`#[ts(type = "..")]`**  
292///   Overrides the type used in TypeScript.  
293///   This is useful when there's a type for which you cannot derive `TS`.
294///   <br/><br/>
295///
296/// - **`#[ts(as = "..")]`**  
297///   Overrides the type of the annotated field, using the provided Rust type instead.
298///   This is useful when there's a type for which you cannot derive `TS`.  
299///   `_` may be used to refer to the type of the field, e.g `#[ts(as = "Option<_>")]`.
300///   <br/><br/>
301///
302/// - **`#[ts(rename = "..")]`**  
303///   Renames this field. To rename all fields of a struct, see the container attribute `#[ts(rename_all = "..")]`.
304///   <br/><br/>
305///
306/// - **`#[ts(inline)]`**  
307///   Inlines the type of this field, replacing its name with its definition.
308///   <br/><br/>
309///
310/// - **`#[ts(skip)]`**  
311///   Skips this field, omitting it from the generated *TypeScript* type.
312///   <br/><br/>
313///
314/// - **`#[ts(optional)]`**  
315///   May be applied on a struct field of type `Option<T>`. By default, such a field would turn into `t: T | null`.  
316///   If `#[ts(optional)]` is present, `t?: T` is generated instead.  
317///   If `#[ts(optional = nullable)]` is present, `t?: T | null` is generated.
318///   <br/><br/>
319///
320/// - **`#[ts(flatten)]`**  
321///   Flatten this field, inlining all the keys of the field's type into its parent.
322///   <br/><br/>
323///   
324/// ### enum attributes
325///
326/// - **`#[ts(tag = "..")]`**  
327///   Changes the representation of the enum to store its tag in a separate field.  
328///   See [the serde docs](https://serde.rs/enum-representations.html) for more information.
329///   <br/><br/>
330///
331/// - **`#[ts(content = "..")]`**  
332///   Changes the representation of the enum to store its content in a separate field.  
333///   See [the serde docs](https://serde.rs/enum-representations.html) for more information.
334///   <br/><br/>
335///
336/// - **`#[ts(untagged)]`**  
337///   Changes the representation of the enum to not include its tag.  
338///   See [the serde docs](https://serde.rs/enum-representations.html) for more information.
339///   <br/><br/>
340///
341/// - **`#[ts(rename_all = "..")]`**  
342///   Rename all variants of this enum.  
343///   Valid values are `lowercase`, `UPPERCASE`, `camelCase`, `snake_case`, `PascalCase`, `SCREAMING_SNAKE_CASE`, "kebab-case" and "SCREAMING-KEBAB-CASE"
344///   <br/><br/>
345///
346/// - **`#[ts(rename_all_fields = "..")]`**  
347///   Renames the fields of all the struct variants of this enum. This is equivalent to using
348///   `#[ts(rename_all = "..")]` on all of the enum's variants.
349///   Valid values are `lowercase`, `UPPERCASE`, `camelCase`, `snake_case`, `PascalCase`, `SCREAMING_SNAKE_CASE`, "kebab-case" and "SCREAMING-KEBAB-CASE"
350///   <br/><br/>
351///  
352/// ### enum variant attributes
353///
354/// - **`#[ts(rename = "..")]`**  
355///   Renames this variant. To rename all variants of an enum, see the container attribute `#[ts(rename_all = "..")]`.
356///   <br/><br/>
357///
358/// - **`#[ts(skip)]`**  
359///   Skip this variant, omitting it from the generated *TypeScript* type.
360///   <br/><br/>
361///
362/// - **`#[ts(untagged)]`**  
363///   Changes this variant to be treated as if the enum was untagged, regardless of the enum's tag
364///   and content attributes
365///   <br/><br/>
366///
367/// - **`#[ts(rename_all = "..")]`**  
368///   Renames all the fields of a struct variant.
369///   Valid values are `lowercase`, `UPPERCASE`, `camelCase`, `snake_case`, `PascalCase`, `SCREAMING_SNAKE_CASE`, "kebab-case" and "SCREAMING-KEBAB-CASE"
370///   <br/><br/>
371pub trait TS {
372    /// If this type does not have generic parameters, then `WithoutGenerics` should just be `Self`.
373    /// If the type does have generic parameters, then all generic parameters must be replaced with
374    /// a dummy type, e.g `ts_rs::Dummy` or `()`.
375    /// The only requirement for these dummy types is that `EXPORT_TO` must be `None`.
376    ///
377    /// # Example:
378    /// ```
379    /// use ts_rs::TS;
380    /// struct GenericType<A, B>(A, B);
381    /// impl<A, B> TS for GenericType<A, B> {
382    ///     type WithoutGenerics = GenericType<ts_rs::Dummy, ts_rs::Dummy>;
383    ///     // ...
384    ///     # fn decl() -> String { todo!() }
385    ///     # fn decl_concrete() -> String { todo!() }
386    ///     # fn name() -> String { todo!() }
387    ///     # fn inline() -> String { todo!() }
388    ///     # fn inline_flattened() -> String { todo!() }
389    /// }
390    /// ```
391    type WithoutGenerics: TS + ?Sized;
392
393    /// JSDoc comment to describe this type in TypeScript - when `TS` is derived, docs are
394    /// automatically read from your doc comments or `#[doc = ".."]` attributes
395    const DOCS: Option<&'static str> = None;
396
397    /// Identifier of this type, excluding generic parameters.
398    fn ident() -> String {
399        // by default, fall back to `TS::name()`.
400        let name = Self::name();
401
402        match name.find('<') {
403            Some(i) => name[..i].to_owned(),
404            None => name,
405        }
406    }
407
408    /// Declaration of this type, e.g. `type User = { user_id: number, ... }`.
409    /// This function will panic if the type has no declaration.
410    ///
411    /// If this type is generic, then all provided generic parameters will be swapped for
412    /// placeholders, resulting in a generic typescript definition.
413    /// Both `SomeType::<i32>::decl()` and `SomeType::<String>::decl()` will therefore result in
414    /// the same TypeScript declaration `type SomeType<A> = ...`.
415    fn decl() -> String;
416
417    /// Declaration of this type using the supplied generic arguments.
418    /// The resulting TypeScript definition will not be generic. For that, see `TS::decl()`.
419    /// If this type is not generic, then this function is equivalent to `TS::decl()`.
420    fn decl_concrete() -> String;
421
422    /// Name of this type in TypeScript, including generic parameters
423    fn name() -> String;
424
425    /// Formats this types definition in TypeScript, e.g `{ user_id: number }`.
426    /// This function will panic if the type cannot be inlined.
427    fn inline() -> String;
428
429    /// Flatten a type declaration.  
430    /// This function will panic if the type cannot be flattened.
431    fn inline_flattened() -> String;
432
433    /// Iterates over all dependency of this type.
434    fn visit_dependencies(_: &mut impl TypeVisitor)
435    where
436        Self: 'static,
437    {
438    }
439
440    /// Iterates over all type parameters of this type.
441    fn visit_generics(_: &mut impl TypeVisitor)
442    where
443        Self: 'static,
444    {
445    }
446
447    /// Resolves all dependencies of this type recursively.
448    fn dependencies() -> Vec<Dependency>
449    where
450        Self: 'static,
451    {
452        let mut deps: Vec<Dependency> = vec![];
453        struct Visit<'a>(&'a mut Vec<Dependency>);
454        impl<'a> TypeVisitor for Visit<'a> {
455            fn visit<T: TS + 'static + ?Sized>(&mut self) {
456                if let Some(dep) = Dependency::from_ty::<T>() {
457                    self.0.push(dep);
458                }
459            }
460        }
461        Self::visit_dependencies(&mut Visit(&mut deps));
462
463        deps
464    }
465
466    /// Manually export this type to the filesystem.
467    /// To export this type together with all of its dependencies, use [`TS::export_all`].
468    ///
469    /// # Automatic Exporting
470    /// Types annotated with `#[ts(export)]`, together with all of their dependencies, will be
471    /// exported automatically whenever `cargo test` is run.  
472    /// In that case, there is no need to manually call this function.
473    ///
474    /// # Target Directory
475    /// The target directory to which the type will be exported may be changed by setting the
476    /// `TS_RS_EXPORT_DIR` environment variable. By default, `./bindings` will be used.
477    ///
478    /// To specify a target directory manually, use [`TS::export_all_to`], which also exports all
479    /// dependencies.
480    ///
481    /// To alter the filename or path of the type within the target directory,
482    /// use `#[ts(export_to = "...")]`.
483    fn export() -> Result<(), ExportError>
484    where
485        Self: 'static,
486    {
487        let path = Self::default_output_path()
488            .ok_or_else(std::any::type_name::<Self>)
489            .map_err(ExportError::CannotBeExported)?;
490
491        export::export_to::<Self, _>(path)
492    }
493
494    /// Manually export this type to the filesystem, together with all of its dependencies.  
495    /// To export only this type, without its dependencies, use [`TS::export`].
496    ///
497    /// # Automatic Exporting
498    /// Types annotated with `#[ts(export)]`, together with all of their dependencies, will be
499    /// exported automatically whenever `cargo test` is run.  
500    /// In that case, there is no need to manually call this function.
501    ///
502    /// # Target Directory
503    /// The target directory to which the types will be exported may be changed by setting the
504    /// `TS_RS_EXPORT_DIR` environment variable. By default, `./bindings` will be used.
505    ///
506    /// To specify a target directory manually, use [`TS::export_all_to`].
507    ///
508    /// To alter the filenames or paths of the types within the target directory,
509    /// use `#[ts(export_to = "...")]`.
510    fn export_all() -> Result<(), ExportError>
511    where
512        Self: 'static,
513    {
514        export::export_all_into::<Self>(&*export::default_out_dir())
515    }
516
517    /// Manually export this type into the given directory, together with all of its dependencies.  
518    /// To export only this type, without its dependencies, use [`TS::export`].
519    ///
520    /// Unlike [`TS::export_all`], this function disregards `TS_RS_EXPORT_DIR`, using the provided
521    /// directory instead.
522    ///
523    /// To alter the filenames or paths of the types within the target directory,
524    /// use `#[ts(export_to = "...")]`.
525    ///
526    /// # Automatic Exporting
527    /// Types annotated with `#[ts(export)]`, together with all of their dependencies, will be
528    /// exported automatically whenever `cargo test` is run.  
529    /// In that case, there is no need to manually call this function.
530    fn export_all_to(out_dir: impl AsRef<Path>) -> Result<(), ExportError>
531    where
532        Self: 'static,
533    {
534        export::export_all_into::<Self>(out_dir)
535    }
536
537    /// Manually generate bindings for this type, returning a [`String`].  
538    /// This function does not format the output, even if the `format` feature is enabled.
539    ///
540    /// # Automatic Exporting
541    /// Types annotated with `#[ts(export)]`, together with all of their dependencies, will be
542    /// exported automatically whenever `cargo test` is run.  
543    /// In that case, there is no need to manually call this function.
544    fn export_to_string() -> Result<String, ExportError>
545    where
546        Self: 'static,
547    {
548        export::export_to_string::<Self>()
549    }
550
551    /// Returns the output path to where `T` should be exported.  
552    /// The returned path does _not_ include the base directory from `TS_RS_EXPORT_DIR`.  
553    ///
554    /// To get the output path containing `TS_RS_EXPORT_DIR`, use [`TS::default_output_path`].
555    ///
556    /// When deriving `TS`, the output path can be altered using `#[ts(export_to = "...")]`.  
557    /// See the documentation of [`TS`] for more details.
558    ///
559    /// The output of this function depends on the environment variable `TS_RS_EXPORT_DIR`, which is
560    /// used as base directory. If it is not set, `./bindings` is used as default directory.
561    ///
562    /// If `T` cannot be exported (e.g because it's a primitive type), this function will return
563    /// `None`.
564    fn output_path() -> Option<&'static Path> {
565        None
566    }
567
568    /// Returns the output path to where `T` should be exported.  
569    ///
570    /// The output of this function depends on the environment variable `TS_RS_EXPORT_DIR`, which is
571    /// used as base directory. If it is not set, `./bindings` is used as default directory.
572    ///
573    /// To get the output path relative to `TS_RS_EXPORT_DIR` and without reading the environment
574    /// variable, use [`TS::output_path`].
575    ///
576    /// When deriving `TS`, the output path can be altered using `#[ts(export_to = "...")]`.  
577    /// See the documentation of [`TS`] for more details.
578    ///
579    /// If `T` cannot be exported (e.g because it's a primitive type), this function will return
580    /// `None`.
581    fn default_output_path() -> Option<PathBuf> {
582        Some(export::default_out_dir().join(Self::output_path()?))
583    }
584}
585
586/// A visitor used to iterate over all dependencies or generics of a type.
587/// When an instance of [`TypeVisitor`] is passed to [`TS::visit_dependencies`] or
588/// [`TS::visit_generics`], the [`TypeVisitor::visit`] method will be invoked for every dependency
589/// or generic parameter respectively.
590pub trait TypeVisitor: Sized {
591    fn visit<T: TS + 'static + ?Sized>(&mut self);
592}
593
594/// A typescript type which is depended upon by other types.
595/// This information is required for generating the correct import statements.
596#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
597pub struct Dependency {
598    /// Type ID of the rust type
599    pub type_id: TypeId,
600    /// Name of the type in TypeScript
601    pub ts_name: String,
602    /// Path to where the type would be exported. By default a filename is derived from the types
603    /// name, which can be customized with `#[ts(export_to = "..")]`.  
604    /// This path does _not_ include a base directory.
605    pub output_path: &'static Path,
606}
607
608impl Dependency {
609    /// Constructs a [`Dependency`] from the given type `T`.
610    /// If `T` is not exportable (meaning `T::EXPORT_TO` is `None`), this function will return
611    /// `None`
612    pub fn from_ty<T: TS + 'static + ?Sized>() -> Option<Self> {
613        let output_path = T::output_path()?;
614        Some(Dependency {
615            type_id: TypeId::of::<T>(),
616            ts_name: T::ident(),
617            output_path,
618        })
619    }
620}
621
622// generate impls for primitive types
623macro_rules! impl_primitives {
624    ($($($ty:ty),* => $l:literal),*) => { $($(
625        impl TS for $ty {
626            type WithoutGenerics = Self;
627            fn name() -> String { $l.to_owned() }
628            fn inline() -> String { <Self as $crate::TS>::name() }
629            fn inline_flattened() -> String { panic!("{} cannot be flattened", <Self as $crate::TS>::name()) }
630            fn decl() -> String { panic!("{} cannot be declared", <Self as $crate::TS>::name()) }
631            fn decl_concrete() -> String { panic!("{} cannot be declared", <Self as $crate::TS>::name()) }
632        }
633    )*)* };
634}
635// generate impls for tuples
636macro_rules! impl_tuples {
637    ( impl $($i:ident),* ) => {
638        impl<$($i: TS),*> TS for ($($i,)*) {
639            type WithoutGenerics = (Dummy, );
640            fn name() -> String {
641                format!("[{}]", [$(<$i as $crate::TS>::name()),*].join(", "))
642            }
643            fn inline() -> String {
644                panic!("tuple cannot be inlined!");
645            }
646            fn visit_generics(v: &mut impl TypeVisitor)
647            where
648                Self: 'static
649            {
650                $(
651                    v.visit::<$i>();
652                    <$i>::visit_generics(v);
653                )*
654            }
655            fn inline_flattened() -> String { panic!("tuple cannot be flattened") }
656            fn decl() -> String { panic!("tuple cannot be declared") }
657            fn decl_concrete() -> String { panic!("tuple cannot be declared") }
658        }
659    };
660    ( $i2:ident $(, $i:ident)* ) => {
661        impl_tuples!(impl $i2 $(, $i)* );
662        impl_tuples!($($i),*);
663    };
664    () => {};
665}
666
667// generate impls for wrapper types
668macro_rules! impl_wrapper {
669    ($($t:tt)*) => {
670        $($t)* {
671            type WithoutGenerics = Self;
672            fn name() -> String { T::name() }
673            fn inline() -> String { T::inline() }
674            fn inline_flattened() -> String { T::inline_flattened() }
675            fn visit_dependencies(v: &mut impl TypeVisitor)
676            where
677                Self: 'static,
678            {
679                T::visit_dependencies(v);
680            }
681
682            fn visit_generics(v: &mut impl TypeVisitor)
683            where
684                Self: 'static,
685            {
686                T::visit_generics(v);
687                v.visit::<T>();
688            }
689            fn decl() -> String { panic!("wrapper type cannot be declared") }
690            fn decl_concrete() -> String { panic!("wrapper type cannot be declared") }
691        }
692    };
693}
694
695// implement TS for the $shadow, deferring to the impl $s
696macro_rules! impl_shadow {
697    (as $s:ty: $($impl:tt)*) => {
698        $($impl)* {
699            type WithoutGenerics = <$s as $crate::TS>::WithoutGenerics;
700            fn ident() -> String { <$s as $crate::TS>::ident() }
701            fn name() -> String { <$s as $crate::TS>::name() }
702            fn inline() -> String { <$s as $crate::TS>::inline() }
703            fn inline_flattened() -> String { <$s as $crate::TS>::inline_flattened() }
704            fn visit_dependencies(v: &mut impl $crate::TypeVisitor)
705            where
706                Self: 'static,
707            {
708                <$s as $crate::TS>::visit_dependencies(v);
709            }
710            fn visit_generics(v: &mut impl $crate::TypeVisitor)
711            where
712                Self: 'static,
713            {
714                <$s as $crate::TS>::visit_generics(v);
715            }
716            fn decl() -> String { <$s as $crate::TS>::decl() }
717            fn decl_concrete() -> String { <$s as $crate::TS>::decl_concrete() }
718            fn output_path() -> Option<&'static std::path::Path> { <$s as $crate::TS>::output_path() }
719        }
720    };
721}
722
723impl<T: TS> TS for Option<T> {
724    type WithoutGenerics = Self;
725
726    fn name() -> String {
727        format!("{} | null", T::name())
728    }
729
730    fn inline() -> String {
731        format!("{} | null", T::inline())
732    }
733
734    fn visit_dependencies(v: &mut impl TypeVisitor)
735    where
736        Self: 'static,
737    {
738        T::visit_dependencies(v);
739    }
740
741    fn visit_generics(v: &mut impl TypeVisitor)
742    where
743        Self: 'static,
744    {
745        T::visit_generics(v);
746        v.visit::<T>();
747    }
748
749    fn decl() -> String {
750        panic!("{} cannot be declared", Self::name())
751    }
752
753    fn decl_concrete() -> String {
754        panic!("{} cannot be declared", Self::name())
755    }
756
757    fn inline_flattened() -> String {
758        panic!("{} cannot be flattened", Self::name())
759    }
760}
761
762impl<T: TS, E: TS> TS for Result<T, E> {
763    type WithoutGenerics = Result<Dummy, Dummy>;
764
765    fn name() -> String {
766        format!("{{ Ok : {} }} | {{ Err : {} }}", T::name(), E::name())
767    }
768
769    fn inline() -> String {
770        format!("{{ Ok : {} }} | {{ Err : {} }}", T::inline(), E::inline())
771    }
772
773    fn visit_dependencies(v: &mut impl TypeVisitor)
774    where
775        Self: 'static,
776    {
777        T::visit_dependencies(v);
778        E::visit_dependencies(v);
779    }
780
781    fn visit_generics(v: &mut impl TypeVisitor)
782    where
783        Self: 'static,
784    {
785        T::visit_generics(v);
786        v.visit::<T>();
787        E::visit_generics(v);
788        v.visit::<E>();
789    }
790
791    fn decl() -> String {
792        panic!("{} cannot be declared", Self::name())
793    }
794
795    fn decl_concrete() -> String {
796        panic!("{} cannot be declared", Self::name())
797    }
798
799    fn inline_flattened() -> String {
800        panic!("{} cannot be flattened", Self::name())
801    }
802}
803
804impl<T: TS> TS for Vec<T> {
805    type WithoutGenerics = Vec<Dummy>;
806
807    fn ident() -> String {
808        "Array".to_owned()
809    }
810
811    fn name() -> String {
812        format!("Array<{}>", T::name())
813    }
814
815    fn inline() -> String {
816        format!("Array<{}>", T::inline())
817    }
818
819    fn visit_dependencies(v: &mut impl TypeVisitor)
820    where
821        Self: 'static,
822    {
823        T::visit_dependencies(v);
824    }
825
826    fn visit_generics(v: &mut impl TypeVisitor)
827    where
828        Self: 'static,
829    {
830        T::visit_generics(v);
831        v.visit::<T>();
832    }
833
834    fn decl() -> String {
835        panic!("{} cannot be declared", Self::name())
836    }
837
838    fn decl_concrete() -> String {
839        panic!("{} cannot be declared", Self::name())
840    }
841
842    fn inline_flattened() -> String {
843        panic!("{} cannot be flattened", Self::name())
844    }
845}
846
847// Arrays longer than this limit will be emitted as Array<T>
848const ARRAY_TUPLE_LIMIT: usize = 64;
849impl<T: TS, const N: usize> TS for [T; N] {
850    type WithoutGenerics = [Dummy; N];
851    fn name() -> String {
852        if N > ARRAY_TUPLE_LIMIT {
853            return Vec::<T>::name();
854        }
855
856        format!(
857            "[{}]",
858            (0..N).map(|_| T::name()).collect::<Box<[_]>>().join(", ")
859        )
860    }
861
862    fn inline() -> String {
863        if N > ARRAY_TUPLE_LIMIT {
864            return Vec::<T>::inline();
865        }
866
867        format!(
868            "[{}]",
869            (0..N).map(|_| T::inline()).collect::<Box<[_]>>().join(", ")
870        )
871    }
872
873    fn visit_dependencies(v: &mut impl TypeVisitor)
874    where
875        Self: 'static,
876    {
877        T::visit_dependencies(v);
878    }
879
880    fn visit_generics(v: &mut impl TypeVisitor)
881    where
882        Self: 'static,
883    {
884        T::visit_generics(v);
885        v.visit::<T>();
886    }
887
888    fn decl() -> String {
889        panic!("{} cannot be declared", Self::name())
890    }
891
892    fn decl_concrete() -> String {
893        panic!("{} cannot be declared", Self::name())
894    }
895
896    fn inline_flattened() -> String {
897        panic!("{} cannot be flattened", Self::name())
898    }
899}
900
901impl<K: TS, V: TS, H> TS for HashMap<K, V, H> {
902    type WithoutGenerics = HashMap<Dummy, Dummy>;
903
904    fn ident() -> String {
905        panic!()
906    }
907
908    fn name() -> String {
909        format!("{{ [key in {}]?: {} }}", K::name(), V::name())
910    }
911
912    fn inline() -> String {
913        format!("{{ [key in {}]?: {} }}", K::inline(), V::inline())
914    }
915
916    fn visit_dependencies(v: &mut impl TypeVisitor)
917    where
918        Self: 'static,
919    {
920        K::visit_dependencies(v);
921        V::visit_dependencies(v);
922    }
923
924    fn visit_generics(v: &mut impl TypeVisitor)
925    where
926        Self: 'static,
927    {
928        K::visit_generics(v);
929        v.visit::<K>();
930        V::visit_generics(v);
931        v.visit::<V>();
932    }
933
934    fn decl() -> String {
935        panic!("{} cannot be declared", Self::name())
936    }
937
938    fn decl_concrete() -> String {
939        panic!("{} cannot be declared", Self::name())
940    }
941
942    fn inline_flattened() -> String {
943        panic!("{} cannot be flattened", Self::name())
944    }
945}
946
947impl<I: TS> TS for Range<I> {
948    type WithoutGenerics = Range<Dummy>;
949    fn name() -> String {
950        format!("{{ start: {}, end: {}, }}", I::name(), I::name())
951    }
952
953    fn visit_dependencies(v: &mut impl TypeVisitor)
954    where
955        Self: 'static,
956    {
957        I::visit_dependencies(v);
958    }
959
960    fn visit_generics(v: &mut impl TypeVisitor)
961    where
962        Self: 'static,
963    {
964        I::visit_generics(v);
965        v.visit::<I>();
966    }
967
968    fn decl() -> String {
969        panic!("{} cannot be declared", Self::name())
970    }
971
972    fn decl_concrete() -> String {
973        panic!("{} cannot be declared", Self::name())
974    }
975
976    fn inline() -> String {
977        panic!("{} cannot be inlined", Self::name())
978    }
979
980    fn inline_flattened() -> String {
981        panic!("{} cannot be flattened", Self::name())
982    }
983}
984
985impl_shadow!(as Range<I>: impl<I: TS> TS for RangeInclusive<I>);
986impl_shadow!(as Vec<T>: impl<T: TS, H> TS for HashSet<T, H>);
987impl_shadow!(as Vec<T>: impl<T: TS> TS for BTreeSet<T>);
988impl_shadow!(as HashMap<K, V>: impl<K: TS, V: TS> TS for BTreeMap<K, V>);
989impl_shadow!(as Vec<T>: impl<T: TS> TS for [T]);
990
991impl_wrapper!(impl<T: TS + ?Sized> TS for &T);
992impl_wrapper!(impl<T: TS + ?Sized> TS for Box<T>);
993impl_wrapper!(impl<T: TS + ?Sized> TS for std::sync::Arc<T>);
994impl_wrapper!(impl<T: TS + ?Sized> TS for std::rc::Rc<T>);
995impl_wrapper!(impl<'a, T: TS + ToOwned + ?Sized> TS for std::borrow::Cow<'a, T>);
996impl_wrapper!(impl<T: TS> TS for std::cell::Cell<T>);
997impl_wrapper!(impl<T: TS> TS for std::cell::RefCell<T>);
998impl_wrapper!(impl<T: TS> TS for std::sync::Mutex<T>);
999impl_wrapper!(impl<T: TS> TS for std::sync::RwLock<T>);
1000impl_wrapper!(impl<T: TS + ?Sized> TS for std::sync::Weak<T>);
1001impl_wrapper!(impl<T: TS> TS for std::marker::PhantomData<T>);
1002
1003impl_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
1004
1005#[cfg(feature = "bigdecimal-impl")]
1006impl_primitives! { bigdecimal::BigDecimal => "string" }
1007
1008#[cfg(feature = "smol_str-impl")]
1009impl_primitives! { smol_str::SmolStr => "string" }
1010
1011#[cfg(feature = "uuid-impl")]
1012impl_primitives! { uuid::Uuid => "string" }
1013
1014#[cfg(feature = "url-impl")]
1015impl_primitives! { url::Url => "string" }
1016
1017#[cfg(feature = "ordered-float-impl")]
1018impl_primitives! { ordered_float::OrderedFloat<f32> => "number" }
1019
1020#[cfg(feature = "ordered-float-impl")]
1021impl_primitives! { ordered_float::OrderedFloat<f64> => "number" }
1022
1023#[cfg(feature = "bson-uuid-impl")]
1024impl_primitives! { bson::oid::ObjectId => "string" }
1025
1026#[cfg(feature = "bson-uuid-impl")]
1027impl_primitives! { bson::Uuid => "string" }
1028
1029#[cfg(feature = "indexmap-impl")]
1030impl_shadow!(as Vec<T>: impl<T: TS> TS for indexmap::IndexSet<T>);
1031
1032#[cfg(feature = "indexmap-impl")]
1033impl_shadow!(as HashMap<K, V>: impl<K: TS, V: TS> TS for indexmap::IndexMap<K, V>);
1034
1035#[cfg(feature = "heapless-impl")]
1036impl_shadow!(as Vec<T>: impl<T: TS, const N: usize> TS for heapless::Vec<T, N>);
1037
1038#[cfg(feature = "semver-impl")]
1039impl_primitives! { semver::Version => "string" }
1040
1041#[cfg(feature = "bytes-impl")]
1042mod bytes {
1043    use super::TS;
1044
1045    impl_shadow!(as Vec<u8>: impl TS for bytes::Bytes);
1046    impl_shadow!(as Vec<u8>: impl TS for bytes::BytesMut);
1047}
1048
1049impl_primitives! {
1050    u8, i8, NonZeroU8, NonZeroI8,
1051    u16, i16, NonZeroU16, NonZeroI16,
1052    u32, i32, NonZeroU32, NonZeroI32,
1053    usize, isize, NonZeroUsize, NonZeroIsize, f32, f64 => "number",
1054    u64, i64, NonZeroU64, NonZeroI64,
1055    u128, i128, NonZeroU128, NonZeroI128 => "bigint",
1056    bool => "boolean",
1057    char, Path, PathBuf, String, str,
1058    Ipv4Addr, Ipv6Addr, IpAddr, SocketAddrV4, SocketAddrV6, SocketAddr => "string",
1059    () => "null"
1060}
1061
1062#[rustfmt::skip]
1063pub(crate) use impl_primitives;
1064#[rustfmt::skip]
1065pub(crate) use impl_shadow;
1066#[rustfmt::skip]
1067pub(crate) use impl_wrapper;
1068
1069#[doc(hidden)]
1070#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
1071pub struct Dummy;
1072
1073impl std::fmt::Display for Dummy {
1074    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1075        write!(f, "{:?}", self)
1076    }
1077}
1078
1079impl TS for Dummy {
1080    type WithoutGenerics = Self;
1081    fn name() -> String {
1082        "Dummy".to_owned()
1083    }
1084
1085    fn decl() -> String {
1086        panic!("{} cannot be declared", Self::name())
1087    }
1088
1089    fn decl_concrete() -> String {
1090        panic!("{} cannot be declared", Self::name())
1091    }
1092
1093    fn inline() -> String {
1094        panic!("{} cannot be inlined", Self::name())
1095    }
1096
1097    fn inline_flattened() -> String {
1098        panic!("{} cannot be flattened", Self::name())
1099    }
1100}