Skip to main content

link_cli/
named_type_links.rs

1//! [`NamedTypeLinks`], the storage interface every layer of the CLI is written
2//! against, and its implementations for the plain store and for each decorator.
3//!
4//! A store that implements this trait can be dropped anywhere in the stack, so
5//! an application can add its own layer -- a cache, a permission check, a
6//! remote store -- without the query processor knowing about it.
7
8use anyhow::{Context, Result};
9use std::collections::HashSet;
10use std::fs::OpenOptions;
11use std::io::{BufWriter, Write};
12use std::path::Path;
13
14use crate::error::LinkError;
15use crate::link::Link;
16use crate::link_storage::{ChangeObserver, LinkStorage};
17use crate::named_types::{NamedTypes, NamedTypesDecorator};
18
19pub trait NamedTypeLinks {
20    fn create(&mut self, source: u32, target: u32) -> u32;
21    fn ensure_created(&mut self, id: u32) -> u32;
22    fn try_ensure_created(&mut self, id: u32) -> Result<u32> {
23        if id == 0 || id == u32::MAX {
24            return Err(LinkError::InvalidFormat(format!(
25                "Cannot ensure unsupported link address {id}"
26            ))
27            .into());
28        }
29
30        Ok(self.ensure_created(id))
31    }
32    fn get_link(&mut self, id: u32) -> Option<Link>;
33    fn exists(&mut self, id: u32) -> bool;
34    fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link>;
35    fn delete(&mut self, id: u32) -> Result<Link>;
36    /// [`Self::delete`], reporting every change the deletion caused.
37    ///
38    /// Deleting a link cascades into every link that still used it, and each of
39    /// those removals is a change in its own right. The C# CLI sees them
40    /// because `AdvancedMixedQueryProcessor.RemoveLinks` hands a handler to the
41    /// store:
42    ///
43    /// ```csharp
44    /// links.Delete(link, (before, after) =>
45    ///     options.ChangesHandler?.Invoke(before, after) ?? links.Constants.Continue);
46    /// ```
47    ///
48    /// The default implementation reports only the link that was asked for,
49    /// which is correct for stores that cannot cascade; every decorator over a
50    /// cascading store overrides it.
51    fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
52        let before = self.delete(id)?;
53        observer(before, Link::null());
54        Ok(before)
55    }
56    fn all_links(&mut self) -> Vec<Link>;
57    fn search(&mut self, source: u32, target: u32) -> Option<u32>;
58    fn get_or_create(&mut self, source: u32, target: u32) -> u32;
59    fn get_name(&mut self, id: u32) -> Result<Option<String>>;
60    fn set_name(&mut self, id: u32, name: &str) -> Result<u32>;
61    fn get_by_name(&mut self, name: &str) -> Result<Option<u32>>;
62    fn remove_name(&mut self, id: u32) -> Result<()>;
63    fn save(&mut self) -> Result<()>;
64
65    fn get_or_create_named(&mut self, name: &str) -> Result<u32> {
66        if let Some(id) = self.get_by_name(name)? {
67            return Ok(id);
68        }
69
70        let id = self.create(0, 0);
71        self.set_name(id, name)?;
72        self.update(id, id, id)?;
73        Ok(id)
74    }
75
76    fn format_reference(&mut self, id: u32) -> Result<String> {
77        Ok(self
78            .get_name(id)?
79            .map(|name| escape_lino_reference(&name))
80            .unwrap_or_else(|| id.to_string()))
81    }
82
83    fn format_lino(&mut self, link: &Link) -> Result<String> {
84        Ok(format!(
85            "({}: {} {})",
86            self.format_reference(link.index)?,
87            self.format_reference(link.source)?,
88            self.format_reference(link.target)?
89        ))
90    }
91
92    fn lino_lines(&mut self) -> Result<Vec<String>> {
93        let mut links = self.all_links();
94        links.sort_by_key(|link| link.index);
95
96        links
97            .iter()
98            .map(|link| self.format_lino(link))
99            .collect::<Result<Vec<_>>>()
100    }
101
102    fn write_lino_output<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
103        let path = path.as_ref();
104        let file = OpenOptions::new()
105            .write(true)
106            .create(true)
107            .truncate(true)
108            .open(path)
109            .with_context(|| format!("Failed to create LiNo output: {}", path.display()))?;
110
111        let mut writer = BufWriter::new(file);
112        for line in self.lino_lines()? {
113            writeln!(writer, "{line}")?;
114        }
115        writer.flush()?;
116        Ok(())
117    }
118
119    fn print_all_lino(&mut self) -> Result<()> {
120        for line in self.lino_lines()? {
121            println!("{line}");
122        }
123        Ok(())
124    }
125
126    fn print_change_lino(&mut self, before: &Option<Link>, after: &Option<Link>) -> Result<()> {
127        let before_text = before
128            .map(|link| self.format_lino(&link))
129            .transpose()?
130            .unwrap_or_default();
131        let after_text = after
132            .map(|link| self.format_lino(&link))
133            .transpose()?
134            .unwrap_or_default();
135        println!("({before_text}) ({after_text})");
136        Ok(())
137    }
138
139    fn format_structure(&mut self, id: u32) -> Result<String> {
140        let mut visited = HashSet::new();
141        self.format_structure_recursive(id, &mut visited)
142    }
143
144    fn format_structure_recursive(
145        &mut self,
146        id: u32,
147        visited: &mut HashSet<u32>,
148    ) -> Result<String> {
149        let link = self.get_link(id).ok_or(LinkError::not_found(id))?;
150        if !visited.insert(id) {
151            return self.format_reference(id);
152        }
153
154        let source = if self.exists(link.source) && !visited.contains(&link.source) {
155            self.format_structure_recursive(link.source, visited)?
156        } else {
157            self.format_reference(link.source)?
158        };
159        let target = self.format_reference(link.target)?;
160        let index = self.format_reference(link.index)?;
161        visited.remove(&id);
162
163        Ok(format!("({index}: {source} {target})"))
164    }
165}
166
167impl NamedTypeLinks for LinkStorage {
168    fn create(&mut self, source: u32, target: u32) -> u32 {
169        LinkStorage::create(self, source, target)
170    }
171
172    fn ensure_created(&mut self, id: u32) -> u32 {
173        LinkStorage::ensure_created(self, id)
174    }
175
176    fn get_link(&mut self, id: u32) -> Option<Link> {
177        self.get(id).copied()
178    }
179
180    fn exists(&mut self, id: u32) -> bool {
181        LinkStorage::exists(self, id)
182    }
183
184    fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link> {
185        LinkStorage::update(self, id, source, target)
186    }
187
188    fn delete(&mut self, id: u32) -> Result<Link> {
189        LinkStorage::delete(self, id)
190    }
191
192    fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
193        LinkStorage::delete_observed(self, id, observer)
194    }
195
196    fn all_links(&mut self) -> Vec<Link> {
197        self.all().into_iter().copied().collect()
198    }
199
200    fn search(&mut self, source: u32, target: u32) -> Option<u32> {
201        LinkStorage::search(self, source, target)
202    }
203
204    fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
205        LinkStorage::get_or_create(self, source, target)
206    }
207
208    fn get_name(&mut self, id: u32) -> Result<Option<String>> {
209        Ok(LinkStorage::get_name(self, id).cloned())
210    }
211
212    fn set_name(&mut self, id: u32, name: &str) -> Result<u32> {
213        LinkStorage::set_name(self, id, name);
214        Ok(id)
215    }
216
217    fn get_by_name(&mut self, name: &str) -> Result<Option<u32>> {
218        Ok(LinkStorage::get_by_name(self, name))
219    }
220
221    fn remove_name(&mut self, id: u32) -> Result<()> {
222        LinkStorage::remove_name(self, id);
223        Ok(())
224    }
225
226    fn save(&mut self) -> Result<()> {
227        LinkStorage::save(self)
228    }
229
230    fn get_or_create_named(&mut self, name: &str) -> Result<u32> {
231        Ok(LinkStorage::get_or_create_named(self, name))
232    }
233}
234
235impl NamedTypeLinks for NamedTypesDecorator {
236    fn create(&mut self, source: u32, target: u32) -> u32 {
237        NamedTypesDecorator::create(self, source, target)
238    }
239
240    fn ensure_created(&mut self, id: u32) -> u32 {
241        NamedTypesDecorator::ensure_created(self, id)
242    }
243
244    fn get_link(&mut self, id: u32) -> Option<Link> {
245        self.get(id).copied()
246    }
247
248    fn exists(&mut self, id: u32) -> bool {
249        NamedTypesDecorator::exists(self, id)
250    }
251
252    fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link> {
253        NamedTypesDecorator::update(self, id, source, target)
254    }
255
256    fn delete(&mut self, id: u32) -> Result<Link> {
257        NamedTypesDecorator::delete(self, id)
258    }
259
260    fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
261        NamedTypesDecorator::delete_observed(self, id, observer)
262    }
263
264    fn all_links(&mut self) -> Vec<Link> {
265        self.all().into_iter().copied().collect()
266    }
267
268    fn search(&mut self, source: u32, target: u32) -> Option<u32> {
269        NamedTypesDecorator::search(self, source, target)
270    }
271
272    fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
273        NamedTypesDecorator::get_or_create(self, source, target)
274    }
275
276    fn get_name(&mut self, id: u32) -> Result<Option<String>> {
277        NamedTypes::get_name(self, id)
278    }
279
280    fn set_name(&mut self, id: u32, name: &str) -> Result<u32> {
281        NamedTypes::set_name(self, id, name)
282    }
283
284    fn get_by_name(&mut self, name: &str) -> Result<Option<u32>> {
285        NamedTypes::get_by_name(self, name)
286    }
287
288    fn remove_name(&mut self, id: u32) -> Result<()> {
289        NamedTypes::remove_name(self, id)
290    }
291
292    fn save(&mut self) -> Result<()> {
293        NamedTypesDecorator::save(self)
294    }
295}
296
297impl NamedTypeLinks for crate::transactions::TransactionsDecorator {
298    fn create(&mut self, source: u32, target: u32) -> u32 {
299        crate::transactions::TransactionsDecorator::create(self, source, target)
300            .expect("TransactionsDecorator::create failed in NamedTypeLinks bridge")
301    }
302
303    fn ensure_created(&mut self, id: u32) -> u32 {
304        crate::transactions::TransactionsDecorator::ensure_created(self, id)
305            .expect("TransactionsDecorator::ensure_created failed in NamedTypeLinks bridge")
306    }
307
308    fn get_link(&mut self, id: u32) -> Option<Link> {
309        self.get(id).copied()
310    }
311
312    fn exists(&mut self, id: u32) -> bool {
313        crate::transactions::TransactionsDecorator::exists(self, id)
314    }
315
316    fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link> {
317        Ok(crate::transactions::TransactionsDecorator::update(
318            self, id, source, target,
319        )?)
320    }
321
322    fn delete(&mut self, id: u32) -> Result<Link> {
323        Ok(crate::transactions::TransactionsDecorator::delete(
324            self, id,
325        )?)
326    }
327
328    fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
329        Ok(crate::transactions::TransactionsDecorator::delete_observed(
330            self, id, observer,
331        )?)
332    }
333
334    fn all_links(&mut self) -> Vec<Link> {
335        self.all().into_iter().copied().collect()
336    }
337
338    fn search(&mut self, source: u32, target: u32) -> Option<u32> {
339        crate::transactions::TransactionsDecorator::search(self, source, target)
340    }
341
342    fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
343        crate::transactions::TransactionsDecorator::get_or_create(self, source, target)
344            .expect("TransactionsDecorator::get_or_create failed in NamedTypeLinks bridge")
345    }
346
347    fn get_name(&mut self, id: u32) -> Result<Option<String>> {
348        NamedTypes::get_name(self.inner_mut(), id)
349    }
350
351    fn set_name(&mut self, id: u32, name: &str) -> Result<u32> {
352        NamedTypes::set_name(self.inner_mut(), id, name)
353    }
354
355    fn get_by_name(&mut self, name: &str) -> Result<Option<u32>> {
356        NamedTypes::get_by_name(self.inner_mut(), name)
357    }
358
359    fn remove_name(&mut self, id: u32) -> Result<()> {
360        NamedTypes::remove_name(self.inner_mut(), id)
361    }
362
363    fn save(&mut self) -> Result<()> {
364        Ok(crate::transactions::TransactionsDecorator::save(self)?)
365    }
366}
367
368impl NamedTypeLinks for crate::version_control::VersionControlDecorator {
369    fn create(&mut self, source: u32, target: u32) -> u32 {
370        crate::version_control::VersionControlDecorator::create(self, source, target)
371            .expect("VersionControlDecorator::create failed in NamedTypeLinks bridge")
372    }
373
374    fn ensure_created(&mut self, id: u32) -> u32 {
375        crate::version_control::VersionControlDecorator::ensure_created(self, id)
376    }
377
378    fn get_link(&mut self, id: u32) -> Option<Link> {
379        self.get(id).copied()
380    }
381
382    fn exists(&mut self, id: u32) -> bool {
383        crate::version_control::VersionControlDecorator::exists(self, id)
384    }
385
386    fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link> {
387        crate::version_control::VersionControlDecorator::update(self, id, source, target)
388    }
389
390    fn delete(&mut self, id: u32) -> Result<Link> {
391        crate::version_control::VersionControlDecorator::delete(self, id)
392    }
393
394    fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
395        crate::version_control::VersionControlDecorator::delete_observed(self, id, observer)
396    }
397
398    fn all_links(&mut self) -> Vec<Link> {
399        self.all().into_iter().copied().collect()
400    }
401
402    fn search(&mut self, source: u32, target: u32) -> Option<u32> {
403        crate::version_control::VersionControlDecorator::search(self, source, target)
404    }
405
406    fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
407        crate::version_control::VersionControlDecorator::get_or_create(self, source, target)
408            .expect("VersionControlDecorator::get_or_create failed in NamedTypeLinks bridge")
409    }
410
411    fn get_name(&mut self, id: u32) -> Result<Option<String>> {
412        NamedTypes::get_name(self.transactions_mut().inner_mut(), id)
413    }
414
415    fn set_name(&mut self, id: u32, name: &str) -> Result<u32> {
416        NamedTypes::set_name(self.transactions_mut().inner_mut(), id, name)
417    }
418
419    fn get_by_name(&mut self, name: &str) -> Result<Option<u32>> {
420        NamedTypes::get_by_name(self.transactions_mut().inner_mut(), name)
421    }
422
423    fn remove_name(&mut self, id: u32) -> Result<()> {
424        NamedTypes::remove_name(self.transactions_mut().inner_mut(), id)
425    }
426
427    fn save(&mut self) -> Result<()> {
428        crate::version_control::VersionControlDecorator::save(self)
429    }
430}
431
432pub fn escape_lino_reference(reference: &str) -> String {
433    if reference.is_empty() || reference.trim().is_empty() {
434        return String::new();
435    }
436
437    let has_single_quote = reference.contains('\'');
438    let has_double_quote = reference.contains('"');
439    let needs_quoting = reference.contains(':')
440        || reference.contains('(')
441        || reference.contains(')')
442        || reference.contains(' ')
443        || reference.contains('\t')
444        || reference.contains('\n')
445        || reference.contains('\r')
446        || has_single_quote
447        || has_double_quote;
448
449    if has_single_quote && has_double_quote {
450        return format!("'{}'", reference.replace('\'', "\\'"));
451    }
452
453    if has_double_quote {
454        return format!("'{reference}'");
455    }
456
457    if has_single_quote {
458        return format!("\"{reference}\"");
459    }
460
461    if needs_quoting {
462        return format!("'{reference}'");
463    }
464
465    reference.to_string()
466}