lenso_contract_runtime/
lib.rs1use std::collections::BTreeMap;
4
5use ::serde::{Serialize, de::DeserializeOwned};
6use base64::{Engine as _, engine::general_purpose::STANDARD};
7
8pub type Int64 = String;
10pub type Uint64 = String;
12pub type Timestamp = String;
14pub type Duration = String;
16pub type OptionalValue<T> = Option<Option<T>>;
18
19#[derive(Clone, Debug, Default, Eq, PartialEq)]
21pub struct Bytes(bytes::Bytes);
22
23impl Bytes {
24 pub fn new(value: impl Into<bytes::Bytes>) -> Self {
26 Self(value.into())
27 }
28
29 #[must_use]
31 pub fn as_slice(&self) -> &[u8] {
32 &self.0
33 }
34
35 #[must_use]
37 pub fn into_shared(self) -> bytes::Bytes {
38 self.0
39 }
40
41 #[must_use]
43 pub fn into_vec(self) -> Vec<u8> {
44 self.0.to_vec()
45 }
46}
47
48impl From<Vec<u8>> for Bytes {
49 fn from(value: Vec<u8>) -> Self {
50 Self(value.into())
51 }
52}
53
54impl From<&[u8]> for Bytes {
55 fn from(value: &[u8]) -> Self {
56 Self(bytes::Bytes::copy_from_slice(value))
57 }
58}
59
60impl From<bytes::Bytes> for Bytes {
61 fn from(value: bytes::Bytes) -> Self {
62 Self(value)
63 }
64}
65
66impl From<Bytes> for bytes::Bytes {
67 fn from(value: Bytes) -> Self {
68 value.0
69 }
70}
71
72impl From<Bytes> for Vec<u8> {
73 fn from(value: Bytes) -> Self {
74 value.into_vec()
75 }
76}
77
78impl AsRef<[u8]> for Bytes {
79 fn as_ref(&self) -> &[u8] {
80 self.as_slice()
81 }
82}
83
84impl std::ops::Deref for Bytes {
85 type Target = [u8];
86
87 fn deref(&self) -> &Self::Target {
88 self.as_slice()
89 }
90}
91
92impl Serialize for Bytes {
93 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
94 where
95 S: ::serde::Serializer,
96 {
97 serializer.serialize_str(&STANDARD.encode(self.as_slice()))
98 }
99}
100
101impl<'de> ::serde::Deserialize<'de> for Bytes {
102 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
103 where
104 D: ::serde::Deserializer<'de>,
105 {
106 let encoded = <String as ::serde::Deserialize>::deserialize(deserializer)?;
107 STANDARD
108 .decode(encoded)
109 .map(Self::from)
110 .map_err(|_| ::serde::de::Error::custom("bytes must be canonical padded base64"))
111 }
112}
113
114#[derive(Clone, Debug, PartialEq, Serialize, ::serde::Deserialize)]
116pub struct UnknownDomainError {
117 pub code: String,
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub payload: Option<serde_json::Value>,
122 #[serde(default, flatten)]
124 pub extra: BTreeMap<String, serde_json::Value>,
125}
126
127pub fn encode_portable_json<T: Serialize>(value: &T) -> Result<String, serde_json::Error> {
129 let value = serde_json::to_value(value)?;
130 validate_portable_json_value(&value).map_err(portable_json_error)?;
131 serde_json::to_string(&value)
132}
133
134pub fn decode_portable_json<T: DeserializeOwned>(wire: &str) -> Result<T, serde_json::Error> {
136 let value: serde_json::Value = serde_json::from_str(wire)?;
137 validate_portable_json_value(&value).map_err(portable_json_error)?;
138 serde_json::from_value(value)
139}
140
141pub fn validate_portable_json_value(value: &serde_json::Value) -> Result<(), String> {
143 match value {
144 serde_json::Value::Number(number) => {
145 let safe = number.as_i64().is_some_and(|value| {
146 (-9_007_199_254_740_991..=9_007_199_254_740_991).contains(&value)
147 }) || number
148 .as_u64()
149 .is_some_and(|value| value <= 9_007_199_254_740_991)
150 || (number.is_f64()
151 && number.as_f64().is_some_and(|value| {
152 value.is_finite()
153 && (value.abs() <= 9_007_199_254_740_991.0 || value.fract() != 0.0)
154 }));
155 if !safe {
156 return Err("wire JSON contains an unsafe number".to_owned());
157 }
158 }
159 serde_json::Value::Array(values) => {
160 for value in values {
161 validate_portable_json_value(value)?;
162 }
163 }
164 serde_json::Value::Object(values) => {
165 for value in values.values() {
166 validate_portable_json_value(value)?;
167 }
168 }
169 serde_json::Value::Null | serde_json::Value::Bool(_) | serde_json::Value::String(_) => {}
170 }
171 Ok(())
172}
173
174fn portable_json_error(detail: String) -> serde_json::Error {
175 serde_json::Error::io(std::io::Error::new(std::io::ErrorKind::InvalidData, detail))
176}
177
178pub mod serde {
180 pub fn deserialize_required<'de, D, T>(deserializer: D) -> Result<T, D::Error>
182 where
183 D: ::serde::Deserializer<'de>,
184 T: ::serde::Deserialize<'de>,
185 {
186 <T as ::serde::Deserialize>::deserialize(deserializer)
187 }
188
189 #[allow(clippy::option_option)]
191 pub fn deserialize_optional_value<'de, D, T>(
192 deserializer: D,
193 ) -> Result<Option<Option<T>>, D::Error>
194 where
195 D: ::serde::Deserializer<'de>,
196 T: ::serde::Deserialize<'de>,
197 {
198 Ok(Some(<Option<T> as ::serde::Deserialize>::deserialize(
199 deserializer,
200 )?))
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn bytes_preserve_canonical_padded_base64_wire_behavior() {
210 for length in 0..=258 {
211 let value = Bytes::from(
212 (0..length)
213 .map(|index| u8::try_from(index % 256).unwrap())
214 .collect::<Vec<_>>(),
215 );
216 let wire = serde_json::to_string(&value).unwrap();
217 assert_eq!(serde_json::from_str::<Bytes>(&wire).unwrap(), value);
218 }
219 for wire in [r#""not base64""#, r#""AQI""#, r#""AQJ=""#] {
220 assert!(serde_json::from_str::<Bytes>(wire).is_err());
221 }
222 }
223
224 #[test]
225 fn shared_bytes_round_trip_without_copying() {
226 let source = bytes::Bytes::from_static(b"shared");
227 let contract = Bytes::from(source.clone());
228 assert_eq!(contract.as_slice().as_ptr(), source.as_ptr());
229 let restored = contract.into_shared();
230 assert_eq!(restored.as_ptr(), source.as_ptr());
231 }
232
233 #[test]
234 fn portable_json_rejects_unsafe_integer_values() {
235 let error = decode_portable_json::<serde_json::Value>("9007199254740992").unwrap_err();
236 assert!(error.to_string().contains("unsafe number"));
237 }
238}