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}