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}