alpm_parsers/error/layer.rs
1//! A single [`Layer`] of a [`ParseStack`] error.
2
3#[cfg(doc)]
4use crate::error::ParseStack;
5use crate::error::context::StringContext;
6
7/// One level of nesting in our parsing stack.
8///
9/// A [`Layer`] is used as part of the [`ParseStack`] struct and created when any kind of parsing
10/// error backtracks/bubbles up past a [`LayerParser`].
11///
12/// All context messages are stored as owned [`String`]s (see [`StringContext`]), so they may be
13/// created at runtime, e.g. by our translation system.
14///
15/// [`LayerParser`]: crate::error::LayerParser
16#[derive(Clone, Debug)]
17pub(crate) struct Layer {
18 /// Name passed to [`LayerExt::layer`](crate::error::LayerExt).
19 pub(crate) name: String,
20 /// Absolute byte offset where this layer's parser began.
21 ///
22 /// [`ParseStack::at`] represents the respective end to this pointer.
23 pub(crate) start: usize,
24 /// Every context call collected within this layer, in order they were attached.
25 // Idea: If the string allocations turn out to be too performance intensive, we could think
26 // about live-concatenating any context strings. I.e. Layer would have three optional strings,
27 // one respective string for layer, description and expected items.
28 // This would move some of the formatting to the Layer, but would save **lots** of string
29 // allocatins.
30 pub(crate) contexts: Vec<StringContext>,
31}
32
33/// A small wrapper around [`Layer`], which allows us to conveniently handle anonymous layers.
34///
35/// Anonymous layers are [`ParseStack::pending`] context that hasn't been added to a layer yet.
36/// This means that the outermost parser layer has not been closed.
37///
38/// Since we still want to show this context to users, we need some form of uniform representation
39/// for it.
40#[derive(Clone, Copy, Debug)]
41pub(crate) enum LayerRef<'layer> {
42 Anonymous {
43 start: usize,
44 contexts: &'layer [StringContext],
45 },
46 Named(&'layer Layer),
47}
48
49impl<'a> LayerRef<'a> {
50 /// The byte offset where this layer begins.
51 pub(crate) fn start(self) -> usize {
52 match self {
53 LayerRef::Anonymous { start, .. } => start,
54 LayerRef::Named(layer) => layer.start,
55 }
56 }
57
58 /// The layer's name.
59 ///
60 /// `None` in case of an anonymous layer.
61 pub(crate) fn name(self) -> Option<&'a str> {
62 match self {
63 LayerRef::Anonymous { .. } => None,
64 LayerRef::Named(layer) => Some(&layer.name),
65 }
66 }
67
68 /// Returns the optional [`StringContext::Label`] messages, joined with commas.
69 ///
70 /// Returns [`None`] if there are no [`StringContext::Label`] items.
71 pub(crate) fn label_message(self) -> Option<String> {
72 let message: Vec<&str> = self
73 .contexts()
74 .iter()
75 .filter_map(StringContext::label)
76 .collect();
77
78 if message.is_empty() {
79 None
80 } else {
81 Some(message.join(", "))
82 }
83 }
84
85 /// All [`StringContext::Description`] items joined in a [`String`], without delimiter.
86 ///
87 /// Returns [`None`], if no descriptions exist or if they are all empty.
88 pub(crate) fn descriptions(self) -> Option<String> {
89 let mut description = String::new();
90 for context in self.contexts() {
91 let StringContext::Description(desc) = context else {
92 continue;
93 };
94 description.push_str(desc);
95 }
96
97 Some(description).filter(|d| !d.is_empty())
98 }
99
100 /// Returns a list of all [`StringContext`] variants with literal values.
101 pub(crate) fn expected_literals(self) -> Vec<String> {
102 self.contexts()
103 .iter()
104 .filter_map(StringContext::expected)
105 .collect()
106 }
107
108 /// Returns the very first [`StringContext::Label`] on this layer.
109 pub(crate) fn label(self) -> Option<&'a str> {
110 self.contexts().iter().find_map(StringContext::label)
111 }
112
113 /// Returns a list of all [`StringContext`] values.
114 fn contexts(self) -> &'a [StringContext] {
115 match self {
116 LayerRef::Anonymous { contexts, .. } => contexts,
117 LayerRef::Named(layer) => &layer.contexts,
118 }
119 }
120}