seam_core/input.rs
1//! What the validator needs from a payload, and nothing more.
2//!
3//! Validation used to require a [`Value`], which meant every binding copied its
4//! host's objects into one before a single rule ran. That copy was pure
5//! overhead: allocated, walked once, dropped. Bindings now implement this trait
6//! for their own runtime's objects and the copy disappears.
7//!
8//! [`Value`] still implements it, so building one by hand stays valid.
9//!
10//! [`Value`]: crate::value::Value
11
12use std::borrow::Cow;
13
14use crate::value::{Int, Slot};
15
16/// What a value is, before asking what it holds.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Kind {
19 Null,
20 Bool,
21 Int,
22 Float,
23 String,
24 Array,
25 Object,
26 /// An integer the host's own numeric type cannot represent exactly.
27 ///
28 /// A JavaScript `number` above 2^53 is already wrong by the time Seam sees
29 /// it. Nothing here can recover the value, so the only honest answer is to
30 /// say so rather than validate a number that is quietly not the one sent.
31 UnsafeInteger,
32 /// A real integer, but wider than 64 bits, so the model cannot hold it.
33 ///
34 /// Its own kind rather than `Foreign` because truncating here is the exact
35 /// bug Seam exists to prevent, and the caller deserves to be told which of
36 /// the two happened.
37 IntegerTooWide,
38 /// Something the model has no place for. A binding reports it rather than
39 /// guessing.
40 Foreign,
41}
42
43impl Kind {
44 pub fn name(self) -> &'static str {
45 match self {
46 Kind::Null => "null",
47 Kind::Bool => "bool",
48 Kind::Int => "integer",
49 Kind::Float => "float",
50 Kind::String => "string",
51 Kind::Array => "array",
52 Kind::Object => "object",
53 Kind::UnsafeInteger => "integer beyond exact precision",
54 Kind::IntegerTooWide => "integer wider than 64 bits",
55 Kind::Foreign => "unsupported value",
56 }
57 }
58}
59
60/// A payload the validator can read in place.
61///
62/// Accessors return `None` when the value is not of that kind, so a caller
63/// never has to check twice. They are infallible on purpose: a binding that
64/// cannot read its own object has a bug, not a validation failure.
65pub trait Input {
66 /// An element of an array, or the value at a key.
67 /// Implementations should make this converge, usually to themselves or to a
68 /// reference to themselves. A type whose child is a strictly new type on
69 /// every level would make the validator recurse forever at compile time.
70 type Child<'a>: Input
71 where
72 Self: 'a;
73
74 fn kind(&self) -> Kind;
75
76 fn as_bool(&self) -> Option<bool>;
77
78 fn as_int(&self) -> Option<Int>;
79
80 fn as_f64(&self) -> Option<f64>;
81
82 fn as_str(&self) -> Option<Cow<'_, str>>;
83
84 /// Elements for an array, keys for an object, zero otherwise.
85 fn len(&self) -> usize;
86
87 fn is_empty(&self) -> bool {
88 self.len() == 0
89 }
90
91 fn item(&self, index: usize) -> Option<Self::Child<'_>>;
92
93 /// Reads a key without collapsing absence into null.
94 fn slot(&self, key: &str) -> Slot<Self::Child<'_>>;
95
96 /// Visits every key of an object, for the unknown-field check.
97 fn each_key(&self, f: &mut dyn FnMut(&str));
98}
99
100impl<T: Input> Input for &T {
101 type Child<'a>
102 = T::Child<'a>
103 where
104 Self: 'a;
105
106 fn kind(&self) -> Kind {
107 (**self).kind()
108 }
109
110 fn as_bool(&self) -> Option<bool> {
111 (**self).as_bool()
112 }
113
114 fn as_int(&self) -> Option<Int> {
115 (**self).as_int()
116 }
117
118 fn as_f64(&self) -> Option<f64> {
119 (**self).as_f64()
120 }
121
122 fn as_str(&self) -> Option<Cow<'_, str>> {
123 (**self).as_str()
124 }
125
126 fn len(&self) -> usize {
127 (**self).len()
128 }
129
130 fn item(&self, index: usize) -> Option<Self::Child<'_>> {
131 (**self).item(index)
132 }
133
134 fn slot(&self, key: &str) -> Slot<Self::Child<'_>> {
135 (**self).slot(key)
136 }
137
138 fn each_key(&self, f: &mut dyn FnMut(&str)) {
139 (**self).each_key(f);
140 }
141}