use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::{FieldDef, FunctionDef, PrimitiveType, TypeDef, TypeRef};
use crate::e2e::config::{CallConfig, E2eConfig};
use crate::e2e::fixture::{Assertion, Fixture};
const JSON_BRIDGE_SKIP: &str = "swift-bridge JSON-bridges it to RustString";
fn field(name: &str, ty: TypeRef, optional: bool) -> FieldDef {
FieldDef {
name: name.to_string(),
ty,
optional,
..FieldDef::default()
}
}
fn bridged_ir() -> (Vec<TypeDef>, Vec<FunctionDef>) {
let type_defs = vec![
TypeDef {
name: "SectionInfo".to_string(),
fields: vec![
field("level", TypeRef::Primitive(PrimitiveType::U32), false),
field("text", TypeRef::String, false),
],
has_serde: true,
..TypeDef::default()
},
TypeDef {
name: "PageMetadata".to_string(),
fields: vec![
field("title", TypeRef::String, false),
field(
"labels",
TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
false,
),
field(
"headings",
TypeRef::Vec(Box::new(TypeRef::Named("SectionInfo".to_string()))),
true,
),
field(
"sections",
TypeRef::Vec(Box::new(TypeRef::Named("SectionInfo".to_string()))),
false,
),
],
has_serde: true,
..TypeDef::default()
},
TypeDef {
name: "ProcessResult".to_string(),
fields: vec![field("metadata", TypeRef::Named("PageMetadata".to_string()), false)],
has_serde: true,
..TypeDef::default()
},
];
let functions = vec![FunctionDef {
name: "process".to_string(),
return_type: TypeRef::Named("ProcessResult".to_string()),
..FunctionDef::default()
}];
(type_defs, functions)
}
fn e2e_config() -> (E2eConfig, CallConfig) {
let call_config = CallConfig {
function: "process".to_string(),
result_var: "result".to_string(),
..CallConfig::default()
};
let mut e2e_config = E2eConfig::default();
e2e_config.calls.insert("process".to_string(), call_config.clone());
e2e_config.result_fields = ["metadata".to_string()].into_iter().collect();
(e2e_config, call_config)
}
fn fixture_showing(path: &str) -> Fixture {
fixture_with_operation(path, serde_json::json!({"op": "show", "path": path, "display": true}))
}
fn fixture_iterating(path: &str, item: &str, fields: &[&str]) -> Fixture {
fixture_with_operation(
path,
serde_json::json!({"op": "iterate", "path": path, "item": item, "fields": fields}),
)
}
fn fixture_with_operation(path: &str, operation: serde_json::Value) -> Fixture {
Fixture {
id: "bridged_leaf".to_string(),
description: "Bridged leaf".to_string(),
call: Some("process".to_string()),
docs: serde_json::from_value(serde_json::json!({
"topic": "guides",
"presentation": {"operations": [operation]}
}))
.expect("docs must parse"),
assertions: vec![Assertion {
assertion_type: "equals".to_string(),
field: Some(path.to_string()),
value: Some(serde_json::json!("Example")),
..Assertion::default()
}],
..Fixture::default()
}
}
fn render_e2e(fixture: &Fixture) -> String {
let (type_defs, functions) = bridged_ir();
let (e2e, call_config) = e2e_config();
let map = super::values::build_swift_first_class_map(&type_defs, &[], &e2e, &call_config);
let config = ResolvedCrateConfig {
name: "sample".into(),
..ResolvedCrateConfig::default()
};
let mut out = String::new();
super::test_method::render_test_method(
&mut out,
fixture,
&e2e,
"process",
"result",
&[],
false,
None,
&map,
"Sample",
&config,
&type_defs,
&[],
&functions,
&[],
);
out
}
fn render_snippet(fixture: &Fixture) -> String {
let (type_defs, functions) = bridged_ir();
let (e2e, _) = e2e_config();
let config = ResolvedCrateConfig {
name: "sample".into(),
..ResolvedCrateConfig::default()
};
super::snippet::render_with_ir(fixture, &e2e, &config, &type_defs, &[], &functions).expect("snippet renders")
}
#[test]
fn should_clamp_a_map_subscript_to_the_bridged_leaf_the_e2e_generator_refuses() {
let fixture = fixture_showing("metadata.labels[\"theme\"]");
let e2e = render_e2e(&fixture);
let snippet = render_snippet(&fixture);
assert!(
e2e.contains(JSON_BRIDGE_SKIP),
"premise: the e2e generator must refuse this step, got:\n{e2e}"
);
assert!(
!snippet.contains("labels()["),
"the snippet must not subscript a RustString leaf, got:\n{snippet}"
);
assert!(
snippet.contains("print(result.metadata().labels())"),
"the snippet must fall back to the readable bridged leaf, got:\n{snippet}"
);
}
#[test]
fn should_clamp_an_indexed_step_into_a_bridged_leaf() {
let fixture = fixture_showing("metadata.headings[0].text");
let e2e = render_e2e(&fixture);
let snippet = render_snippet(&fixture);
assert!(
e2e.contains("JSONSerialization") && !e2e.contains(JSON_BRIDGE_SKIP),
"premise: the e2e generator must decode-and-navigate this step, not refuse it, got:\n{e2e}"
);
assert!(
!e2e.contains("headings()[0]") && !e2e.contains("headings()?[0]"),
"the e2e generator must not subscript a RustString leaf either, got:\n{e2e}"
);
assert!(
!snippet.contains("headings()[0]") && !snippet.contains("headings()?[0]"),
"the snippet must not index a RustString leaf, got:\n{snippet}"
);
assert!(
snippet.contains("result.metadata().headings()"),
"the snippet must still show the bridged leaf itself, got:\n{snippet}"
);
}
#[test]
fn should_drop_an_iterate_over_a_json_bridged_leaf() {
let fixture = fixture_iterating("metadata.headings", "section", &["text"]);
let snippet = render_snippet(&fixture);
assert!(
!snippet.contains("for section in"),
"the snippet must not iterate a RustString leaf, got:\n{snippet}"
);
assert!(
snippet.contains("print(result)"),
"dropping the only operation must fall back to showing the whole result, got:\n{snippet}"
);
}
#[test]
fn should_leave_a_countable_vec_leaf_indexable_in_both_generators() {
let fixture = fixture_showing("metadata.sections[0].text");
let e2e = render_e2e(&fixture);
let snippet = render_snippet(&fixture);
assert!(
!e2e.contains(JSON_BRIDGE_SKIP),
"premise: a countable RustVec leaf must not be refused, got:\n{e2e}"
);
assert!(
snippet.contains("result.metadata().sections()[0].text()"),
"the snippet must keep indexing a countable RustVec leaf, got:\n{snippet}"
);
}
#[test]
fn should_agree_with_the_e2e_generator_about_every_step_past_a_leaf() {
let cases = [
("metadata.labels[\"theme\"]", "labels()", false),
("metadata.headings[0].text", "headings()", false),
("metadata.sections[0].text", "sections()", true),
];
for (path, accessor, countable) in cases {
let fixture = fixture_showing(path);
let e2e = render_e2e(&fixture);
let snippet = render_snippet(&fixture);
let subscripted = |out: &str| out.contains(&format!("{accessor}[")) || out.contains(&format!("{accessor}?["));
assert_eq!(
subscripted(&snippet),
countable,
"the snippet generator spells `{path}` wrongly: a bridged leaf must never be \
subscripted and a countable one always must\n--- snippet ---\n{snippet}"
);
if countable {
assert!(
e2e.contains("[0]"),
"the e2e generator must still index a countable RustVec leaf for `{path}`, \
whether inline or through a hoisted local\n--- e2e ---\n{e2e}"
);
} else {
assert!(
!subscripted(&e2e),
"the e2e generator must never subscript the JSON-bridged accessor for `{path}`\
\n--- e2e ---\n{e2e}"
);
}
}
}