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