use std::{
borrow::Cow,
io,
path::{Path, PathBuf},
};
use specta::{datatype::DeprecatedType, Language, TypeMap};
use specta_serde::is_valid_ty;
use crate::{comments, detect_duplicate_type_names, export_named_datatype, ExportError};
#[derive(Debug)]
#[non_exhaustive]
pub struct CommentFormatterArgs<'a> {
pub docs: &'a Cow<'static, str>,
pub deprecated: Option<&'a DeprecatedType>,
}
pub type CommentFormatterFn = fn(CommentFormatterArgs) -> String;
pub type FormatterFn = fn(&Path) -> io::Result<()>;
#[derive(Debug, Clone, Default)]
pub enum BigIntExportBehavior {
String,
Number,
BigInt,
#[default]
Fail,
#[doc(hidden)]
FailWithReason(&'static str),
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Typescript {
pub header: Cow<'static, str>,
pub remove_default_header: bool,
pub bigint: BigIntExportBehavior,
pub comment_exporter: Option<CommentFormatterFn>,
pub formatter: Option<FormatterFn>,
}
impl Default for Typescript {
fn default() -> Self {
Self {
header: Cow::Borrowed(""),
remove_default_header: false,
bigint: Default::default(),
comment_exporter: Some(comments::js_doc),
formatter: None,
}
}
}
impl Typescript {
pub fn new() -> Self {
Default::default()
}
pub fn header(mut self, header: impl Into<Cow<'static, str>>) -> Self {
self.header = header.into();
self
}
pub fn remove_default_header(mut self) -> Self {
self.remove_default_header = true;
self
}
pub fn bigint(mut self, bigint: BigIntExportBehavior) -> Self {
self.bigint = bigint;
self
}
pub fn comment_style(mut self, exporter: CommentFormatterFn) -> Self {
self.comment_exporter = Some(exporter);
self
}
pub fn formatter(mut self, formatter: FormatterFn) -> Self {
self.formatter = Some(formatter);
self
}
}
impl Language for Typescript {
type Error = ExportError;
fn export(&self, type_map: TypeMap) -> Result<String, Self::Error> {
let mut out = self.header.to_string();
if !self.remove_default_header {
out += "// This file has been generated by Specta. DO NOT EDIT.\n\n";
}
if let Some((ty_name, l0, l1)) = detect_duplicate_type_names(&type_map).into_iter().next() {
return Err(ExportError::DuplicateTypeName(ty_name, l0, l1));
}
for (_, ty) in type_map.iter() {
is_valid_ty(&ty.inner, &type_map)?;
out += &export_named_datatype(self, ty, &type_map)?;
out += "\n\n";
}
Ok(out)
}
fn format(&self, path: &Path) -> Result<(), Self::Error> {
if let Some(formatter) = self.formatter {
formatter(path)?;
}
Ok(())
}
}