1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3use std::path::{Path, PathBuf};
4
5pub const COMPONENT_MANIFEST_SCHEMA: u32 = 2;
6pub const COMPONENT_ABI_VERSION: u32 = 1;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(deny_unknown_fields)]
15pub struct ComponentProvidedContract {
16 pub contract: String,
17 pub interface_version: u32,
18 pub contract_hash: String,
19 pub implementation: String,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum ComponentDeliveryMode {
29 Download,
30 Bundled,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(deny_unknown_fields)]
35pub struct ComponentIdentity {
36 pub crate_name: String,
37 pub component: String,
38 pub version: String,
39 pub target: String,
40 pub feature_hash: String,
41 pub contract_hash: String,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(deny_unknown_fields)]
51pub struct ComponentLibrary {
52 pub file: String,
53 pub sha256: String,
54 pub size: u64,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58#[serde(deny_unknown_fields)]
59pub struct ComponentManifest {
60 pub schema_version: u32,
61 pub abi_version: u32,
62 pub identity: ComponentIdentity,
63 pub provides: Vec<ComponentProvidedContract>,
65 pub features: Vec<String>,
66 pub default_features: bool,
67 pub library: ComponentLibrary,
68}
69
70impl ComponentManifest {
71 pub fn canonical_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
72 canonical_json(self)
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(deny_unknown_fields)]
78pub struct ComponentSignature {
79 pub algorithm: String,
80 pub key_id: String,
81 pub signature: String,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85#[serde(deny_unknown_fields)]
86pub struct ComponentArtifactRecord {
87 pub manifest: ComponentManifest,
88 pub manifest_sha256: String,
89 pub archive: String,
90 pub archive_sha256: String,
91 pub archive_size: u64,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub signature: Option<ComponentSignature>,
94}
95
96impl ComponentArtifactRecord {
97 #[must_use]
98 pub fn record_path(&self, output_dir: &Path) -> PathBuf {
99 output_dir.join(format!("{}.record.json", self.archive))
100 }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104#[serde(deny_unknown_fields)]
105pub struct ComponentLock {
106 pub schema_version: u32,
107 pub public_keys: BTreeMap<String, String>,
108 pub artifacts: Vec<ComponentLockEntry>,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112#[serde(deny_unknown_fields)]
113pub struct ComponentLockEntry {
114 pub identity: ComponentIdentity,
115 pub provides: Vec<ComponentProvidedContract>,
118 pub mode: ComponentDeliveryMode,
122 pub url: String,
123 pub sha256: String,
124 pub size: u64,
125 pub manifest_sha256: String,
126 pub key_id: String,
127}
128
129pub fn canonical_json<T: Serialize>(value: &T) -> Result<Vec<u8>, serde_json::Error> {
130 let mut bytes = serde_json::to_vec(value)?;
131 bytes.push(b'\n');
132 Ok(bytes)
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn canonical_json_is_compact_and_newline_terminated() {
141 let identity = ComponentIdentity {
142 crate_name: "demo-core".into(),
143 component: "fast".into(),
144 version: "1.0.0".into(),
145 target: "aarch64-apple-darwin".into(),
146 feature_hash: "11".repeat(32),
147 contract_hash: "22".repeat(32),
148 };
149 let bytes = canonical_json(&identity).unwrap();
150 assert!(bytes.ends_with(b"\n"));
151 assert!(!bytes.contains(&b' '));
152 }
153
154 #[test]
155 fn lock_rejects_unknown_fields() {
156 let json = r#"{"schema_version":1,"public_keys":{},"artifacts":[],"extra":true}"#;
157 assert!(serde_json::from_str::<ComponentLock>(json).is_err());
158 }
159}