Skip to main content

boxferry_compose/
source.rs

1//! Explicit input bundle for one processed Compose project.
2
3use std::collections::BTreeMap;
4
5use boxferry_model::{Identifier, ModelError, SourceId};
6use compose_lens::{merge::MergedProject, profiles::ProfileSelection, source::SourceId as ComposeSourceId};
7
8/// A merged Compose project and the caller-owned context needed to import it safely.
9#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct ComposeSource {
11    project: MergedProject,
12    fallback_application_name: Identifier,
13    source_ids: BTreeMap<ComposeSourceId, SourceId>,
14    profile_selection: Option<ProfileSelection>,
15}
16
17impl ComposeSource {
18    /// Creates a source without guessing profiles or source filenames from ambient state.
19    ///
20    /// Every Compose source initially receives the stable neutral identity
21    /// `compose-source-<numeric-id>`. Call [`Self::with_source_id`] to replace it with a caller-owned
22    /// path, URI, or other display identity. The project's explicit top-level `name` wins when
23    /// present; the fallback is used when Compose project naming was supplied externally or
24    /// omitted.
25    ///
26    /// # Errors
27    ///
28    /// Returns [`ModelError`] if a generated fallback source identity violates the neutral-model
29    /// invariant. This cannot occur for the current `compose-source-<u32>` spelling, but keeping
30    /// construction fallible avoids a hidden panic if that policy changes.
31    pub fn new(project: MergedProject, fallback_application_name: Identifier) -> Result<Self, ModelError> {
32        let source_ids = project
33            .source_ids()
34            .iter()
35            .copied()
36            .map(|source_id| {
37                SourceId::new(format!("compose-source-{}", source_id.get())).map(|neutral| (source_id, neutral))
38            })
39            .collect::<Result<_, _>>()?;
40        Ok(Self {
41            project,
42            fallback_application_name,
43            source_ids,
44            profile_selection: None,
45        })
46    }
47
48    /// Assigns a caller-owned neutral identity to one Compose source document.
49    ///
50    /// Unknown Compose source IDs are retained in the map but cannot contribute provenance unless
51    /// they also occur in the merged project.
52    #[must_use]
53    pub fn with_source_id(mut self, compose: ComposeSourceId, neutral: SourceId) -> Self {
54        self.source_ids.insert(compose, neutral);
55        self
56    }
57
58    /// Attaches the explicit selection produced by `ComposeLens` project processing.
59    #[must_use]
60    pub fn with_profile_selection(mut self, selection: ProfileSelection) -> Self {
61        self.profile_selection = Some(selection);
62        self
63    }
64
65    /// Returns the merged native project.
66    #[must_use]
67    pub const fn project(&self) -> &MergedProject {
68        &self.project
69    }
70
71    /// Returns the caller-selected fallback application name.
72    #[must_use]
73    pub const fn fallback_application_name(&self) -> &Identifier {
74        &self.fallback_application_name
75    }
76
77    /// Resolves a Compose source identity into its neutral-model identity.
78    #[must_use]
79    pub fn source_id(&self, compose: ComposeSourceId) -> Option<&SourceId> {
80        self.source_ids.get(&compose)
81    }
82
83    /// Returns the explicit profile selection, when one was supplied.
84    #[must_use]
85    pub const fn profile_selection(&self) -> Option<&ProfileSelection> {
86        self.profile_selection.as_ref()
87    }
88}