Skip to main content

xidl_parser/versioning/
timeline.rs

1use super::VersioningError;
2use crate::error::ParserResult;
3use crate::hir::{Annotation, Enumerator, ParserProperties, ScopedName};
4use std::collections::BTreeMap;
5
6/// Position of a version inside the declaration order of the `@version` enum.
7///
8/// Versions are compared by this index only; the display value is never parsed
9/// or sorted.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct VersionId(usize);
12
13impl VersionId {
14    /// Zero-based position in the version enum's declaration order.
15    pub fn index(self) -> usize {
16        self.0
17    }
18}
19
20/// One member of the API version enum.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct VersionDef {
23    /// Stable machine name of the version: the enum member ident.
24    pub ident: String,
25    /// Display value of the version: the `@rename("...")` argument, or the ident.
26    pub value: String,
27}
28
29/// Ordered API version history declared by the `@version` enum.
30#[derive(Debug, Clone)]
31pub struct VersionTimeline {
32    enum_path: Vec<String>,
33    enum_ident: String,
34    versions: Vec<VersionDef>,
35}
36
37impl VersionTimeline {
38    pub(crate) fn from_members(
39        enum_path: String,
40        members: &[Enumerator],
41    ) -> Result<Self, VersioningError> {
42        if members.is_empty() {
43            return Err(VersioningError::EmptyVersionEnum { ident: enum_path });
44        }
45        let enum_path = enum_path
46            .split("::")
47            .map(ToString::to_string)
48            .collect::<Vec<_>>();
49        let enum_ident = enum_path.last().cloned().unwrap_or_default();
50        let mut versions = Vec::new();
51        let mut values: BTreeMap<String, String> = BTreeMap::new();
52        for member in members {
53            let value = member_value(member);
54            if let Some(first) = values.get(&value) {
55                return Err(VersioningError::DuplicateVersionValue {
56                    value,
57                    first: first.clone(),
58                    second: member.ident.clone(),
59                });
60            }
61            values.insert(value.clone(), member.ident.clone());
62            versions.push(VersionDef {
63                ident: member.ident.clone(),
64                value,
65            });
66        }
67        Ok(Self {
68            enum_path,
69            enum_ident,
70            versions,
71        })
72    }
73
74    /// Ident of the enum that declares the version history.
75    pub fn enum_ident(&self) -> &str {
76        self.enum_ident.as_str()
77    }
78
79    /// Number of declared versions.
80    pub fn len(&self) -> usize {
81        self.versions.len()
82    }
83
84    /// Whether the timeline declares no versions.
85    pub fn is_empty(&self) -> bool {
86        self.versions.is_empty()
87    }
88
89    /// Version definition for a position in declaration order.
90    pub fn get(&self, version: VersionId) -> Option<&VersionDef> {
91        self.versions.get(version.0)
92    }
93
94    /// All versions in declaration order.
95    pub fn iter(&self) -> impl Iterator<Item = (VersionId, &VersionDef)> {
96        self.versions
97            .iter()
98            .enumerate()
99            .map(|(index, def)| (VersionId(index), def))
100    }
101
102    /// Every declared version as a `, `-separated `ident (value)` list.
103    pub fn declared(&self) -> String {
104        self.versions
105            .iter()
106            .map(|def| format!("{} ({})", def.ident, def.value))
107            .collect::<Vec<_>>()
108            .join(", ")
109    }
110
111    /// Resolves a version by member ident or display value.
112    pub(crate) fn find(&self, raw: &str) -> Result<VersionId, VersioningError> {
113        let matches = self
114            .versions
115            .iter()
116            .enumerate()
117            .filter(|(_, def)| def.ident == raw || def.value == raw)
118            .map(|(index, _)| index)
119            .collect::<Vec<_>>();
120        match matches.as_slice() {
121            [index] => Ok(VersionId(*index)),
122            [] => Err(VersioningError::UnknownSelectedVersion {
123                selection: raw.to_string(),
124                declared: self.declared(),
125            }),
126            [first, second, ..] => Err(VersioningError::AmbiguousSelectedVersion {
127                selection: raw.to_string(),
128                first: self.versions[*first].ident.clone(),
129                second: self.versions[*second].ident.clone(),
130            }),
131        }
132    }
133
134    /// Resolves an `Enum::member` reference against the timeline.
135    ///
136    /// Returns `None` when the reference does not name a declared version of
137    /// this timeline.
138    pub(crate) fn resolve(&self, reference: &ScopedName) -> Option<VersionId> {
139        let (member, prefix) = reference.name.split_last()?;
140        let qualified = prefix == self.enum_path.as_slice()
141            || (prefix.len() == 1 && prefix[0] == self.enum_ident);
142        if !qualified {
143            return None;
144        }
145        let index = self.versions.iter().position(|def| &def.ident == member)?;
146        Some(VersionId(index))
147    }
148}
149
150fn member_value(member: &Enumerator) -> String {
151    member
152        .annotations
153        .iter()
154        .find_map(|annotation| match annotation {
155            Annotation::Rename { name } => Some(name.clone()),
156            _ => None,
157        })
158        .unwrap_or_else(|| member.ident.clone())
159}
160
161/// Versions requested by the build for one generation run.
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub enum VersionSelection {
164    /// Every version declared by the timeline, in declaration order.
165    All,
166    /// Explicit member idents or display values.
167    Versions(Vec<String>),
168}
169
170impl VersionSelection {
171    /// Reads `api_version` / `api_versions` from generation properties.
172    ///
173    /// Returns `None` when neither property is present.
174    pub(crate) fn from_properties(
175        properties: &ParserProperties,
176    ) -> ParserResult<Option<VersionSelection>> {
177        let single = property(properties, super::API_VERSION_PROPERTY);
178        let multiple = property(properties, super::API_VERSIONS_PROPERTY);
179        match (single, multiple) {
180            (Some(_), Some(_)) => Err(VersioningError::ConflictingSelection {
181                first: super::API_VERSION_PROPERTY.to_string(),
182                second: super::API_VERSIONS_PROPERTY.to_string(),
183            }
184            .into()),
185            (Some(raw), None) => Self::single(&raw).map(Some),
186            (None, Some(raw)) => Self::multiple(&raw).map(Some),
187            (None, None) => Ok(None),
188        }
189    }
190
191    fn single(raw: &str) -> ParserResult<Self> {
192        let raw = raw.trim();
193        if raw.is_empty() {
194            return Err(VersioningError::EmptySelection.into());
195        }
196        if raw == "*" {
197            return Err(VersioningError::SelectionRequiresOneVersion {
198                selection: raw.to_string(),
199            }
200            .into());
201        }
202        Ok(Self::Versions(vec![raw.to_string()]))
203    }
204
205    fn multiple(raw: &str) -> ParserResult<Self> {
206        let items = raw
207            .split(',')
208            .map(str::trim)
209            .filter(|item| !item.is_empty())
210            .collect::<Vec<_>>();
211        if items.is_empty() {
212            return Err(VersioningError::EmptySelection.into());
213        }
214        if items.contains(&"*") {
215            return Ok(Self::All);
216        }
217        Ok(Self::Versions(
218            items.into_iter().map(ToString::to_string).collect(),
219        ))
220    }
221
222    /// Resolves the selection into timeline positions, sorted by declaration order.
223    pub(crate) fn resolve(&self, timeline: &VersionTimeline) -> ParserResult<Vec<VersionId>> {
224        match self {
225            Self::All => Ok(timeline.iter().map(|(id, _)| id).collect()),
226            Self::Versions(raw) => {
227                let mut resolved = Vec::new();
228                for item in raw {
229                    let id = timeline.find(item)?;
230                    if !resolved.contains(&id) {
231                        resolved.push(id);
232                    }
233                }
234                resolved.sort();
235                Ok(resolved)
236            }
237        }
238    }
239
240    /// Renders the request for diagnostics.
241    pub(crate) fn describe(&self) -> String {
242        match self {
243            Self::All => "*".to_string(),
244            Self::Versions(raw) => raw.join(", "),
245        }
246    }
247}
248
249fn property(properties: &ParserProperties, key: &str) -> Option<String> {
250    properties
251        .get(key)
252        .and_then(serde_json::Value::as_str)
253        .map(ToString::to_string)
254}