1use greentic_pack::builder::PackMeta;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct InfoReport {
6 pub info_schema_version: u32,
7 pub name: String,
8 pub version: String,
9 pub kind: Option<String>,
10 pub description: Option<String>,
11 pub authors: Vec<String>,
12 pub license: Option<String>,
13 pub homepage: Option<String>,
14 pub support: Option<String>,
15 pub vendor: Option<String>,
16 pub created_at_utc: String,
17 pub signature: SignatureInfo,
18 pub components: Vec<ComponentInfo>,
19 pub entry_flows: Vec<String>,
20 pub imports: Vec<ImportInfo>,
21 pub interfaces: Vec<String>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct SignatureInfo {
26 pub status: SignatureStatus,
27 pub key_id: Option<String>,
28}
29
30#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
31#[serde(rename_all = "lowercase")]
32pub enum SignatureStatus {
33 Signed,
34 Unsigned,
35 Invalid,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct ComponentInfo {
40 pub component_id: String,
41 pub version: String,
42 pub kind: Option<String>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ImportInfo {
47 pub pack_id: String,
48 pub version_req: String,
49}
50
51impl InfoReport {
52 pub fn from_pack_meta_and_signature(meta: &PackMeta, signature: SignatureInfo) -> Self {
53 Self {
54 info_schema_version: 1,
55 name: meta.name.clone(),
56 version: meta.version.to_string(),
57 kind: meta
58 .kind
59 .as_ref()
60 .map(|k| pack_kind_to_string(k).to_string()),
61 description: meta.description.clone(),
62 authors: meta.authors.clone(),
63 license: meta.license.clone(),
64 homepage: meta.homepage.clone(),
65 support: meta.support.clone(),
66 vendor: meta.vendor.clone(),
67 created_at_utc: meta.created_at_utc.clone(),
68 signature,
69 components: meta
70 .components
71 .iter()
72 .map(|c| ComponentInfo {
73 component_id: c.component_id.clone(),
74 version: c.version.clone(),
75 kind: c.kind.clone(),
76 })
77 .collect(),
78 entry_flows: meta.entry_flows.clone(),
79 imports: meta
80 .imports
81 .iter()
82 .map(|i| ImportInfo {
83 pack_id: i.pack_id.clone(),
84 version_req: i.version_req.clone(),
85 })
86 .collect(),
87 interfaces: meta
88 .interfaces
89 .iter()
90 .map(|b| format!("{}:{}@{}", b.package, b.world, b.version))
91 .collect(),
92 }
93 }
94}
95
96fn pack_kind_to_string(k: &greentic_pack::PackKind) -> &'static str {
97 use greentic_pack::PackKind as K;
98 match k {
102 K::Application => "application",
103 K::SourceProvider => "source-provider",
104 K::Scanner => "scanner",
105 K::Signing => "signing",
106 K::Attestation => "attestation",
107 K::PolicyEngine => "policy-engine",
108 K::OciProvider => "oci-provider",
109 K::BillingProvider => "billing-provider",
110 K::SearchProvider => "search-provider",
111 K::RecommendationProvider => "recommendation-provider",
112 K::DistributionBundle => "distribution-bundle",
113 K::RolloutStrategy => "rollout-strategy",
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn from_pack_meta_projects_all_fields() {
123 use greentic_pack::PackKind;
124 use greentic_pack::builder::{ComponentDescriptor, ImportRef, PackMeta};
125 use greentic_pack::repo::InterfaceBinding;
126
127 let meta = PackMeta {
128 pack_version: 1,
129 pack_id: "ex".into(),
130 version: semver::Version::parse("1.2.3").unwrap(),
131 name: "ex".into(),
132 kind: Some(PackKind::Application),
133 description: Some("demo".into()),
134 authors: vec!["Alice".into()],
135 license: Some("MIT".into()),
136 homepage: None,
137 support: None,
138 vendor: None,
139 imports: vec![ImportRef {
140 pack_id: "greentic/core".into(),
141 version_req: "^0.6.0".into(),
142 }],
143 entry_flows: vec!["flows/a.ygtc".into()],
144 created_at_utc: "2026-01-01T00:00:00Z".into(),
145 events: None,
146 repo: None,
147 messaging: None,
148 interfaces: vec![InterfaceBinding {
149 package: "greentic".into(),
150 world: "component".into(),
151 version: "0.6.0".into(),
152 note: None,
153 }],
154 annotations: Default::default(),
155 distribution: None,
156 components: vec![ComponentDescriptor {
157 component_id: "c".into(),
158 version: "0.1.0".into(),
159 digest: "sha256:x".into(),
160 artifact_path: "components/c.wasm".into(),
161 kind: Some("messaging".into()),
162 artifact_type: Some("component/wasm".into()),
163 tags: vec![],
164 platform: None,
165 entrypoint: None,
166 }],
167 };
168 let sig = SignatureInfo {
169 status: SignatureStatus::Unsigned,
170 key_id: None,
171 };
172
173 let report = InfoReport::from_pack_meta_and_signature(&meta, sig);
174
175 assert_eq!(report.info_schema_version, 1);
176 assert_eq!(report.name, "ex");
177 assert_eq!(report.version, "1.2.3");
178 assert_eq!(report.kind.as_deref(), Some("application"));
179 assert_eq!(report.authors, vec!["Alice".to_string()]);
180 assert_eq!(report.license.as_deref(), Some("MIT"));
181 assert_eq!(report.created_at_utc, "2026-01-01T00:00:00Z");
182 assert_eq!(report.components.len(), 1);
183 assert_eq!(report.components[0].component_id, "c");
184 assert_eq!(report.components[0].version, "0.1.0");
185 assert_eq!(report.components[0].kind.as_deref(), Some("messaging"));
186 assert_eq!(report.entry_flows, vec!["flows/a.ygtc".to_string()]);
187 assert_eq!(report.imports.len(), 1);
188 assert_eq!(report.imports[0].pack_id, "greentic/core");
189 assert_eq!(report.imports[0].version_req, "^0.6.0");
190 assert_eq!(
191 report.interfaces,
192 vec!["greentic:component@0.6.0".to_string()]
193 );
194 assert_eq!(report.signature.status, SignatureStatus::Unsigned);
195 }
196
197 #[test]
198 fn json_has_schema_version_one() {
199 let report = InfoReport {
200 info_schema_version: 1,
201 name: "x".into(),
202 version: "0.1.0".into(),
203 kind: None,
204 description: None,
205 authors: vec![],
206 license: None,
207 homepage: None,
208 support: None,
209 vendor: None,
210 created_at_utc: "2026-01-01T00:00:00Z".into(),
211 signature: SignatureInfo {
212 status: SignatureStatus::Unsigned,
213 key_id: None,
214 },
215 components: vec![],
216 entry_flows: vec![],
217 imports: vec![],
218 interfaces: vec![],
219 };
220 let v: serde_json::Value = serde_json::to_value(&report).unwrap();
221 assert_eq!(v["info_schema_version"], 1);
222 assert_eq!(v["signature"]["status"], "unsigned");
223 }
224}