xidl_parser/versioning/
mod.rs1mod error;
18mod lifecycle;
19mod paths;
20mod project;
21mod timeline;
22
23#[cfg(test)]
24mod tests;
25
26pub use error::VersioningError;
27pub(crate) use timeline::VersionSelection;
28pub use timeline::{VersionDef, VersionId, VersionTimeline};
29
30use lifecycle::VersionVisitor as _;
31
32use crate::error::{ParseError, ParserResult};
33use crate::hir::{ParserProperties, Specification};
34use crate::rest_hir::ProjectedHir;
35
36pub const API_VERSION_PROPERTY: &str = "api_version";
38pub const API_VERSIONS_PROPERTY: &str = "api_versions";
40pub const VERSION_LAYOUT_PROPERTY: &str = "version_layout";
42pub const API_VERSION_MODULES_PROPERTY: &str = "api_version_modules";
48pub const LAYOUT_MERGED: &str = "merged";
50pub const LAYOUT_PER_VERSION: &str = "per_version";
52
53#[derive(Debug)]
55pub struct VersionedProjection {
56 pub versions: Vec<VersionDef>,
58 pub version: Option<VersionDef>,
61 pub hir: ProjectedHir,
63}
64
65#[derive(Debug, Clone)]
67pub struct VersioningRecord {
68 timeline: VersionTimeline,
69}
70
71impl VersioningRecord {
72 pub fn analyze(spec: &Specification) -> ParserResult<Option<VersioningRecord>> {
78 let discovery = lifecycle::discover(&spec.0)?;
79 let Some((ident, members)) = discovery.version_enum else {
80 if let Some((annotation, decl)) = discovery.saw_lifecycle {
81 return Err(VersioningError::MissingVersionEnum { annotation, decl }.into());
82 }
83 return Ok(None);
84 };
85 let timeline = VersionTimeline::from_members(ident, &members)?;
86 let mut validator = lifecycle::LifecycleValidator {
87 timeline: &timeline,
88 };
89 validator.walk(&spec.0, "", lifecycle::Availability::full())?;
90 Ok(Some(VersioningRecord { timeline }))
91 }
92
93 pub fn timeline(&self) -> &VersionTimeline {
95 &self.timeline
96 }
97
98 pub fn project(&self, spec: &Specification, version: VersionId) -> ParserResult<Specification> {
100 project::project_version(spec, &self.timeline, version)
101 }
102
103 pub fn merge(
106 &self,
107 spec: &Specification,
108 versions: &[VersionId],
109 ) -> ParserResult<Specification> {
110 project::merge_versions(spec, &self.timeline, versions)
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub(crate) enum VersionLayout {
117 Merged,
119 PerVersion,
121}
122
123impl VersionLayout {
124 pub(crate) fn from_properties(properties: &ParserProperties) -> ParserResult<Self> {
126 let Some(value) = properties
127 .get(VERSION_LAYOUT_PROPERTY)
128 .and_then(serde_json::Value::as_str)
129 else {
130 return Ok(Self::Merged);
131 };
132 match value {
133 LAYOUT_MERGED => Ok(Self::Merged),
134 LAYOUT_PER_VERSION => Ok(Self::PerVersion),
135 other => Err(VersioningError::UnknownVersionLayout {
136 value: other.to_string(),
137 }
138 .into()),
139 }
140 }
141}
142
143pub(crate) fn selection_from_properties(
148 properties: &ParserProperties,
149) -> ParserResult<Option<VersionSelection>> {
150 VersionSelection::from_properties(properties)
151}
152
153pub(crate) fn selected_versions(
155 selection: Option<&VersionSelection>,
156 record: &VersioningRecord,
157) -> ParserResult<Vec<VersionId>> {
158 match selection {
159 Some(selection) => selection.resolve(&record.timeline),
160 None => Ok(record.timeline.iter().map(|(id, _)| id).collect()),
161 }
162}
163
164pub(crate) fn unversioned_selection_error(selection: &VersionSelection) -> ParseError {
166 VersioningError::UnversionedSpecification {
167 selection: selection.describe(),
168 }
169 .into()
170}