lenso_contract_runtime/
lib.rs1use std::collections::BTreeMap;
4
5use ::serde::{Serialize, de::DeserializeOwned};
6use base64::{Engine as _, engine::general_purpose::STANDARD};
7use serde_json::value::RawValue;
8
9pub type Int64 = String;
11pub type Uint64 = String;
13pub type Timestamp = String;
15pub type Duration = String;
17pub type OptionalValue<T> = Option<Option<T>>;
19
20pub struct RawJson(Box<RawValue>);
25
26impl RawJson {
27 pub fn new(value: impl Into<String>) -> Result<Self, serde_json::Error> {
29 let value = value.into();
30 let parsed: serde_json::Value = serde_json::from_str(&value)?;
31 validate_portable_json_value(&parsed).map_err(portable_json_error)?;
32 RawValue::from_string(value).map(Self)
33 }
34
35 #[must_use]
37 pub fn as_str(&self) -> &str {
38 self.0.get()
39 }
40
41 #[must_use]
43 pub fn into_string(self) -> String {
44 self.0.get().to_owned()
45 }
46}
47
48impl Default for RawJson {
49 fn default() -> Self {
50 Self(RawValue::from_string("null".to_owned()).expect("null is valid JSON"))
51 }
52}
53
54impl Clone for RawJson {
55 fn clone(&self) -> Self {
56 Self::new(self.as_str()).expect("RawJson always contains validated JSON")
57 }
58}
59
60impl std::fmt::Debug for RawJson {
61 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 formatter
63 .debug_tuple("RawJson")
64 .field(&self.as_str())
65 .finish()
66 }
67}
68
69impl PartialEq for RawJson {
70 fn eq(&self, other: &Self) -> bool {
71 self.as_str() == other.as_str()
72 }
73}
74
75impl Eq for RawJson {}
76
77impl std::str::FromStr for RawJson {
78 type Err = serde_json::Error;
79
80 fn from_str(value: &str) -> Result<Self, Self::Err> {
81 Self::new(value)
82 }
83}
84
85impl TryFrom<String> for RawJson {
86 type Error = serde_json::Error;
87
88 fn try_from(value: String) -> Result<Self, Self::Error> {
89 Self::new(value)
90 }
91}
92
93impl TryFrom<&str> for RawJson {
94 type Error = serde_json::Error;
95
96 fn try_from(value: &str) -> Result<Self, Self::Error> {
97 Self::new(value)
98 }
99}
100
101impl AsRef<str> for RawJson {
102 fn as_ref(&self) -> &str {
103 self.as_str()
104 }
105}
106
107impl std::ops::Deref for RawJson {
108 type Target = str;
109
110 fn deref(&self) -> &Self::Target {
111 self.as_str()
112 }
113}
114
115impl std::fmt::Display for RawJson {
116 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 formatter.write_str(self.as_str())
118 }
119}
120
121impl Serialize for RawJson {
122 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
123 where
124 S: ::serde::Serializer,
125 {
126 serializer.serialize_str(self.as_str())
127 }
128}
129
130impl<'de> ::serde::Deserialize<'de> for RawJson {
131 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
132 where
133 D: ::serde::Deserializer<'de>,
134 {
135 let value = <String as ::serde::Deserialize>::deserialize(deserializer)?;
136 Self::new(value).map_err(::serde::de::Error::custom)
137 }
138}
139
140#[derive(Clone, Debug, Default, Eq, PartialEq)]
142pub struct Json<T>(T);
143
144impl<T> Json<T> {
145 pub const fn new(value: T) -> Self {
147 Self(value)
148 }
149
150 pub const fn as_inner(&self) -> &T {
152 &self.0
153 }
154
155 pub fn into_inner(self) -> T {
157 self.0
158 }
159}
160
161impl<T> From<T> for Json<T> {
162 fn from(value: T) -> Self {
163 Self::new(value)
164 }
165}
166
167impl<T> std::ops::Deref for Json<T> {
168 type Target = T;
169
170 fn deref(&self) -> &Self::Target {
171 self.as_inner()
172 }
173}
174
175impl<T: Serialize> Serialize for Json<T> {
176 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
177 where
178 S: ::serde::Serializer,
179 {
180 let encoded = encode_portable_json(&self.0).map_err(::serde::ser::Error::custom)?;
181 serializer.serialize_str(&encoded)
182 }
183}
184
185impl<'de, T: DeserializeOwned> ::serde::Deserialize<'de> for Json<T> {
186 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
187 where
188 D: ::serde::Deserializer<'de>,
189 {
190 let encoded = <String as ::serde::Deserialize>::deserialize(deserializer)?;
191 decode_portable_json(&encoded)
192 .map(Self)
193 .map_err(::serde::de::Error::custom)
194 }
195}
196
197#[derive(Clone, Debug, Default, Eq, PartialEq)]
199pub struct Bytes(bytes::Bytes);
200
201impl Bytes {
202 pub fn new(value: impl Into<bytes::Bytes>) -> Self {
204 Self(value.into())
205 }
206
207 #[must_use]
209 pub fn as_slice(&self) -> &[u8] {
210 &self.0
211 }
212
213 #[must_use]
215 pub fn into_shared(self) -> bytes::Bytes {
216 self.0
217 }
218
219 #[must_use]
221 pub fn into_vec(self) -> Vec<u8> {
222 self.0.to_vec()
223 }
224}
225
226impl From<Vec<u8>> for Bytes {
227 fn from(value: Vec<u8>) -> Self {
228 Self(value.into())
229 }
230}
231
232impl From<&[u8]> for Bytes {
233 fn from(value: &[u8]) -> Self {
234 Self(bytes::Bytes::copy_from_slice(value))
235 }
236}
237
238impl From<bytes::Bytes> for Bytes {
239 fn from(value: bytes::Bytes) -> Self {
240 Self(value)
241 }
242}
243
244impl From<Bytes> for bytes::Bytes {
245 fn from(value: Bytes) -> Self {
246 value.0
247 }
248}
249
250impl From<Bytes> for Vec<u8> {
251 fn from(value: Bytes) -> Self {
252 value.into_vec()
253 }
254}
255
256impl AsRef<[u8]> for Bytes {
257 fn as_ref(&self) -> &[u8] {
258 self.as_slice()
259 }
260}
261
262impl std::ops::Deref for Bytes {
263 type Target = [u8];
264
265 fn deref(&self) -> &Self::Target {
266 self.as_slice()
267 }
268}
269
270impl Serialize for Bytes {
271 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
272 where
273 S: ::serde::Serializer,
274 {
275 serializer.serialize_str(&STANDARD.encode(self.as_slice()))
276 }
277}
278
279impl<'de> ::serde::Deserialize<'de> for Bytes {
280 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
281 where
282 D: ::serde::Deserializer<'de>,
283 {
284 let encoded = <String as ::serde::Deserialize>::deserialize(deserializer)?;
285 STANDARD
286 .decode(encoded)
287 .map(Self::from)
288 .map_err(|_| ::serde::de::Error::custom("bytes must be canonical padded base64"))
289 }
290}
291
292#[derive(Clone, Debug, PartialEq, Serialize, ::serde::Deserialize)]
294pub struct UnknownDomainError {
295 pub code: String,
297 #[serde(skip_serializing_if = "Option::is_none")]
299 pub payload: Option<serde_json::Value>,
300 #[serde(default, flatten)]
302 pub extra: BTreeMap<String, serde_json::Value>,
303}
304
305pub fn encode_portable_json<T: Serialize>(value: &T) -> Result<String, serde_json::Error> {
307 let value = serde_json::to_value(value)?;
308 validate_portable_json_value(&value).map_err(portable_json_error)?;
309 serde_json::to_string(&value)
310}
311
312pub fn decode_portable_json<T: DeserializeOwned>(wire: &str) -> Result<T, serde_json::Error> {
314 let value: serde_json::Value = serde_json::from_str(wire)?;
315 validate_portable_json_value(&value).map_err(portable_json_error)?;
316 serde_json::from_value(value)
317}
318
319pub fn validate_portable_json_value(value: &serde_json::Value) -> Result<(), String> {
321 match value {
322 serde_json::Value::Number(number) => {
323 let safe = number.as_i64().is_some_and(|value| {
324 (-9_007_199_254_740_991..=9_007_199_254_740_991).contains(&value)
325 }) || number
326 .as_u64()
327 .is_some_and(|value| value <= 9_007_199_254_740_991)
328 || (number.is_f64()
329 && number.as_f64().is_some_and(|value| {
330 value.is_finite()
331 && (value.abs() <= 9_007_199_254_740_991.0 || value.fract() != 0.0)
332 }));
333 if !safe {
334 return Err("wire JSON contains an unsafe number".to_owned());
335 }
336 }
337 serde_json::Value::Array(values) => {
338 for value in values {
339 validate_portable_json_value(value)?;
340 }
341 }
342 serde_json::Value::Object(values) => {
343 for value in values.values() {
344 validate_portable_json_value(value)?;
345 }
346 }
347 serde_json::Value::Null | serde_json::Value::Bool(_) | serde_json::Value::String(_) => {}
348 }
349 Ok(())
350}
351
352fn portable_json_error(detail: String) -> serde_json::Error {
353 serde_json::Error::io(std::io::Error::new(std::io::ErrorKind::InvalidData, detail))
354}
355
356pub mod serde {
358 pub fn deserialize_required<'de, D, T>(deserializer: D) -> Result<T, D::Error>
360 where
361 D: ::serde::Deserializer<'de>,
362 T: ::serde::Deserialize<'de>,
363 {
364 <T as ::serde::Deserialize>::deserialize(deserializer)
365 }
366
367 #[allow(clippy::option_option)]
369 pub fn deserialize_optional_value<'de, D, T>(
370 deserializer: D,
371 ) -> Result<Option<Option<T>>, D::Error>
372 where
373 D: ::serde::Deserializer<'de>,
374 T: ::serde::Deserialize<'de>,
375 {
376 Ok(Some(<Option<T> as ::serde::Deserialize>::deserialize(
377 deserializer,
378 )?))
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385
386 #[test]
387 fn bytes_preserve_canonical_padded_base64_wire_behavior() {
388 for length in 0..=258 {
389 let value = Bytes::from(
390 (0..length)
391 .map(|index| u8::try_from(index % 256).unwrap())
392 .collect::<Vec<_>>(),
393 );
394 let wire = serde_json::to_string(&value).unwrap();
395 assert_eq!(serde_json::from_str::<Bytes>(&wire).unwrap(), value);
396 }
397 for wire in [r#""not base64""#, r#""AQI""#, r#""AQJ=""#] {
398 assert!(serde_json::from_str::<Bytes>(wire).is_err());
399 }
400 }
401
402 #[test]
403 fn shared_bytes_round_trip_without_copying() {
404 let source = bytes::Bytes::from_static(b"shared");
405 let contract = Bytes::from(source.clone());
406 assert_eq!(contract.as_slice().as_ptr(), source.as_ptr());
407 let restored = contract.into_shared();
408 assert_eq!(restored.as_ptr(), source.as_ptr());
409 }
410
411 #[test]
412 fn portable_json_rejects_unsafe_integer_values() {
413 let error = decode_portable_json::<serde_json::Value>("9007199254740992").unwrap_err();
414 assert!(error.to_string().contains("unsafe number"));
415 }
416
417 #[test]
418 fn raw_json_validates_content_without_changing_the_string_wire_shape() {
419 let raw = RawJson::new(r#"{"ready":true}"#).unwrap();
420 assert_eq!(
421 serde_json::to_string(&raw).unwrap(),
422 r#""{\"ready\":true}""#
423 );
424 assert_eq!(
425 serde_json::from_str::<RawJson>(r#""{\"ready\":true}""#).unwrap(),
426 raw
427 );
428 assert!(RawJson::new("not JSON").is_err());
429 assert!(RawJson::new("9007199254740992").is_err());
430 assert_eq!(raw.as_ref(), r#"{"ready":true}"#);
431 assert_eq!(raw.to_string(), r#"{"ready":true}"#);
432 }
433
434 #[test]
435 fn typed_json_round_trips_as_one_json_string() {
436 let value = Json::new(BTreeMap::from([("answer".to_owned(), 42_i32)]));
437 let wire = serde_json::to_string(&value).unwrap();
438 assert_eq!(wire, r#""{\"answer\":42}""#);
439 assert_eq!(
440 serde_json::from_str::<Json<BTreeMap<String, i32>>>(&wire).unwrap(),
441 value
442 );
443 }
444}