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