cratestack-macros 0.4.4

Rust-native schema-first framework for typed HTTP APIs, generated clients, and backend services.
Documentation
use cratestack_core::{Field, TypeDecl};

pub(crate) fn find_auth_field<'a>(
    auth: Option<&'a cratestack_core::AuthBlock>,
    types: &'a [TypeDecl],
    field: &str,
) -> Result<&'a Field, String> {
    let auth = auth.ok_or_else(|| {
        format!("read policy references auth field `{field}` but schema has no auth block")
    })?;
    if let Some(exact) = auth.fields.iter().find(|candidate| candidate.name == field) {
        return Ok(exact);
    }
    resolve_auth_field_path(auth, types, field)
}

pub(super) fn parse_string_literal(value: &str) -> Option<&str> {
    if let Some(value) = value
        .strip_prefix('"')
        .and_then(|value| value.strip_suffix('"'))
    {
        return Some(value);
    }
    value
        .strip_prefix('\'')
        .and_then(|value| value.strip_suffix('\''))
}

pub(super) fn parse_builtin_policy_call(term: &str) -> Option<Result<(&str, &str), String>> {
    let open = term.find('(')?;
    let name = term[..open].trim();
    let args = term[open + 1..].strip_suffix(')')?.trim();
    if name.is_empty() {
        return Some(Err(format!("invalid policy function `{term}`")));
    }
    if args.is_empty() {
        return Some(Err(format!(
            "policy function `{name}` requires a single string literal argument"
        )));
    }
    if args.contains(',') {
        return Some(Err(format!(
            "policy function `{name}` currently supports exactly one string literal argument"
        )));
    }
    let Some(value) = parse_string_literal(args) else {
        return Some(Err(format!(
            "policy function `{name}` requires a string literal argument"
        )));
    };
    Some(Ok((name, value)))
}

fn resolve_auth_field_path<'a>(
    auth: &'a cratestack_core::AuthBlock,
    types: &'a [TypeDecl],
    path: &str,
) -> Result<&'a Field, String> {
    let Some((root, rest)) = path.split_once('.') else {
        return Err(format!("unknown auth field `{path}` in read policy"));
    };
    let root_field = auth
        .fields
        .iter()
        .find(|candidate| candidate.name == root)
        .ok_or_else(|| format!("unknown auth field `{path}` in read policy"))?;
    resolve_auth_type_field_path(types, &root_field.ty.name, rest, path)
}

fn resolve_auth_type_field_path<'a>(
    types: &'a [TypeDecl],
    type_name: &str,
    path: &str,
    original_path: &str,
) -> Result<&'a Field, String> {
    let ty = types
        .iter()
        .find(|candidate| candidate.name == type_name)
        .ok_or_else(|| format!("unknown auth field `{original_path}` in read policy"))?;
    let Some((head, tail)) = path.split_once('.') else {
        return ty
            .fields
            .iter()
            .find(|candidate| candidate.name == path)
            .ok_or_else(|| format!("unknown auth field `{original_path}` in read policy"));
    };
    let field = ty
        .fields
        .iter()
        .find(|candidate| candidate.name == head)
        .ok_or_else(|| format!("unknown auth field `{original_path}` in read policy"))?;
    resolve_auth_type_field_path(types, &field.ty.name, tail, original_path)
}