Skip to main content

compose_lens/model/
environment.rs

1//! Service environment forms.
2
3use super::{BooleanValue, ComposeScalar, FieldReference, Located};
4use crate::source::SourceSpan;
5
6/// One array-syntax environment entry.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct EnvironmentListEntry {
9    raw: Located<String>,
10    name: String,
11    value: Option<String>,
12}
13
14impl EnvironmentListEntry {
15    pub(super) fn parse(raw: Located<String>) -> Self {
16        let (name, value) = raw.value().split_once('=').map_or_else(
17            || (raw.value().clone(), None),
18            |(name, value)| (name.to_owned(), Some(value.to_owned())),
19        );
20        Self { raw, name, value }
21    }
22
23    /// Returns the complete semantic entry and its source span.
24    #[must_use]
25    pub const fn raw(&self) -> &Located<String> {
26        &self.raw
27    }
28
29    /// Returns the variable name.
30    #[must_use]
31    pub fn name(&self) -> &str {
32        &self.name
33    }
34
35    /// Returns the value after the first equals sign.
36    ///
37    /// `None` means no equals sign was authored; `Some("")` means an explicitly empty value.
38    #[must_use]
39    pub fn value(&self) -> Option<&str> {
40        self.value.as_deref()
41    }
42}
43
44/// One mapping-syntax environment entry.
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct EnvironmentMapEntry {
47    name: Located<String>,
48    value: Located<ComposeScalar>,
49    span: SourceSpan,
50}
51
52impl EnvironmentMapEntry {
53    pub(super) const fn new(name: Located<String>, value: Located<ComposeScalar>, span: SourceSpan) -> Self {
54        Self { name, value, span }
55    }
56
57    /// Returns the environment-variable name.
58    #[must_use]
59    pub const fn name(&self) -> &Located<String> {
60        &self.name
61    }
62
63    /// Returns the unprocessed scalar value; null remains distinct from an empty string.
64    #[must_use]
65    pub const fn value(&self) -> &Located<ComposeScalar> {
66        &self.value
67    }
68
69    /// Returns the complete mapping-entry span.
70    #[must_use]
71    pub const fn span(&self) -> SourceSpan {
72        self.span
73    }
74}
75
76/// A service environment with array or mapping syntax retained.
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub enum Environment {
79    /// Array syntax.
80    List {
81        /// The complete sequence span.
82        span: SourceSpan,
83        /// Entries in authored order.
84        entries: Vec<EnvironmentListEntry>,
85    },
86    /// Mapping syntax.
87    Map {
88        /// The complete mapping span.
89        span: SourceSpan,
90        /// Entries in authored order.
91        entries: Vec<EnvironmentMapEntry>,
92    },
93}
94
95impl Environment {
96    /// Returns the complete environment value span.
97    #[must_use]
98    pub const fn span(&self) -> SourceSpan {
99        match self {
100            Self::List { span, .. } | Self::Map { span, .. } => *span,
101        }
102    }
103}
104
105/// One service `env_file` entry with short or long syntax retained.
106#[derive(Debug, Clone, PartialEq, Eq)]
107pub enum EnvironmentFile {
108    /// Scalar path syntax.
109    Short(Located<String>),
110    /// Mapping syntax with path options.
111    Long(Box<LongEnvironmentFile>),
112}
113
114impl EnvironmentFile {
115    /// Returns the environment-file path in either syntax form.
116    #[must_use]
117    pub const fn path(&self) -> Option<&Located<String>> {
118        match self {
119            Self::Short(path) => Some(path),
120            Self::Long(value) => value.path(),
121        }
122    }
123}
124
125/// One mapping-syntax service `env_file` entry.
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct LongEnvironmentFile {
128    span: SourceSpan,
129    path: Option<Located<String>>,
130    required: Option<Located<BooleanValue>>,
131    format: Option<EnvironmentFileFormat>,
132    extension_fields: Vec<FieldReference>,
133    unknown_fields: Vec<FieldReference>,
134}
135
136impl LongEnvironmentFile {
137    pub(super) const fn new(span: SourceSpan) -> Self {
138        Self {
139            span,
140            path: None,
141            required: None,
142            format: None,
143            extension_fields: Vec::new(),
144            unknown_fields: Vec::new(),
145        }
146    }
147
148    pub(super) fn set_path(&mut self, value: Located<String>) {
149        self.path = Some(value);
150    }
151
152    pub(super) fn set_required(&mut self, value: Located<BooleanValue>) {
153        self.required = Some(value);
154    }
155
156    pub(super) fn set_format(&mut self, value: EnvironmentFileFormat) {
157        self.format = Some(value);
158    }
159
160    pub(super) fn push_extension(&mut self, value: FieldReference) {
161        self.extension_fields.push(value);
162    }
163
164    pub(super) fn push_unknown(&mut self, value: FieldReference) {
165        self.unknown_fields.push(value);
166    }
167
168    /// Returns the complete long-syntax entry span.
169    #[must_use]
170    pub const fn span(&self) -> SourceSpan {
171        self.span
172    }
173
174    /// Returns the required environment-file path.
175    #[must_use]
176    pub const fn path(&self) -> Option<&Located<String>> {
177        self.path.as_ref()
178    }
179
180    /// Returns the explicit required-file choice; absence means Compose's default `true`.
181    #[must_use]
182    pub const fn required(&self) -> Option<&Located<BooleanValue>> {
183        self.required.as_ref()
184    }
185
186    /// Returns the explicit file format; absence means Compose's default parser.
187    #[must_use]
188    pub const fn format(&self) -> Option<&EnvironmentFileFormat> {
189        self.format.as_ref()
190    }
191
192    /// Returns retained `x-` fields.
193    #[must_use]
194    pub fn extension_fields(&self) -> &[FieldReference] {
195        &self.extension_fields
196    }
197
198    /// Returns unrecognized long-syntax fields.
199    #[must_use]
200    pub fn unknown_fields(&self) -> &[FieldReference] {
201        &self.unknown_fields
202    }
203}
204
205/// Raw-preserving long-syntax `env_file.format` value.
206#[derive(Debug, Clone, PartialEq, Eq)]
207pub struct EnvironmentFileFormat {
208    raw: Located<String>,
209    kind: EnvironmentFileFormatKind,
210}
211
212impl EnvironmentFileFormat {
213    pub(crate) fn parse(raw: Located<String>) -> Self {
214        let kind = EnvironmentFileFormatKind::classify(raw.value());
215        Self { raw, kind }
216    }
217
218    /// Returns the authored format scalar and its source span.
219    #[must_use]
220    pub const fn raw(&self) -> &Located<String> {
221        &self.raw
222    }
223
224    /// Returns the non-destructive format classification.
225    #[must_use]
226    pub const fn kind(&self) -> EnvironmentFileFormatKind {
227        self.kind
228    }
229
230    /// Reports whether Compose defines this value or interpolation still defers it.
231    #[must_use]
232    pub const fn is_valid(&self) -> bool {
233        !matches!(self.kind, EnvironmentFileFormatKind::Other)
234    }
235}
236
237/// Classification of an `env_file.format` value.
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239#[non_exhaustive]
240pub enum EnvironmentFileFormatKind {
241    /// Compose's raw environment-file parser.
242    Raw,
243    /// A value that still contains interpolation.
244    Expression,
245    /// An invalid or provider-specific value retained for diagnostics.
246    Other,
247}
248
249impl EnvironmentFileFormatKind {
250    pub(crate) fn classify(value: &str) -> Self {
251        match value {
252            "raw" => Self::Raw,
253            value if value.contains('$') => Self::Expression,
254            _ => Self::Other,
255        }
256    }
257}