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