greentic_interfaces/
mappers.rs

1//! Conversion helpers between generated WIT bindings and `greentic-types`.
2
3use std::convert::{TryFrom, TryInto};
4
5use greentic_types as types;
6use semver::Version;
7use time::OffsetDateTime;
8
9use crate::bindings;
10
11type MapperResult<T> = Result<T, types::GreenticError>;
12
13fn invalid_input(msg: impl Into<String>) -> types::GreenticError {
14    types::GreenticError::new(types::ErrorCode::InvalidInput, msg)
15}
16
17fn i128_to_i64(value: i128) -> MapperResult<i64> {
18    value
19        .try_into()
20        .map_err(|_| invalid_input("numeric overflow converting deadline"))
21}
22
23fn timestamp_ms_to_offset(ms: i64) -> MapperResult<OffsetDateTime> {
24    let nanos = (ms as i128)
25        .checked_mul(1_000_000)
26        .ok_or_else(|| invalid_input("timestamp overflow"))?;
27    OffsetDateTime::from_unix_timestamp_nanos(nanos)
28        .map_err(|err| invalid_input(format!("invalid timestamp: {err}")))
29}
30
31fn offset_to_timestamp_ms(dt: &OffsetDateTime) -> MapperResult<i64> {
32    let nanos = dt.unix_timestamp_nanos();
33    let ms = nanos
34        .checked_div(1_000_000)
35        .ok_or_else(|| invalid_input("timestamp division overflow"))?;
36    ms.try_into()
37        .map_err(|_| invalid_input("timestamp overflow converting to milliseconds"))
38}
39
40type WitTenantCtx = bindings::greentic::interfaces_types::types::TenantCtx;
41type WitImpersonation = bindings::greentic::interfaces_types::types::Impersonation;
42type WitSessionCursor = bindings::greentic::interfaces_types::types::SessionCursor;
43type WitOutcome = bindings::greentic::interfaces_types::types::Outcome;
44type WitOutcomePending = bindings::greentic::interfaces_types::types::OutcomePending;
45type WitOutcomeError = bindings::greentic::interfaces_types::types::OutcomeError;
46type WitErrorCode = bindings::greentic::interfaces_types::types::ErrorCode;
47type WitAllowList = bindings::greentic::interfaces_types::types::AllowList;
48type WitProtocol = bindings::greentic::interfaces_types::types::Protocol;
49type WitNetworkPolicy = bindings::greentic::interfaces_types::types::NetworkPolicy;
50type WitSpanContext = bindings::greentic::interfaces_types::types::SpanContext;
51type WitPackRef = bindings::greentic::interfaces_types::types::PackRef;
52type WitSignature = bindings::greentic::interfaces_types::types::Signature;
53type WitSignatureAlgorithm = bindings::greentic::interfaces_types::types::SignatureAlgorithm;
54
55impl TryFrom<WitImpersonation> for types::Impersonation {
56    type Error = types::GreenticError;
57
58    fn try_from(value: WitImpersonation) -> MapperResult<Self> {
59        Ok(Self {
60            actor_id: value.actor_id.try_into()?,
61            reason: value.reason,
62        })
63    }
64}
65
66impl From<types::Impersonation> for WitImpersonation {
67    fn from(value: types::Impersonation) -> Self {
68        Self {
69            actor_id: value.actor_id.into(),
70            reason: value.reason,
71        }
72    }
73}
74
75impl TryFrom<WitTenantCtx> for types::TenantCtx {
76    type Error = types::GreenticError;
77
78    fn try_from(value: WitTenantCtx) -> MapperResult<Self> {
79        let WitTenantCtx {
80            env,
81            tenant,
82            tenant_id,
83            team,
84            team_id,
85            user,
86            user_id,
87            trace_id,
88            correlation_id,
89            session_id,
90            flow_id,
91            node_id,
92            provider_id,
93            deadline_ms,
94            attempt,
95            idempotency_key,
96            impersonation,
97        } = value;
98
99        let deadline =
100            deadline_ms.map(|ms| types::InvocationDeadline::from_unix_millis(ms as i128));
101
102        let env = env.try_into()?;
103        let tenant = tenant.try_into()?;
104        let tenant_id = tenant_id.try_into()?;
105        let team = team.map(|item| item.try_into()).transpose()?;
106        let team_id = team_id.map(|item| item.try_into()).transpose()?;
107        let user = user.map(|item| item.try_into()).transpose()?;
108        let user_id = user_id.map(|item| item.try_into()).transpose()?;
109        let impersonation = impersonation
110            .map(types::Impersonation::try_from)
111            .transpose()?;
112
113        Ok(Self {
114            env,
115            tenant,
116            tenant_id,
117            team,
118            team_id,
119            user,
120            user_id,
121            session_id,
122            flow_id,
123            node_id,
124            provider_id,
125            trace_id,
126            correlation_id,
127            deadline,
128            attempt,
129            idempotency_key,
130            impersonation,
131        })
132    }
133}
134
135impl TryFrom<types::TenantCtx> for WitTenantCtx {
136    type Error = types::GreenticError;
137
138    fn try_from(value: types::TenantCtx) -> MapperResult<Self> {
139        let deadline_ms = match value.deadline {
140            Some(deadline) => Some(i128_to_i64(deadline.unix_millis())?),
141            None => None,
142        };
143
144        Ok(Self {
145            env: value.env.into(),
146            tenant: value.tenant.into(),
147            tenant_id: value.tenant_id.into(),
148            team: value.team.map(Into::into),
149            team_id: value.team_id.map(Into::into),
150            user: value.user.map(Into::into),
151            user_id: value.user_id.map(Into::into),
152            session_id: value.session_id,
153            flow_id: value.flow_id,
154            node_id: value.node_id,
155            provider_id: value.provider_id,
156            trace_id: value.trace_id,
157            correlation_id: value.correlation_id,
158            deadline_ms,
159            attempt: value.attempt,
160            idempotency_key: value.idempotency_key,
161            impersonation: value.impersonation.map(Into::into),
162        })
163    }
164}
165
166impl From<WitSessionCursor> for types::SessionCursor {
167    fn from(value: WitSessionCursor) -> Self {
168        Self {
169            node_pointer: value.node_pointer,
170            wait_reason: value.wait_reason,
171            outbox_marker: value.outbox_marker,
172        }
173    }
174}
175
176impl From<types::SessionCursor> for WitSessionCursor {
177    fn from(value: types::SessionCursor) -> Self {
178        Self {
179            node_pointer: value.node_pointer,
180            wait_reason: value.wait_reason,
181            outbox_marker: value.outbox_marker,
182        }
183    }
184}
185
186impl From<WitErrorCode> for types::ErrorCode {
187    fn from(value: WitErrorCode) -> Self {
188        match value {
189            WitErrorCode::Unknown => Self::Unknown,
190            WitErrorCode::InvalidInput => Self::InvalidInput,
191            WitErrorCode::NotFound => Self::NotFound,
192            WitErrorCode::Conflict => Self::Conflict,
193            WitErrorCode::Timeout => Self::Timeout,
194            WitErrorCode::Unauthenticated => Self::Unauthenticated,
195            WitErrorCode::PermissionDenied => Self::PermissionDenied,
196            WitErrorCode::RateLimited => Self::RateLimited,
197            WitErrorCode::Unavailable => Self::Unavailable,
198            WitErrorCode::Internal => Self::Internal,
199        }
200    }
201}
202
203impl From<types::ErrorCode> for WitErrorCode {
204    fn from(value: types::ErrorCode) -> Self {
205        match value {
206            types::ErrorCode::Unknown => Self::Unknown,
207            types::ErrorCode::InvalidInput => Self::InvalidInput,
208            types::ErrorCode::NotFound => Self::NotFound,
209            types::ErrorCode::Conflict => Self::Conflict,
210            types::ErrorCode::Timeout => Self::Timeout,
211            types::ErrorCode::Unauthenticated => Self::Unauthenticated,
212            types::ErrorCode::PermissionDenied => Self::PermissionDenied,
213            types::ErrorCode::RateLimited => Self::RateLimited,
214            types::ErrorCode::Unavailable => Self::Unavailable,
215            types::ErrorCode::Internal => Self::Internal,
216        }
217    }
218}
219
220impl From<WitOutcome> for types::Outcome<String> {
221    fn from(value: WitOutcome) -> Self {
222        match value {
223            WitOutcome::Done(val) => Self::Done(val),
224            WitOutcome::Pending(payload) => Self::Pending {
225                reason: payload.reason,
226                expected_input: payload.expected_input,
227            },
228            WitOutcome::Error(payload) => Self::Error {
229                code: payload.code.into(),
230                message: payload.message,
231            },
232        }
233    }
234}
235
236impl From<types::Outcome<String>> for WitOutcome {
237    fn from(value: types::Outcome<String>) -> Self {
238        match value {
239            types::Outcome::Done(val) => Self::Done(val),
240            types::Outcome::Pending {
241                reason,
242                expected_input,
243            } => Self::Pending(WitOutcomePending {
244                reason,
245                expected_input,
246            }),
247            types::Outcome::Error { code, message } => Self::Error(WitOutcomeError {
248                code: code.into(),
249                message,
250            }),
251        }
252    }
253}
254
255impl From<WitProtocol> for types::Protocol {
256    fn from(value: WitProtocol) -> Self {
257        match value {
258            WitProtocol::Http => Self::Http,
259            WitProtocol::Https => Self::Https,
260            WitProtocol::Tcp => Self::Tcp,
261            WitProtocol::Udp => Self::Udp,
262            WitProtocol::Grpc => Self::Grpc,
263            WitProtocol::Custom(v) => Self::Custom(v),
264        }
265    }
266}
267
268impl From<types::Protocol> for WitProtocol {
269    fn from(value: types::Protocol) -> Self {
270        match value {
271            types::Protocol::Http => Self::Http,
272            types::Protocol::Https => Self::Https,
273            types::Protocol::Tcp => Self::Tcp,
274            types::Protocol::Udp => Self::Udp,
275            types::Protocol::Grpc => Self::Grpc,
276            types::Protocol::Custom(v) => Self::Custom(v),
277        }
278    }
279}
280
281impl From<WitAllowList> for types::AllowList {
282    fn from(value: WitAllowList) -> Self {
283        Self {
284            domains: value.domains,
285            ports: value.ports,
286            protocols: value.protocols.into_iter().map(Into::into).collect(),
287        }
288    }
289}
290
291impl From<types::AllowList> for WitAllowList {
292    fn from(value: types::AllowList) -> Self {
293        Self {
294            domains: value.domains,
295            ports: value.ports,
296            protocols: value.protocols.into_iter().map(Into::into).collect(),
297        }
298    }
299}
300
301impl From<WitNetworkPolicy> for types::NetworkPolicy {
302    fn from(value: WitNetworkPolicy) -> Self {
303        Self {
304            egress: value.egress.into(),
305            deny_on_miss: value.deny_on_miss,
306        }
307    }
308}
309
310impl From<types::NetworkPolicy> for WitNetworkPolicy {
311    fn from(value: types::NetworkPolicy) -> Self {
312        Self {
313            egress: value.egress.into(),
314            deny_on_miss: value.deny_on_miss,
315        }
316    }
317}
318
319impl TryFrom<WitSpanContext> for types::SpanContext {
320    type Error = types::GreenticError;
321
322    fn try_from(value: WitSpanContext) -> MapperResult<Self> {
323        let WitSpanContext {
324            tenant,
325            session_id,
326            flow_id,
327            node_id,
328            provider,
329            start_ms,
330            end_ms,
331        } = value;
332
333        let start = start_ms.map(timestamp_ms_to_offset).transpose()?;
334        let end = end_ms.map(timestamp_ms_to_offset).transpose()?;
335        let tenant = tenant.try_into()?;
336
337        Ok(Self {
338            tenant,
339            session_id: session_id.map(types::SessionKey::from),
340            flow_id,
341            node_id,
342            provider,
343            start,
344            end,
345        })
346    }
347}
348
349impl TryFrom<types::SpanContext> for WitSpanContext {
350    type Error = types::GreenticError;
351
352    fn try_from(value: types::SpanContext) -> MapperResult<Self> {
353        let start_ms = value
354            .start
355            .as_ref()
356            .map(offset_to_timestamp_ms)
357            .transpose()?;
358        let end_ms = value.end.as_ref().map(offset_to_timestamp_ms).transpose()?;
359
360        Ok(Self {
361            tenant: value.tenant.into(),
362            session_id: value.session_id.map(|key| key.to_string()),
363            flow_id: value.flow_id,
364            node_id: value.node_id,
365            provider: value.provider,
366            start_ms,
367            end_ms,
368        })
369    }
370}
371
372impl From<WitSignatureAlgorithm> for types::SignatureAlgorithm {
373    fn from(value: WitSignatureAlgorithm) -> Self {
374        match value {
375            WitSignatureAlgorithm::Ed25519 => Self::Ed25519,
376            WitSignatureAlgorithm::Other(v) => Self::Other(v),
377        }
378    }
379}
380
381impl From<types::SignatureAlgorithm> for WitSignatureAlgorithm {
382    fn from(value: types::SignatureAlgorithm) -> Self {
383        match value {
384            types::SignatureAlgorithm::Ed25519 => Self::Ed25519,
385            types::SignatureAlgorithm::Other(v) => Self::Other(v),
386        }
387    }
388}
389
390impl From<WitSignature> for types::Signature {
391    fn from(value: WitSignature) -> Self {
392        Self {
393            key_id: value.key_id,
394            algorithm: value.algorithm.into(),
395            signature: value.signature,
396        }
397    }
398}
399
400impl From<types::Signature> for WitSignature {
401    fn from(value: types::Signature) -> Self {
402        Self {
403            key_id: value.key_id,
404            algorithm: value.algorithm.into(),
405            signature: value.signature,
406        }
407    }
408}
409
410impl TryFrom<WitPackRef> for types::PackRef {
411    type Error = types::GreenticError;
412
413    fn try_from(value: WitPackRef) -> MapperResult<Self> {
414        let version = Version::parse(&value.version)
415            .map_err(|err| invalid_input(format!("invalid version: {err}")))?;
416        Ok(Self {
417            oci_url: value.oci_url,
418            version,
419            digest: value.digest,
420            signatures: value.signatures.into_iter().map(Into::into).collect(),
421        })
422    }
423}
424
425impl From<types::PackRef> for WitPackRef {
426    fn from(value: types::PackRef) -> Self {
427        Self {
428            oci_url: value.oci_url,
429            version: value.version.to_string(),
430            digest: value.digest,
431            signatures: value.signatures.into_iter().map(Into::into).collect(),
432        }
433    }
434}
435
436#[cfg(test)]
437mod tests {
438    use super::*;
439    use std::convert::TryFrom;
440
441    fn fixture_id<T>(value: &str) -> T
442    where
443        T: TryFrom<String, Error = types::GreenticError>,
444    {
445        T::try_from(value.to_owned())
446            .unwrap_or_else(|err| panic!("invalid fixture identifier '{value}': {err}"))
447    }
448
449    fn sample_tenant_ctx() -> types::TenantCtx {
450        types::TenantCtx {
451            env: fixture_id("prod"),
452            tenant: fixture_id("tenant-1"),
453            tenant_id: fixture_id("tenant-1"),
454            team: Some(fixture_id("team-42")),
455            team_id: Some(fixture_id("team-42")),
456            user: Some(fixture_id("user-7")),
457            user_id: Some(fixture_id("user-7")),
458            session_id: Some("sess-42".into()),
459            flow_id: Some("flow-42".into()),
460            node_id: Some("node-42".into()),
461            provider_id: Some("provider-42".into()),
462            trace_id: Some("trace".into()),
463            correlation_id: Some("corr".into()),
464            deadline: Some(types::InvocationDeadline::from_unix_millis(
465                1_700_000_000_000,
466            )),
467            attempt: 2,
468            idempotency_key: Some("idem".into()),
469            impersonation: Some(types::Impersonation {
470                actor_id: fixture_id("actor"),
471                reason: Some("maintenance".into()),
472            }),
473        }
474    }
475
476    #[test]
477    fn tenant_ctx_roundtrip() {
478        let ctx = sample_tenant_ctx();
479        let wit = match WitTenantCtx::try_from(ctx.clone()) {
480            Ok(value) => value,
481            Err(err) => panic!("failed to map to wit: {err}"),
482        };
483        let back = match types::TenantCtx::try_from(wit) {
484            Ok(value) => value,
485            Err(err) => panic!("failed to map from wit: {err}"),
486        };
487        assert_eq!(back.env.as_str(), ctx.env.as_str());
488        assert_eq!(back.tenant.as_str(), ctx.tenant.as_str());
489        assert!(back.impersonation.is_some());
490        assert!(ctx.impersonation.is_some());
491        assert_eq!(
492            back.impersonation.as_ref().map(|imp| imp.actor_id.as_str()),
493            ctx.impersonation.as_ref().map(|imp| imp.actor_id.as_str())
494        );
495        assert_eq!(back.session_id, ctx.session_id);
496        assert_eq!(back.flow_id, ctx.flow_id);
497        assert_eq!(back.node_id, ctx.node_id);
498        assert_eq!(back.provider_id, ctx.provider_id);
499    }
500
501    #[test]
502    fn outcome_roundtrip() {
503        let pending = types::Outcome::Pending {
504            reason: "waiting".into(),
505            expected_input: Some(vec!["input".into()]),
506        };
507        let wit = WitOutcome::from(pending.clone());
508        let back = types::Outcome::from(wit);
509        match back {
510            types::Outcome::Pending { reason, .. } => {
511                assert_eq!(reason, "waiting");
512            }
513            _ => panic!("expected pending"),
514        }
515    }
516}