greentic_component/store/
meta.rs1use anyhow::Result;
2use semver::Version;
3use serde::{Deserialize, Serialize};
4use sha2::{Digest, Sha256};
5#[cfg(feature = "abi")]
6use std::collections::BTreeSet;
7use wasm_metadata::Producers;
8#[cfg(feature = "abi")]
9use wit_parser::{InterfaceId, PackageName, Resolve, WorldId, WorldItem, WorldKey};
10
11use super::ComponentId;
12
13#[derive(Clone, Debug, Serialize, Deserialize)]
14pub struct MetaInfo {
15 pub id: ComponentId,
16 pub size: u64,
17 pub abi_version: String,
18 pub provider_name: Option<String>,
19 pub provider_version: Option<String>,
20 pub capabilities: Vec<String>,
21}
22
23pub async fn compute_id_and_meta(bytes: &[u8]) -> Result<(ComponentId, MetaInfo)> {
24 let mut hasher = Sha256::new();
25 hasher.update(bytes);
26 let digest = hex::encode(hasher.finalize());
27 let id = ComponentId(format!("sha256:{digest}"));
28
29 let size = bytes.len() as u64;
30
31 let mut abi_version = "greentic-abi-0".to_string();
32 let mut provider_name = None;
33 let mut provider_version = None;
34 let mut capabilities = Vec::new();
35
36 #[cfg(feature = "abi")]
37 if let Some(extracted) = extract_from_wit_metadata(bytes) {
38 abi_version = extracted.abi_version;
39 provider_name = extracted.provider_name;
40 provider_version = extracted.provider_version;
41 capabilities = extracted.capabilities;
42 }
43
44 if (provider_name.is_none() || provider_version.is_none())
46 && let Ok(Some(producers)) = Producers::from_wasm(bytes)
47 && let Some(processed_by) = producers.get("processed-by")
48 && let Some(version) = processed_by.get("greentic-interfaces")
49 {
50 provider_name.get_or_insert_with(|| "greentic-interfaces".to_string());
51 provider_version.get_or_insert_with(|| version.clone());
52 abi_version = format!("greentic-abi-{}", semver_major(version));
53 }
54
55 let meta = MetaInfo {
56 id: id.clone(),
57 size,
58 abi_version,
59 provider_name,
60 provider_version,
61 capabilities,
62 };
63
64 Ok((id, meta))
65}
66
67struct ExtractedMeta {
68 abi_version: String,
69 provider_name: Option<String>,
70 provider_version: Option<String>,
71 capabilities: Vec<String>,
72}
73
74#[cfg(feature = "abi")]
75fn extract_from_wit_metadata(bytes: &[u8]) -> Option<ExtractedMeta> {
76 let decoded = crate::wasm::decode_world(bytes).ok()?;
77 let resolve = decoded.resolve;
78 let world_id = decoded.world;
79
80 let world = &resolve.worlds[world_id];
81 let mut abi_version = "greentic-abi-0".to_string();
82 let mut provider_name = None;
83 let mut provider_version = None;
84
85 if let Some(pkg_id) = world.package {
86 let pkg = &resolve.packages[pkg_id];
87 provider_name = Some(pkg.name.name.clone());
88 if let Some(version) = &pkg.name.version {
89 provider_version = Some(version.to_string());
90 abi_version = format!("greentic-abi-{}", version.major);
91 }
92 }
93
94 let capabilities = collect_import_capabilities(&resolve, world_id);
95
96 Some(ExtractedMeta {
97 abi_version,
98 provider_name,
99 provider_version,
100 capabilities,
101 })
102}
103
104#[cfg(feature = "abi")]
105fn collect_import_capabilities(resolve: &Resolve, world_id: WorldId) -> Vec<String> {
106 let world = &resolve.worlds[world_id];
107 let mut caps = BTreeSet::new();
108
109 for (key, item) in &world.imports {
110 match item {
111 WorldItem::Interface { id, .. } => {
112 caps.insert(interface_label(resolve, *id, key));
113 }
114 WorldItem::Function(func) => {
115 caps.insert(format!("func:{}", func.name));
116 }
117 WorldItem::Type(_) => {}
118 }
119 }
120
121 caps.into_iter().collect()
122}
123
124#[cfg(feature = "abi")]
125fn interface_label(resolve: &Resolve, iface_id: InterfaceId, key: &WorldKey) -> String {
126 let iface = &resolve.interfaces[iface_id];
127 let name = iface
128 .name
129 .as_ref()
130 .map(|s| s.to_string())
131 .or_else(|| key_as_name(key))
132 .unwrap_or_else(|| "interface".to_string());
133
134 if let Some(pkg_id) = iface.package {
135 let pkg = &resolve.packages[pkg_id];
136 format_package(&pkg.name, Some(&name))
137 } else {
138 name
139 }
140}
141
142#[cfg(feature = "abi")]
143fn format_package(pkg: &PackageName, name: Option<&str>) -> String {
144 match (&pkg.name, &pkg.namespace, &pkg.version) {
145 (pkg_name, ns, Some(version)) => match name {
146 Some(name) => format!("{ns}:{pkg_name}/{name}@{version}"),
147 None => format!("{ns}:{pkg_name}@{version}"),
148 },
149 (pkg_name, ns, None) => match name {
150 Some(name) => format!("{ns}:{pkg_name}/{name}"),
151 None => format!("{ns}:{pkg_name}"),
152 },
153 }
154}
155
156#[cfg(feature = "abi")]
157fn key_as_name(key: &WorldKey) -> Option<String> {
158 match key {
159 WorldKey::Name(name) => Some(name.to_string()),
160 WorldKey::Interface(_) => None,
161 }
162}
163
164fn semver_major(version: &str) -> u64 {
165 Version::parse(version).map(|v| v.major).unwrap_or(0)
166}