1use std::fmt;
16
17use serde::de::{self, Deserializer, Unexpected, Visitor};
18use serde::{Deserialize, Serialize, Serializer};
19
20pub const SCALAR_ERROR_MARKER: &str = "wire scalar rejected";
23
24pub const MAX_ID_BYTES: usize = 256;
26
27pub const JS_SAFE_INTEGER_MAX: u64 = (1 << 53) - 1;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct WireScalarError {
34 pub message: String,
35}
36
37impl WireScalarError {
38 pub fn new(message: impl Into<String>) -> Self {
39 Self {
40 message: message.into(),
41 }
42 }
43}
44
45impl fmt::Display for WireScalarError {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 write!(f, "{SCALAR_ERROR_MARKER}: {}", self.message)
48 }
49}
50
51impl std::error::Error for WireScalarError {}
52
53fn scalar_error<E: de::Error>(message: impl fmt::Display) -> E {
54 E::custom(format!("{SCALAR_ERROR_MARKER}: {message}"))
55}
56
57#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
67pub struct WireU64(u64);
68
69impl WireU64 {
70 pub const ZERO: Self = Self(0);
71
72 pub const fn new(value: u64) -> Self {
73 Self(value)
74 }
75
76 pub const fn get(self) -> u64 {
77 self.0
78 }
79
80 pub const fn is_js_safe(self) -> bool {
83 self.0 <= JS_SAFE_INTEGER_MAX
84 }
85
86 pub fn parse(text: &str) -> Result<Self, WireScalarError> {
87 if text.is_empty() {
88 return Err(WireScalarError::new("u64 decimal string is empty"));
89 }
90 if !text.bytes().all(|b| b.is_ascii_digit()) {
91 return Err(WireScalarError::new(format!(
92 "u64 must be a canonical decimal string, got {text:?}"
93 )));
94 }
95 if text.len() > 1 && text.starts_with('0') {
96 return Err(WireScalarError::new(format!(
97 "u64 decimal string must not have leading zeros, got {text:?}"
98 )));
99 }
100 text.parse::<u64>().map(Self).map_err(|_| {
101 WireScalarError::new(format!("u64 decimal string {text:?} is out of range"))
102 })
103 }
104}
105
106impl fmt::Display for WireU64 {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 write!(f, "{}", self.0)
109 }
110}
111
112impl From<u64> for WireU64 {
113 fn from(value: u64) -> Self {
114 Self(value)
115 }
116}
117
118impl Serialize for WireU64 {
119 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
120 serializer.collect_str(&self.0)
121 }
122}
123
124struct WireU64Visitor;
125
126impl Visitor<'_> for WireU64Visitor {
127 type Value = WireU64;
128
129 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 f.write_str("a canonical decimal string encoding a u64")
131 }
132
133 fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
134 WireU64::parse(value).map_err(|err| scalar_error(err.message))
135 }
136
137 fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
138 Err(scalar_error(format!(
139 "u64 must be a decimal string, got the JSON number {value}"
140 )))
141 }
142
143 fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
144 Err(scalar_error(format!(
145 "u64 must be a decimal string, got the JSON number {value}"
146 )))
147 }
148
149 fn visit_f64<E: de::Error>(self, value: f64) -> Result<Self::Value, E> {
150 Err(scalar_error(format!(
151 "u64 must be a decimal string, got the JSON number {value}"
152 )))
153 }
154
155 fn visit_bool<E: de::Error>(self, value: bool) -> Result<Self::Value, E> {
156 Err(scalar_error(format!(
157 "u64 must be a decimal string, got the boolean {value}"
158 )))
159 }
160
161 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
162 Err(scalar_error("u64 must be a decimal string, got null"))
163 }
164}
165
166impl<'de> Deserialize<'de> for WireU64 {
167 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
168 deserializer.deserialize_any(WireU64Visitor)
172 }
173}
174
175#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
188pub struct Ppm(u32);
189
190impl Ppm {
191 pub const ZERO: Self = Self(0);
192 pub const ONE: Self = Self(1_000_000);
193 pub const MAX_PPM: u32 = 1_000_000;
194
195 pub fn new(parts_per_million: u32) -> Result<Self, WireScalarError> {
196 if parts_per_million > Self::MAX_PPM {
197 return Err(WireScalarError::new(format!(
198 "ratio {parts_per_million} ppm exceeds 1.0 ({} ppm)",
199 Self::MAX_PPM
200 )));
201 }
202 Ok(Self(parts_per_million))
203 }
204
205 pub const fn get(self) -> u32 {
206 self.0
207 }
208
209 pub fn as_ratio(self) -> f64 {
211 f64::from(self.0) / f64::from(Self::MAX_PPM)
212 }
213
214 pub fn from_ratio(ratio: f64) -> Result<Self, WireScalarError> {
217 if !ratio.is_finite() {
218 return Err(WireScalarError::new("ratio must be finite"));
219 }
220 if !(0.0..=1.0).contains(&ratio) {
221 return Err(WireScalarError::new(format!(
222 "ratio {ratio} is outside [0.0, 1.0]"
223 )));
224 }
225 Self::new((ratio * f64::from(Self::MAX_PPM)).round() as u32)
226 }
227}
228
229impl Serialize for Ppm {
230 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
231 serializer.serialize_u32(self.0)
232 }
233}
234
235struct PpmVisitor;
236
237impl Visitor<'_> for PpmVisitor {
238 type Value = Ppm;
239
240 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 f.write_str("an integer number of parts-per-million in [0, 1000000]")
242 }
243
244 fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
245 u32::try_from(value)
246 .map_err(|_| scalar_error::<E>(format!("{value} ppm is out of range")))
247 .and_then(|value| Ppm::new(value).map_err(|err| scalar_error(err.message)))
248 }
249
250 fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
251 if value < 0 {
252 return Err(scalar_error(format!(
253 "ppm must not be negative, got {value}"
254 )));
255 }
256 self.visit_u64(value as u64)
257 }
258
259 fn visit_f64<E: de::Error>(self, value: f64) -> Result<Self::Value, E> {
260 Err(scalar_error(format!(
261 "policy ratios are fixed-point parts-per-million integers, got the float {value}"
262 )))
263 }
264
265 fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
266 Err(scalar_error(format!(
267 "ppm must be a JSON integer, got the string {value:?}"
268 )))
269 }
270
271 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
272 Err(scalar_error("ppm must be a JSON integer, got null"))
273 }
274}
275
276impl<'de> Deserialize<'de> for Ppm {
277 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
278 deserializer.deserialize_any(PpmVisitor)
279 }
280}
281
282#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
290pub struct FiniteF64(f64);
291
292impl FiniteF64 {
293 pub fn new(value: f64) -> Result<Self, WireScalarError> {
294 if !value.is_finite() {
295 return Err(WireScalarError::new(format!(
296 "observation float must be finite, got {value}"
297 )));
298 }
299 Ok(Self(value))
300 }
301
302 pub const fn get(self) -> f64 {
303 self.0
304 }
305}
306
307impl Serialize for FiniteF64 {
308 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
309 serializer.serialize_f64(self.0)
310 }
311}
312
313struct FiniteF64Visitor;
314
315impl Visitor<'_> for FiniteF64Visitor {
316 type Value = FiniteF64;
317
318 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 f.write_str("a finite JSON number")
320 }
321
322 fn visit_f64<E: de::Error>(self, value: f64) -> Result<Self::Value, E> {
323 FiniteF64::new(value).map_err(|err| scalar_error(err.message))
324 }
325
326 fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
327 FiniteF64::new(value as f64).map_err(|err| scalar_error(err.message))
328 }
329
330 fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
331 FiniteF64::new(value as f64).map_err(|err| scalar_error(err.message))
332 }
333
334 fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
335 Err(scalar_error(format!(
336 "observation float must be a JSON number, got the string {value:?}"
337 )))
338 }
339}
340
341impl<'de> Deserialize<'de> for FiniteF64 {
342 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
343 deserializer.deserialize_any(FiniteF64Visitor)
344 }
345}
346
347#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
358pub struct CanonicalBytes(Vec<u8>);
359
360impl CanonicalBytes {
361 pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
362 Self(bytes.into())
363 }
364
365 pub fn as_slice(&self) -> &[u8] {
366 &self.0
367 }
368
369 pub fn into_vec(self) -> Vec<u8> {
370 self.0
371 }
372
373 pub fn len(&self) -> usize {
374 self.0.len()
375 }
376
377 pub fn is_empty(&self) -> bool {
378 self.0.is_empty()
379 }
380}
381
382#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
383#[serde(rename_all = "snake_case")]
384enum BytesEncoding {
385 Base64,
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize)]
389#[serde(deny_unknown_fields)]
390struct CanonicalBytesProjection {
391 encoding: BytesEncoding,
392 data: String,
393}
394
395impl Serialize for CanonicalBytes {
396 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
397 CanonicalBytesProjection {
398 encoding: BytesEncoding::Base64,
399 data: base64_encode(&self.0),
400 }
401 .serialize(serializer)
402 }
403}
404
405impl<'de> Deserialize<'de> for CanonicalBytes {
406 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
407 let projection = CanonicalBytesProjection::deserialize(deserializer)?;
408 base64_decode(&projection.data)
409 .map(Self)
410 .map_err(|err| scalar_error(err.message))
411 }
412}
413
414const BASE64_ALPHABET: &[u8; 64] =
415 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
416
417fn base64_encode(bytes: &[u8]) -> String {
418 let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
419 for chunk in bytes.chunks(3) {
420 let b0 = chunk[0] as u32;
421 let b1 = *chunk.get(1).unwrap_or(&0) as u32;
422 let b2 = *chunk.get(2).unwrap_or(&0) as u32;
423 let triple = (b0 << 16) | (b1 << 8) | b2;
424 out.push(BASE64_ALPHABET[(triple >> 18) as usize & 0x3f] as char);
425 out.push(BASE64_ALPHABET[(triple >> 12) as usize & 0x3f] as char);
426 out.push(if chunk.len() > 1 {
427 BASE64_ALPHABET[(triple >> 6) as usize & 0x3f] as char
428 } else {
429 '='
430 });
431 out.push(if chunk.len() > 2 {
432 BASE64_ALPHABET[triple as usize & 0x3f] as char
433 } else {
434 '='
435 });
436 }
437 out
438}
439
440fn base64_value(byte: u8) -> Option<u32> {
441 match byte {
442 b'A'..=b'Z' => Some(u32::from(byte - b'A')),
443 b'a'..=b'z' => Some(u32::from(byte - b'a') + 26),
444 b'0'..=b'9' => Some(u32::from(byte - b'0') + 52),
445 b'+' => Some(62),
446 b'/' => Some(63),
447 _ => None,
448 }
449}
450
451fn base64_decode(text: &str) -> Result<Vec<u8>, WireScalarError> {
455 let bytes = text.as_bytes();
456 if !bytes.len().is_multiple_of(4) {
457 return Err(WireScalarError::new(
458 "base64 payload length must be a multiple of 4 (canonical padding)",
459 ));
460 }
461 let mut out = Vec::with_capacity(bytes.len() / 4 * 3);
462 for (index, chunk) in bytes.chunks(4).enumerate() {
463 let is_last = index == bytes.len() / 4 - 1;
464 let pad = chunk.iter().filter(|&&b| b == b'=').count();
465 if pad > 0 && !is_last {
466 return Err(WireScalarError::new(
467 "base64 padding may only end the payload",
468 ));
469 }
470 if pad > 2 || (pad > 0 && chunk[3] != b'=') || (pad == 2 && chunk[2] != b'=') {
471 return Err(WireScalarError::new("malformed base64 padding"));
472 }
473 let mut triple = 0u32;
474 for (position, &byte) in chunk.iter().enumerate() {
475 let value = if byte == b'=' {
476 0
477 } else {
478 base64_value(byte).ok_or_else(|| {
479 WireScalarError::new(format!("illegal base64 character {:?}", byte as char))
480 })?
481 };
482 triple |= value << (18 - 6 * position);
483 }
484 out.push((triple >> 16) as u8);
485 if pad < 2 {
486 out.push((triple >> 8) as u8);
487 }
488 if pad < 1 {
489 out.push(triple as u8);
490 }
491 }
492 Ok(out)
493}
494
495pub const BOUNDED_JSON_MAX_DEPTH: usize = 64;
506pub const BOUNDED_JSON_MAX_ENTRIES: usize = 1024;
508
509#[derive(Debug, Clone, Default, PartialEq)]
513pub struct BoundedJson(serde_json::Value);
514
515impl BoundedJson {
516 pub fn new(value: serde_json::Value) -> Result<Self, WireScalarError> {
517 validate_bounded(&value, 1)?;
518 Ok(Self(value))
519 }
520
521 pub fn null() -> Self {
522 Self(serde_json::Value::Null)
523 }
524
525 pub fn get(&self) -> &serde_json::Value {
526 &self.0
527 }
528
529 pub fn into_value(self) -> serde_json::Value {
530 self.0
531 }
532
533 pub fn is_null(&self) -> bool {
534 self.0.is_null()
535 }
536}
537
538fn validate_bounded(value: &serde_json::Value, depth: usize) -> Result<(), WireScalarError> {
539 if depth > BOUNDED_JSON_MAX_DEPTH {
540 return Err(WireScalarError::new(format!(
541 "payload nests deeper than {BOUNDED_JSON_MAX_DEPTH}"
542 )));
543 }
544 match value {
545 serde_json::Value::Array(items) => {
546 if items.len() > BOUNDED_JSON_MAX_ENTRIES {
547 return Err(WireScalarError::new(format!(
548 "payload container has {} entries; the bound is {BOUNDED_JSON_MAX_ENTRIES}",
549 items.len()
550 )));
551 }
552 items
553 .iter()
554 .try_for_each(|item| validate_bounded(item, depth + 1))
555 }
556 serde_json::Value::Object(map) => {
557 if map.len() > BOUNDED_JSON_MAX_ENTRIES {
558 return Err(WireScalarError::new(format!(
559 "payload container has {} entries; the bound is {BOUNDED_JSON_MAX_ENTRIES}",
560 map.len()
561 )));
562 }
563 map.values()
564 .try_for_each(|item| validate_bounded(item, depth + 1))
565 }
566 serde_json::Value::Number(number) => {
567 match number.as_f64() {
570 Some(float) if !float.is_finite() => {
571 Err(WireScalarError::new("payload contains a non-finite number"))
572 }
573 _ => Ok(()),
574 }
575 }
576 _ => Ok(()),
577 }
578}
579
580impl Serialize for BoundedJson {
581 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
582 self.0.serialize(serializer)
583 }
584}
585
586impl<'de> Deserialize<'de> for BoundedJson {
587 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
588 let value = serde_json::Value::deserialize(deserializer)?;
589 Self::new(value).map_err(|err| scalar_error(err.message))
590 }
591}
592
593fn validate_id(label: &'static str, value: &str) -> Result<(), WireScalarError> {
598 if value.is_empty() {
599 return Err(WireScalarError::new(format!("{label} must not be empty")));
600 }
601 if value.len() > MAX_ID_BYTES {
602 return Err(WireScalarError::new(format!(
603 "{label} is {} bytes; the bound is {MAX_ID_BYTES}",
604 value.len()
605 )));
606 }
607 if value.chars().any(char::is_control) {
608 return Err(WireScalarError::new(format!(
609 "{label} must not contain control characters"
610 )));
611 }
612 Ok(())
613}
614
615macro_rules! wire_id {
616 ($(#[$doc:meta])* $name:ident, $label:literal) => {
617 $(#[$doc])*
618 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
619 pub struct $name(String);
620
621 impl $name {
622 pub fn new(value: impl Into<String>) -> Result<Self, WireScalarError> {
623 let value = value.into();
624 validate_id($label, &value)?;
625 Ok(Self(value))
626 }
627
628 pub fn as_str(&self) -> &str {
629 &self.0
630 }
631
632 pub fn into_string(self) -> String {
633 self.0
634 }
635 }
636
637 impl fmt::Display for $name {
638 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
639 f.write_str(&self.0)
640 }
641 }
642
643 impl Serialize for $name {
644 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
645 serializer.serialize_str(&self.0)
646 }
647 }
648
649 impl<'de> Deserialize<'de> for $name {
650 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
651 struct IdVisitor;
652
653 impl Visitor<'_> for IdVisitor {
654 type Value = $name;
655
656 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
657 f.write_str(concat!("a non-empty ", $label, " string"))
658 }
659
660 fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
661 $name::new(value).map_err(|err| scalar_error(err.message))
662 }
663
664 fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
665 Err(scalar_error(format_args!(
666 "{} must be a branded string, got the number {}",
667 $label,
668 Unexpected::Unsigned(value)
669 )))
670 }
671
672 fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
673 Err(scalar_error(format_args!(
674 "{} must be a branded string, got the number {}",
675 $label,
676 Unexpected::Signed(value)
677 )))
678 }
679
680 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
681 Err(scalar_error(concat!($label, " must be a branded string, got null")))
682 }
683 }
684
685 deserializer.deserialize_any(IdVisitor)
686 }
687 }
688 };
689}
690
691wire_id!(
692 OperationId,
694 "operation id"
695);
696wire_id!(
697 InputId,
700 "input id"
701);
702wire_id!(
703 EffectId,
705 "effect id"
706);
707wire_id!(
708 CallId,
710 "call id"
711);
712wire_id!(
713 TaskId,
715 "task id"
716);
717wire_id!(
718 AttemptId,
720 "attempt id"
721);
722wire_id!(
723 WorkflowId,
725 "workflow id"
726);
727wire_id!(
728 NodeId,
730 "node id"
731);
732wire_id!(
733 SignalId,
735 "signal id"
736);
737wire_id!(
738 DeliveryId,
740 "delivery id"
741);
742wire_id!(
743 HandleId,
745 "handle id"
746);
747wire_id!(
748 MemoryBindingId,
750 "memory binding id"
751);
752
753impl Ppm {
758 pub const fn from_ppm_const(parts_per_million: u32) -> Self {
765 if parts_per_million > Self::MAX_PPM {
766 Self(Self::MAX_PPM)
767 } else {
768 Self(parts_per_million)
769 }
770 }
771}