forge-guardrails 0.1.2

Foundation types for an LLM-agent workflow framework
Documentation
use super::{DEFAULT_CLI_HOST, DEFAULT_MAX_RETRIES, DEFAULT_PROXY_PORT};
use crate::cli::Cli;

pub(crate) fn cli_host(cli: &Cli) -> Result<String, String> {
    Ok(cli
        .host
        .as_deref()
        .map(|host| validate_nonempty(host, "--host").map(ToOwned::to_owned))
        .transpose()?
        .unwrap_or_else(|| DEFAULT_CLI_HOST.to_string()))
}

pub(crate) fn cli_port(cli: &Cli) -> Result<u16, String> {
    validate_nonzero_u16(cli.port.unwrap_or(DEFAULT_PROXY_PORT), "--port")
}

pub(crate) fn cli_model(cli: &Cli, default: &str) -> Result<String, String> {
    Ok(cli
        .model
        .as_deref()
        .map(|model| validate_nonempty(model, "--model").map(ToOwned::to_owned))
        .transpose()?
        .unwrap_or_else(|| default.to_string()))
}

pub(crate) fn cli_max_retries(cli: &Cli) -> Result<i32, String> {
    validate_nonnegative_i32(
        cli.max_retries.unwrap_or(DEFAULT_MAX_RETRIES),
        "--max-retries",
    )
}

pub(crate) fn require_cli_model(cli: &Cli) -> Result<String, String> {
    cli.model
        .as_deref()
        .map(|model| validate_nonempty(model, "--model").map(ToOwned::to_owned))
        .transpose()?
        .ok_or_else(|| "--backend ollama requires --model".to_string())
}

pub(crate) fn require_cli_gguf(cli: &Cli, backend: &str) -> Result<String, String> {
    cli.gguf
        .as_deref()
        .map(|gguf| validate_nonempty(gguf, "--gguf").map(ToOwned::to_owned))
        .transpose()?
        .ok_or_else(|| format!("--backend {backend} requires --gguf"))
}

pub(crate) fn require_cli_llamafile_runtime(cli: &Cli) -> Result<String, String> {
    cli.llamafile_runtime
        .as_deref()
        .map(|runtime| validate_nonempty(runtime, "--llamafile-runtime").map(ToOwned::to_owned))
        .transpose()?
        .ok_or_else(|| "--backend llamafile requires --llamafile-runtime".to_string())
}

pub(crate) fn validate_nonempty<'a>(value: &'a str, label: &str) -> Result<&'a str, String> {
    let trimmed = value.trim();
    if trimmed.is_empty() {
        Err(format!("{label} cannot be empty"))
    } else {
        Ok(trimmed)
    }
}

pub(crate) fn validate_nonzero_u16(value: u16, label: &str) -> Result<u16, String> {
    if value == 0 {
        Err(format!("{label} cannot be 0"))
    } else {
        Ok(value)
    }
}

pub(crate) fn validate_optional_positive_i64(
    value: Option<i64>,
    label: &str,
) -> Result<Option<i64>, String> {
    value
        .map(|tokens| validate_positive_i64(tokens, label))
        .transpose()
}

pub(crate) fn validate_positive_i64(value: i64, label: &str) -> Result<i64, String> {
    if value <= 0 {
        Err(format!("{label} must be positive"))
    } else {
        Ok(value)
    }
}

pub(crate) fn validate_nonnegative_i32(value: i32, label: &str) -> Result<i32, String> {
    if value < 0 {
        Err(format!("{label} must be non-negative"))
    } else {
        Ok(value)
    }
}

pub(crate) fn resolve_serialize(cli: &Cli, default: bool) -> bool {
    if cli.serialize {
        true
    } else if cli.no_serialize {
        false
    } else {
        default
    }
}

pub(crate) fn normalized_extra_flags(flags: &[String]) -> Vec<String> {
    if flags.first().is_some_and(|flag| flag == "--") {
        flags[1..].to_vec()
    } else {
        flags.to_vec()
    }
}