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            correlation_id,
163            session_id,
164            flow_id,
165            node_id,
166            provider_id,
167            deadline_ms,
168            attempt,
169            idempotency_key,
170            impersonation,
171            attributes,
172        } = value;
173
174        let deadline =
175            deadline_ms.map(|ms| types::InvocationDeadline::from_unix_millis(ms as i128));
176
177        let env = env.try_into()?;
178        let tenant = tenant.try_into()?;
179        let tenant_id = tenant_id.try_into()?;
180        let team = team.map(|item| item.try_into()).transpose()?;
181        let team_id = team_id.map(|item| item.try_into()).transpose()?;
182        let user = user.map(|item| item.try_into()).transpose()?;
183        let user_id = user_id.map(|item| item.try_into()).transpose()?;
184        let impersonation = impersonation
185            .map(types::Impersonation::try_from)
186            .transpose()?;
187        let attributes: BTreeMap<String, String> = attributes.into_iter().collect();
188
189        Ok(Self {
190            env,
191            tenant,
192            tenant_id,
193            team,
194            team_id,
195            user,
196            user_id,
197            session_id,
198            flow_id,
199            node_id,
200            provider_id,
201            trace_id,
202            correlation_id,
203            attributes,
204            deadline,
205            attempt,
206            idempotency_key,
207            impersonation,
208        })
209    }
210}
211
212impl TryFrom<types::TenantCtx> for WitTenantCtx {
213    type Error = types::GreenticError;
214
215    fn try_from(value: types::TenantCtx) -> MapperResult<Self> {
216        let deadline_ms = match value.deadline {
217            Some(deadline) => Some(i128_to_i64(deadline.unix_millis())?),
218            None => None,
219        };
220        let attributes: Vec<(String, String)> = value.attributes.into_iter().collect();
221
222        Ok(Self {
223            env: value.env.into(),
224            tenant: value.tenant.into(),
225            tenant_id: value.tenant_id.into(),
226            team: value.team.map(Into::into),
227            team_id: value.team_id.map(Into::into),
228            user: value.user.map(Into::into),
229            user_id: value.user_id.map(Into::into),
230            session_id: value.session_id,
231            flow_id: value.flow_id,
232            node_id: value.node_id,
233            provider_id: value.provider_id,
234            trace_id: value.trace_id,
235            correlation_id: value.correlation_id,
236            attributes,
237            deadline_ms,
238            attempt: value.attempt,
239            idempotency_key: value.idempotency_key,
240            impersonation: value.impersonation.map(Into::into),
241        })
242    }
243}
244
245impl From<WitSessionCursor> for types::SessionCursor {
246    fn from(value: WitSessionCursor) -> Self {
247        Self {
248            node_pointer: value.node_pointer,
249            wait_reason: value.wait_reason,
250            outbox_marker: value.outbox_marker,
251        }
252    }
253}
254
255impl From<types::SessionCursor> for WitSessionCursor {
256    fn from(value: types::SessionCursor) -> Self {
257        Self {
258            node_pointer: value.node_pointer,
259            wait_reason: value.wait_reason,
260            outbox_marker: value.outbox_marker,
261        }
262    }
263}
264
265impl From<WitErrorCode> for types::ErrorCode {
266    fn from(value: WitErrorCode) -> Self {
267        match value {
268            WitErrorCode::Unknown => Self::Unknown,
269            WitErrorCode::InvalidInput => Self::InvalidInput,
270            WitErrorCode::NotFound => Self::NotFound,
271            WitErrorCode::Conflict => Self::Conflict,
272            WitErrorCode::Timeout => Self::Timeout,
273            WitErrorCode::Unauthenticated => Self::Unauthenticated,
274            WitErrorCode::PermissionDenied => Self::PermissionDenied,
275            WitErrorCode::RateLimited => Self::RateLimited,
276            WitErrorCode::Unavailable => Self::Unavailable,
277            WitErrorCode::Internal => Self::Internal,
278        }
279    }
280}
281
282impl From<types::ErrorCode> for WitErrorCode {
283    fn from(value: types::ErrorCode) -> Self {
284        match value {
285            types::ErrorCode::Unknown => Self::Unknown,
286            types::ErrorCode::InvalidInput => Self::InvalidInput,
287            types::ErrorCode::NotFound => Self::NotFound,
288            types::ErrorCode::Conflict => Self::Conflict,
289            types::ErrorCode::Timeout => Self::Timeout,
290            types::ErrorCode::Unauthenticated => Self::Unauthenticated,
291            types::ErrorCode::PermissionDenied => Self::PermissionDenied,
292            types::ErrorCode::RateLimited => Self::RateLimited,
293            types::ErrorCode::Unavailable => Self::Unavailable,
294            types::ErrorCode::Internal => Self::Internal,
295        }
296    }
297}
298
299impl From<WitOutcome> for types::Outcome<String> {
300    fn from(value: WitOutcome) -> Self {
301        match value {
302            WitOutcome::Done(val) => Self::Done(val),
303            WitOutcome::Pending(payload) => Self::Pending {
304                reason: payload.reason,
305                expected_input: payload.expected_input,
306            },
307            WitOutcome::Error(payload) => Self::Error {
308                code: payload.code.into(),
309                message: payload.message,
310            },
311        }
312    }
313}
314
315impl From<types::Outcome<String>> for WitOutcome {
316    fn from(value: types::Outcome<String>) -> Self {
317        match value {
318            types::Outcome::Done(val) => Self::Done(val),
319            types::Outcome::Pending {
320                reason,
321                expected_input,
322            } => Self::Pending(WitOutcomePending {
323                reason,
324                expected_input,
325            }),
326            types::Outcome::Error { code, message } => Self::Error(WitOutcomeError {
327                code: code.into(),
328                message,
329            }),
330        }
331    }
332}
333
334impl From<WitProtocol> for types::Protocol {
335    fn from(value: WitProtocol) -> Self {
336        match value {
337            WitProtocol::Http => Self::Http,
338            WitProtocol::Https => Self::Https,
339            WitProtocol::Tcp => Self::Tcp,
340            WitProtocol::Udp => Self::Udp,
341            WitProtocol::Grpc => Self::Grpc,
342            WitProtocol::Custom(v) => Self::Custom(v),
343        }
344    }
345}
346
347impl From<types::Protocol> for WitProtocol {
348    fn from(value: types::Protocol) -> Self {
349        match value {
350            types::Protocol::Http => Self::Http,
351            types::Protocol::Https => Self::Https,
352            types::Protocol::Tcp => Self::Tcp,
353            types::Protocol::Udp => Self::Udp,
354            types::Protocol::Grpc => Self::Grpc,
355            types::Protocol::Custom(v) => Self::Custom(v),
356        }
357    }
358}
359
360impl From<WitAllowList> for types::AllowList {
361    fn from(value: WitAllowList) -> Self {
362        Self {
363            domains: value.domains,
364            ports: value.ports,
365            protocols: value.protocols.into_iter().map(Into::into).collect(),
366        }
367    }
368}
369
370impl From<types::AllowList> for WitAllowList {
371    fn from(value: types::AllowList) -> Self {
372        Self {
373            domains: value.domains,
374            ports: value.ports,
375            protocols: value.protocols.into_iter().map(Into::into).collect(),
376        }
377    }
378}
379
380impl From<WitNetworkPolicy> for types::NetworkPolicy {
381    fn from(value: WitNetworkPolicy) -> Self {
382        Self {
383            egress: value.egress.into(),
384            deny_on_miss: value.deny_on_miss,
385        }
386    }
387}
388
389impl From<types::NetworkPolicy> for WitNetworkPolicy {
390    fn from(value: types::NetworkPolicy) -> Self {
391        Self {
392            egress: value.egress.into(),
393            deny_on_miss: value.deny_on_miss,
394        }
395    }
396}
397
398impl TryFrom<WitSpanContext> for types::SpanContext {
399    type Error = types::GreenticError;
400
401    fn try_from(value: WitSpanContext) -> MapperResult<Self> {
402        let WitSpanContext {
403            tenant,
404            session_id,
405            flow_id,
406            node_id,
407            provider,
408            start_ms,
409            end_ms,
410        } = value;
411
412        let start = start_ms.map(timestamp_ms_to_offset).transpose()?;
413        let end = end_ms.map(timestamp_ms_to_offset).transpose()?;
414        let tenant = tenant.try_into()?;
415
416        Ok(Self {
417            tenant,
418            session_id: session_id.map(types::SessionKey::from),
419            flow_id,
420            node_id,
421            provider,
422            start,
423            end,
424        })
425    }
426}
427
428impl TryFrom<types::SpanContext> for WitSpanContext {
429    type Error = types::GreenticError;
430
431    fn try_from(value: types::SpanContext) -> MapperResult<Self> {
432        let start_ms = value
433            .start
434            .as_ref()
435            .map(offset_to_timestamp_ms)
436            .transpose()?;
437        let end_ms = value.end.as_ref().map(offset_to_timestamp_ms).transpose()?;
438
439        Ok(Self {
440            tenant: value.tenant.into(),
441            session_id: value.session_id.map(|key| key.to_string()),
442            flow_id: value.flow_id,
443            node_id: value.node_id,
444            provider: value.provider,
445            start_ms,
446            end_ms,
447        })
448    }
449}
450
451impl From<WitSignatureAlgorithm> for types::SignatureAlgorithm {
452    fn from(value: WitSignatureAlgorithm) -> Self {
453        match value {
454            WitSignatureAlgorithm::Ed25519 => Self::Ed25519,
455            WitSignatureAlgorithm::Other(v) => Self::Other(v),
456        }
457    }
458}
459
460impl From<types::SignatureAlgorithm> for WitSignatureAlgorithm {
461    fn from(value: types::SignatureAlgorithm) -> Self {
462        match value {
463            types::SignatureAlgorithm::Ed25519 => Self::Ed25519,
464            types::SignatureAlgorithm::Other(v) => Self::Other(v),
465        }
466    }
467}
468
469impl From<WitSignature> for types::Signature {
470    fn from(value: WitSignature) -> Self {
471        Self {
472            key_id: value.key_id,
473            algorithm: value.algorithm.into(),
474            signature: value.signature,
475        }
476    }
477}
478
479impl From<types::Signature> for WitSignature {
480    fn from(value: types::Signature) -> Self {
481        Self {
482            key_id: value.key_id,
483            algorithm: value.algorithm.into(),
484            signature: value.signature,
485        }
486    }
487}
488
489impl TryFrom<WitPackRef> for types::PackRef {
490    type Error = types::GreenticError;
491
492    fn try_from(value: WitPackRef) -> MapperResult<Self> {
493        let version = Version::parse(&value.version)
494            .map_err(|err| invalid_input(format!("invalid version: {err}")))?;
495        Ok(Self {
496            oci_url: value.oci_url,
497            version,
498            digest: value.digest,
499            signatures: value.signatures.into_iter().map(Into::into).collect(),
500        })
501    }
502}
503
504impl From<types::PackRef> for WitPackRef {
505    fn from(value: types::PackRef) -> Self {
506        Self {
507            oci_url: value.oci_url,
508            version: value.version.to_string(),
509            digest: value.digest,
510            signatures: value.signatures.into_iter().map(Into::into).collect(),
511        }
512    }
513}
514
515/// Convert the shared `FlowKind` into the WIT `flow-kind`.
516pub fn flow_kind_to_wit(kind: types::FlowKind) -> WitCommonFlowKind {
517    match kind {
518        types::FlowKind::Messaging => WitCommonFlowKind::Messaging,
519        types::FlowKind::Event => WitCommonFlowKind::Event,
520        types::FlowKind::ComponentConfig => WitCommonFlowKind::ComponentConfig,
521        types::FlowKind::Job => WitCommonFlowKind::Job,
522        types::FlowKind::Http => WitCommonFlowKind::Http,
523    }
524}
525
526/// Convert a WIT `flow-kind` into the shared `FlowKind`.
527pub fn flow_kind_from_wit(kind: WitCommonFlowKind) -> types::FlowKind {
528    match kind {
529        WitCommonFlowKind::Messaging => types::FlowKind::Messaging,
530        WitCommonFlowKind::Event => types::FlowKind::Event,
531        WitCommonFlowKind::ComponentConfig => types::FlowKind::ComponentConfig,
532        WitCommonFlowKind::Job => types::FlowKind::Job,
533        WitCommonFlowKind::Http => types::FlowKind::Http,
534    }
535}
536
537fn flow_kind_from_pack_wit(kind: WitPackFlowKind) -> types::FlowKind {
538    match kind {
539        WitPackFlowKind::Messaging => types::FlowKind::Messaging,
540        WitPackFlowKind::Event => types::FlowKind::Event,
541        WitPackFlowKind::ComponentConfig => types::FlowKind::ComponentConfig,
542        WitPackFlowKind::Job => types::FlowKind::Job,
543        WitPackFlowKind::Http => types::FlowKind::Http,
544    }
545}
546
547fn flow_kind_to_pack_wit(kind: types::FlowKind) -> WitPackFlowKind {
548    match kind {
549        types::FlowKind::Messaging => WitPackFlowKind::Messaging,
550        types::FlowKind::Event => WitPackFlowKind::Event,
551        types::FlowKind::ComponentConfig => WitPackFlowKind::ComponentConfig,
552        types::FlowKind::Job => WitPackFlowKind::Job,
553        types::FlowKind::Http => WitPackFlowKind::Http,
554    }
555}
556
557/// Convert the shared `PackKind` into the WIT `pack-kind`.
558pub fn pack_kind_to_wit(kind: types::PackKind) -> WitPackKind {
559    match kind {
560        types::PackKind::Application => WitPackKind::Application,
561        types::PackKind::Provider => WitPackKind::Provider,
562        types::PackKind::Infrastructure => WitPackKind::Infrastructure,
563        types::PackKind::Library => WitPackKind::Library,
564    }
565}
566
567/// Convert a WIT `pack-kind` into the shared `PackKind`.
568pub fn pack_kind_from_wit(kind: WitPackKind) -> types::PackKind {
569    match kind {
570        WitPackKind::Application => types::PackKind::Application,
571        WitPackKind::Provider => types::PackKind::Provider,
572        WitPackKind::Infrastructure => types::PackKind::Infrastructure,
573        WitPackKind::Library => types::PackKind::Library,
574    }
575}
576
577/// Convert a WIT `tenant-ctx` (v1 subset) into the shared `TenantCtx`.
578pub fn tenant_ctx_from_common(ctx: WitCommonTenantCtx) -> MapperResult<types::TenantCtx> {
579    let WitCommonTenantCtx {
580        env,
581        tenant_id,
582        team_id,
583        user_id,
584        session_id,
585        flow_id,
586        node_id,
587    } = ctx;
588
589    Ok(types::TenantCtx {
590        env: env.try_into()?,
591        tenant: tenant_id.clone().try_into()?,
592        tenant_id: tenant_id.try_into()?,
593        team: team_id.clone().map(|id| id.try_into()).transpose()?,
594        team_id: team_id.map(|id| id.try_into()).transpose()?,
595        user: user_id.clone().map(|id| id.try_into()).transpose()?,
596        user_id: user_id.map(|id| id.try_into()).transpose()?,
597        session_id,
598        flow_id,
599        node_id,
600        provider_id: None,
601        trace_id: None,
602        correlation_id: None,
603        attributes: BTreeMap::new(),
604        deadline: None,
605        attempt: 0,
606        idempotency_key: None,
607        impersonation: None,
608    })
609}
610
611/// Convert a shared `TenantCtx` into the WIT `tenant-ctx` (v1 subset).
612pub fn tenant_ctx_to_common(ctx: types::TenantCtx) -> MapperResult<WitCommonTenantCtx> {
613    Ok(WitCommonTenantCtx {
614        env: ctx.env.into(),
615        tenant_id: ctx.tenant_id.into(),
616        team_id: ctx.team_id.map(Into::into),
617        user_id: ctx.user_id.map(Into::into),
618        session_id: ctx.session_id,
619        flow_id: ctx.flow_id,
620        node_id: ctx.node_id,
621    })
622}
623
624/// Convert a WIT `component-outcome` into the normalized struct.
625pub fn component_outcome_from_wit(outcome: WitComponentOutcome) -> ComponentOutcome {
626    let status = match outcome.status {
627        WitOutcomeStatus::Done => ComponentOutcomeStatus::Done,
628        WitOutcomeStatus::Pending => ComponentOutcomeStatus::Pending,
629        WitOutcomeStatus::Error => ComponentOutcomeStatus::Error,
630    };
631
632    ComponentOutcome {
633        status,
634        code: outcome.code,
635        payload: outcome.payload_json,
636        metadata: outcome.metadata_json,
637    }
638}
639
640/// Convert a normalized component outcome into the WIT `component-outcome`.
641pub fn component_outcome_to_wit(outcome: ComponentOutcome) -> WitComponentOutcome {
642    let status = match outcome.status {
643        ComponentOutcomeStatus::Done => WitOutcomeStatus::Done,
644        ComponentOutcomeStatus::Pending => WitOutcomeStatus::Pending,
645        ComponentOutcomeStatus::Error => WitOutcomeStatus::Error,
646    };
647
648    WitComponentOutcome {
649        status,
650        code: outcome.code,
651        payload_json: outcome.payload,
652        metadata_json: outcome.metadata,
653    }
654}
655
656/// Convert a WIT pack descriptor into the shared struct.
657pub fn pack_descriptor_from_wit(desc: WitPackDescriptor) -> MapperResult<PackDescriptor> {
658    Ok(PackDescriptor {
659        pack_id: desc.pack_id.try_into()?,
660        version: Version::parse(&desc.version)
661            .map_err(|err| invalid_input(format!("invalid version: {err}")))?,
662        kind: pack_kind_from_wit(desc.kind),
663        publisher: desc.publisher,
664    })
665}
666
667/// Convert a pack descriptor into the WIT shape.
668pub fn pack_descriptor_to_wit(desc: PackDescriptor) -> WitPackDescriptor {
669    WitPackDescriptor {
670        pack_id: desc.pack_id.into(),
671        version: desc.version.to_string(),
672        kind: pack_kind_to_wit(desc.kind),
673        publisher: desc.publisher,
674    }
675}
676
677/// Convert a WIT flow descriptor into the shared struct.
678pub fn flow_descriptor_from_wit(desc: WitFlowDescriptor) -> MapperResult<FlowDescriptor> {
679    Ok(FlowDescriptor {
680        id: desc.id.try_into()?,
681        kind: flow_kind_from_pack_wit(desc.kind),
682        tags: desc.tags,
683        entrypoints: desc.entrypoints,
684    })
685}
686
687/// Convert a flow descriptor into the WIT shape.
688pub fn flow_descriptor_to_wit(desc: FlowDescriptor) -> WitFlowDescriptor {
689    WitFlowDescriptor {
690        id: desc.id.into(),
691        kind: flow_kind_to_pack_wit(desc.kind),
692        tags: desc.tags,
693        entrypoints: desc.entrypoints,
694    }
695}
696
697#[cfg(test)]
698mod tests {
699    use super::*;
700    use std::convert::TryFrom;
701
702    fn fixture_id<T>(value: &str) -> T
703    where
704        T: TryFrom<String, Error = types::GreenticError>,
705    {
706        T::try_from(value.to_owned())
707            .unwrap_or_else(|err| panic!("invalid fixture identifier '{value}': {err}"))
708    }
709
710    fn sample_tenant_ctx() -> types::TenantCtx {
711        types::TenantCtx {
712            env: fixture_id("prod"),
713            tenant: fixture_id("tenant-1"),
714            tenant_id: fixture_id("tenant-1"),
715            team: Some(fixture_id("team-42")),
716            team_id: Some(fixture_id("team-42")),
717            user: Some(fixture_id("user-7")),
718            user_id: Some(fixture_id("user-7")),
719            attributes: BTreeMap::new(),
720            session_id: Some("sess-42".into()),
721            flow_id: Some("flow-42".into()),
722            node_id: Some("node-42".into()),
723            provider_id: Some("provider-42".into()),
724            trace_id: Some("trace".into()),
725            correlation_id: Some("corr".into()),
726            deadline: Some(types::InvocationDeadline::from_unix_millis(
727                1_700_000_000_000,
728            )),
729            attempt: 2,
730            idempotency_key: Some("idem".into()),
731            impersonation: Some(types::Impersonation {
732                actor_id: fixture_id("actor"),
733                reason: Some("maintenance".into()),
734            }),
735        }
736    }
737
738    #[test]
739    fn tenant_ctx_roundtrip() {
740        let ctx = sample_tenant_ctx();
741        let wit = match WitTenantCtx::try_from(ctx.clone()) {
742            Ok(value) => value,
743            Err(err) => panic!("failed to map to wit: {err}"),
744        };
745        let back = match types::TenantCtx::try_from(wit) {
746            Ok(value) => value,
747            Err(err) => panic!("failed to map from wit: {err}"),
748        };
749        assert_eq!(back.env.as_str(), ctx.env.as_str());
750        assert_eq!(back.tenant.as_str(), ctx.tenant.as_str());
751        assert!(back.impersonation.is_some());
752        assert!(ctx.impersonation.is_some());
753        assert_eq!(
754            back.impersonation.as_ref().map(|imp| imp.actor_id.as_str()),
755            ctx.impersonation.as_ref().map(|imp| imp.actor_id.as_str())
756        );
757        assert_eq!(back.session_id, ctx.session_id);
758        assert_eq!(back.flow_id, ctx.flow_id);
759        assert_eq!(back.node_id, ctx.node_id);
760        assert_eq!(back.provider_id, ctx.provider_id);
761    }
762
763    #[test]
764    fn outcome_roundtrip() {
765        let pending = types::Outcome::Pending {
766            reason: "waiting".into(),
767            expected_input: Some(vec!["input".into()]),
768        };
769        let wit = WitOutcome::from(pending.clone());
770        let back = types::Outcome::from(wit);
771        match back {
772            types::Outcome::Pending { reason, .. } => {
773                assert_eq!(reason, "waiting");
774            }
775            _ => panic!("expected pending"),
776        }
777    }
778}