Skip to main content

vortex_edition/
session.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! The [`EditionSession`] session variable: the per-session registry of editions and
5//! edition inclusions.
6
7use std::any::Any;
8use std::collections::BTreeMap;
9use std::sync::Arc;
10
11use parking_lot::RwLock;
12use vortex_session::SessionExt;
13use vortex_session::SessionGuard;
14use vortex_session::SessionVar;
15use vortex_session::registry::Id;
16
17use crate::Edition;
18use crate::EditionDeclaration;
19use crate::EditionError;
20use crate::EditionId;
21use crate::EditionInclusion;
22use crate::parse_release;
23
24/// The session's registry of editions and edition inclusions.
25///
26/// Starts empty and is populated at initialization time: the `vortex` facade seeds the
27/// first-party declarations (`vortex::editions`), and crates registering additional
28/// encodings declare their inclusions (and, for new families, editions) alongside their
29/// encoding registration. Clones share the same underlying registry, matching the other
30/// session registries.
31#[derive(Clone, Debug, Default)]
32pub struct EditionSession {
33    inner: Arc<RwLock<Inner>>,
34}
35
36#[derive(Debug, Default)]
37struct Inner {
38    /// Keyed by the display form of the edition id.
39    editions: BTreeMap<String, Edition>,
40    /// Keyed by interned encoding id; ordered by the id's string form.
41    inclusions: BTreeMap<Id, EditionInclusion>,
42}
43
44impl EditionSession {
45    /// Create a session variable with no declarations.
46    pub fn empty() -> Self {
47        Self {
48            inner: Arc::new(RwLock::new(Inner::default())),
49        }
50    }
51
52    /// Declare an edition together with the encodings that join the family at it. Each
53    /// added encoding's membership (`since`) is the declared edition; members of earlier
54    /// editions are inherited and must not be restated.
55    pub fn declare(&self, declaration: &EditionDeclaration) -> Result<(), EditionError> {
56        self.declare_edition(declaration.edition)?;
57        for encoding in declaration.added {
58            self.declare_inclusion(EditionInclusion::new(*encoding, declaration.edition.id))?;
59        }
60        Ok(())
61    }
62
63    /// Declare an edition. Errors if an edition with the same id is already declared.
64    pub fn declare_edition(&self, edition: Edition) -> Result<(), EditionError> {
65        let mut inner = self.inner.write();
66        let key = edition.id.to_string();
67        if inner.editions.contains_key(&key) {
68            return Err(EditionError::new(format!("duplicate edition {key}")));
69        }
70        inner.editions.insert(key, edition);
71        Ok(())
72    }
73
74    /// Declare an edition inclusion. Errors if the encoding already has one: an encoding
75    /// belongs to exactly one family, with one membership interval.
76    pub fn declare_inclusion(&self, inclusion: EditionInclusion) -> Result<(), EditionError> {
77        let mut inner = self.inner.write();
78        if inner.inclusions.contains_key(&inclusion.encoding_id) {
79            return Err(EditionError::new(format!(
80                "duplicate edition inclusion for encoding {}",
81                inclusion.encoding_id
82            )));
83        }
84        inner.inclusions.insert(inclusion.encoding_id, inclusion);
85        Ok(())
86    }
87
88    /// All declared editions, sorted by family and then chronologically. The newest frozen
89    /// edition of each family is that family's `current` edition; unversioned editions are
90    /// drafts.
91    pub fn editions(&self) -> Vec<Edition> {
92        let mut editions: Vec<Edition> = self.inner.read().editions.values().copied().collect();
93        editions.sort_by_key(|e| (e.id.family, e.id.year, e.id.month, e.id.version));
94        editions
95    }
96
97    /// Find a declared edition by id.
98    pub fn find(&self, id: &EditionId) -> Option<Edition> {
99        self.inner.read().editions.get(&id.to_string()).copied()
100    }
101
102    /// The newest frozen edition of a family, if any. Drafts are never current.
103    pub fn current(&self, family: &str) -> Option<Edition> {
104        self.editions()
105            .into_iter()
106            .filter(|e| e.id.family == family && !e.is_draft())
107            .next_back()
108    }
109
110    /// Compute the full encoding set of an edition: every declared inclusion of the
111    /// edition's family whose `since` is at or before it, sorted by encoding id.
112    pub fn encodings_in(&self, edition: &EditionId) -> Vec<EditionInclusion> {
113        // The map is keyed by encoding id, so the values are already sorted by it.
114        self.inner
115            .read()
116            .inclusions
117            .values()
118            .filter(|inclusion| inclusion.since.is_at_or_before(edition))
119            .copied()
120            .collect()
121    }
122
123    /// Validate all registered declarations. Errors on inclusions referencing undeclared
124    /// editions, editions out of chronological order within a family (unversioned drafts
125    /// must be newest), malformed version strings, and members requiring a release newer
126    /// than their edition declares.
127    pub fn validate(&self) -> Result<(), EditionError> {
128        let editions = self.editions();
129
130        for edition in &editions {
131            edition.id.validate()?;
132            if let Some(version) = edition.min_vortex_version
133                && parse_release(version).is_none()
134            {
135                return Err(EditionError::new(format!(
136                    "edition {} declares malformed min_vortex_version {version:?}",
137                    edition.id
138                )));
139            }
140        }
141
142        // Within each family, frozen editions must precede drafts: a frozen edition after
143        // an unversioned one would imply the draft was skipped.
144        for pair in editions.windows(2) {
145            let (prev, next) = (&pair[0], &pair[1]);
146            if prev.id.family == next.id.family && prev.is_draft() && !next.is_draft() {
147                return Err(EditionError::new(format!(
148                    "frozen edition {} follows draft {}; drafts must be newest in a family",
149                    next.id, prev.id,
150                )));
151            }
152        }
153
154        let inner = self.inner.read();
155        for inclusion in inner.inclusions.values() {
156            inclusion.validate()?;
157
158            let Some(edition) = inner.editions.get(&inclusion.since.to_string()) else {
159                return Err(EditionError::new(format!(
160                    "encoding {} is included in undeclared edition {}",
161                    inclusion.encoding_id, inclusion.since
162                )));
163            };
164
165            if let Some(required) = inclusion.required_vortex_release.and_then(parse_release)
166                && let Some(declared) = edition.min_vortex_version.and_then(parse_release)
167                && required > declared
168            {
169                return Err(EditionError::new(format!(
170                    "encoding {} requires release {}, newer than edition {}'s declared \
171                     min_vortex_version",
172                    inclusion.encoding_id,
173                    inclusion.required_vortex_release.unwrap_or_default(),
174                    edition.id,
175                )));
176            }
177        }
178
179        Ok(())
180    }
181}
182
183impl SessionVar for EditionSession {
184    fn as_any(&self) -> &dyn Any {
185        self
186    }
187
188    fn as_any_mut(&mut self) -> &mut dyn Any {
189        self
190    }
191}
192
193/// Session data for Vortex editions.
194pub trait EditionSessionExt: SessionExt {
195    /// Returns the edition registry.
196    fn editions(&self) -> SessionGuard<'_, EditionSession> {
197        self.get::<EditionSession>()
198    }
199}
200
201impl<S: SessionExt> EditionSessionExt for S {}