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
9pub 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}