litellm-rs 0.6.0

A high-performance AI Gateway written in Rust, providing OpenAI-compatible APIs with intelligent routing, load balancing, and enterprise features
Documentation
use super::GatewayConfig;
use serde_json::{Map, Value};
use std::collections::HashMap;

fn gateway_from_alias_value(value: Option<Value>) -> GatewayConfig {
    let mut gateway = serde_json::to_value(GatewayConfig::default())
        .expect("default gateway should serialize for fixture construction");
    let object = gateway
        .as_object_mut()
        .expect("serialized gateway should be an object");
    match value {
        Some(value) => {
            object.insert("model_aliases".to_string(), value);
        }
        None => {
            object.remove("model_aliases");
        }
    }
    serde_json::from_value(gateway).expect("gateway alias fixture should deserialize")
}

fn alias_map(entries: &[(&str, &str)]) -> HashMap<String, String> {
    entries
        .iter()
        .map(|(alias, target)| ((*alias).to_string(), (*target).to_string()))
        .collect()
}

#[test]
fn model_aliases_default_and_serde_are_backward_compatible() {
    assert!(GatewayConfig::default().model_aliases.is_empty());
    assert!(gateway_from_alias_value(None).model_aliases.is_empty());
    assert!(
        gateway_from_alias_value(Some(Value::Object(Map::new())))
            .model_aliases
            .is_empty()
    );

    let configured =
        gateway_from_alias_value(Some(serde_json::json!({"public-chat": "provider-model"})));
    assert_eq!(
        configured
            .model_aliases
            .get("public-chat")
            .map(String::as_str),
        Some("provider-model")
    );
}

#[test]
fn model_alias_merge_is_key_wise_with_overlay_wins() {
    let base = GatewayConfig {
        model_aliases: alias_map(&[("stable", "model-v1"), ("base-only", "base-model")]),
        ..Default::default()
    };
    let overlay = GatewayConfig {
        model_aliases: alias_map(&[("stable", "model-v2"), ("overlay-only", "overlay-model")]),
        ..Default::default()
    };

    let merged = base.clone().merge(overlay);
    assert_eq!(
        merged.model_aliases,
        alias_map(&[
            ("stable", "model-v2"),
            ("base-only", "base-model"),
            ("overlay-only", "overlay-model"),
        ])
    );

    assert_eq!(
        base.clone()
            .merge(gateway_from_alias_value(None))
            .model_aliases,
        base.model_aliases
    );
    assert_eq!(
        base.clone()
            .merge(gateway_from_alias_value(Some(Value::Object(Map::new()))))
            .model_aliases,
        base.model_aliases
    );
}

#[test]
fn layered_yaml_alias_with_surrounding_whitespace_fails_validation() {
    let base = GatewayConfig {
        model_aliases: alias_map(&[("stable", "model-v1")]),
        ..Default::default()
    };
    let yaml_aliases: HashMap<String, String> =
        serde_yml::from_str("' public ': gpt-4o").expect("alias YAML should deserialize");
    let overlay = GatewayConfig {
        model_aliases: yaml_aliases,
        ..Default::default()
    };

    let merged = base.merge(overlay);
    assert!(merged.model_aliases.contains_key(" public "));
    let error = GatewayConfig::validate_model_alias_map(&merged.model_aliases)
        .expect_err("layered YAML aliases must fail closed on surrounding whitespace");
    assert!(
        error.contains("cannot contain leading or trailing whitespace"),
        "{error}"
    );
}

#[test]
fn gateway_unknown_fields_remain_rejected() {
    let mut gateway =
        serde_json::to_value(GatewayConfig::default()).expect("default gateway should serialize");
    gateway
        .as_object_mut()
        .expect("gateway should serialize as an object")
        .insert("unknown_alias_policy".to_string(), Value::Bool(true));

    assert!(serde_json::from_value::<GatewayConfig>(gateway).is_err());
}