Skip to main content

lightshuttle_export/
lower.rs

1//! Lowering: turn a parsed manifest into the neutral [`ExportModel`] (IR).
2//!
3//! This is the first stage of the export pipeline. The IR produced here is
4//! consumed by every emitter without further resolution of manifest details.
5
6use lightshuttle_manifest::Manifest;
7use lightshuttle_spec::from_resource;
8
9use crate::error::{ExportError, Result};
10use crate::model::{ExportModel, ExportProject, ExportService};
11
12/// Lowers a [`lightshuttle_manifest::Manifest`] into an [`ExportModel`].
13///
14/// Each manifest resource is resolved through `lightshuttle-spec`, so the
15/// resulting model inherits the same image, port, environment, and healthcheck
16/// defaults that the runtime applies. This keeps `lightshuttle up` and
17/// `lightshuttle export` in sync with no manual duplication.
18///
19/// The raw `export:` section is carried through unchanged; emitters read it
20/// via the [`crate::resolve`] helpers to apply per-target overrides.
21///
22/// # Errors
23///
24/// Returns [`ExportError::Spec`] when a resource cannot be resolved into a
25/// container specification by `lightshuttle-spec`.
26pub fn lower(manifest: &Manifest) -> Result<ExportModel> {
27    let project = ExportProject {
28        name: manifest.project.name.clone(),
29        version: manifest.project.version.clone(),
30    };
31
32    let mut services = Vec::with_capacity(manifest.resources.len());
33    for (name, kind) in &manifest.resources {
34        let resolved = from_resource(&manifest.project.name, name, kind).map_err(|source| {
35            ExportError::Spec {
36                resource: name.clone(),
37                source,
38            }
39        })?;
40        services.push(ExportService {
41            spec: resolved.spec,
42            depends_on: kind.depends_on().to_vec(),
43        });
44    }
45
46    Ok(ExportModel {
47        project,
48        services,
49        export: manifest.export.clone(),
50    })
51}