Skip to main content

meta_language/api_styles/
fixtures.rs

1use crate::api_styles::FluentNetworkApi;
2use crate::configuration::ParseConfiguration;
3use crate::link_network::{LinkId, LinkMetadata, LinkNetwork, LinkType};
4use crate::query::LinkQuery;
5use crate::substitution::SubstitutionRule;
6use crate::transform::ReplacementRule;
7use crate::translation_rules::{TranslationRule, TranslationRuleSet};
8
9/// Runs one named API-style parity fixture.
10///
11/// # Errors
12///
13/// Returns a message when the fixture name is unknown or the behavior under
14/// test does not satisfy the expected contract.
15pub fn run_api_style_fixture(name: &str) -> Result<(), String> {
16    match name {
17        "parse.direct" => fixture_parse_direct(),
18        "parse.fluent" => fixture_parse_fluent(),
19        "parse.lino_text" => fixture_parse_lino_text(),
20        "query.direct" => fixture_query_direct(),
21        "query.fluent" => fixture_query_fluent(),
22        "query.link_cli_read_identity" => fixture_query_link_cli_read_identity(),
23        "query.sexpression" => fixture_query_sexpression(),
24        "transform.direct" => fixture_transform_direct(),
25        "transform.fluent" => fixture_transform_fluent(),
26        "transform.link_cli_update" => fixture_transform_link_cli_update(),
27        "transform.sexpression" => fixture_transform_sexpression(),
28        "substitute.direct" => fixture_substitute_direct(),
29        "substitute.fluent" => fixture_substitute_fluent(),
30        "substitute.link_cli_crud" | "substitute.lino_text" => fixture_substitute_link_cli_crud(),
31        "serialize.direct" | "serialize.lino_roundtrip" => fixture_serialize_direct(),
32        "serialize.fluent" => fixture_serialize_fluent(),
33        "snapshot.direct" => fixture_snapshot_direct(),
34        "snapshot.fluent" => fixture_snapshot_fluent(),
35        "translate.direct" => fixture_translate_direct(),
36        "translate.fluent" => fixture_translate_fluent(),
37        "translate.lino_rules" => fixture_translate_lino_rules(),
38        "verify.direct" => fixture_verify_direct(),
39        "verify.fluent" => fixture_verify_fluent(),
40        other => Err(format!("unknown API-style fixture `{other}`")),
41    }
42}
43
44fn fixture_parse_direct() -> Result<(), String> {
45    let network = LinkNetwork::parse("alpha", "txt", ParseConfiguration::default());
46    ensure(
47        network.reconstruct_text() == "alpha",
48        "direct parse did not round-trip",
49    )
50}
51
52fn fixture_parse_fluent() -> Result<(), String> {
53    let output =
54        LinkNetwork::parse_fluent("alpha", "txt", ParseConfiguration::default()).reconstruct();
55    ensure(output == "alpha", "fluent parse did not round-trip")
56}
57
58fn fixture_parse_lino_text() -> Result<(), String> {
59    let network = LinkNetwork::parse("(one two)\n", "LiNo", ParseConfiguration::default());
60    let has_relation = network
61        .links()
62        .any(|link| link.metadata().link_type() == Some(LinkType::Relation));
63    ensure(has_relation, "LiNo text parse did not create a relation")
64}
65
66fn fixture_query_direct() -> Result<(), String> {
67    let network = query_fixture_network();
68    let query = LinkQuery::by_type(LinkType::Concept).with_term("needle");
69    ensure(
70        network.query_links(&query).len() == 1,
71        "direct query did not find the concept",
72    )
73}
74
75fn fixture_query_fluent() -> Result<(), String> {
76    let network = query_fixture_network();
77    let query = LinkQuery::by_type(LinkType::Concept).with_term("needle");
78    let pipeline = network.into_fluent().find(query);
79    ensure(
80        pipeline.matches.len() == 1,
81        "fluent query did not retain one match",
82    )
83}
84
85fn fixture_query_link_cli_read_identity() -> Result<(), String> {
86    let mut network = link_cli_identity_network();
87    let report = network
88        .apply_link_cli_substitution_text("((1: 1 1)) ((1: 1 1))")
89        .map_err(|error| error.to_string())?;
90    ensure(
91        report.updated() == [LinkId::from_u64(1)],
92        "link-cli read identity did not echo the matched link",
93    )
94}
95
96fn fixture_query_sexpression() -> Result<(), String> {
97    let network = LinkNetwork::parse(
98        "const value = 1;\n",
99        "JavaScript",
100        ParseConfiguration::default(),
101    );
102    let query =
103        LinkQuery::from_sexpression("(identifier) @name").map_err(|error| error.to_string())?;
104    ensure(
105        !network.find(&query).is_empty(),
106        "S-expression query did not match identifiers",
107    )
108}
109
110fn fixture_transform_direct() -> Result<(), String> {
111    let output = direct_transform_output()?;
112    ensure(
113        output == "const renamed = call(renamed);\n",
114        "direct transform output mismatch",
115    )
116}
117
118fn fixture_transform_fluent() -> Result<(), String> {
119    let output = fluent_transform_output()?;
120    ensure(
121        output == "const renamed = call(renamed);\n",
122        "fluent transform output mismatch",
123    )
124}
125
126fn fixture_transform_link_cli_update() -> Result<(), String> {
127    let mut network = link_cli_identity_network();
128    let report = network
129        .apply_link_cli_substitution_text("((1: 1 1)) ((1: 1 2))")
130        .map_err(|error| error.to_string())?;
131    ensure(
132        report.updated() == [LinkId::from_u64(1)]
133            && network.link(LinkId::from_u64(1)).is_some_and(|link| {
134                link.references() == [LinkId::from_u64(1), LinkId::from_u64(2)]
135            }),
136        "link-cli update did not rewrite the matched link",
137    )
138}
139
140fn fixture_transform_sexpression() -> Result<(), String> {
141    fixture_transform_direct()
142}
143
144fn fixture_substitute_direct() -> Result<(), String> {
145    let mut network = LinkNetwork::new();
146    let one = network.insert_point("1");
147    let two = network.insert_point("2");
148    let relation = network.insert_link(
149        [one, one],
150        LinkMetadata::new().with_link_type(LinkType::Relation),
151    );
152    let report = network.apply_substitution(&SubstitutionRule::new([one, one], [one, two]));
153    ensure(
154        report.updated() == [relation],
155        "direct substitution did not update the relation",
156    )
157}
158
159fn fixture_substitute_fluent() -> Result<(), String> {
160    let mut network = LinkNetwork::new();
161    let one = network.insert_point("1");
162    let two = network.insert_point("2");
163    let relation = network.insert_link(
164        [one, one],
165        LinkMetadata::new().with_link_type(LinkType::Relation),
166    );
167    let pipeline = network
168        .into_fluent()
169        .substitute(SubstitutionRule::new([one, one], [one, two]));
170    ensure(
171        pipeline.last_report().substitution().updated() == [relation],
172        "fluent substitution did not update the relation",
173    )
174}
175
176fn fixture_substitute_link_cli_crud() -> Result<(), String> {
177    let mut network = LinkNetwork::new();
178    let create = network
179        .apply_link_cli_substitution_text("() ((1 1))")
180        .map_err(|error| error.to_string())?;
181    let relation = create.created()[0];
182    let update = network
183        .apply_link_cli_substitution_text("((1: 1 1)) ((1: 1 2))")
184        .map_err(|error| error.to_string())?;
185    let delete = network
186        .apply_link_cli_substitution_text("((1 2)) ()")
187        .map_err(|error| error.to_string())?;
188    ensure(
189        relation == LinkId::from_u64(1)
190            && update.updated() == [relation]
191            && delete.deleted() == [relation],
192        "link-cli create/update/delete fixture failed",
193    )
194}
195
196fn fixture_serialize_direct() -> Result<(), String> {
197    let network = LinkNetwork::parse("alpha", "txt", ParseConfiguration::default());
198    let lino = network.to_lino();
199    let restored = LinkNetwork::from_lino(&lino).map_err(|error| error.to_string())?;
200    ensure(
201        restored.to_lino() == lino,
202        "direct LiNo serialization did not round-trip",
203    )
204}
205
206fn fixture_serialize_fluent() -> Result<(), String> {
207    let network = LinkNetwork::parse("alpha", "txt", ParseConfiguration::default());
208    let lino = network.into_fluent().serialize();
209    ensure(
210        LinkNetwork::from_lino(&lino).is_ok(),
211        "fluent serialization did not produce loadable LiNo",
212    )
213}
214
215fn fixture_snapshot_direct() -> Result<(), String> {
216    let network = LinkNetwork::parse("alpha", "txt", ParseConfiguration::default());
217    let snapshot = network.snapshot(1, "fixture");
218    ensure(
219        snapshot.version() == 1 && snapshot.network().reconstruct_text() == "alpha",
220        "direct snapshot did not preserve the network",
221    )
222}
223
224fn fixture_snapshot_fluent() -> Result<(), String> {
225    let snapshot = LinkNetwork::parse("alpha", "txt", ParseConfiguration::default())
226        .into_fluent()
227        .snapshot(1, "fixture");
228    ensure(
229        snapshot.version() == 1 && snapshot.network().reconstruct_text() == "alpha",
230        "fluent snapshot did not preserve the network",
231    )
232}
233
234fn fixture_translate_direct() -> Result<(), String> {
235    let (network, rules) = translation_fixture();
236    ensure(
237        network.reconstruct_text_as_with_rules("Spanish", ParseConfiguration::default(), &rules)
238            == "hola",
239        "direct translation fixture failed",
240    )
241}
242
243fn fixture_translate_fluent() -> Result<(), String> {
244    let (network, rules) = translation_fixture();
245    ensure(
246        network
247            .into_fluent()
248            .translate("Spanish", ParseConfiguration::default(), &rules)
249            == "hola",
250        "fluent translation fixture failed",
251    )
252}
253
254fn fixture_translate_lino_rules() -> Result<(), String> {
255    let (_network, rules) = translation_fixture();
256    let lino = rules.to_lino();
257    let restored = TranslationRuleSet::from_lino(&lino).map_err(|error| error.to_string())?;
258    ensure(
259        restored == rules,
260        "LiNo translation rules did not round-trip",
261    )
262}
263
264fn fixture_verify_direct() -> Result<(), String> {
265    let network = LinkNetwork::parse("alpha", "txt", ParseConfiguration::default());
266    ensure(
267        network.verify_full_match(None).is_clean(),
268        "direct verification reported a clean fixture as invalid",
269    )
270}
271
272fn fixture_verify_fluent() -> Result<(), String> {
273    let report = LinkNetwork::parse("alpha", "txt", ParseConfiguration::default())
274        .into_fluent()
275        .verify(None);
276    ensure(
277        report.is_clean(),
278        "fluent verification reported a clean fixture as invalid",
279    )
280}
281
282fn direct_transform_output() -> Result<String, String> {
283    let mut network = transform_fixture_network();
284    let query = transform_query()?;
285    let matches = network.find(&query);
286    let report = network.replace(
287        &matches,
288        &ReplacementRule::captured_text("target", "renamed"),
289    );
290    ensure(!report.is_empty(), "direct transform made no replacements")?;
291    Ok(network.reconstruct_text())
292}
293
294fn fluent_transform_output() -> Result<String, String> {
295    Ok(transform_fixture_network()
296        .into_fluent()
297        .find(transform_query()?)
298        .replace(ReplacementRule::captured_text("target", "renamed"))
299        .reconstruct())
300}
301
302fn transform_fixture_network() -> LinkNetwork {
303    LinkNetwork::parse(
304        "const oldName = call(oldName);\n",
305        "JavaScript",
306        ParseConfiguration::default(),
307    )
308}
309
310fn transform_query() -> Result<LinkQuery, String> {
311    LinkQuery::from_sexpression(
312        r#"
313        (identifier) @target
314        (#eq? @target "oldName")
315        "#,
316    )
317    .map_err(|error| error.to_string())
318}
319
320fn query_fixture_network() -> LinkNetwork {
321    let mut network = LinkNetwork::new();
322    network.insert_point("needle");
323    network
324}
325
326fn link_cli_identity_network() -> LinkNetwork {
327    let mut network = LinkNetwork::new();
328    network.insert_link(
329        [LinkId::from_u64(1), LinkId::from_u64(1)],
330        LinkMetadata::new().with_link_type(LinkType::Relation),
331    );
332    network
333}
334
335fn translation_fixture() -> (LinkNetwork, TranslationRuleSet) {
336    let mut network = LinkNetwork::new();
337    let concept = network.insert_concept_expression("greeting", "English", "hello");
338    network.insert_link(
339        [concept],
340        LinkMetadata::new()
341            .with_link_type(LinkType::Semantic)
342            .with_named(true)
343            .with_term("proposition:greeting"),
344    );
345    let rules = TranslationRuleSet::new("greeting").with_rule(
346        TranslationRule::new(
347            "spanish greeting",
348            LinkQuery::by_type(LinkType::Semantic).with_term("proposition:greeting"),
349        )
350        .with_template("Spanish", "hola"),
351    );
352    (network, rules)
353}
354
355fn ensure(condition: bool, message: &str) -> Result<(), String> {
356    if condition {
357        Ok(())
358    } else {
359        Err(message.to_string())
360    }
361}