greentic_distributor_client/
wit_client.rs1use crate::{ArtifactLocation, CacheInfo, SignatureSummary};
2use crate::{ComponentDigest, ComponentStatus};
3use crate::{
4 DistributorClient, DistributorEnvironmentId, DistributorError, PackStatusResponse,
5 ResolveComponentRequest, ResolveComponentResponse, SecretFormat, SecretKey, SecretRequirement,
6 SecretScope, TenantCtx,
7};
8use anyhow::anyhow;
9use async_trait::async_trait;
10use greentic_interfaces_guest::distributor_api as wit;
11#[cfg(target_arch = "wasm32")]
12use greentic_interfaces_guest::distributor_api::DistributorApiImports;
13use serde_json::Value;
14
15#[async_trait]
16pub trait DistributorApiBindings: Send + Sync {
17 async fn resolve_component(
18 &self,
19 req: wit::ResolveComponentRequest,
20 ) -> Result<wit::ResolveComponentResponse, anyhow::Error>;
21
22 async fn get_pack_status(
23 &self,
24 tenant_id: &str,
25 environment_id: &str,
26 pack_id: &str,
27 ) -> Result<String, anyhow::Error>;
28
29 async fn get_pack_status_v2(
30 &self,
31 tenant_id: &str,
32 environment_id: &str,
33 pack_id: &str,
34 ) -> Result<wit::PackStatusResponse, anyhow::Error>;
35
36 async fn warm_pack(
37 &self,
38 tenant_id: &str,
39 environment_id: &str,
40 pack_id: &str,
41 ) -> Result<(), anyhow::Error>;
42}
43
44#[derive(Clone)]
45pub struct WitDistributorClient<B: DistributorApiBindings> {
46 bindings: B,
47}
48
49impl<B: DistributorApiBindings> WitDistributorClient<B> {
50 pub fn new(bindings: B) -> Self {
51 Self { bindings }
52 }
53}
54
55#[derive(Clone, Default)]
57pub struct GeneratedDistributorApiBindings;
58
59#[async_trait]
60impl DistributorApiBindings for GeneratedDistributorApiBindings {
61 async fn resolve_component(
62 &self,
63 req: wit::ResolveComponentRequest,
64 ) -> Result<wit::ResolveComponentResponse, anyhow::Error> {
65 #[cfg(target_arch = "wasm32")]
66 {
67 let api = DistributorApiImports::new();
68 Ok(api.resolve_component(&req))
69 }
70 #[cfg(not(target_arch = "wasm32"))]
71 {
72 let _ = req;
73 Err(anyhow!(
74 "distributor-api imports are only available on wasm32 targets"
75 ))
76 }
77 }
78
79 async fn get_pack_status(
80 &self,
81 tenant_id: &str,
82 environment_id: &str,
83 pack_id: &str,
84 ) -> Result<String, anyhow::Error> {
85 #[cfg(target_arch = "wasm32")]
86 {
87 let api = DistributorApiImports::new();
88 Ok(api.get_pack_status(
89 &tenant_id.to_string(),
90 &environment_id.to_string(),
91 &pack_id.to_string(),
92 ))
93 }
94 #[cfg(not(target_arch = "wasm32"))]
95 {
96 let _ = (tenant_id, environment_id, pack_id);
97 Err(anyhow!(
98 "distributor-api imports are only available on wasm32 targets"
99 ))
100 }
101 }
102
103 async fn get_pack_status_v2(
104 &self,
105 tenant_id: &str,
106 environment_id: &str,
107 pack_id: &str,
108 ) -> Result<wit::PackStatusResponse, anyhow::Error> {
109 #[cfg(target_arch = "wasm32")]
110 {
111 let api = DistributorApiImports::new();
112 Ok(api.get_pack_status_v2(
113 &tenant_id.to_string(),
114 &environment_id.to_string(),
115 &pack_id.to_string(),
116 ))
117 }
118 #[cfg(not(target_arch = "wasm32"))]
119 {
120 let _ = (tenant_id, environment_id, pack_id);
121 Err(anyhow!(
122 "distributor-api imports are only available on wasm32 targets"
123 ))
124 }
125 }
126
127 async fn warm_pack(
128 &self,
129 tenant_id: &str,
130 environment_id: &str,
131 pack_id: &str,
132 ) -> Result<(), anyhow::Error> {
133 #[cfg(target_arch = "wasm32")]
134 {
135 let api = DistributorApiImports::new();
136 api.warm_pack(
137 &tenant_id.to_string(),
138 &environment_id.to_string(),
139 &pack_id.to_string(),
140 );
141 Ok(())
142 }
143 #[cfg(not(target_arch = "wasm32"))]
144 {
145 let _ = (tenant_id, environment_id, pack_id);
146 Err(anyhow!(
147 "distributor-api imports are only available on wasm32 targets"
148 ))
149 }
150 }
151}
152
153#[async_trait]
154impl<B> DistributorClient for WitDistributorClient<B>
155where
156 B: DistributorApiBindings,
157{
158 async fn resolve_component(
159 &self,
160 req: ResolveComponentRequest,
161 ) -> Result<ResolveComponentResponse, DistributorError> {
162 let wit_req = to_wit_request(req)?;
163 let resp = self
164 .bindings
165 .resolve_component(wit_req)
166 .await
167 .map_err(|e| {
168 DistributorError::Wit(e.to_string())
171 })?;
172 from_wit_response(resp)
173 }
174
175 async fn get_pack_status(
176 &self,
177 tenant: &TenantCtx,
178 env: &DistributorEnvironmentId,
179 pack_id: &str,
180 ) -> Result<Value, DistributorError> {
181 let payload = self
182 .bindings
183 .get_pack_status(tenant.tenant_id.as_str(), env.as_str(), pack_id)
184 .await
185 .map_err(|e| DistributorError::Wit(e.to_string()))?;
186 serde_json::from_str(&payload).map_err(DistributorError::Serde)
187 }
188
189 async fn get_pack_status_v2(
190 &self,
191 tenant: &TenantCtx,
192 env: &DistributorEnvironmentId,
193 pack_id: &str,
194 ) -> Result<PackStatusResponse, DistributorError> {
195 let payload = self
196 .bindings
197 .get_pack_status_v2(tenant.tenant_id.as_str(), env.as_str(), pack_id)
198 .await
199 .map_err(|e| DistributorError::Wit(e.to_string()))?;
200 from_wit_pack_status(payload)
201 }
202
203 async fn warm_pack(
204 &self,
205 tenant: &TenantCtx,
206 env: &DistributorEnvironmentId,
207 pack_id: &str,
208 ) -> Result<(), DistributorError> {
209 self.bindings
210 .warm_pack(tenant.tenant_id.as_str(), env.as_str(), pack_id)
211 .await
212 .map_err(|e| DistributorError::Wit(e.to_string()))
213 }
214}
215
216fn to_wit_request(
217 req: ResolveComponentRequest,
218) -> Result<wit::ResolveComponentRequest, DistributorError> {
219 Ok(wit::ResolveComponentRequest {
220 tenant_id: req.tenant.tenant_id.to_string(),
221 environment_id: req.environment_id.as_str().to_string(),
222 pack_id: req.pack_id,
223 component_id: req.component_id,
224 version: req.version,
225 extra: serde_json::to_string(&req.extra)?,
226 })
227}
228
229fn from_wit_response(
230 resp: wit::ResolveComponentResponse,
231) -> Result<ResolveComponentResponse, DistributorError> {
232 let status = match resp.component_status {
233 wit::ComponentStatus::Pending => ComponentStatus::Pending,
234 wit::ComponentStatus::Ready => ComponentStatus::Ready,
235 wit::ComponentStatus::Failed => ComponentStatus::Failed {
236 reason: "failed".to_string(),
237 },
238 };
239 let artifact = match resp.artifact_location.kind.as_str() {
240 "file" | "file_path" => ArtifactLocation::FilePath {
241 path: resp.artifact_location.value,
242 },
243 "oci" | "oci_reference" => ArtifactLocation::OciReference {
244 reference: resp.artifact_location.value,
245 },
246 _ => ArtifactLocation::DistributorInternal {
247 handle: resp.artifact_location.value,
248 },
249 };
250 let signature = SignatureSummary {
251 verified: resp.signature_summary.verified,
252 signer: resp.signature_summary.signer,
253 extra: serde_json::from_str(&resp.signature_summary.extra)?,
254 };
255 let cache = CacheInfo {
256 size_bytes: resp.cache_info.size_bytes,
257 last_used_utc: resp.cache_info.last_used_utc,
258 last_refreshed_utc: resp.cache_info.last_refreshed_utc,
259 };
260 Ok(ResolveComponentResponse {
261 status,
262 digest: ComponentDigest(resp.digest),
263 artifact,
264 signature,
265 cache,
266 secret_requirements: from_wit_secret_requirements(resp.secret_requirements)?,
267 })
268}
269
270fn from_wit_pack_status(
271 resp: wit::PackStatusResponse,
272) -> Result<PackStatusResponse, DistributorError> {
273 Ok(PackStatusResponse {
274 status: resp.status,
275 secret_requirements: from_wit_secret_requirements(resp.secret_requirements)?,
276 extra: serde_json::from_str(&resp.extra)?,
277 })
278}
279
280fn from_wit_secret_requirements(
281 reqs: impl WitSecretRequirementsExt,
282) -> Result<Option<Vec<SecretRequirement>>, DistributorError> {
283 reqs.into_optional_vec()
284 .map(|requirements| {
285 requirements
286 .into_iter()
287 .map(from_wit_secret_requirement)
288 .collect()
289 })
290 .transpose()
291}
292
293trait WitSecretRequirementsExt {
294 fn into_optional_vec(self) -> Option<Vec<wit::SecretRequirement>>;
295}
296
297impl WitSecretRequirementsExt for Vec<wit::SecretRequirement> {
298 fn into_optional_vec(self) -> Option<Vec<wit::SecretRequirement>> {
299 Some(self)
300 }
301}
302
303impl WitSecretRequirementsExt for Option<Vec<wit::SecretRequirement>> {
304 fn into_optional_vec(self) -> Option<Vec<wit::SecretRequirement>> {
305 self
306 }
307}
308
309fn from_wit_secret_requirement(
310 req: wit::SecretRequirement,
311) -> Result<SecretRequirement, DistributorError> {
312 let key = SecretKey::parse(&req.key).map_err(|e| {
313 DistributorError::InvalidResponse(format!("invalid secret key `{}`: {e}", req.key))
314 })?;
315 let scope = req.scope.map(|scope| SecretScope {
316 env: scope.env,
317 tenant: scope.tenant,
318 team: scope.team,
319 });
320 let format = req.format.map(|format| match format as u8 {
321 0 => SecretFormat::Bytes,
322 1 => SecretFormat::Text,
323 2 => SecretFormat::Json,
324 _ => unreachable!("unexpected WIT secret format discriminant"),
325 });
326 let schema = match req.schema {
327 Some(schema) => Some(serde_json::from_str(&schema)?),
328 None => None,
329 };
330 let mut requirement = SecretRequirement::default();
331 requirement.key = key;
332 requirement.required = req.required;
333 requirement.description = req.description;
334 requirement.scope = scope;
335 requirement.format = format;
336 requirement.schema = schema;
337 requirement.examples = req.examples;
338 Ok(requirement)
339}