use crate::elm::RESERVED_TYPE_NAMES;
use crate::module::ModuleImports;
use crate::{Error, Exporter};
use std::borrow::Cow;
use specta::Types;
use specta::datatype::{DataType, NamedDataType};
pub mod elm;
pub mod enums;
pub mod fields;
pub mod list;
pub mod map;
pub mod primitive;
pub mod reference;
pub mod structs;
pub mod tuples;
#[derive(Debug, Clone)]
pub struct NDT<'a> {
ndt: &'a NamedDataType,
}
impl<'a> From<&'a NamedDataType> for NDT<'a> {
fn from(ndt: &'a NamedDataType) -> Self {
Self { ndt }
}
}
impl<'a> NDT<'a> {
pub(crate) fn render<E: Exporter>(
&'a self,
s: &mut String,
imports: &'a mut ModuleImports,
exporter: &E,
types: &Types,
dt: &DataType,
parent_name: Option<&str>,
) -> Result<(), Error> {
let have_we_included_refs_here = false;
if have_we_included_refs_here {
todo!() }
match dt {
DataType::Primitive(p) => {
s.push_str(primitive::render(p, self.rust_type_path().to_string())?)
}
DataType::Generic(_) => panic!("No generics form Elm, thanks"),
DataType::List(list) => {
list::render(s, imports, exporter, types, list, self)?;
}
DataType::Map(map) => {
map::render(s, imports, types, map, self, exporter)?;
}
DataType::Nullable(inner) => {
let mut rendered = String::new();
self.render(&mut rendered, imports, exporter, types, inner, parent_name)?;
if !rendered.starts_with("Maybe") {
s.push_str("Maybe ");
};
s.push_str(&rendered);
}
DataType::Struct(st) => {
structs::render(s, imports, exporter, types, self, st, parent_name)?
}
DataType::Enum(enm) => enums::render(s, imports, exporter, types, enm, self)?,
DataType::Tuple(tuple) => {
tuples::render(s, imports, exporter, types, self, tuple)?;
}
DataType::Intersection(parts) => {
for (idx, ty) in parts.iter().enumerate() {
if idx != 0 {
s.push_str(" & ");
}
let needs_parentheses = parts.len() > 1 && intersection_part_is_union(ty);
if needs_parentheses {
s.push('(');
}
self.render(s, imports, exporter, types, ty, parent_name)?;
if needs_parentheses {
s.push(')');
}
}
}
DataType::Reference(r) => reference::render(s, imports, exporter, types, r, self)?,
}
Ok(())
}
pub fn inner(&self) -> &'a NamedDataType {
self.ndt
}
fn path_string(&self) -> String {
self.ndt.module_path.to_string()
}
pub fn rust_type_path(&self) -> Cow<'static, str> {
let ndt = self.ndt;
if ndt.module_path.is_empty() {
ndt.name.clone()
} else {
Cow::Owned(format!("{}::{}", ndt.module_path, ndt.name))
}
}
pub fn sanitise_type_name(&self) -> Result<String, Error> {
let path = self.path_string();
let ident = &self.ndt.name;
if ident.is_empty() {
return Err(Error::empty_name(path));
}
if let Some(name) = RESERVED_TYPE_NAMES.iter().find(|v| **v == ident) {
return Err(Error::forbidden_name(path, name));
}
if let Some(first_char) = ident.chars().next()
&& !first_char.is_alphabetic()
&& first_char != '_'
{
return Err(Error::invalid_name(path, ident.to_string()));
}
if ident
.find(|c: char| !c.is_alphanumeric() && c != '_')
.is_some()
{
return Err(Error::invalid_name(path, ident.to_string()));
}
Ok(ident.to_string())
}
}
fn intersection_part_is_union(ty: &DataType) -> bool {
match ty {
DataType::Nullable(_) => true,
DataType::Enum(enm) => enm.variants.len() > 1,
DataType::Intersection(parts) if parts.len() == 1 => intersection_part_is_union(&parts[0]),
_ => false,
}
}