Skip to main content

presolve_compiler/
metaframework_handoff.rs

1//! Compiler-owned static request and deployment handoffs for Phase Q.
2
3use std::collections::BTreeMap;
4
5use serde::Serialize;
6
7use crate::platform::Digest;
8use crate::RouteManifestV1;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
11pub struct StaticRequestHandoffV1 {
12    pub schema_version: u32,
13    pub routes: Vec<StaticRequestRouteV1>,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
17pub struct StaticRequestRouteV1 {
18    pub method: String,
19    pub path: String,
20    pub artifact: String,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
24pub struct DeployableReleaseManifestV1 {
25    pub schema_version: u32,
26    pub release_id: String,
27    pub route_manifest_digest: String,
28    pub artifacts: Vec<DeployableArtifactV1>,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
32pub struct DeployableArtifactV1 {
33    pub path: String,
34    pub digest: String,
35    pub size: u64,
36}
37
38/// Creates static GET request ownership from the compiler route manifest.
39/// It intentionally does not create handlers, SSR, loaders, or server actions.
40#[must_use]
41pub fn build_static_request_handoff_v1(routes: &RouteManifestV1) -> StaticRequestHandoffV1 {
42    StaticRequestHandoffV1 {
43        schema_version: 1,
44        routes: routes
45            .routes
46            .iter()
47            .map(|route| StaticRequestRouteV1 {
48                method: "GET".into(),
49                path: route.path.clone(),
50                artifact: format!("{}/index.html", route.artifact_root),
51            })
52            .collect(),
53    }
54}
55
56/// Derives a provider-neutral immutable release inventory from compiler bytes.
57#[must_use]
58pub fn build_deployable_release_manifest_v1(
59    routes: &RouteManifestV1,
60    artifacts: &BTreeMap<std::path::PathBuf, Vec<u8>>,
61) -> DeployableReleaseManifestV1 {
62    let inventory = artifacts
63        .iter()
64        .map(|(path, bytes)| DeployableArtifactV1 {
65            path: path.to_string_lossy().replace('\\', "/"),
66            digest: Digest::sha256(bytes).to_string(),
67            size: u64::try_from(bytes.len()).expect("artifact size exceeds u64"),
68        })
69        .collect::<Vec<_>>();
70    let route_manifest = serde_json::to_vec(routes).expect("route manifest serializes");
71    let route_manifest_digest = Digest::sha256(&route_manifest).to_string();
72    let release_id = Digest::sha256(
73        inventory
74            .iter()
75            .map(|artifact| format!("{}:{}:{}\n", artifact.path, artifact.digest, artifact.size))
76            .collect::<String>(),
77    )
78    .to_string();
79    DeployableReleaseManifestV1 {
80        schema_version: 1,
81        release_id,
82        route_manifest_digest,
83        artifacts: inventory,
84    }
85}
86
87#[must_use]
88pub fn static_request_handoff_json_v1(value: &StaticRequestHandoffV1) -> String {
89    serde_json::to_string_pretty(value).expect("request handoff serializes") + "\n"
90}
91
92#[must_use]
93pub fn deployable_release_manifest_json_v1(value: &DeployableReleaseManifestV1) -> String {
94    serde_json::to_string_pretty(value).expect("release manifest serializes") + "\n"
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use crate::RouteManifestEntryV1;
101
102    #[test]
103    fn derives_static_requests_and_stable_release_inventory() {
104        let routes = RouteManifestV1 {
105            schema_version: 1,
106            routes: vec![RouteManifestEntryV1 {
107                path: "/".into(),
108                component_id: "component:home".into(),
109                artifact_root: "routes/root".into(),
110                parent_path: None,
111            }],
112        };
113        let request = build_static_request_handoff_v1(&routes);
114        assert_eq!(request.routes[0].artifact, "routes/root/index.html");
115        let mut artifacts = BTreeMap::new();
116        artifacts.insert("routes/root/index.html".into(), b"<main />".to_vec());
117        let first = build_deployable_release_manifest_v1(&routes, &artifacts);
118        let second = build_deployable_release_manifest_v1(&routes, &artifacts);
119        assert_eq!(first.release_id, second.release_id);
120        assert!(deployable_release_manifest_json_v1(&first).contains("release_id"));
121    }
122}