use crate::e2e::config::{CallConfig, CallOverride, E2eConfig};
use crate::e2e::fixture::{Fixture, FixtureDocsClient};
pub fn trailing_args(
docs_client: Option<&FixtureDocsClient>,
e2e_config: &E2eConfig,
call_config: &CallConfig,
language: &str,
fallback: &[&str],
) -> Vec<String> {
if let Some(args) = docs_client.and_then(|client| client.args_for(language)) {
return args.to_vec();
}
call_config
.overrides
.get(language)
.map(|overrides| overrides.client_factory_trailing_args.clone())
.filter(|args| !args.is_empty())
.or_else(|| {
e2e_config
.call
.overrides
.get(language)
.map(|overrides| overrides.client_factory_trailing_args.clone())
.filter(|args| !args.is_empty())
})
.unwrap_or_else(|| fallback.iter().map(|arg| (*arg).to_string()).collect())
}
pub fn docs_base_url(docs_client: Option<&FixtureDocsClient>) -> Option<&str> {
docs_client.and_then(|client| client.base_url.as_deref())
}
fn call_or_default_override<'a>(
e2e_config: &'a E2eConfig,
call_config: &'a CallConfig,
language: &str,
pick: impl Fn(&'a CallOverride) -> Option<&'a str>,
) -> Option<&'a str> {
call_config
.overrides
.get(language)
.and_then(&pick)
.or_else(|| e2e_config.call.overrides.get(language).and_then(&pick))
}
pub fn plain_factory<'a>(e2e_config: &'a E2eConfig, call_config: &'a CallConfig, language: &str) -> Option<&'a str> {
call_or_default_override(e2e_config, call_config, language, |overrides| {
overrides.client_factory.as_deref()
})
}
pub fn call_client_factory<'a>(
e2e_config: &'a E2eConfig,
call_config: &'a CallConfig,
language: &str,
) -> Option<&'a str> {
plain_factory(e2e_config, call_config, language).filter(|factory| !factory.is_empty())
}
pub fn is_free_function_call(e2e_config: &E2eConfig, call_config: &CallConfig, language: &str) -> bool {
plain_factory(e2e_config, call_config, language).is_some_and(str::is_empty)
}
pub fn php_factory<'a>(e2e_config: &'a E2eConfig, call_config: &'a CallConfig) -> Option<&'a str> {
call_or_default_override(e2e_config, call_config, "php", |overrides| {
overrides.php_client_factory.as_deref()
})
}
pub fn from_json_factory<'a>(
e2e_config: &'a E2eConfig,
call_config: &'a CallConfig,
language: &str,
) -> Option<&'a str> {
call_or_default_override(e2e_config, call_config, language, |overrides| {
overrides.client_factory_from_json.as_deref()
})
}
pub fn client_config_json(fixture: &Fixture) -> Option<String> {
fixture.docs_client()?.config.as_ref().map(serde_json::Value::to_string)
}
pub fn client_config_call<'a>(
fixture: &Fixture,
e2e_config: &'a E2eConfig,
call_config: &'a CallConfig,
language: &str,
) -> Option<(&'a str, String)> {
let json = client_config_json(fixture)?;
Some((from_json_factory(e2e_config, call_config, language)?, json))
}
pub fn js_client_construction(
fixture: &Fixture,
e2e_config: &E2eConfig,
call_config: &CallConfig,
language: &str,
factory: &str,
) -> (String, String) {
match client_config_call(fixture, e2e_config, call_config, language) {
Some((from_json_factory, json)) => (
from_json_factory.to_string(),
format!(
"const client = {from_json_factory}(\"{}\");",
crate::e2e::escape::escape_js(&json)
),
),
None => (
factory.to_string(),
format!("const client = {factory}(\"your-api-key\");"),
),
}
}
const CLIENT_OPTIONS_LANGUAGES: [&str; 2] = ["rust", "python"];
const PYTHON_KEYWORDS: [&str; 35] = [
"False", "None", "True", "and", "as", "assert", "async", "await", "break", "class", "continue", "def", "del",
"elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "is", "lambda", "nonlocal",
"not", "or", "pass", "raise", "return", "try", "while", "with", "yield",
];
pub fn client_options_exclusion(fixture: &Fixture, e2e_config: &E2eConfig, language: &str) -> Option<&'static str> {
let options = fixture.client_options.as_ref()?;
let is_identifier = |name: &str| {
name.chars()
.next()
.is_some_and(|first| first.is_ascii_alphabetic() || first == '_')
&& name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
};
if !options.keys().all(|name| is_identifier(name)) {
return Some("client option names must be plain identifiers");
}
if language == "python" && options.keys().any(|name| PYTHON_KEYWORDS.contains(&name.as_str())) {
return Some("client option names must not be Python keywords");
}
if !CLIENT_OPTIONS_LANGUAGES.contains(&language) {
return Some("client options are not applied by this language's e2e generator");
}
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
if call_client_factory(e2e_config, call_config, language).is_none() {
return Some("client options need a client_factory for this call");
}
if language == "rust" && from_json_factory(e2e_config, call_config, language).is_none() {
return Some("client options need client_factory_from_json for rust");
}
None
}
pub fn client_config_exclusion(
fixture: &Fixture,
e2e_config: &E2eConfig,
language: &str,
renders_client_config: bool,
) -> Option<String> {
client_config_json(fixture)?;
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
if !renders_client_config {
return Some(format!(
"fixture declares `docs.client.config`, which the `{language}` snippet generator cannot render"
));
}
if from_json_factory(e2e_config, call_config, language).is_none() {
return Some(format!(
"fixture declares `docs.client.config` but `[crates.e2e.call.overrides.{language}] client_factory_from_json` is not configured"
));
}
None
}
pub fn ensure_client_config_applied(
fixture: &Fixture,
e2e_config: &E2eConfig,
language: &str,
body: &str,
) -> anyhow::Result<()> {
if client_config_json(fixture).is_none() {
return Ok(());
}
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let Some(factory) = from_json_factory(e2e_config, call_config, language) else {
return Ok(());
};
let normalize = |text: &str| text.to_lowercase().replace('_', "");
if normalize(body).contains(&normalize(factory)) {
return Ok(());
}
anyhow::bail!(
"fixture declares `docs.client.config`, but the rendered `{language}` snippet never calls `{factory}`: \
its call resolves no `client_factory` to configure"
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::e2e::config::CallOverride;
const FALLBACK: [&str; 3] = ["None", "None", "None"];
fn config_with_trailing_args(args: &[&str]) -> E2eConfig {
let mut call = CallConfig::default();
call.overrides.insert(
"rust".into(),
CallOverride {
client_factory_trailing_args: args.iter().map(|arg| (*arg).to_string()).collect(),
..CallOverride::default()
},
);
E2eConfig {
call,
..E2eConfig::default()
}
}
fn docs_client() -> FixtureDocsClient {
FixtureDocsClient {
base_url: Some("https://llm.internal.example.com/v1".into()),
args: [("rust".to_string(), vec!["Some(60)".to_string(), "None".to_string()])]
.into_iter()
.collect(),
..FixtureDocsClient::default()
}
}
#[test]
fn unconfigured_projects_keep_the_argument_list_the_generator_hardcoded() {
let e2e_config = E2eConfig::default();
assert_eq!(
trailing_args(None, &e2e_config, &e2e_config.call, "rust", &FALLBACK),
vec!["None", "None", "None"],
"an absent override must not shorten the factory's argument list"
);
}
#[test]
fn file_level_override_replaces_the_fallback() {
let e2e_config = config_with_trailing_args(&["Some(30)", "Some(5)"]);
assert_eq!(
trailing_args(None, &e2e_config, &e2e_config.call, "rust", &FALLBACK),
vec!["Some(30)", "Some(5)"]
);
}
#[test]
fn override_is_scoped_to_its_own_language() {
let e2e_config = config_with_trailing_args(&["Some(30)"]);
assert_eq!(
trailing_args(None, &e2e_config, &e2e_config.call, "java", &["null"]),
vec!["null"],
"a rust override must not leak into the java factory call"
);
}
#[test]
fn named_call_override_wins_over_the_file_level_one() {
let e2e_config = config_with_trailing_args(&["file"]);
let mut named = CallConfig::default();
named.overrides.insert(
"rust".into(),
CallOverride {
client_factory_trailing_args: vec!["call".into()],
..CallOverride::default()
},
);
assert_eq!(
trailing_args(None, &e2e_config, &named, "rust", &FALLBACK),
vec!["call"]
);
}
#[test]
fn named_call_without_its_own_list_inherits_the_file_level_one() {
let e2e_config = config_with_trailing_args(&["file"]);
assert_eq!(
trailing_args(None, &e2e_config, &CallConfig::default(), "rust", &FALLBACK),
vec!["file"],
"a named call that omits the key must not fall through to the fallback"
);
}
#[test]
fn fixture_docs_client_outranks_every_configured_list() {
let client = docs_client();
let e2e_config = config_with_trailing_args(&["file"]);
assert_eq!(
trailing_args(Some(&client), &e2e_config, &e2e_config.call, "rust", &FALLBACK),
vec!["Some(60)", "None"]
);
assert_eq!(
trailing_args(Some(&client), &e2e_config, &e2e_config.call, "java", &["null"]),
vec!["null"],
"a language the fixture says nothing about keeps the configured default"
);
}
#[test]
fn a_caller_that_declares_no_docs_client_reads_no_base_url() {
assert_eq!(docs_base_url(None), None);
assert_eq!(
docs_base_url(Some(&docs_client())),
Some("https://llm.internal.example.com/v1")
);
assert_eq!(
docs_base_url(Some(&FixtureDocsClient::default())),
None,
"a docs client that only overrides trailing args must not invent an endpoint"
);
}
fn config_fixture() -> Fixture {
serde_json::from_value(serde_json::json!({
"id": "enforced",
"description": "a configured client",
"docs": {
"topic": "budget",
"client": {"config": {"budget": {"global_limit": 0.0, "enforcement": "hard"}, "api_key": "k"}},
},
}))
.expect("fixture parses")
}
fn config_with_factories(from_json: Option<&str>, plain: Option<&str>) -> E2eConfig {
let mut call = CallConfig::default();
call.overrides.insert(
"go".into(),
CallOverride {
client_factory: plain.map(str::to_string),
client_factory_from_json: from_json.map(str::to_string),
..CallOverride::default()
},
);
E2eConfig {
call,
..E2eConfig::default()
}
}
#[test]
fn plain_factories_resolve_the_call_override_then_the_file_level_one() {
let e2e_config = config_with_factories(None, Some("Plain"));
assert_eq!(
plain_factory(&e2e_config, &CallConfig::default(), "go"),
Some("Plain"),
"a named call without its own override inherits the file-level factory"
);
let mut named = CallConfig::default();
named.overrides.insert(
"go".into(),
CallOverride {
client_factory: Some("CallPlain".into()),
..CallOverride::default()
},
);
assert_eq!(plain_factory(&e2e_config, &named, "go"), Some("CallPlain"));
assert_eq!(plain_factory(&e2e_config, &named, "java"), None);
}
#[test]
fn the_php_factory_is_read_from_php_client_factory_only() {
let mut e2e_config = E2eConfig::default();
e2e_config.call.overrides.insert(
"php".into(),
CallOverride {
client_factory: Some("NotRead".into()),
..CallOverride::default()
},
);
assert_eq!(php_factory(&e2e_config, &CallConfig::default()), None);
e2e_config
.call
.overrides
.get_mut("php")
.expect("php override")
.php_client_factory = Some("createClient".into());
assert_eq!(php_factory(&e2e_config, &CallConfig::default()), Some("createClient"));
}
#[test]
fn client_config_serializes_compactly_with_sorted_keys() {
assert_eq!(
client_config_json(&config_fixture()).as_deref(),
Some(r#"{"api_key":"k","budget":{"enforcement":"hard","global_limit":0.0}}"#)
);
assert_eq!(client_config_json(&Fixture::default()), None);
}
#[test]
fn from_json_factory_prefers_the_call_override_then_the_file_level_one() {
let e2e_config = config_with_factories(Some("FromJson"), Some("Plain"));
assert_eq!(
from_json_factory(&e2e_config, &CallConfig::default(), "go"),
Some("FromJson"),
"a named call without its own override inherits the file-level factory"
);
let mut named = CallConfig::default();
named.overrides.insert(
"go".into(),
CallOverride {
client_factory_from_json: Some("CallFromJson".into()),
..CallOverride::default()
},
);
assert_eq!(from_json_factory(&e2e_config, &named, "go"), Some("CallFromJson"));
assert_eq!(
from_json_factory(&e2e_config, &named, "java"),
None,
"a factory configured for one language must not leak into another"
);
}
#[test]
fn a_fixture_without_client_config_never_selects_the_json_factory() {
let e2e_config = config_with_factories(Some("FromJson"), Some("Plain"));
assert!(client_config_call(&Fixture::default(), &e2e_config, &e2e_config.call, "go").is_none());
let (factory, json) =
client_config_call(&config_fixture(), &e2e_config, &e2e_config.call, "go").expect("config call");
assert_eq!(factory, "FromJson");
assert!(json.starts_with('{') && json.contains("\"enforcement\":\"hard\""));
}
#[test]
fn a_language_that_cannot_apply_client_config_is_excluded_with_a_reason() {
let fixture = config_fixture();
let configured = config_with_factories(Some("FromJson"), Some("Plain"));
assert_eq!(client_config_exclusion(&fixture, &configured, "go", true), None);
assert_eq!(
client_config_exclusion(&Fixture::default(), &E2eConfig::default(), "go", false),
None,
"a fixture without client_config is never excluded, whatever the language supports"
);
let unsupported = client_config_exclusion(&fixture, &configured, "go", false).expect("excluded");
assert!(unsupported.contains("cannot render"), "{unsupported}");
let unconfigured = config_with_factories(None, Some("Plain"));
let reason = client_config_exclusion(&fixture, &unconfigured, "go", true).expect("excluded");
assert!(reason.contains("client_factory_from_json"), "{reason}");
}
#[test]
fn a_snippet_that_never_calls_the_json_factory_is_rejected() {
let fixture = config_fixture();
let e2e_config = config_with_factories(Some("CreateClientFromJSON"), Some("CreateClient"));
assert!(
ensure_client_config_applied(
&fixture,
&e2e_config,
"go",
"client, err := pkg.CreateClient(\"your-api-key\", nil, nil, nil, nil)"
)
.is_err(),
"the plain factory must not satisfy a client_config fixture"
);
assert!(ensure_client_config_applied(&fixture, &e2e_config, "go", "pkg.CreateClientFromJSON(`{}`)").is_ok());
assert!(
ensure_client_config_applied(&fixture, &e2e_config, "go", "pkg.create_client_from_json(x)").is_ok(),
"a generator may respell the configured name in its own casing"
);
assert!(
ensure_client_config_applied(&Fixture::default(), &e2e_config, "go", "pkg.CreateClient()").is_ok(),
"a fixture without client_config has nothing to apply"
);
}
fn options_fixture() -> Fixture {
serde_json::from_value(serde_json::json!({
"id": "timeout_options",
"description": "a client with a short timeout",
"client_options": {"timeout_secs": 1, "max_retries": 0},
}))
.expect("fixture parses")
}
fn config_with_factory_for(language: &str, plain: Option<&str>, from_json: Option<&str>) -> E2eConfig {
let mut call = CallConfig::default();
call.overrides.insert(
language.into(),
CallOverride {
client_factory: plain.map(str::to_string),
client_factory_from_json: from_json.map(str::to_string),
..CallOverride::default()
},
);
E2eConfig {
call,
..E2eConfig::default()
}
}
#[test]
fn a_fixture_without_client_options_is_never_excluded_for_them() {
let config = config_with_factory_for("go", Some("CreateClient"), None);
assert_eq!(client_options_exclusion(&Fixture::default(), &config, "go"), None);
}
#[test]
fn a_language_that_cannot_apply_client_options_excludes_the_fixture_with_a_reason() {
let config = config_with_factory_for("go", Some("CreateClient"), Some("CreateClientFromJSON"));
let reason = client_options_exclusion(&options_fixture(), &config, "go").expect("go is excluded");
assert!(reason.contains("client options"), "{reason}");
}
#[test]
fn python_applies_client_options_when_the_call_has_a_client_factory() {
let config = config_with_factory_for("python", Some("create_client"), None);
assert_eq!(client_options_exclusion(&options_fixture(), &config, "python"), None);
let bare = config_with_factory_for("python", None, None);
assert!(client_options_exclusion(&options_fixture(), &bare, "python").is_some());
}
#[test]
fn rust_applies_client_options_only_through_the_json_factory() {
let with_json = config_with_factory_for("rust", Some("create_client"), Some("create_client_from_json"));
assert_eq!(client_options_exclusion(&options_fixture(), &with_json, "rust"), None);
let without_json = config_with_factory_for("rust", Some("create_client"), None);
let reason = client_options_exclusion(&options_fixture(), &without_json, "rust").expect("excluded");
assert!(reason.contains("client_factory_from_json"), "{reason}");
}
#[test]
fn python_refuses_a_client_option_named_like_a_python_keyword() {
let config = config_with_factory_for("python", Some("create_client"), None);
for keyword in PYTHON_KEYWORDS {
let mut fixture = options_fixture();
fixture
.client_options
.as_mut()
.expect("options")
.insert(keyword.to_string(), serde_json::json!(1));
let reason = client_options_exclusion(&fixture, &config, "python")
.unwrap_or_else(|| panic!("`{keyword}` must be refused"));
assert!(reason.contains("Python keywords"), "{reason}");
}
}
#[test]
fn rust_accepts_a_client_option_named_like_a_python_keyword() {
let config = config_with_factory_for("rust", Some("create_client"), Some("create_client_from_json"));
let mut fixture = options_fixture();
fixture
.client_options
.as_mut()
.expect("options")
.insert("class".to_string(), serde_json::json!(1));
assert_eq!(
client_options_exclusion(&fixture, &config, "rust"),
None,
"the Rust suite writes option names as JSON keys"
);
}
#[test]
fn client_option_names_must_be_plain_identifiers() {
let mut fixture = options_fixture();
fixture
.client_options
.as_mut()
.expect("options")
.insert("timeout secs".to_string(), serde_json::json!(1));
let config = config_with_factory_for("python", Some("create_client"), None);
assert!(client_options_exclusion(&fixture, &config, "python").is_some());
}
}