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