Skip to main content

greentic_interfaces/
mappers.rs

1//! Conversion helpers between generated WIT bindings and `greentic-types`.
2
3use std::collections::BTreeMap;
4use std::convert::{TryFrom, TryInto};
5
6use greentic_types as types;
7use semver::Version;
8use time::OffsetDateTime;
9
10use crate::bindings;
11
12type MapperResult<T> = Result<T, types::GreenticError>;
13
14fn invalid_input(msg: impl Into<String>) -> types::GreenticError {
15    types::GreenticError::new(types::ErrorCode::InvalidInput, msg)
16}
17
18fn i128_to_i64(value: i128) -> MapperResult<i64> {
19    value
20        .try_into()
21        .map_err(|_| invalid_input("numeric overflow converting deadline"))
22}
23
24fn timestamp_ms_to_offset(ms: i64) -> MapperResult<OffsetDateTime> {
25    let nanos = (ms as i128)
26        .checked_mul(1_000_000)
27        .ok_or_else(|| invalid_input("timestamp overflow"))?;
28    OffsetDateTime::from_unix_timestamp_nanos(nanos)
29        .map_err(|err| invalid_input(format!("invalid timestamp: {err}")))
30}
31
32fn offset_to_timestamp_ms(dt: &OffsetDateTime) -> MapperResult<i64> {
33    let nanos = dt.unix_timestamp_nanos();
34    let ms = nanos
35        .checked_div(1_000_000)
36        .ok_or_else(|| invalid_input("timestamp division overflow"))?;
37    ms.try_into()
38        .map_err(|_| invalid_input("timestamp overflow converting to milliseconds"))
39}
40
41type WitTenantCtx = bindings::greentic::interfaces_types::types::TenantCtx;
42type WitImpersonation = bindings::greentic::interfaces_types::types::Impersonation;
43type WitSessionCursor = bindings::greentic::interfaces_types::types::SessionCursor;
44type WitOutcome = bindings::greentic::interfaces_types::types::Outcome;
45type WitOutcomePending = bindings::greentic::interfaces_types::types::OutcomePending;
46type WitOutcomeError = bindings::greentic::interfaces_types::types::OutcomeError;
47type WitErrorCode = bindings::greentic::interfaces_types::types::ErrorCode;
48type WitAllowList = bindings::greentic::interfaces_types::types::AllowList;
49type WitProtocol = bindings::greentic::interfaces_types::types::Protocol;
50type WitNetworkPolicy = bindings::greentic::interfaces_types::types::NetworkPolicy;
51type WitSpanContext = bindings::greentic::interfaces_types::types::SpanContext;
52type WitPackRef = bindings::greentic::interfaces_types::types::PackRef;
53type WitSignature = bindings::greentic::interfaces_types::types::Signature;
54type WitSignatureAlgorithm = bindings::greentic::interfaces_types::types::SignatureAlgorithm;
55type WitCommonFlowKind =
56    bindings::greentic_common_types_0_1_0_common::exports::greentic::common_types::types::FlowKind;
57type WitCommonTenantCtx =
58    bindings::greentic_common_types_0_1_0_common::exports::greentic::common_types::types::TenantCtx;
59type WitOutcomeStatus = bindings::greentic_common_types_0_1_0_common::exports::greentic::common_types::types::OutcomeStatus;
60type WitComponentOutcome = bindings::greentic_common_types_0_1_0_common::exports::greentic::common_types::types::ComponentOutcome;
61type WitPackKind = bindings::greentic_pack_export_v1_0_1_0_pack_host::exports::greentic::pack_export_v1::pack_api::PackKind;
62type WitPackDescriptor =
63    bindings::greentic_pack_export_v1_0_1_0_pack_host::exports::greentic::pack_export_v1::pack_api::PackDescriptor;
64type WitFlowDescriptor =
65    bindings::greentic_pack_export_v1_0_1_0_pack_host::exports::greentic::pack_export_v1::pack_api::FlowDescriptor;
66type WitPackFlowKind =
67    bindings::greentic_pack_export_v1_0_1_0_pack_host::greentic::common_types::types::FlowKind;
68
69/// Normalized component outcome mirroring `greentic:common-types/component-outcome`.
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub enum ComponentOutcomeStatus {
72    /// Component finished successfully.
73    Done,
74    /// Component needs more input.
75    Pending,
76    /// Component failed.
77    Error,
78}
79
80/// Component outcome payload used by the v1 ABI.
81#[derive(Clone, Debug, PartialEq, Eq)]
82pub struct ComponentOutcome {
83    /// Status reported by the component.
84    pub status: ComponentOutcomeStatus,
85    /// Optional routing code.
86    pub code: Option<String>,
87    /// JSON payload returned by the component.
88    pub payload: String,
89    /// Optional metadata JSON blob.
90    pub metadata: Option<String>,
91}
92
93/// Minimal pack descriptor mirroring the v1 pack-export ABI.
94#[derive(Clone, Debug, PartialEq, Eq)]
95pub struct PackDescriptor {
96    /// Logical pack identifier.
97    pub pack_id: types::PackId,
98    /// Pack version.
99    pub version: Version,
100    /// Pack kind classification.
101    pub kind: types::PackKind,
102    /// Declared publisher.
103    pub publisher: String,
104}
105
106/// Minimal flow descriptor mirroring the v1 pack-export ABI.
107#[derive(Clone, Debug, PartialEq, Eq)]
108pub struct FlowDescriptor {
109    /// Flow identifier.
110    pub id: types::FlowId,
111    /// Flow kind classification.
112    pub kind: types::FlowKind,
113    /// Flow tags.
114    pub tags: Vec<String>,
115    /// Flow entrypoints.
116    pub entrypoints: Vec<String>,
117}
118
119/// Convert a WIT `TenantCtx` into the shared `greentic_types::TenantCtx`.
120pub fn tenant_ctx_from_wit(ctx: WitTenantCtx) -> MapperResult<types::TenantCtx> {
121    types::TenantCtx::try_from(ctx)
122}
123
124/// Convert a shared `greentic_types::TenantCtx` into the WIT `TenantCtx`.
125pub fn tenant_ctx_to_wit(ctx: types::TenantCtx) -> MapperResult<WitTenantCtx> {
126    WitTenantCtx::try_from(ctx)
127}
128
129impl TryFrom<WitImpersonation> for types::Impersonation {
130    type Error = types::GreenticError;
131
132    fn try_from(value: WitImpersonation) -> MapperResult<Self> {
133        Ok(Self {
134            actor_id: value.actor_id.try_into()?,
135            reason: value.reason,
136        })
137    }
138}
139
140impl From<types::Impersonation> for WitImpersonation {
141    fn from(value: types::Impersonation) -> Self {
142        Self {
143            actor_id: value.actor_id.into(),
144            reason: value.reason,
145        }
146    }
147}
148
149impl TryFrom<WitTenantCtx> for types::TenantCtx {
150    type Error = types::GreenticError;
151
152    fn try_from(value: WitTenantCtx) -> MapperResult<Self> {
153        let WitTenantCtx {
154            env,
155            tenant,
156            tenant_id,
157            team,
158            team_id,
159            user,
160            user_id,
161            trace_id,
162            i18n_id,
163            correlation_id,
164            session_id,
165            flow_id,
166            node_id,
167            provider_id,
168            deadline_ms,
169            attempt,
170            idempotency_key,
171            impersonation,
172            attributes,
173        } = value;
174
175        let deadline =
176            deadline_ms.map(|ms| types::InvocationDeadline::from_unix_millis(ms as i128));
177
178        let env = env.try_into()?;
179        let tenant = tenant.try_into()?;
180        let tenant_id = tenant_id.try_into()?;
181        let team = team.map(|item| item.try_into()).transpose()?;
182        let team_id = team_id.map(|item| item.try_into()).transpose()?;
183        let user = user.map(|item| item.try_into()).transpose()?;
184        let user_id = user_id.map(|item| item.try_into()).transpose()?;
185        let impersonation = impersonation
186            .map(types::Impersonation::try_from)
187            .transpose()?;
188        let attributes: BTreeMap<String, String> = attributes.into_iter().collect();
189
190        Ok(Self {
191            env,
192            tenant,
193            tenant_id,
194            team,
195            team_id,
196            user,
197            user_id,
198            session_id,
199            flow_id,
200            node_id,
201            provider_id,
202            trace_id,
203            i18n_id,
204            correlation_id,
205            attributes,
206            deadline,
207            attempt,
208            idempotency_key,
209            impersonation,
210        })
211    }
212}
213
214impl TryFrom<types::TenantCtx> for WitTenantCtx {
215    type Error = types::GreenticError;
216
217    fn try_from(value: types::TenantCtx) -> MapperResult<Self> {
218        let deadline_ms = match value.deadline {
219            Some(deadline) => Some(i128_to_i64(deadline.unix_millis())?),
220            None => None,
221        };
222        let attributes: Vec<(String, String)> = value.attributes.into_iter().collect();
223
224        Ok(Self {
225            env: value.env.into(),
226            tenant: value.tenant.into(),
227            tenant_id: value.tenant_id.into(),
228            team: value.team.map(Into::into),
229            team_id: value.team_id.map(Into::into),
230            user: value.user.map(Into::into),
231            user_id: value.user_id.map(Into::into),
232            session_id: value.session_id,
233            flow_id: value.flow_id,
234            node_id: value.node_id,
235            provider_id: value.provider_id,
236            trace_id: value.trace_id,
237            i18n_id: value.i18n_id.clone(),
238            correlation_id: value.correlation_id,
239            attributes,
240            deadline_ms,
241            attempt: value.attempt,
242            idempotency_key: value.idempotency_key,
243            impersonation: value.impersonation.map(Into::into),
244        })
245    }
246}
247
248impl From<WitSessionCursor> for types::SessionCursor {
249    fn from(value: WitSessionCursor) -> Self {
250        Self {
251            node_pointer: value.node_pointer,
252            wait_reason: value.wait_reason,
253            outbox_marker: value.outbox_marker,
254        }
255    }
256}
257
258impl From<types::SessionCursor> for WitSessionCursor {
259    fn from(value: types::SessionCursor) -> Self {
260        Self {
261            node_pointer: value.node_pointer,
262            wait_reason: value.wait_reason,
263            outbox_marker: value.outbox_marker,
264        }
265    }
266}
267
268impl From<WitErrorCode> for types::ErrorCode {
269    fn from(value: WitErrorCode) -> Self {
270        match value {
271            WitErrorCode::Unknown => Self::Unknown,
272            WitErrorCode::InvalidInput => Self::InvalidInput,
273            WitErrorCode::NotFound => Self::NotFound,
274            WitErrorCode::Conflict => Self::Conflict,
275            WitErrorCode::Timeout => Self::Timeout,
276            WitErrorCode::Unauthenticated => Self::Unauthenticated,
277            WitErrorCode::PermissionDenied => Self::PermissionDenied,
278            WitErrorCode::RateLimited => Self::RateLimited,
279            WitErrorCode::Unavailable => Self::Unavailable,
280            WitErrorCode::Internal => Self::Internal,
281        }
282    }
283}
284
285impl From<types::ErrorCode> for WitErrorCode {
286    fn from(value: types::ErrorCode) -> Self {
287        match value {
288            types::ErrorCode::Unknown => Self::Unknown,
289            types::ErrorCode::InvalidInput => Self::InvalidInput,
290            types::ErrorCode::NotFound => Self::NotFound,
291            types::ErrorCode::Conflict => Self::Conflict,
292            types::ErrorCode::Timeout => Self::Timeout,
293            types::ErrorCode::Unauthenticated => Self::Unauthenticated,
294            types::ErrorCode::PermissionDenied => Self::PermissionDenied,
295            types::ErrorCode::RateLimited => Self::RateLimited,
296            types::ErrorCode::Unavailable => Self::Unavailable,
297            types::ErrorCode::Internal => Self::Internal,
298        }
299    }
300}
301
302impl From<WitOutcome> for types::Outcome<String> {
303    fn from(value: WitOutcome) -> Self {
304        match value {
305            WitOutcome::Done(val) => Self::Done(val),
306            WitOutcome::Pending(payload) => Self::Pending {
307                reason: payload.reason,
308                expected_input: payload.expected_input,
309            },
310            WitOutcome::Error(payload) => Self::Error {
311                code: payload.code.into(),
312                message: payload.message,
313            },
314        }
315    }
316}
317
318impl From<types::Outcome<String>> for WitOutcome {
319    fn from(value: types::Outcome<String>) -> Self {
320        match value {
321            types::Outcome::Done(val) => Self::Done(val),
322            types::Outcome::Pending {
323                reason,
324                expected_input,
325            } => Self::Pending(WitOutcomePending {
326                reason,
327                expected_input,
328            }),
329            types::Outcome::Error { code, message } => Self::Error(WitOutcomeError {
330                code: code.into(),
331                message,
332            }),
333        }
334    }
335}
336
337impl From<WitProtocol> for types::Protocol {
338    fn from(value: WitProtocol) -> Self {
339        match value {
340            WitProtocol::Http => Self::Http,
341            WitProtocol::Https => Self::Https,
342            WitProtocol::Tcp => Self::Tcp,
343            WitProtocol::Udp => Self::Udp,
344            WitProtocol::Grpc => Self::Grpc,
345            WitProtocol::Custom(v) => Self::Custom(v),
346        }
347    }
348}
349
350impl From<types::Protocol> for WitProtocol {
351    fn from(value: types::Protocol) -> Self {
352        match value {
353            types::Protocol::Http => Self::Http,
354            types::Protocol::Https => Self::Https,
355            types::Protocol::Tcp => Self::Tcp,
356            types::Protocol::Udp => Self::Udp,
357            types::Protocol::Grpc => Self::Grpc,
358            types::Protocol::Custom(v) => Self::Custom(v),
359        }
360    }
361}
362
363impl From<WitAllowList> for types::AllowList {
364    fn from(value: WitAllowList) -> Self {
365        Self {
366            domains: value.domains,
367            ports: value.ports,
368            protocols: value.protocols.into_iter().map(Into::into).collect(),
369        }
370    }
371}
372
373impl From<types::AllowList> for WitAllowList {
374    fn from(value: types::AllowList) -> Self {
375        Self {
376            domains: value.domains,
377            ports: value.ports,
378            protocols: value.protocols.into_iter().map(Into::into).collect(),
379        }
380    }
381}
382
383impl From<WitNetworkPolicy> for types::NetworkPolicy {
384    fn from(value: WitNetworkPolicy) -> Self {
385        Self {
386            egress: value.egress.into(),
387            deny_on_miss: value.deny_on_miss,
388        }
389    }
390}
391
392impl From<types::NetworkPolicy> for WitNetworkPolicy {
393    fn from(value: types::NetworkPolicy) -> Self {
394        Self {
395            egress: value.egress.into(),
396            deny_on_miss: value.deny_on_miss,
397        }
398    }
399}
400
401impl TryFrom<WitSpanContext> for types::SpanContext {
402    type Error = types::GreenticError;
403
404    fn try_from(value: WitSpanContext) -> MapperResult<Self> {
405        let WitSpanContext {
406            tenant,
407            session_id,
408            flow_id,
409            node_id,
410            provider,
411            start_ms,
412            end_ms,
413        } = value;
414
415        let start = start_ms.map(timestamp_ms_to_offset).transpose()?;
416        let end = end_ms.map(timestamp_ms_to_offset).transpose()?;
417        let tenant = tenant.try_into()?;
418
419        Ok(Self {
420            tenant,
421            session_id: session_id.map(types::SessionKey::from),
422            flow_id,
423            node_id,
424            provider,
425            start,
426            end,
427        })
428    }
429}
430
431impl TryFrom<types::SpanContext> for WitSpanContext {
432    type Error = types::GreenticError;
433
434    fn try_from(value: types::SpanContext) -> MapperResult<Self> {
435        let start_ms = value
436            .start
437            .as_ref()
438            .map(offset_to_timestamp_ms)
439            .transpose()?;
440        let end_ms = value.end.as_ref().map(offset_to_timestamp_ms).transpose()?;
441
442        Ok(Self {
443            tenant: value.tenant.into(),
444            session_id: value.session_id.map(|key| key.to_string()),
445            flow_id: value.flow_id,
446            node_id: value.node_id,
447            provider: value.provider,
448            start_ms,
449            end_ms,
450        })
451    }
452}
453
454impl From<WitSignatureAlgorithm> for types::SignatureAlgorithm {
455    fn from(value: WitSignatureAlgorithm) -> Self {
456        match value {
457            WitSignatureAlgorithm::Ed25519 => Self::Ed25519,
458            WitSignatureAlgorithm::Other(v) => Self::Other(v),
459        }
460    }
461}
462
463impl From<types::SignatureAlgorithm> for WitSignatureAlgorithm {
464    fn from(value: types::SignatureAlgorithm) -> Self {
465        match value {
466            types::SignatureAlgorithm::Ed25519 => Self::Ed25519,
467            types::SignatureAlgorithm::Other(v) => Self::Other(v),
468        }
469    }
470}
471
472impl From<WitSignature> for types::Signature {
473    fn from(value: WitSignature) -> Self {
474        Self {
475            key_id: value.key_id,
476            algorithm: value.algorithm.into(),
477            signature: value.signature,
478        }
479    }
480}
481
482impl From<types::Signature> for WitSignature {
483    fn from(value: types::Signature) -> Self {
484        Self {
485            key_id: value.key_id,
486            algorithm: value.algorithm.into(),
487            signature: value.signature,
488        }
489    }
490}
491
492impl TryFrom<WitPackRef> for types::PackRef {
493    type Error = types::GreenticError;
494
495    fn try_from(value: WitPackRef) -> MapperResult<Self> {
496        let version = Version::parse(&value.version)
497            .map_err(|err| invalid_input(format!("invalid version: {err}")))?;
498        Ok(Self {
499            oci_url: value.oci_url,
500            version,
501            digest: value.digest,
502            signatures: value.signatures.into_iter().map(Into::into).collect(),
503        })
504    }
505}
506
507impl From<types::PackRef> for WitPackRef {
508    fn from(value: types::PackRef) -> Self {
509        Self {
510            oci_url: value.oci_url,
511            version: value.version.to_string(),
512            digest: value.digest,
513            signatures: value.signatures.into_iter().map(Into::into).collect(),
514        }
515    }
516}
517
518/// Convert the shared `FlowKind` into the WIT `flow-kind`.
519pub fn flow_kind_to_wit(kind: types::FlowKind) -> WitCommonFlowKind {
520    match kind {
521        types::FlowKind::Messaging => WitCommonFlowKind::Messaging,
522        types::FlowKind::Event => WitCommonFlowKind::Event,
523        types::FlowKind::ComponentConfig => WitCommonFlowKind::ComponentConfig,
524        types::FlowKind::Job => WitCommonFlowKind::Job,
525        types::FlowKind::Http => WitCommonFlowKind::Http,
526    }
527}
528
529/// Convert a WIT `flow-kind` into the shared `FlowKind`.
530pub fn flow_kind_from_wit(kind: WitCommonFlowKind) -> types::FlowKind {
531    match kind {
532        WitCommonFlowKind::Messaging => types::FlowKind::Messaging,
533        WitCommonFlowKind::Event => types::FlowKind::Event,
534        WitCommonFlowKind::ComponentConfig => types::FlowKind::ComponentConfig,
535        WitCommonFlowKind::Job => types::FlowKind::Job,
536        WitCommonFlowKind::Http => types::FlowKind::Http,
537    }
538}
539
540fn flow_kind_from_pack_wit(kind: WitPackFlowKind) -> types::FlowKind {
541    match kind {
542        WitPackFlowKind::Messaging => types::FlowKind::Messaging,
543        WitPackFlowKind::Event => types::FlowKind::Event,
544        WitPackFlowKind::ComponentConfig => types::FlowKind::ComponentConfig,
545        WitPackFlowKind::Job => types::FlowKind::Job,
546        WitPackFlowKind::Http => types::FlowKind::Http,
547    }
548}
549
550fn flow_kind_to_pack_wit(kind: types::FlowKind) -> WitPackFlowKind {
551    match kind {
552        types::FlowKind::Messaging => WitPackFlowKind::Messaging,
553        types::FlowKind::Event => WitPackFlowKind::Event,
554        types::FlowKind::ComponentConfig => WitPackFlowKind::ComponentConfig,
555        types::FlowKind::Job => WitPackFlowKind::Job,
556        types::FlowKind::Http => WitPackFlowKind::Http,
557    }
558}
559
560/// Convert the shared `PackKind` into the WIT `pack-kind`.
561pub fn pack_kind_to_wit(kind: types::PackKind) -> WitPackKind {
562    match kind {
563        types::PackKind::Application => WitPackKind::Application,
564        types::PackKind::Provider => WitPackKind::Provider,
565        types::PackKind::Infrastructure => WitPackKind::Infrastructure,
566        types::PackKind::Library => WitPackKind::Library,
567    }
568}
569
570/// Convert a WIT `pack-kind` into the shared `PackKind`.
571pub fn pack_kind_from_wit(kind: WitPackKind) -> types::PackKind {
572    match kind {
573        WitPackKind::Application => types::PackKind::Application,
574        WitPackKind::Provider => types::PackKind::Provider,
575        WitPackKind::Infrastructure => types::PackKind::Infrastructure,
576        WitPackKind::Library => types::PackKind::Library,
577    }
578}
579
580/// Convert a WIT `tenant-ctx` (v1 subset) into the shared `TenantCtx`.
581pub fn tenant_ctx_from_common(ctx: WitCommonTenantCtx) -> MapperResult<types::TenantCtx> {
582    let WitCommonTenantCtx {
583        env,
584        tenant_id,
585        team_id,
586        user_id,
587        i18n_id,
588        session_id,
589        flow_id,
590        node_id,
591    } = ctx;
592
593    Ok(types::TenantCtx {
594        env: env.try_into()?,
595        tenant: tenant_id.clone().try_into()?,
596        tenant_id: tenant_id.try_into()?,
597        team: team_id.clone().map(|id| id.try_into()).transpose()?,
598        team_id: team_id.map(|id| id.try_into()).transpose()?,
599        user: user_id.clone().map(|id| id.try_into()).transpose()?,
600        user_id: user_id.map(|id| id.try_into()).transpose()?,
601        session_id,
602        flow_id,
603        node_id,
604        provider_id: None,
605        trace_id: None,
606        i18n_id: i18n_id.map(|id| id.to_string()),
607        correlation_id: None,
608        attributes: BTreeMap::new(),
609        deadline: None,
610        attempt: 0,
611        idempotency_key: None,
612        impersonation: None,
613    })
614}
615
616/// Convert a shared `TenantCtx` into the WIT `tenant-ctx` (v1 subset).
617pub fn tenant_ctx_to_common(ctx: types::TenantCtx) -> MapperResult<WitCommonTenantCtx> {
618    Ok(WitCommonTenantCtx {
619        env: ctx.env.into(),
620        tenant_id: ctx.tenant_id.into(),
621        team_id: ctx.team_id.map(Into::into),
622        user_id: ctx.user_id.map(Into::into),
623        i18n_id: ctx.i18n_id.clone(),
624        session_id: ctx.session_id,
625        flow_id: ctx.flow_id,
626        node_id: ctx.node_id,
627    })
628}
629
630/// Convert a WIT `component-outcome` into the normalized struct.
631pub fn component_outcome_from_wit(outcome: WitComponentOutcome) -> ComponentOutcome {
632    let status = match outcome.status {
633        WitOutcomeStatus::Done => ComponentOutcomeStatus::Done,
634        WitOutcomeStatus::Pending => ComponentOutcomeStatus::Pending,
635        WitOutcomeStatus::Error => ComponentOutcomeStatus::Error,
636    };
637
638    ComponentOutcome {
639        status,
640        code: outcome.code,
641        payload: outcome.payload_json,
642        metadata: outcome.metadata_json,
643    }
644}
645
646/// Convert a normalized component outcome into the WIT `component-outcome`.
647pub fn component_outcome_to_wit(outcome: ComponentOutcome) -> WitComponentOutcome {
648    let status = match outcome.status {
649        ComponentOutcomeStatus::Done => WitOutcomeStatus::Done,
650        ComponentOutcomeStatus::Pending => WitOutcomeStatus::Pending,
651        ComponentOutcomeStatus::Error => WitOutcomeStatus::Error,
652    };
653
654    WitComponentOutcome {
655        status,
656        code: outcome.code,
657        payload_json: outcome.payload,
658        metadata_json: outcome.metadata,
659    }
660}
661
662/// Convert a WIT pack descriptor into the shared struct.
663pub fn pack_descriptor_from_wit(desc: WitPackDescriptor) -> MapperResult<PackDescriptor> {
664    Ok(PackDescriptor {
665        pack_id: desc.pack_id.try_into()?,
666        version: Version::parse(&desc.version)
667            .map_err(|err| invalid_input(format!("invalid version: {err}")))?,
668        kind: pack_kind_from_wit(desc.kind),
669        publisher: desc.publisher,
670    })
671}
672
673/// Convert a pack descriptor into the WIT shape.
674pub fn pack_descriptor_to_wit(desc: PackDescriptor) -> WitPackDescriptor {
675    WitPackDescriptor {
676        pack_id: desc.pack_id.into(),
677        version: desc.version.to_string(),
678        kind: pack_kind_to_wit(desc.kind),
679        publisher: desc.publisher,
680    }
681}
682
683/// Convert a WIT flow descriptor into the shared struct.
684pub fn flow_descriptor_from_wit(desc: WitFlowDescriptor) -> MapperResult<FlowDescriptor> {
685    Ok(FlowDescriptor {
686        id: desc.id.try_into()?,
687        kind: flow_kind_from_pack_wit(desc.kind),
688        tags: desc.tags,
689        entrypoints: desc.entrypoints,
690    })
691}
692
693/// Convert a flow descriptor into the WIT shape.
694pub fn flow_descriptor_to_wit(desc: FlowDescriptor) -> WitFlowDescriptor {
695    WitFlowDescriptor {
696        id: desc.id.into(),
697        kind: flow_kind_to_pack_wit(desc.kind),
698        tags: desc.tags,
699        entrypoints: desc.entrypoints,
700    }
701}
702
703#[cfg(test)]
704mod tests {
705    use super::*;
706    use std::convert::TryFrom;
707
708    fn fixture_id<T>(value: &str) -> T
709    where
710        T: TryFrom<String, Error = types::GreenticError>,
711    {
712        T::try_from(value.to_owned())
713            .unwrap_or_else(|err| panic!("invalid fixture identifier '{value}': {err}"))
714    }
715
716    fn sample_tenant_ctx() -> types::TenantCtx {
717        types::TenantCtx {
718            env: fixture_id("prod"),
719            tenant: fixture_id("tenant-1"),
720            tenant_id: fixture_id("tenant-1"),
721            team: Some(fixture_id("team-42")),
722            team_id: Some(fixture_id("team-42")),
723            user: Some(fixture_id("user-7")),
724            user_id: Some(fixture_id("user-7")),
725            attributes: BTreeMap::new(),
726            session_id: Some("sess-42".into()),
727            flow_id: Some("flow-42".into()),
728            node_id: Some("node-42".into()),
729            provider_id: Some("provider-42".into()),
730            trace_id: Some("trace".into()),
731            i18n_id: Some("en-US".into()),
732            correlation_id: Some("corr".into()),
733            deadline: Some(types::InvocationDeadline::from_unix_millis(
734                1_700_000_000_000,
735            )),
736            attempt: 2,
737            idempotency_key: Some("idem".into()),
738            impersonation: Some(types::Impersonation {
739                actor_id: fixture_id("actor"),
740                reason: Some("maintenance".into()),
741            }),
742        }
743    }
744
745    #[test]
746    fn tenant_ctx_roundtrip() {
747        let ctx = sample_tenant_ctx();
748        let wit = match WitTenantCtx::try_from(ctx.clone()) {
749            Ok(value) => value,
750            Err(err) => panic!("failed to map to wit: {err}"),
751        };
752        let back = match types::TenantCtx::try_from(wit) {
753            Ok(value) => value,
754            Err(err) => panic!("failed to map from wit: {err}"),
755        };
756        assert_eq!(back.env.as_str(), ctx.env.as_str());
757        assert_eq!(back.tenant.as_str(), ctx.tenant.as_str());
758        assert!(back.impersonation.is_some());
759        assert!(ctx.impersonation.is_some());
760        assert_eq!(
761            back.impersonation.as_ref().map(|imp| imp.actor_id.as_str()),
762            ctx.impersonation.as_ref().map(|imp| imp.actor_id.as_str())
763        );
764        assert_eq!(back.session_id, ctx.session_id);
765        assert_eq!(back.flow_id, ctx.flow_id);
766        assert_eq!(back.node_id, ctx.node_id);
767        assert_eq!(back.provider_id, ctx.provider_id);
768    }
769
770    #[test]
771    fn outcome_roundtrip() {
772        let pending = types::Outcome::Pending {
773            reason: "waiting".into(),
774            expected_input: Some(vec!["input".into()]),
775        };
776        let wit = WitOutcome::from(pending.clone());
777        let back = types::Outcome::from(wit);
778        match back {
779            types::Outcome::Pending { reason, .. } => {
780                assert_eq!(reason, "waiting");
781            }
782            _ => panic!("expected pending"),
783        }
784    }
785}