Skip to main content

compose_lens/model/
remaining.rs

1//! Structured values for the final closed-schema Compose keys.
2
3use super::{BooleanValue, Command, ComposeScalar, Environment, FieldReference, Located};
4use crate::source::SourceSpan;
5use std::fmt;
6
7/// An authored service `label_file` value with its scalar or list form retained.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct LabelFiles {
10    span: SourceSpan,
11    form: LabelFilesForm,
12    unmodeled_items: Vec<SourceSpan>,
13}
14
15impl LabelFiles {
16    pub(crate) const fn new(span: SourceSpan, form: LabelFilesForm, unmodeled_items: Vec<SourceSpan>) -> Self {
17        Self {
18            span,
19            form,
20            unmodeled_items,
21        }
22    }
23
24    /// Returns the complete authored field-value span.
25    #[must_use]
26    pub const fn span(&self) -> SourceSpan {
27        self.span
28    }
29
30    /// Returns the exact scalar or ordered-list form without reading label files.
31    #[must_use]
32    pub const fn form(&self) -> &LabelFilesForm {
33        &self.form
34    }
35
36    /// Returns spans for malformed list items retained in the syntax document.
37    #[must_use]
38    pub fn unmodeled_items(&self) -> &[SourceSpan] {
39        &self.unmodeled_items
40    }
41}
42
43/// The exact authored syntax form of service `label_file`.
44#[derive(Debug, Clone, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum LabelFilesForm {
47    /// One label-file path scalar.
48    Scalar(Located<String>),
49    /// An ordered list of label-file paths, including an explicitly empty list.
50    List(Vec<Located<String>>),
51}
52
53/// Top-level Compose includes in their short and long forms.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct Includes {
56    span: SourceSpan,
57    items: Vec<IncludeItem>,
58    unmodeled_fields: Vec<FieldReference>,
59}
60
61#[allow(
62    missing_docs,
63    reason = "the documented type contract covers its conventional accessors"
64)]
65impl Includes {
66    pub(crate) fn new(span: SourceSpan, items: Vec<IncludeItem>, unmodeled_fields: Vec<FieldReference>) -> Self {
67        Self {
68            span,
69            items,
70            unmodeled_fields,
71        }
72    }
73    #[must_use]
74    pub const fn span(&self) -> SourceSpan {
75        self.span
76    }
77    #[must_use]
78    pub fn items(&self) -> &[IncludeItem] {
79        &self.items
80    }
81    #[must_use]
82    pub fn unmodeled_fields(&self) -> &[FieldReference] {
83        &self.unmodeled_fields
84    }
85}
86
87/// One authored include declaration.
88#[derive(Debug, Clone, PartialEq, Eq)]
89#[non_exhaustive]
90pub enum IncludeItem {
91    /// A short scalar path.
92    Short(Located<String>),
93    /// A long include mapping.
94    Long(IncludeLong),
95    /// An invalid form retained in the syntax document and reported diagnostically.
96    Unmodeled,
97}
98
99/// Long-form include values. Paths remain inert source data: no documents or env files are read.
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct IncludeLong {
102    span: SourceSpan,
103    paths: Vec<Located<String>>,
104    env_files: Vec<Located<String>>,
105    project_directory: Option<Located<String>>,
106    unmodeled_fields: Vec<FieldReference>,
107}
108
109#[allow(
110    missing_docs,
111    reason = "the documented type contract covers its conventional accessors"
112)]
113impl IncludeLong {
114    pub(crate) fn new(
115        span: SourceSpan,
116        paths: Vec<Located<String>>,
117        env_files: Vec<Located<String>>,
118        project_directory: Option<Located<String>>,
119        unmodeled_fields: Vec<FieldReference>,
120    ) -> Self {
121        Self {
122            span,
123            paths,
124            env_files,
125            project_directory,
126            unmodeled_fields,
127        }
128    }
129    #[must_use]
130    pub const fn span(&self) -> SourceSpan {
131        self.span
132    }
133    #[must_use]
134    pub fn paths(&self) -> &[Located<String>] {
135        &self.paths
136    }
137    #[must_use]
138    pub fn env_files(&self) -> &[Located<String>] {
139        &self.env_files
140    }
141    #[must_use]
142    pub const fn project_directory(&self) -> Option<&Located<String>> {
143        self.project_directory.as_ref()
144    }
145    #[must_use]
146    pub fn unmodeled_fields(&self) -> &[FieldReference] {
147        &self.unmodeled_fields
148    }
149}
150
151/// Top-level model definitions keyed by their Compose model name.
152#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct ModelDefinitions {
154    span: SourceSpan,
155    definitions: Vec<ModelDefinition>,
156    unmodeled_fields: Vec<FieldReference>,
157}
158
159#[allow(
160    missing_docs,
161    reason = "the documented type contract covers its conventional accessors"
162)]
163impl ModelDefinitions {
164    pub(crate) fn new(
165        span: SourceSpan,
166        definitions: Vec<ModelDefinition>,
167        unmodeled_fields: Vec<FieldReference>,
168    ) -> Self {
169        Self {
170            span,
171            definitions,
172            unmodeled_fields,
173        }
174    }
175    #[must_use]
176    pub const fn span(&self) -> SourceSpan {
177        self.span
178    }
179    #[must_use]
180    pub fn definitions(&self) -> &[ModelDefinition] {
181        &self.definitions
182    }
183    #[must_use]
184    pub fn definition(&self, name: &str) -> Option<&ModelDefinition> {
185        self.definitions
186            .iter()
187            .find(|definition| definition.key.value() == name)
188    }
189    #[must_use]
190    pub fn unmodeled_fields(&self) -> &[FieldReference] {
191        &self.unmodeled_fields
192    }
193}
194
195/// One source-aware top-level model definition.
196#[derive(Debug, Clone, PartialEq, Eq)]
197pub struct ModelDefinition {
198    key: Located<String>,
199    span: SourceSpan,
200    name: Option<Located<String>>,
201    model: Option<Located<String>>,
202    context_size: Option<Located<ComposeScalar>>,
203    runtime_flags: Vec<Located<String>>,
204    unmodeled_fields: Vec<FieldReference>,
205}
206
207#[allow(
208    missing_docs,
209    reason = "the documented type contract covers its conventional accessors"
210)]
211impl ModelDefinition {
212    pub(crate) fn new(key: Located<String>, span: SourceSpan) -> Self {
213        Self {
214            key,
215            span,
216            name: None,
217            model: None,
218            context_size: None,
219            runtime_flags: Vec::new(),
220            unmodeled_fields: Vec::new(),
221        }
222    }
223    pub(crate) fn set_name(&mut self, value: Located<String>) {
224        self.name = Some(value);
225    }
226    pub(crate) fn set_model(&mut self, value: Located<String>) {
227        self.model = Some(value);
228    }
229    pub(crate) fn set_context_size(&mut self, value: Located<ComposeScalar>) {
230        self.context_size = Some(value);
231    }
232    pub(crate) fn set_runtime_flags(&mut self, values: Vec<Located<String>>) {
233        self.runtime_flags = values;
234    }
235    pub(crate) fn push_unmodeled(&mut self, field: FieldReference) {
236        self.unmodeled_fields.push(field);
237    }
238    #[must_use]
239    pub const fn key(&self) -> &Located<String> {
240        &self.key
241    }
242    #[must_use]
243    pub const fn span(&self) -> SourceSpan {
244        self.span
245    }
246    #[must_use]
247    pub const fn name(&self) -> Option<&Located<String>> {
248        self.name.as_ref()
249    }
250    #[must_use]
251    pub const fn model(&self) -> Option<&Located<String>> {
252        self.model.as_ref()
253    }
254    #[must_use]
255    pub const fn context_size(&self) -> Option<&Located<ComposeScalar>> {
256        self.context_size.as_ref()
257    }
258    #[must_use]
259    pub fn runtime_flags(&self) -> &[Located<String>] {
260        &self.runtime_flags
261    }
262    #[must_use]
263    pub fn unmodeled_fields(&self) -> &[FieldReference] {
264        &self.unmodeled_fields
265    }
266}
267
268/// Per-service bindings to top-level model definitions.
269#[derive(Debug, Clone, PartialEq, Eq)]
270pub struct ServiceModels {
271    span: SourceSpan,
272    bindings: Vec<ServiceModelBinding>,
273    unmodeled_fields: Vec<FieldReference>,
274}
275
276#[allow(
277    missing_docs,
278    reason = "the documented type contract covers its conventional accessors"
279)]
280impl ServiceModels {
281    pub(crate) fn new(
282        span: SourceSpan,
283        bindings: Vec<ServiceModelBinding>,
284        unmodeled_fields: Vec<FieldReference>,
285    ) -> Self {
286        Self {
287            span,
288            bindings,
289            unmodeled_fields,
290        }
291    }
292    #[must_use]
293    pub const fn span(&self) -> SourceSpan {
294        self.span
295    }
296    #[must_use]
297    pub fn bindings(&self) -> &[ServiceModelBinding] {
298        &self.bindings
299    }
300    #[must_use]
301    pub fn unmodeled_fields(&self) -> &[FieldReference] {
302        &self.unmodeled_fields
303    }
304}
305
306/// One service model binding from a scalar list or mapping form.
307#[derive(Debug, Clone, PartialEq, Eq)]
308pub struct ServiceModelBinding {
309    model: Located<String>,
310    span: SourceSpan,
311    endpoint_var: Option<Located<String>>,
312    model_var: Option<Located<String>>,
313    unmodeled_fields: Vec<FieldReference>,
314}
315
316#[allow(
317    missing_docs,
318    reason = "the documented type contract covers its conventional accessors"
319)]
320impl ServiceModelBinding {
321    pub(crate) fn new(model: Located<String>, span: SourceSpan) -> Self {
322        Self {
323            model,
324            span,
325            endpoint_var: None,
326            model_var: None,
327            unmodeled_fields: Vec::new(),
328        }
329    }
330    pub(crate) fn set_endpoint_var(&mut self, value: Located<String>) {
331        self.endpoint_var = Some(value);
332    }
333    pub(crate) fn set_model_var(&mut self, value: Located<String>) {
334        self.model_var = Some(value);
335    }
336    pub(crate) fn push_unmodeled(&mut self, field: FieldReference) {
337        self.unmodeled_fields.push(field);
338    }
339    #[must_use]
340    pub const fn model(&self) -> &Located<String> {
341        &self.model
342    }
343    #[must_use]
344    pub const fn span(&self) -> SourceSpan {
345        self.span
346    }
347    #[must_use]
348    pub const fn endpoint_var(&self) -> Option<&Located<String>> {
349        self.endpoint_var.as_ref()
350    }
351    #[must_use]
352    pub const fn model_var(&self) -> Option<&Located<String>> {
353        self.model_var.as_ref()
354    }
355    #[must_use]
356    pub fn unmodeled_fields(&self) -> &[FieldReference] {
357        &self.unmodeled_fields
358    }
359}
360
361/// Service GPU declarations in scalar `all` or detailed list form.
362#[derive(Debug, Clone, PartialEq, Eq)]
363pub enum Gpus {
364    /// The portable scalar selector, normally `all`.
365    All(Located<String>),
366    /// Ordered long-form selectors.
367    Devices {
368        /// Span of the complete selector sequence.
369        span: SourceSpan,
370        /// Selectors in authored order.
371        devices: Vec<GpuDevice>,
372        /// Exact spans of malformed selector items retained in the syntax document.
373        unmodeled_items: Vec<SourceSpan>,
374    },
375}
376
377#[allow(missing_docs, reason = "the enum contract covers the shared span accessor")]
378impl Gpus {
379    #[must_use]
380    pub const fn span(&self) -> SourceSpan {
381        match self {
382            Self::All(value) => value.span(),
383            Self::Devices { span, .. } => *span,
384        }
385    }
386
387    /// Returns malformed selector-item spans retained without device allocation interpretation.
388    #[must_use]
389    pub fn unmodeled_items(&self) -> &[SourceSpan] {
390        match self {
391            Self::All(_) => &[],
392            Self::Devices { unmodeled_items, .. } => unmodeled_items,
393        }
394    }
395}
396
397/// One source-aware long-form GPU selector.
398#[derive(Debug, Clone, PartialEq, Eq)]
399pub struct GpuDevice {
400    span: SourceSpan,
401    capabilities: Vec<Located<String>>,
402    count: Option<Located<ComposeScalar>>,
403    device_ids: Vec<Located<String>>,
404    driver: Option<Located<String>>,
405    options: Option<GpuOptions>,
406    unmodeled_fields: Vec<FieldReference>,
407}
408
409#[allow(
410    missing_docs,
411    reason = "the documented type contract covers its conventional accessors"
412)]
413impl GpuDevice {
414    pub(crate) fn new(span: SourceSpan) -> Self {
415        Self {
416            span,
417            capabilities: Vec::new(),
418            count: None,
419            device_ids: Vec::new(),
420            driver: None,
421            options: None,
422            unmodeled_fields: Vec::new(),
423        }
424    }
425    pub(crate) fn set_capabilities(&mut self, values: Vec<Located<String>>) {
426        self.capabilities = values;
427    }
428    pub(crate) fn set_count(&mut self, value: Located<ComposeScalar>) {
429        self.count = Some(value);
430    }
431    pub(crate) fn set_device_ids(&mut self, values: Vec<Located<String>>) {
432        self.device_ids = values;
433    }
434    pub(crate) fn set_driver(&mut self, value: Located<String>) {
435        self.driver = Some(value);
436    }
437    pub(crate) fn set_options(&mut self, value: GpuOptions) {
438        self.options = Some(value);
439    }
440    pub(crate) fn push_unmodeled(&mut self, field: FieldReference) {
441        self.unmodeled_fields.push(field);
442    }
443    #[must_use]
444    pub const fn span(&self) -> SourceSpan {
445        self.span
446    }
447    #[must_use]
448    pub fn capabilities(&self) -> &[Located<String>] {
449        &self.capabilities
450    }
451    #[must_use]
452    pub const fn count(&self) -> Option<&Located<ComposeScalar>> {
453        self.count.as_ref()
454    }
455    #[must_use]
456    pub fn device_ids(&self) -> &[Located<String>] {
457        &self.device_ids
458    }
459    #[must_use]
460    pub const fn driver(&self) -> Option<&Located<String>> {
461        self.driver.as_ref()
462    }
463    #[must_use]
464    pub const fn options(&self) -> Option<&GpuOptions> {
465        self.options.as_ref()
466    }
467    #[must_use]
468    pub fn unmodeled_fields(&self) -> &[FieldReference] {
469        &self.unmodeled_fields
470    }
471}
472
473/// GPU selector options in their authored mapping or list form.
474#[derive(Debug, Clone, PartialEq, Eq)]
475#[non_exhaustive]
476pub enum GpuOptions {
477    /// Ordered mapping entries with scalar values.
478    Mapping(Vec<super::KeyValueEntry>),
479    /// Ordered raw string option entries.
480    List(Vec<Located<String>>),
481}
482
483impl GpuOptions {
484    /// Returns mapping entries when the selector used mapping syntax.
485    #[must_use]
486    pub fn as_mapping(&self) -> Option<&[super::KeyValueEntry]> {
487        let Self::Mapping(entries) = self else {
488            return None;
489        };
490        Some(entries)
491    }
492
493    /// Returns list entries when the selector used list syntax.
494    #[must_use]
495    pub fn as_list(&self) -> Option<&[Located<String>]> {
496        let Self::List(items) = self else {
497            return None;
498        };
499        Some(items)
500    }
501}
502
503/// Side-effect-free `develop` watch configuration.
504#[derive(Debug, Clone, PartialEq, Eq)]
505pub struct Develop {
506    span: SourceSpan,
507    watch: Vec<DevelopWatch>,
508    unmodeled_fields: Vec<FieldReference>,
509    unmodeled_items: Vec<SourceSpan>,
510}
511
512#[allow(
513    missing_docs,
514    reason = "the documented type contract covers its conventional accessors"
515)]
516impl Develop {
517    pub(crate) fn new(
518        span: SourceSpan,
519        watch: Vec<DevelopWatch>,
520        unmodeled_fields: Vec<FieldReference>,
521        unmodeled_items: Vec<SourceSpan>,
522    ) -> Self {
523        Self {
524            span,
525            watch,
526            unmodeled_fields,
527            unmodeled_items,
528        }
529    }
530    #[must_use]
531    pub const fn span(&self) -> SourceSpan {
532        self.span
533    }
534    #[must_use]
535    pub fn watch(&self) -> &[DevelopWatch] {
536        &self.watch
537    }
538    #[must_use]
539    pub fn unmodeled_fields(&self) -> &[FieldReference] {
540        &self.unmodeled_fields
541    }
542
543    /// Returns exact spans of malformed watch items retained in the syntax document.
544    #[must_use]
545    pub fn unmodeled_items(&self) -> &[SourceSpan] {
546        &self.unmodeled_items
547    }
548}
549
550/// One declared watch action. The type deliberately never watches a path or executes a command.
551#[derive(Debug, Clone, PartialEq, Eq)]
552pub struct DevelopWatch {
553    span: SourceSpan,
554    action: Option<Located<String>>,
555    path: Option<Located<String>>,
556    target: Option<Located<String>>,
557    ignore: Vec<Located<String>>,
558    include: Vec<Located<String>>,
559    initial_sync: Option<Located<BooleanValue>>,
560    exec: Option<DevelopWatchExec>,
561    unmodeled_fields: Vec<FieldReference>,
562}
563
564#[allow(
565    missing_docs,
566    reason = "the documented type contract covers its conventional accessors"
567)]
568impl DevelopWatch {
569    pub(crate) fn new(span: SourceSpan) -> Self {
570        Self {
571            span,
572            action: None,
573            path: None,
574            target: None,
575            ignore: Vec::new(),
576            include: Vec::new(),
577            initial_sync: None,
578            exec: None,
579            unmodeled_fields: Vec::new(),
580        }
581    }
582    pub(crate) fn set_action(&mut self, value: Located<String>) {
583        self.action = Some(value);
584    }
585    pub(crate) fn set_path(&mut self, value: Located<String>) {
586        self.path = Some(value);
587    }
588    pub(crate) fn set_target(&mut self, value: Located<String>) {
589        self.target = Some(value);
590    }
591    pub(crate) fn set_ignore(&mut self, values: Vec<Located<String>>) {
592        self.ignore = values;
593    }
594    pub(crate) fn set_include(&mut self, values: Vec<Located<String>>) {
595        self.include = values;
596    }
597    pub(crate) fn set_initial_sync(&mut self, value: Located<BooleanValue>) {
598        self.initial_sync = Some(value);
599    }
600    pub(crate) fn set_exec(&mut self, value: DevelopWatchExec) {
601        self.exec = Some(value);
602    }
603    pub(crate) fn push_unmodeled(&mut self, field: FieldReference) {
604        self.unmodeled_fields.push(field);
605    }
606    #[must_use]
607    pub const fn span(&self) -> SourceSpan {
608        self.span
609    }
610    #[must_use]
611    pub const fn action(&self) -> Option<&Located<String>> {
612        self.action.as_ref()
613    }
614    #[must_use]
615    pub const fn path(&self) -> Option<&Located<String>> {
616        self.path.as_ref()
617    }
618    #[must_use]
619    pub const fn target(&self) -> Option<&Located<String>> {
620        self.target.as_ref()
621    }
622    #[must_use]
623    pub fn ignore(&self) -> &[Located<String>] {
624        &self.ignore
625    }
626    #[must_use]
627    pub fn include(&self) -> &[Located<String>] {
628        &self.include
629    }
630    #[must_use]
631    pub const fn initial_sync(&self) -> Option<&Located<BooleanValue>> {
632        self.initial_sync.as_ref()
633    }
634    #[must_use]
635    pub const fn exec(&self) -> Option<&DevelopWatchExec> {
636        self.exec.as_ref()
637    }
638    #[must_use]
639    pub fn unmodeled_fields(&self) -> &[FieldReference] {
640        &self.unmodeled_fields
641    }
642}
643
644/// Side-effect-free `develop.watch.exec` configuration.
645///
646/// This only retains the hook declaration. It never starts a watcher, executes
647/// a command, resolves a user, or reads an environment file.
648#[derive(Clone, PartialEq, Eq)]
649pub struct DevelopWatchExec {
650    span: SourceSpan,
651    command: Option<Command>,
652    user: Option<Located<String>>,
653    privileged: Option<Located<BooleanValue>>,
654    working_dir: Option<Located<String>>,
655    environment: Option<Environment>,
656    unmodeled_fields: Vec<FieldReference>,
657}
658
659impl fmt::Debug for DevelopWatchExec {
660    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
661        let mut debug = formatter.debug_struct("DevelopWatchExec");
662        debug
663            .field("span", &self.span)
664            .field("command", &self.command)
665            .field("user", &self.user)
666            .field("privileged", &self.privileged)
667            .field("working_dir", &self.working_dir)
668            .field("environment", &self.environment.as_ref().map(|_| "<redacted>"))
669            .field("unmodeled_fields", &self.unmodeled_fields)
670            .finish()
671    }
672}
673
674#[allow(
675    missing_docs,
676    reason = "the documented type contract covers its conventional accessors"
677)]
678impl DevelopWatchExec {
679    pub(crate) fn new(span: SourceSpan) -> Self {
680        Self {
681            span,
682            command: None,
683            user: None,
684            privileged: None,
685            working_dir: None,
686            environment: None,
687            unmodeled_fields: Vec::new(),
688        }
689    }
690    pub(crate) fn set_command(&mut self, value: Command) {
691        self.command = Some(value);
692    }
693    pub(crate) fn set_user(&mut self, value: Located<String>) {
694        self.user = Some(value);
695    }
696    pub(crate) fn set_privileged(&mut self, value: Located<BooleanValue>) {
697        self.privileged = Some(value);
698    }
699    pub(crate) fn set_working_dir(&mut self, value: Located<String>) {
700        self.working_dir = Some(value);
701    }
702    pub(crate) fn set_environment(&mut self, value: Environment) {
703        self.environment = Some(value);
704    }
705    pub(crate) fn push_unmodeled(&mut self, value: FieldReference) {
706        self.unmodeled_fields.push(value);
707    }
708    #[must_use]
709    pub const fn span(&self) -> SourceSpan {
710        self.span
711    }
712    #[must_use]
713    pub const fn command(&self) -> Option<&Command> {
714        self.command.as_ref()
715    }
716    #[must_use]
717    pub const fn user(&self) -> Option<&Located<String>> {
718        self.user.as_ref()
719    }
720    #[must_use]
721    pub const fn privileged(&self) -> Option<&Located<BooleanValue>> {
722        self.privileged.as_ref()
723    }
724    #[must_use]
725    pub const fn working_dir(&self) -> Option<&Located<String>> {
726        self.working_dir.as_ref()
727    }
728    #[must_use]
729    pub const fn environment(&self) -> Option<&Environment> {
730        self.environment.as_ref()
731    }
732    #[must_use]
733    pub fn unmodeled_fields(&self) -> &[FieldReference] {
734        &self.unmodeled_fields
735    }
736}