use crate::e2e::config::E2eConfig;
use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::{Fixture, FixtureDocsOperation};
use heck::ToLowerCamelCase;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub(crate) struct PresentationOperation {
pub(crate) kind: &'static str,
pub(crate) expression: String,
pub(crate) item: String,
pub(crate) fields: Vec<String>,
pub(crate) optional: bool,
pub(crate) display: bool,
pub(crate) destructure_source: String,
pub(crate) destructure_item: String,
pub(crate) shown_optional: bool,
pub(crate) field_optionals: Vec<bool>,
pub(crate) field_displays: Vec<bool>,
}
fn clamp_swift_json_bridged_paths(
operations: Vec<FixtureDocsOperation>,
resolver: &FieldResolver,
) -> Vec<FixtureDocsOperation> {
let mut clamped: Vec<FixtureDocsOperation> = Vec::with_capacity(operations.len());
for operation in operations {
let kept = match operation {
FixtureDocsOperation::Show { path, display } => Some(FixtureDocsOperation::Show {
path: resolver.swift_json_bridged_traversal_prefix(&path).unwrap_or(path),
display,
}),
FixtureDocsOperation::Iterate { ref path, .. }
if resolver.swift_json_bridged_iteration_prefix(path).is_some() =>
{
None
}
other => Some(other),
};
if let Some(kept) = kept.filter(|kept| !clamped.contains(kept)) {
clamped.push(kept);
}
}
clamped
}
fn path_yields_optional(resolver: &FieldResolver, path: &str) -> bool {
let segments: Vec<&str> = path.split('.').collect();
(1..=segments.len()).any(|length| resolver.is_optional(&segments[..length].join(".")))
}
pub(crate) fn resolve(
fixture: &Fixture,
e2e_config: &E2eConfig,
language: &str,
type_defs: &[crate::core::ir::TypeDef],
functions: &[crate::core::ir::FunctionDef],
) -> Vec<PresentationOperation> {
if fixture.docs.is_none() {
return Vec::new();
}
let call = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let resolver = build_resolver(e2e_config, call, language, type_defs, functions);
resolve_with(fixture, e2e_config, language, &resolver, type_defs, functions)
}
fn build_resolver(
e2e_config: &E2eConfig,
call: &crate::core::config::e2e::CallConfig,
language: &str,
type_defs: &[crate::core::ir::TypeDef],
functions: &[crate::core::ir::FunctionDef],
) -> FieldResolver {
let (ir_reachable_fields, ir_known_excluded_fields, ir_optional_fields) = FieldResolver::ir_field_sets(type_defs);
anchor_to_declared_result_type(
FieldResolver::new(
e2e_config.effective_fields(call),
e2e_config.effective_fields_optional(call),
e2e_config.effective_result_fields(call),
e2e_config.effective_fields_array(call),
e2e_config.effective_fields_method_calls(call),
)
.with_ir_fields(ir_reachable_fields, ir_known_excluded_fields, ir_optional_fields),
call,
language,
type_defs,
functions,
)
}
fn anchor_to_declared_result_type(
resolver: FieldResolver,
call: &crate::core::config::e2e::CallConfig,
language: &str,
type_defs: &[crate::core::ir::TypeDef],
functions: &[crate::core::ir::FunctionDef],
) -> FieldResolver {
let root_type = crate::e2e::codegen::call_ir::resolve_declared_result_type(
call,
language,
crate::e2e::codegen::call_ir::CallIr { functions, type_defs },
);
let result_fields = FieldResolver::ir_result_field_facts(type_defs, language);
let resolver = resolver.with_ir_result_fields(result_fields, root_type.clone());
resolver.with_ir_collection_map(FieldResolver::ir_collection_fields(type_defs), root_type)
}
fn resolver_anchored_at_element(
resolver: &FieldResolver,
path: &str,
language: &str,
type_defs: &[crate::core::ir::TypeDef],
) -> FieldResolver {
let Some(element_type) = resolver.collection_element_type(path) else {
return resolver.clone();
};
let result_fields = FieldResolver::ir_result_field_facts(type_defs, language);
let resolver = resolver
.clone()
.with_ir_result_fields(result_fields, Some(element_type.clone()));
resolver.with_ir_collection_map(FieldResolver::ir_collection_fields(type_defs), Some(element_type))
}
pub(crate) fn apply_derived_shows(
fixture: &mut Fixture,
e2e_config: &E2eConfig,
language: &str,
type_defs: &[crate::core::ir::TypeDef],
functions: &[crate::core::ir::FunctionDef],
) {
if fixture.docs.is_none() || fixture.has_docs_presentation() {
return;
}
let call = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let resolver = build_resolver(e2e_config, call, language, type_defs, functions);
let paths: Vec<String> = default_operations_from_assertions(fixture, call, language, &resolver)
.into_iter()
.filter_map(|operation| match operation {
FixtureDocsOperation::Show { path, .. } => Some(path),
FixtureDocsOperation::Iterate { .. } => None,
})
.collect();
if paths.is_empty() {
return;
}
if let Some(docs) = fixture.docs.as_mut() {
docs.shows = paths;
}
}
pub(crate) fn resolve_with(
fixture: &Fixture,
e2e_config: &E2eConfig,
language: &str,
resolver: &FieldResolver,
type_defs: &[crate::core::ir::TypeDef],
functions: &[crate::core::ir::FunctionDef],
) -> Vec<PresentationOperation> {
let Some(docs) = fixture.docs.as_ref() else {
return Vec::new();
};
let call = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let resolver = &anchor_to_declared_result_type(resolver.clone(), call, language, type_defs, functions);
let result_var = call.effective_result_var();
let result_root = root_variable(language, result_var);
let operations = docs
.shows
.iter()
.cloned()
.map(|path| FixtureDocsOperation::Show { path, display: false })
.chain(
docs.presentation
.iter()
.flat_map(|presentation| presentation.operations.iter().cloned()),
)
.collect::<Vec<_>>();
let operations = validate_authored_operations(operations, fixture, call, language, resolver);
let operations = if operations.is_empty() {
default_operations_from_assertions(fixture, call, language, resolver)
} else {
operations
};
let operations = clamp_swift_json_bridged_paths(operations, resolver);
let operations = if language == "rust" {
downgrade_display_unsafe_operations(operations, resolver, &fixture.id)
} else {
operations
};
let resolver = &resolver
.clone()
.with_anchored_optional_paths(operations.iter().map(|operation| match operation {
FixtureDocsOperation::Show { path, .. } | FixtureDocsOperation::Iterate { path, .. } => path.as_str(),
}));
operations
.iter()
.map(|operation| match operation {
FixtureDocsOperation::Show { path, display } => PresentationOperation {
kind: "show",
expression: resolver.accessor(path, language, &result_root),
item: String::new(),
fields: Vec::new(),
optional: false,
display: *display,
destructure_source: String::new(),
destructure_item: String::new(),
shown_optional: path_yields_optional(resolver, path),
field_optionals: Vec::new(),
field_displays: Vec::new(),
},
FixtureDocsOperation::Iterate {
path,
item,
fields,
display,
optional,
} => {
let (destructure_source, destructure_item, expression) =
typescript_first_item(path, language, resolver, &result_root);
let item_root = root_variable(language, item);
let field_displays = iterate_field_displays(fields, *display, path, language, resolver, &fixture.id);
let item_resolver = resolver_anchored_at_element(resolver, path, language, type_defs);
PresentationOperation {
kind: "iterate",
expression,
item: item.clone(),
fields: fields
.iter()
.map(|field| item_resolver.accessor(field, language, &item_root))
.collect(),
optional: *optional || resolver.is_optional(path),
display: *display,
destructure_source,
destructure_item,
shown_optional: false,
field_optionals: fields
.iter()
.map(|field| path_yields_optional(&item_resolver, field))
.collect(),
field_displays,
}
}
})
.collect()
}
fn downgrade_display_unsafe_operations(
operations: Vec<FixtureDocsOperation>,
resolver: &FieldResolver,
fixture_id: &str,
) -> Vec<FixtureDocsOperation> {
operations
.into_iter()
.map(|operation| match operation {
FixtureDocsOperation::Show { path, display: true } if resolver.is_display_unsafe(&path) => {
warn_display_unsafe(fixture_id, &path);
FixtureDocsOperation::Show { path, display: false }
}
FixtureDocsOperation::Iterate {
path,
item,
fields,
display: true,
optional,
} if fields.is_empty() && resolver.is_display_unsafe(&path) => {
warn_display_unsafe(fixture_id, &path);
FixtureDocsOperation::Iterate {
path,
item,
fields,
display: false,
optional,
}
}
other => other,
})
.collect()
}
fn warn_display_unsafe(fixture_id: &str, path: &str) {
tracing::warn!(
target: "alef::e2e::presentation",
fixture = fixture_id,
path,
"fixture `{fixture_id}` sets `display: true` on `{path}`, but its resolved type is a \
struct/enum alef cannot confirm implements `Display` (extract does not record `Display` \
impls). Falling back to the debug formatter so the generated Rust snippet still \
compiles -- if `{path}`'s type genuinely implements `Display`, this warning cannot be \
resolved from the fixture alone."
);
}
fn iterate_field_displays(
fields: &[String],
display: bool,
collection_path: &str,
language: &str,
resolver: &FieldResolver,
fixture_id: &str,
) -> Vec<bool> {
if !display {
return fields.iter().map(|_| false).collect();
}
if language != "rust" {
return fields.iter().map(|_| true).collect();
}
let element_type = resolver.collection_element_type(collection_path);
fields
.iter()
.map(|field| {
let safe = element_type
.as_deref()
.is_some_and(|element_type| resolver.is_declared_field_display_safe(element_type, field));
if !safe {
warn_iterate_field_display_unsafe(fixture_id, collection_path, field);
}
safe
})
.collect()
}
fn warn_iterate_field_display_unsafe(fixture_id: &str, collection_path: &str, field: &str) {
tracing::warn!(
target: "alef::e2e::presentation",
fixture = fixture_id,
collection_path,
field,
"fixture `{fixture_id}` sets `display: true` while iterating `{collection_path}`, but \
per-item field `{field}` is not a `String`/`char`/numeric/`bool` primitive alef can \
positively confirm implements `Display`. Falling back to the debug formatter for this \
field so the generated Rust snippet still compiles -- if `{field}`'s type genuinely \
implements `Display`, this warning cannot be resolved from the fixture alone."
);
}
fn default_operations_from_assertions(
fixture: &Fixture,
call: &crate::core::config::e2e::CallConfig,
language: &str,
resolver: &FieldResolver,
) -> Vec<FixtureDocsOperation> {
if call.returns_void
|| call.effective_result_is_simple(language)
|| call.effective_result_is_bytes(language)
|| fixture.assertions.iter().any(|a| a.assertion_type == "error")
{
return Vec::new();
}
let fixture_is_streaming =
crate::e2e::codegen::streaming_assertions::resolve_is_streaming(fixture, call.streaming_enabled());
let mut seen_fields = Vec::new();
fixture
.assertions
.iter()
.filter_map(|assertion| assertion.field.as_deref())
.filter(|field| shows_on_result(field, resolver, fixture_is_streaming, language))
.filter(|field| {
let is_new = !seen_fields.contains(field);
if is_new {
seen_fields.push(*field);
}
is_new
})
.map(|field| FixtureDocsOperation::Show {
path: field.to_string(),
display: false,
})
.collect()
}
fn shows_on_result(field: &str, resolver: &FieldResolver, fixture_is_streaming: bool, language: &str) -> bool {
if !path_is_renderable_at_all(field, fixture_is_streaming) {
return false;
}
if !resolver.is_valid_for_result(field) {
return false;
}
ir_permits_result_path(field, resolver, language)
}
fn authored_shows_on_result(field: &str, resolver: &FieldResolver, fixture_is_streaming: bool, language: &str) -> bool {
if !path_is_renderable_at_all(field, fixture_is_streaming) {
return false;
}
ir_permits_result_path(field, resolver, language)
}
fn path_is_renderable_at_all(field: &str, fixture_is_streaming: bool) -> bool {
!field.is_empty()
&& !(fixture_is_streaming && crate::e2e::codegen::streaming_assertions::is_streaming_virtual_field(field))
}
fn ir_permits_result_path(field: &str, resolver: &FieldResolver, language: &str) -> bool {
if !resolver.has_ir_result_evidence() || resolver.result_field_oracle_knows(field) != Some(false) {
return true;
}
if let Some(config_key) = resolver.declaring_config_key(field) {
tracing::warn!(
target: "alef::e2e::presentation",
field,
language,
config_key,
"`{field}` is declared in `[e2e].{config_key}` but the `{language}` binding's result \
type has no such member, so the documentation snippet omits it. Correct the path \
or drop it from `{config_key}`."
);
}
false
}
fn validate_authored_operations(
operations: Vec<FixtureDocsOperation>,
fixture: &Fixture,
call: &crate::core::config::e2e::CallConfig,
language: &str,
resolver: &FieldResolver,
) -> Vec<FixtureDocsOperation> {
let fixture_is_streaming =
crate::e2e::codegen::streaming_assertions::resolve_is_streaming(fixture, call.streaming_enabled());
operations
.into_iter()
.filter_map(|operation| match operation {
FixtureDocsOperation::Show { path, display } => {
let renderable = authored_shows_on_result(&path, resolver, fixture_is_streaming, language);
renderable.then_some(FixtureDocsOperation::Show { path, display })
}
FixtureDocsOperation::Iterate {
path,
item,
fields,
display,
optional,
} => {
if !authored_shows_on_result(&path, resolver, fixture_is_streaming, language) {
return None;
}
let element_type = resolver.collection_element_type(&path);
let fields = fields
.into_iter()
.filter(|field| iterate_field_is_renderable(element_type.as_deref(), field, resolver, &path))
.collect();
Some(FixtureDocsOperation::Iterate {
path,
item,
fields,
display,
optional,
})
}
})
.collect()
}
fn iterate_field_is_renderable(
element_type: Option<&str>,
field: &str,
resolver: &FieldResolver,
collection_path: &str,
) -> bool {
let Some(element_type) = element_type else {
return true;
};
match resolver.is_declared_field_of_type(element_type, field) {
Some(false) => {
tracing::warn!(
target: "alef::e2e::presentation",
collection_path,
element_type,
field,
"fixture iterates `{collection_path}` and shows per-item field `{field}`, but \
`{element_type}` has no such member. Dropping the field rather than emitting a \
non-compiling accessor -- correct the field name in the fixture's `docs` block."
);
false
}
_ => true,
}
}
fn root_variable(language: &str, name: &str) -> String {
if language == "php" {
format!("${name}")
} else {
name.to_string()
}
}
fn typescript_first_item(
path: &str,
language: &str,
resolver: &FieldResolver,
result_var: &str,
) -> (String, String, String) {
if matches!(language, "node" | "wasm")
&& let Some((source, tail)) = path.split_once("[0].")
{
let source = resolver.accessor(source, language, result_var);
let tail_camel = tail
.split('.')
.map(|segment| segment.to_lower_camel_case())
.collect::<Vec<_>>()
.join(".");
return (
format!("{source} ?? []"),
"first".into(),
format!("first?.{tail_camel}"),
);
}
(
String::new(),
String::new(),
resolver.accessor(path, language, result_var),
)
}
#[cfg(test)]
#[path = "presentation/tests.rs"]
mod tests;
#[cfg(test)]
#[path = "presentation/derived_show_resolution_tests.rs"]
mod derived_show_resolution_tests;
#[cfg(test)]
#[path = "presentation/anchored_result_facts_tests.rs"]
mod anchored_result_facts_tests;
#[cfg(test)]
#[path = "presentation/deep_result_path_tests.rs"]
mod deep_result_path_tests;
#[cfg(test)]
#[path = "presentation/wasm_optional_leaf_field_tests.rs"]
mod wasm_optional_leaf_field_tests;
#[cfg(test)]
#[path = "presentation/node_wasm_iterate_tail_casing_tests.rs"]
mod node_wasm_iterate_tail_casing_tests;
#[cfg(test)]
#[path = "presentation/authored_operation_validation_tests.rs"]
mod authored_operation_validation_tests;
#[cfg(test)]
#[path = "presentation/iterate_field_display_safety_tests.rs"]
mod iterate_field_display_safety_tests;
#[cfg(test)]
#[path = "presentation/iterate_element_anchor_tests.rs"]
mod iterate_element_anchor_tests;