cratestack-parser 0.3.6

Rust-native schema-first framework for typed HTTP APIs, generated clients, and backend services.
Documentation
use chumsky::prelude::*;
use cratestack_core::{SourceSpan, TypeArity, TypeRef};

use crate::diagnostics::SchemaError;
use crate::line_helpers::Line;

pub(super) fn parse_type_ref(
    raw: &str,
    line: &Line<'_>,
    raw_offset: usize,
) -> Result<TypeRef, SchemaError> {
    let parser = text::ident::<_, extra::Err<Simple<char>>>()
        .then(choice((
            just("[]").to(TypeArity::List),
            just("?").to(TypeArity::Optional),
            end().to(TypeArity::Required),
        )))
        .then_ignore(end());

    parser
        .parse(raw)
        .into_result()
        .map(|(name, arity)| TypeRef {
            name: name.to_owned(),
            name_span: SourceSpan {
                start: line.start + raw_offset,
                end: line.start + raw_offset + name.len(),
                line: line.number,
            },
            arity,
            generic_args: Vec::new(),
        })
        .or_else(|_| parse_builtin_generic_type_ref(raw, line, raw_offset))
        .map_err(|_| {
            SchemaError::new(
                format!("invalid type reference: {raw}"),
                line.start..line.start + line.raw.len(),
                line.number,
            )
        })
}

fn parse_builtin_generic_type_ref(
    raw: &str,
    line: &Line<'_>,
    raw_offset: usize,
) -> Result<TypeRef, ()> {
    let (base, arity) = if let Some(base) = raw.strip_suffix("[]") {
        (base.trim(), TypeArity::List)
    } else if let Some(base) = raw.strip_suffix('?') {
        (base.trim(), TypeArity::Optional)
    } else {
        (raw.trim(), TypeArity::Required)
    };

    let Some(inner) = base
        .strip_prefix("Page<")
        .and_then(|value| value.strip_suffix('>'))
    else {
        return Err(());
    };

    let inner_offset = base.find('<').ok_or(())? + 1;
    let inner = parse_type_ref(inner.trim(), line, raw_offset + inner_offset).map_err(|_| ())?;
    Ok(TypeRef {
        name: "Page".to_owned(),
        name_span: SourceSpan {
            start: line.start + raw_offset,
            end: line.start + raw_offset + "Page".len(),
            line: line.number,
        },
        arity,
        generic_args: vec![inner],
    })
}