Skip to main content

ts_rs/
lib.rs

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