use std::borrow::Cow;
use specta::{
Types,
datatype::{DataType, Field, Fields, NamedDataType, NamedReferenceType, Reference},
};
use crate::{
Error, Exporter,
module::ModuleImports,
types::{NDT, elm::sanitise_key},
};
pub fn render<E: Exporter>(
s: &mut String,
imports: &mut ModuleImports,
exporter: &E,
types: &Types,
ndt: &NDT,
key: Cow<'static, str>,
(field, ty): (&Field, &DataType),
parent_name: Option<&str>,
force_inline: bool,
ty_override: Option<&str>,
) -> Result<(), Error> {
let field_name_safe = sanitise_key(key, parent_name)?;
let key = if field.optional {
format!("{field_name_safe}?").into()
} else {
field_name_safe
};
let value = match ty_override {
Some(ty_override) => ty_override.to_string(),
None => {
let mut value = String::new();
datatype_with_inline_attr(
&mut value,
imports,
exporter,
types,
ty,
ndt,
parent_name,
force_inline,
)?;
value
}
};
s.push_str(&key);
s.push_str(": ");
s.push_str(&value);
Ok(())
}
pub(crate) fn datatype_with_inline_attr<'a, E: Exporter>(
s: &mut String,
imports: &'a mut ModuleImports,
exporter: &E,
types: &Types,
dt: &DataType,
ndt: &'a NDT,
parent_name: Option<&str>,
shallow_inline: bool,
) -> Result<(), Error> {
if shallow_inline {
let inline_path = &[ndt.rust_type_path()].join(".");
return ndt
.render(s, imports, exporter, types, dt, parent_name)
.map_err(|err| err.with_inline_trace(inline_named_datatype(types, dt), inline_path));
}
ndt.render(s, imports, exporter, types, dt, parent_name)
}
fn inline_named_datatype<'a>(types: &'a Types, dt: &DataType) -> Option<&'a NamedDataType> {
match dt {
DataType::Reference(Reference::Named(r)) => types.get(r),
_ => None,
}
}
pub fn trailing_optional_run<'a>(fields: impl Iterator<Item = &'a Field>) -> usize {
let mut run_start = 0;
for (idx, field) in fields.enumerate() {
if !field.optional {
run_start = idx + 1;
}
}
run_start
}
pub fn optional_element_needs_parens(ty: &DataType) -> bool {
match ty {
DataType::Nullable(_) | DataType::Enum(_) | DataType::Intersection(_) => true,
DataType::Reference(Reference::Named(reference)) => match &reference.inner {
NamedReferenceType::Inline { dt, .. } => optional_element_needs_parens(dt),
_ => false,
},
DataType::Struct(strct) => match &strct.fields {
Fields::Unnamed(unnamed) if unnamed.fields.len() == 1 => unnamed
.fields
.first()
.and_then(|field| field.ty.as_ref())
.is_some_and(optional_element_needs_parens),
_ => false,
},
_ => false,
}
}