specta-elm 0.0.1

Yey! now your Rust types in Elm ;)
Documentation
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,
        },
        // A lone live unnamed field renders as its bare inner type.
        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,
    }
}