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