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