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