use crate::core::config::e2e::sample_url::has_url_scheme;
use crate::core::config::e2e::{
CallConfig, DocsSampleBaseUrl, SampleUrlManifest, SampleUrlTemplate, merge_manifest_vars,
resolve_templated_sample_url,
};
use crate::e2e::fixture::Fixture;
use std::collections::BTreeMap;
fn resolve_relative_url(
path: &str,
sample_base_url: DocsSampleBaseUrl<'_>,
template: Option<&SampleUrlTemplate>,
vars: &BTreeMap<String, String>,
) -> String {
resolve_templated_sample_url(template, path, vars).unwrap_or_else(|| sample_base_url.join(path))
}
enum DeclaredValue {
Undeclared,
HarnessRelative(serde_json::Value),
AlreadyMeaningful,
}
fn scalar_declared_value(
input: &serde_json::Value,
field: &str,
sample_base_url: DocsSampleBaseUrl<'_>,
template: Option<&SampleUrlTemplate>,
vars: &BTreeMap<String, String>,
) -> DeclaredValue {
let Some(value) = crate::e2e::codegen::resolve_field(input, field).as_str() else {
return DeclaredValue::Undeclared;
};
if has_url_scheme(value) {
DeclaredValue::AlreadyMeaningful
} else {
DeclaredValue::HarnessRelative(serde_json::Value::String(resolve_relative_url(
value,
sample_base_url,
template,
vars,
)))
}
}
fn list_declared_value(
input: &serde_json::Value,
field: &str,
sample_base_url: DocsSampleBaseUrl<'_>,
template: Option<&SampleUrlTemplate>,
vars: &BTreeMap<String, String>,
) -> DeclaredValue {
let Some(elements) = crate::e2e::codegen::resolve_urls_field(input, field).as_array() else {
return DeclaredValue::Undeclared;
};
let Some(urls) = elements
.iter()
.map(serde_json::Value::as_str)
.collect::<Option<Vec<_>>>()
else {
return DeclaredValue::AlreadyMeaningful;
};
if !urls.is_empty() && urls.iter().all(|url| has_url_scheme(url)) {
return DeclaredValue::AlreadyMeaningful;
}
let rewritten = urls
.into_iter()
.map(|url| {
if has_url_scheme(url) {
serde_json::Value::String(url.to_string())
} else {
serde_json::Value::String(resolve_relative_url(url, sample_base_url, template, vars))
}
})
.collect();
DeclaredValue::HarnessRelative(serde_json::Value::Array(rewritten))
}
pub(super) fn with_default_mock_url_literals(
mut fixture: Fixture,
call: &CallConfig,
sample_base_url: DocsSampleBaseUrl<'_>,
template: Option<&SampleUrlTemplate>,
manifest: Option<&SampleUrlManifest>,
) -> Fixture {
let candidates: Vec<(String, bool)> = fixture
.resolved_args(call)
.iter()
.filter_map(|arg| match arg.arg_type.as_str() {
"mock_url" => Some((arg.field.clone(), false)),
"mock_url_list" => Some((arg.field.clone(), true)),
_ => None,
})
.collect();
let empty_vars = BTreeMap::new();
let fixture_vars = fixture.docs.as_ref().map_or(&empty_vars, |docs| &docs.sample_url_vars);
let body_file = fixture.docs.as_ref().and_then(|docs| docs.body_file.as_deref());
let vars = merge_manifest_vars(manifest, body_file, fixture_vars);
let mut injected_any = false;
for (field, is_list) in candidates {
let declared = if is_list {
list_declared_value(&fixture.input, &field, sample_base_url, template, &vars)
} else {
scalar_declared_value(&fixture.input, &field, sample_base_url, template, &vars)
};
let value = match declared {
DeclaredValue::Undeclared => {
if is_list {
serde_json::json!([resolve_relative_url("", sample_base_url, template, &vars)])
} else {
serde_json::Value::String(resolve_relative_url("", sample_base_url, template, &vars))
}
}
DeclaredValue::HarnessRelative(rewritten) => rewritten,
DeclaredValue::AlreadyMeaningful => {
if !fixture.preserve_input_urls {
tracing::warn!(
target: "alef::e2e::snippets::mock_url_defaults",
fixture = %fixture.id,
field = %field,
"fixture declares an absolute URL for a mock_url/mock_url_list argument \
without setting preserve_input_urls; the literal is discarded and the \
mock-server address is bound instead, in both the executable e2e suite \
and this fixture's documentation snippet -- set preserve_input_urls = \
true if the URL is meaningful to the test, or remove the declared value \
to let the zero-edit default stand in"
);
}
continue;
}
};
if set_input_field(&mut fixture.input, &field, value) {
injected_any = true;
}
}
if injected_any {
fixture.preserve_input_urls = true;
}
fixture
}
fn set_input_field(input: &mut serde_json::Value, field_path: &str, value: serde_json::Value) -> bool {
let path = field_path.strip_prefix("input.").unwrap_or(field_path);
if path.is_empty() || path == "input" {
return false;
}
let mut segments = path.split('.').peekable();
let mut current = input;
while let Some(segment) = segments.next() {
if !current.is_object() {
*current = serde_json::Value::Object(serde_json::Map::new());
}
let Some(map) = current.as_object_mut() else {
return false;
};
if segments.peek().is_none() {
map.insert(segment.to_string(), value);
return true;
}
current = map
.entry(segment.to_string())
.or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
}
false
}
#[cfg(test)]
#[path = "mock_url_defaults_tests.rs"]
mod tests;