Skip to main content

vortex_edition/
session.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Session variables for registered and enabled editions.
5
6use std::any::Any;
7use std::collections::BTreeMap;
8use std::sync::Arc;
9
10use parking_lot::RwLock;
11use vortex_session::ArcSwapMap;
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
44/// Registry of enabled editions, keyed by interned edition family.
45type EditionsByFamily = ArcSwapMap<Id, EditionId>;
46
47/// The editions enabled for writing in a session.
48///
49/// At most one edition is enabled per family. Enabling a newer or older edition from the
50/// same family replaces the previous selection. This is separate from [`EditionSession`]:
51/// registration describes what a session knows how to reason about, while enabling is the
52/// explicit writer policy.
53///
54/// Backed by an [`ArcSwapMap`] keyed by edition family, so clones observe the same selection
55/// and enabling an edition replaces the family's previous entry.
56#[derive(Clone, Debug, Default)]
57pub struct EnabledEditions {
58    inner: EditionsByFamily,
59}
60
61impl EnabledEditions {
62    /// Return the enabled editions.
63    pub fn editions(&self) -> Vec<EditionId> {
64        self.inner.read(|map| map.values().copied().collect())
65    }
66
67    fn enable(&self, edition: EditionId) {
68        // The family is a `&'static str`; `Into<Id>` interns it once at enable time (a rare
69        // config-time write, never on the read path).
70        self.inner.insert(Id::from(edition.family), edition);
71    }
72}
73
74impl EditionSession {
75    /// Create a session variable with no declarations.
76    pub fn empty() -> Self {
77        Self {
78            inner: Arc::new(RwLock::new(Inner::default())),
79        }
80    }
81
82    /// Declare an edition together with the encodings that join the family at it. Each
83    /// added encoding's membership (`since`) is the declared edition; members of earlier
84    /// editions are inherited and must not be restated.
85    pub fn declare(&self, declaration: &EditionDeclaration) -> Result<(), EditionError> {
86        self.declare_edition(declaration.edition)?;
87        for encoding in declaration.added {
88            self.declare_inclusion(EditionInclusion::new(*encoding, declaration.edition.id))?;
89        }
90        Ok(())
91    }
92
93    /// Declare an edition. Errors if an edition with the same id is already declared.
94    pub fn declare_edition(&self, edition: Edition) -> Result<(), EditionError> {
95        let mut inner = self.inner.write();
96        let key = edition.id.to_string();
97        if inner.editions.contains_key(&key) {
98            return Err(EditionError::new(format!("duplicate edition {key}")));
99        }
100        inner.editions.insert(key, edition);
101        Ok(())
102    }
103
104    /// Declare an edition inclusion. Errors if the encoding already has one: an encoding
105    /// belongs to exactly one family, with one membership interval.
106    pub fn declare_inclusion(&self, inclusion: EditionInclusion) -> Result<(), EditionError> {
107        let mut inner = self.inner.write();
108        if inner.inclusions.contains_key(&inclusion.encoding_id) {
109            return Err(EditionError::new(format!(
110                "duplicate edition inclusion for encoding {}",
111                inclusion.encoding_id
112            )));
113        }
114        inner.inclusions.insert(inclusion.encoding_id, inclusion);
115        Ok(())
116    }
117
118    /// All declared editions, sorted by family and then chronologically. The newest frozen
119    /// edition of each family is that family's `current` edition; unversioned editions are
120    /// drafts.
121    pub fn editions(&self) -> Vec<Edition> {
122        let mut editions: Vec<Edition> = self.inner.read().editions.values().copied().collect();
123        editions.sort_by_key(|e| (e.id.family, e.id.year, e.id.month, e.id.version));
124        editions
125    }
126
127    /// Find a declared edition by id.
128    pub fn find(&self, id: &EditionId) -> Option<Edition> {
129        self.inner.read().editions.get(&id.to_string()).copied()
130    }
131
132    /// The newest frozen edition of a family, if any. Drafts are never current.
133    pub fn current(&self, family: &str) -> Option<Edition> {
134        self.editions()
135            .into_iter()
136            .filter(|e| e.id.family == family && !e.is_draft())
137            .next_back()
138    }
139
140    /// Compute the full encoding set of an edition: every declared inclusion of the
141    /// edition's family whose `since` is at or before it, sorted by encoding id.
142    pub fn encodings_in(&self, edition: &EditionId) -> Vec<EditionInclusion> {
143        // The map is keyed by encoding id, so the values are already sorted by it.
144        self.inner
145            .read()
146            .inclusions
147            .values()
148            .filter(|inclusion| inclusion.since.is_at_or_before(edition))
149            .copied()
150            .collect()
151    }
152
153    /// Validate all registered declarations. Errors on inclusions referencing undeclared
154    /// editions, editions out of chronological order within a family (unversioned drafts
155    /// must be newest), malformed version strings, and members requiring a release newer
156    /// than their edition declares.
157    pub fn validate(&self) -> Result<(), EditionError> {
158        let editions = self.editions();
159
160        for edition in &editions {
161            edition.id.validate()?;
162            if let Some(version) = edition.min_vortex_version
163                && parse_release(version).is_none()
164            {
165                return Err(EditionError::new(format!(
166                    "edition {} declares malformed min_vortex_version {version:?}",
167                    edition.id
168                )));
169            }
170        }
171
172        // Within each family, frozen editions must precede drafts: a frozen edition after
173        // an unversioned one would imply the draft was skipped.
174        for pair in editions.windows(2) {
175            let (prev, next) = (&pair[0], &pair[1]);
176            if prev.id.family == next.id.family && prev.is_draft() && !next.is_draft() {
177                return Err(EditionError::new(format!(
178                    "frozen edition {} follows draft {}; drafts must be newest in a family",
179                    next.id, prev.id,
180                )));
181            }
182        }
183
184        let inner = self.inner.read();
185        for inclusion in inner.inclusions.values() {
186            inclusion.validate()?;
187
188            let Some(edition) = inner.editions.get(&inclusion.since.to_string()) else {
189                return Err(EditionError::new(format!(
190                    "encoding {} is included in undeclared edition {}",
191                    inclusion.encoding_id, inclusion.since
192                )));
193            };
194
195            if let Some(required) = inclusion.required_vortex_release.and_then(parse_release)
196                && let Some(declared) = edition.min_vortex_version.and_then(parse_release)
197                && required > declared
198            {
199                return Err(EditionError::new(format!(
200                    "encoding {} requires release {}, newer than edition {}'s declared \
201                     min_vortex_version",
202                    inclusion.encoding_id,
203                    inclusion.required_vortex_release.unwrap_or_default(),
204                    edition.id,
205                )));
206            }
207        }
208
209        Ok(())
210    }
211}
212
213impl SessionVar for EditionSession {
214    fn as_any(&self) -> &dyn Any {
215        self
216    }
217
218    fn as_any_mut(&mut self) -> &mut dyn Any {
219        self
220    }
221}
222
223impl SessionVar for EnabledEditions {
224    fn as_any(&self) -> &dyn Any {
225        self
226    }
227
228    fn as_any_mut(&mut self) -> &mut dyn Any {
229        self
230    }
231}
232
233/// Session data for Vortex editions.
234pub trait EditionSessionExt: SessionExt {
235    /// Returns the edition registry.
236    fn editions(&self) -> SessionGuard<'_, EditionSession> {
237        self.get::<EditionSession>()
238    }
239
240    /// Returns the editions enabled for writing.
241    ///
242    /// Accessing this method installs the enabled-editions session variable if it is absent, with
243    /// an initially empty selection.
244    fn enabled_editions(&self) -> SessionGuard<'_, EnabledEditions> {
245        self.get::<EnabledEditions>()
246    }
247
248    /// Register an edition declaration with this session.
249    fn register_edition(&self, declaration: &EditionDeclaration) -> Result<(), EditionError> {
250        self.editions().declare(declaration)
251    }
252
253    /// Enable a registered edition for writing.
254    ///
255    /// Enabling an edition replaces the enabled edition from the same family. An edition
256    /// must be registered first so a typo or unavailable third-party declaration cannot
257    /// silently produce an empty writable set.
258    fn enable_edition(&self, edition: EditionId) -> Result<(), EditionError> {
259        if self.editions().find(&edition).is_none() {
260            return Err(EditionError::new(format!(
261                "cannot enable unregistered edition {edition}"
262            )));
263        }
264        self.enabled_editions().enable(edition);
265        Ok(())
266    }
267
268    /// Resolve the encodings in all enabled editions.
269    ///
270    /// When the enabled-editions variable is absent or no editions are enabled, this returns an
271    /// empty vector and therefore permits no edition encodings.
272    fn enabled_encoding_ids(&self) -> Vec<Id> {
273        let Some(enabled) = self.get_opt::<EnabledEditions>() else {
274            return vec![];
275        };
276        let editions = self.editions();
277        let mut ids: Vec<Id> = enabled
278            .editions()
279            .iter()
280            .flat_map(|edition| editions.encodings_in(edition))
281            .map(|inclusion| inclusion.encoding_id)
282            .collect();
283        ids.sort_unstable();
284        ids.dedup();
285        ids
286    }
287}
288
289impl<S: SessionExt> EditionSessionExt for S {}