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