specta_typescript/
typescript.rs1use std::{borrow::Cow, io, path::Path};
2
3use specta::{datatype::DeprecatedType, Language, TypeCollection};
4use specta_serde::is_valid_ty;
5
6use crate::{comments, detect_duplicate_type_names, export_named_datatype, ExportError};
7
8#[derive(Debug)]
9#[non_exhaustive]
10pub struct CommentFormatterArgs<'a> {
11 pub docs: &'a Cow<'static, str>,
12 pub deprecated: Option<&'a DeprecatedType>,
13}
14
15pub type CommentFormatterFn = fn(CommentFormatterArgs) -> String; pub type FormatterFn = fn(&Path) -> io::Result<()>;
20
21#[derive(Debug, Clone, Default)]
26pub enum BigIntExportBehavior {
27 String,
31 Number,
35 BigInt,
37 #[default]
41 Fail,
42 #[doc(hidden)]
44 FailWithReason(&'static str),
45}
46
47#[derive(Debug, Clone)]
49#[non_exhaustive]
50pub struct Typescript {
51 pub header: Cow<'static, str>,
53 pub framework_header: Cow<'static, str>,
55 pub bigint: BigIntExportBehavior,
57 pub comment_exporter: Option<CommentFormatterFn>,
59 pub formatter: Option<FormatterFn>,
61}
62
63impl Default for Typescript {
64 fn default() -> Self {
65 Self {
66 header: Cow::Borrowed(""),
67 framework_header: Cow::Borrowed(
68 "// This file has been generated by Specta. DO NOT EDIT.",
69 ),
70 bigint: Default::default(),
71 comment_exporter: Some(comments::js_doc),
72 formatter: None,
73 }
74 }
75}
76
77impl Typescript {
78 pub fn new() -> Self {
80 Default::default()
81 }
82
83 #[doc(hidden)] pub fn framework_header(mut self, header: impl Into<Cow<'static, str>>) -> Self {
87 self.framework_header = header.into();
88 self
89 }
90
91 pub fn header(mut self, header: impl Into<Cow<'static, str>>) -> Self {
95 self.header = header.into();
96 self
97 }
98
99 pub fn bigint(mut self, bigint: BigIntExportBehavior) -> Self {
101 self.bigint = bigint;
102 self
103 }
104
105 pub fn comment_style(mut self, exporter: CommentFormatterFn) -> Self {
114 self.comment_exporter = Some(exporter);
115 self
116 }
117
118 pub fn formatter(mut self, formatter: FormatterFn) -> Self {
126 self.formatter = Some(formatter);
127 self
128 }
129
130 pub fn export(&self, types: &TypeCollection) -> Result<String, ExportError> {
132 let mut out = self.header.to_string();
133 if !out.is_empty() {
134 out.push('\n');
135 }
136 out += &self.framework_header;
137 out.push_str("\n\n");
138
139 if let Some((ty_name, l0, l1)) = detect_duplicate_type_names(&types).into_iter().next() {
140 return Err(ExportError::DuplicateTypeName(ty_name, l0, l1));
141 }
142
143 for (_, ty) in types.into_iter() {
144 is_valid_ty(&ty.inner, &types)?;
145
146 out += &export_named_datatype(self, ty, &types)?;
147 out += "\n\n";
148 }
149
150 Ok(out)
151 }
152
153 pub fn export_to(
155 &self,
156 path: impl AsRef<Path>,
157 types: &TypeCollection,
158 ) -> Result<(), ExportError> {
159 let path = path.as_ref();
160 if let Some(parent) = path.parent() {
161 std::fs::create_dir_all(parent)?;
162 }
163 std::fs::write(
164 &path,
165 self.export(types).map(|s| format!("{}{s}", self.header))?,
166 )?;
167 if let Some(formatter) = self.formatter {
168 formatter(path)?;
169 }
170 Ok(())
171 }
172
173 pub fn format(&self, path: impl AsRef<Path>) -> Result<(), ExportError> {
175 if let Some(formatter) = self.formatter {
176 formatter(path.as_ref())?;
177 }
178 Ok(())
179 }
180}