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