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}