1use 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 = crate::canonical::types::TenantCtx;
42type WitImpersonation = crate::canonical::types::Impersonation;
43type WitSessionCursor = crate::canonical::types::SessionCursor;
44type WitOutcome = crate::canonical::types::Outcome;
45type WitOutcomePending = crate::canonical::types::OutcomePending;
46type WitOutcomeError = crate::canonical::types::OutcomeError;
47type WitErrorCode = crate::canonical::types::ErrorCode;
48type WitAllowList = crate::canonical::types::AllowList;
49type WitProtocol = crate::canonical::types::Protocol;
50type WitNetworkPolicy = crate::canonical::types::NetworkPolicy;
51type WitSpanContext = crate::canonical::types::SpanContext;
52type WitPackRef = crate::canonical::types::PackRef;
53type WitSignature = crate::canonical::types::Signature;
54type WitSignatureAlgorithm = crate::canonical::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#[derive(Clone, Debug, PartialEq, Eq)]
71pub enum ComponentOutcomeStatus {
72 Done,
74 Pending,
76 Error,
78}
79
80#[derive(Clone, Debug, PartialEq, Eq)]
82pub struct ComponentOutcome {
83 pub status: ComponentOutcomeStatus,
85 pub code: Option<String>,
87 pub payload: String,
89 pub metadata: Option<String>,
91}
92
93#[derive(Clone, Debug, PartialEq, Eq)]
95pub struct PackDescriptor {
96 pub pack_id: types::PackId,
98 pub version: Version,
100 pub kind: types::PackKind,
102 pub publisher: String,
104}
105
106#[derive(Clone, Debug, PartialEq, Eq)]
108pub struct FlowDescriptor {
109 pub id: types::FlowId,
111 pub kind: types::FlowKind,
113 pub tags: Vec<String>,
115 pub entrypoints: Vec<String>,
117}
118
119pub fn tenant_ctx_from_wit(ctx: WitTenantCtx) -> MapperResult<types::TenantCtx> {
121 types::TenantCtx::try_from(ctx)
122}
123
124pub 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
518pub 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
529pub 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
560pub 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
570pub 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
580pub 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 let tenant_id: types::TenantId = tenant_id.try_into()?;
593 let tenant = tenant_id.clone();
594 let team = team_id
595 .as_ref()
596 .map(|id| id.as_str().try_into())
597 .transpose()?;
598 let team_id = team_id.map(|id| id.try_into()).transpose()?;
599 let user = user_id
600 .as_ref()
601 .map(|id| id.as_str().try_into())
602 .transpose()?;
603 let user_id = user_id.map(|id| id.try_into()).transpose()?;
604
605 Ok(types::TenantCtx {
606 env: env.try_into()?,
607 tenant,
608 tenant_id,
609 team,
610 team_id,
611 user,
612 user_id,
613 session_id,
614 flow_id,
615 node_id,
616 provider_id: None,
617 trace_id: None,
618 i18n_id,
619 correlation_id: None,
620 attributes: BTreeMap::new(),
621 deadline: None,
622 attempt: 0,
623 idempotency_key: None,
624 impersonation: None,
625 })
626}
627
628pub fn tenant_ctx_to_common(ctx: types::TenantCtx) -> MapperResult<WitCommonTenantCtx> {
630 Ok(WitCommonTenantCtx {
631 env: ctx.env.into(),
632 tenant_id: ctx.tenant_id.into(),
633 team_id: ctx.team_id.map(Into::into),
634 user_id: ctx.user_id.map(Into::into),
635 i18n_id: ctx.i18n_id,
636 session_id: ctx.session_id,
637 flow_id: ctx.flow_id,
638 node_id: ctx.node_id,
639 })
640}
641
642pub fn component_outcome_from_wit(outcome: WitComponentOutcome) -> ComponentOutcome {
644 let status = match outcome.status {
645 WitOutcomeStatus::Done => ComponentOutcomeStatus::Done,
646 WitOutcomeStatus::Pending => ComponentOutcomeStatus::Pending,
647 WitOutcomeStatus::Error => ComponentOutcomeStatus::Error,
648 };
649
650 ComponentOutcome {
651 status,
652 code: outcome.code,
653 payload: outcome.payload_json,
654 metadata: outcome.metadata_json,
655 }
656}
657
658pub fn component_outcome_to_wit(outcome: ComponentOutcome) -> WitComponentOutcome {
660 let status = match outcome.status {
661 ComponentOutcomeStatus::Done => WitOutcomeStatus::Done,
662 ComponentOutcomeStatus::Pending => WitOutcomeStatus::Pending,
663 ComponentOutcomeStatus::Error => WitOutcomeStatus::Error,
664 };
665
666 WitComponentOutcome {
667 status,
668 code: outcome.code,
669 payload_json: outcome.payload,
670 metadata_json: outcome.metadata,
671 }
672}
673
674pub fn pack_descriptor_from_wit(desc: WitPackDescriptor) -> MapperResult<PackDescriptor> {
676 Ok(PackDescriptor {
677 pack_id: desc.pack_id.try_into()?,
678 version: Version::parse(&desc.version)
679 .map_err(|err| invalid_input(format!("invalid version: {err}")))?,
680 kind: pack_kind_from_wit(desc.kind),
681 publisher: desc.publisher,
682 })
683}
684
685pub fn pack_descriptor_to_wit(desc: PackDescriptor) -> WitPackDescriptor {
687 WitPackDescriptor {
688 pack_id: desc.pack_id.into(),
689 version: desc.version.to_string(),
690 kind: pack_kind_to_wit(desc.kind),
691 publisher: desc.publisher,
692 }
693}
694
695pub fn flow_descriptor_from_wit(desc: WitFlowDescriptor) -> MapperResult<FlowDescriptor> {
697 Ok(FlowDescriptor {
698 id: desc.id.try_into()?,
699 kind: flow_kind_from_pack_wit(desc.kind),
700 tags: desc.tags,
701 entrypoints: desc.entrypoints,
702 })
703}
704
705pub fn flow_descriptor_to_wit(desc: FlowDescriptor) -> WitFlowDescriptor {
707 WitFlowDescriptor {
708 id: desc.id.into(),
709 kind: flow_kind_to_pack_wit(desc.kind),
710 tags: desc.tags,
711 entrypoints: desc.entrypoints,
712 }
713}
714
715#[cfg(test)]
716mod tests {
717 use super::*;
718 use std::convert::TryFrom;
719
720 fn fixture_id<T>(value: &str) -> T
721 where
722 T: TryFrom<String, Error = types::GreenticError>,
723 {
724 T::try_from(value.to_owned())
725 .unwrap_or_else(|err| panic!("invalid fixture identifier '{value}': {err}"))
726 }
727
728 fn sample_tenant_ctx() -> types::TenantCtx {
729 types::TenantCtx {
730 env: fixture_id("prod"),
731 tenant: fixture_id("tenant-1"),
732 tenant_id: fixture_id("tenant-1"),
733 team: Some(fixture_id("team-42")),
734 team_id: Some(fixture_id("team-42")),
735 user: Some(fixture_id("user-7")),
736 user_id: Some(fixture_id("user-7")),
737 attributes: BTreeMap::new(),
738 session_id: Some("sess-42".into()),
739 flow_id: Some("flow-42".into()),
740 node_id: Some("node-42".into()),
741 provider_id: Some("provider-42".into()),
742 trace_id: Some("trace".into()),
743 i18n_id: Some("en-US".into()),
744 correlation_id: Some("corr".into()),
745 deadline: Some(types::InvocationDeadline::from_unix_millis(
746 1_700_000_000_000,
747 )),
748 attempt: 2,
749 idempotency_key: Some("idem".into()),
750 impersonation: Some(types::Impersonation {
751 actor_id: fixture_id("actor"),
752 reason: Some("maintenance".into()),
753 }),
754 }
755 }
756
757 #[test]
758 fn tenant_ctx_roundtrip() {
759 let ctx = sample_tenant_ctx();
760 let wit = match WitTenantCtx::try_from(ctx.clone()) {
761 Ok(value) => value,
762 Err(err) => panic!("failed to map to wit: {err}"),
763 };
764 let back = match types::TenantCtx::try_from(wit) {
765 Ok(value) => value,
766 Err(err) => panic!("failed to map from wit: {err}"),
767 };
768 assert_eq!(back.env.as_str(), ctx.env.as_str());
769 assert_eq!(back.tenant.as_str(), ctx.tenant.as_str());
770 assert!(back.impersonation.is_some());
771 assert!(ctx.impersonation.is_some());
772 assert_eq!(
773 back.impersonation.as_ref().map(|imp| imp.actor_id.as_str()),
774 ctx.impersonation.as_ref().map(|imp| imp.actor_id.as_str())
775 );
776 assert_eq!(back.session_id, ctx.session_id);
777 assert_eq!(back.flow_id, ctx.flow_id);
778 assert_eq!(back.node_id, ctx.node_id);
779 assert_eq!(back.provider_id, ctx.provider_id);
780 }
781
782 #[test]
783 fn outcome_roundtrip() {
784 let pending = types::Outcome::Pending {
785 reason: "waiting".into(),
786 expected_input: Some(vec!["input".into()]),
787 };
788 let wit = WitOutcome::from(pending.clone());
789 let back = types::Outcome::from(wit);
790 match back {
791 types::Outcome::Pending { reason, .. } => {
792 assert_eq!(reason, "waiting");
793 }
794 _ => panic!("expected pending"),
795 }
796 }
797}