Skip to main content

lightshuttle_export/
model.rs

1//! Neutral intermediate representation (IR) produced by the lowering step
2//! and consumed by every emitter.
3//!
4//! The IR sits between the parsed manifest and the target-specific emission.
5//! [`ExportModel`] is the root: it holds [`ExportProject`] metadata and a
6//! flat list of [`ExportService`] entries already resolved through
7//! `lightshuttle-spec`. Each emitter reads the model read-only and writes its
8//! output into [`ExportArtifacts`], a list of [`ExportFile`] values that the
9//! CLI layer writes to disk.
10
11use std::path::PathBuf;
12
13use lightshuttle_manifest::ExportConfig;
14use lightshuttle_spec::ContainerSpec;
15
16/// Target-agnostic model of a stack ready to be emitted.
17#[derive(Debug, Clone)]
18pub struct ExportModel {
19    /// Project metadata carried from the manifest.
20    pub project: ExportProject,
21    /// Services in manifest declaration order.
22    pub services: Vec<ExportService>,
23    /// Raw `export:` section, resolved per target by each emitter.
24    pub export: Option<ExportConfig>,
25}
26
27/// Project metadata relevant to an export.
28#[derive(Debug, Clone)]
29pub struct ExportProject {
30    /// Project name, used as the default namespace and chart name.
31    pub name: String,
32    /// Free-form project version, used as the default chart version.
33    pub version: Option<String>,
34}
35
36/// One service in the export model: a resolved container specification
37/// plus the resources it depends on.
38#[derive(Debug, Clone)]
39pub struct ExportService {
40    /// Resolved container specification (image, env, ports, volumes,
41    /// healthcheck) as produced by `lightshuttle-spec`.
42    pub spec: ContainerSpec,
43    /// Names of the resources this service depends on.
44    pub depends_on: Vec<String>,
45}
46
47/// Supported export targets.
48///
49/// Each variant maps to one [`crate::Emitter`] implementation and one
50/// CLI argument value (see [`Target::label`]).
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum Target {
53    /// A `docker-compose.yml` file, emitted by [`crate::ComposeEmitter`].
54    Compose,
55    /// Plain Kubernetes manifests, emitted by [`crate::KubernetesEmitter`].
56    Kubernetes,
57    /// A Helm chart (`Chart.yaml`, `values.yaml`, templates), emitted by
58    /// [`crate::HelmEmitter`].
59    Helm,
60}
61
62impl Target {
63    /// Returns the stable lower-case label for this target.
64    ///
65    /// The label doubles as the CLI argument value and as the default output
66    /// sub-directory name.
67    ///
68    /// ```rust
69    /// use lightshuttle_export::Target;
70    ///
71    /// assert_eq!(Target::Compose.label(), "compose");
72    /// assert_eq!(Target::Kubernetes.label(), "kubernetes");
73    /// assert_eq!(Target::Helm.label(), "helm");
74    /// ```
75    #[must_use]
76    pub fn label(self) -> &'static str {
77        match self {
78            Self::Compose => "compose",
79            Self::Kubernetes => "kubernetes",
80            Self::Helm => "helm",
81        }
82    }
83}
84
85impl std::fmt::Display for Target {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        f.write_str(self.label())
88    }
89}
90
91/// A set of named files produced by an emitter, ready to be written to disk
92/// by the CLI layer.
93///
94/// Files are stored in deterministic emission order. The CLI writes them under
95/// a per-target output directory; the relative paths inside [`ExportFile`]
96/// determine the final names.
97///
98/// ```rust
99/// use lightshuttle_export::ExportArtifacts;
100///
101/// let mut artifacts = ExportArtifacts::new();
102/// artifacts.push("docker-compose.yml", "services: {}\n");
103/// assert_eq!(artifacts.files.len(), 1);
104/// assert_eq!(artifacts.files[0].path.to_str().unwrap(), "docker-compose.yml");
105/// ```
106#[derive(Debug, Clone, Default)]
107pub struct ExportArtifacts {
108    /// Files in deterministic emission order.
109    pub files: Vec<ExportFile>,
110}
111
112impl ExportArtifacts {
113    /// Creates an empty artifact set.
114    #[must_use]
115    pub fn new() -> Self {
116        Self::default()
117    }
118
119    /// Appends a file at `path` with the given `contents`.
120    ///
121    /// `path` is relative to the export output directory. `contents` is the
122    /// complete textual content of the file.
123    pub fn push(&mut self, path: impl Into<PathBuf>, contents: impl Into<String>) {
124        self.files.push(ExportFile {
125            path: path.into(),
126            contents: contents.into(),
127        });
128    }
129}
130
131/// A single emitted file: a relative path and its textual contents.
132#[derive(Debug, Clone)]
133pub struct ExportFile {
134    /// Path relative to the export output directory.
135    pub path: PathBuf,
136    /// Full textual contents of the file.
137    pub contents: String,
138}