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 Serialize for RawJson {
94 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
95 where
96 S: ::serde::Serializer,
97 {
98 serializer.serialize_str(self.as_str())
99 }
100}
101
102impl<'de> ::serde::Deserialize<'de> for RawJson {
103 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
104 where
105 D: ::serde::Deserializer<'de>,
106 {
107 let value = <String as ::serde::Deserialize>::deserialize(deserializer)?;
108 Self::new(value).map_err(::serde::de::Error::custom)
109 }
110}
111
112#[derive(Clone, Debug, Default, Eq, PartialEq)]
114pub struct Json<T>(T);
115
116impl<T> Json<T> {
117 pub const fn new(value: T) -> Self {
119 Self(value)
120 }
121
122 pub const fn as_inner(&self) -> &T {
124 &self.0
125 }
126
127 pub fn into_inner(self) -> T {
129 self.0
130 }
131}
132
133impl<T> From<T> for Json<T> {
134 fn from(value: T) -> Self {
135 Self::new(value)
136 }
137}
138
139impl<T> std::ops::Deref for Json<T> {
140 type Target = T;
141
142 fn deref(&self) -> &Self::Target {
143 self.as_inner()
144 }
145}
146
147impl<T: Serialize> Serialize for Json<T> {
148 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
149 where
150 S: ::serde::Serializer,
151 {
152 let encoded = encode_portable_json(&self.0).map_err(::serde::ser::Error::custom)?;
153 serializer.serialize_str(&encoded)
154 }
155}
156
157impl<'de, T: DeserializeOwned> ::serde::Deserialize<'de> for Json<T> {
158 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159 where
160 D: ::serde::Deserializer<'de>,
161 {
162 let encoded = <String as ::serde::Deserialize>::deserialize(deserializer)?;
163 decode_portable_json(&encoded)
164 .map(Self)
165 .map_err(::serde::de::Error::custom)
166 }
167}
168
169#[derive(Clone, Debug, Default, Eq, PartialEq)]
171pub struct Bytes(bytes::Bytes);
172
173impl Bytes {
174 pub fn new(value: impl Into<bytes::Bytes>) -> Self {
176 Self(value.into())
177 }
178
179 #[must_use]
181 pub fn as_slice(&self) -> &[u8] {
182 &self.0
183 }
184
185 #[must_use]
187 pub fn into_shared(self) -> bytes::Bytes {
188 self.0
189 }
190
191 #[must_use]
193 pub fn into_vec(self) -> Vec<u8> {
194 self.0.to_vec()
195 }
196}
197
198impl From<Vec<u8>> for Bytes {
199 fn from(value: Vec<u8>) -> Self {
200 Self(value.into())
201 }
202}
203
204impl From<&[u8]> for Bytes {
205 fn from(value: &[u8]) -> Self {
206 Self(bytes::Bytes::copy_from_slice(value))
207 }
208}
209
210impl From<bytes::Bytes> for Bytes {
211 fn from(value: bytes::Bytes) -> Self {
212 Self(value)
213 }
214}
215
216impl From<Bytes> for bytes::Bytes {
217 fn from(value: Bytes) -> Self {
218 value.0
219 }
220}
221
222impl From<Bytes> for Vec<u8> {
223 fn from(value: Bytes) -> Self {
224 value.into_vec()
225 }
226}
227
228impl AsRef<[u8]> for Bytes {
229 fn as_ref(&self) -> &[u8] {
230 self.as_slice()
231 }
232}
233
234impl std::ops::Deref for Bytes {
235 type Target = [u8];
236
237 fn deref(&self) -> &Self::Target {
238 self.as_slice()
239 }
240}
241
242impl Serialize for Bytes {
243 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
244 where
245 S: ::serde::Serializer,
246 {
247 serializer.serialize_str(&STANDARD.encode(self.as_slice()))
248 }
249}
250
251impl<'de> ::serde::Deserialize<'de> for Bytes {
252 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
253 where
254 D: ::serde::Deserializer<'de>,
255 {
256 let encoded = <String as ::serde::Deserialize>::deserialize(deserializer)?;
257 STANDARD
258 .decode(encoded)
259 .map(Self::from)
260 .map_err(|_| ::serde::de::Error::custom("bytes must be canonical padded base64"))
261 }
262}
263
264#[derive(Clone, Debug, PartialEq, Serialize, ::serde::Deserialize)]
266pub struct UnknownDomainError {
267 pub code: String,
269 #[serde(skip_serializing_if = "Option::is_none")]
271 pub payload: Option<serde_json::Value>,
272 #[serde(default, flatten)]
274 pub extra: BTreeMap<String, serde_json::Value>,
275}
276
277pub fn encode_portable_json<T: Serialize>(value: &T) -> Result<String, serde_json::Error> {
279 let value = serde_json::to_value(value)?;
280 validate_portable_json_value(&value).map_err(portable_json_error)?;
281 serde_json::to_string(&value)
282}
283
284pub fn decode_portable_json<T: DeserializeOwned>(wire: &str) -> Result<T, serde_json::Error> {
286 let value: serde_json::Value = serde_json::from_str(wire)?;
287 validate_portable_json_value(&value).map_err(portable_json_error)?;
288 serde_json::from_value(value)
289}
290
291pub fn validate_portable_json_value(value: &serde_json::Value) -> Result<(), String> {
293 match value {
294 serde_json::Value::Number(number) => {
295 let safe = number.as_i64().is_some_and(|value| {
296 (-9_007_199_254_740_991..=9_007_199_254_740_991).contains(&value)
297 }) || number
298 .as_u64()
299 .is_some_and(|value| value <= 9_007_199_254_740_991)
300 || (number.is_f64()
301 && number.as_f64().is_some_and(|value| {
302 value.is_finite()
303 && (value.abs() <= 9_007_199_254_740_991.0 || value.fract() != 0.0)
304 }));
305 if !safe {
306 return Err("wire JSON contains an unsafe number".to_owned());
307 }
308 }
309 serde_json::Value::Array(values) => {
310 for value in values {
311 validate_portable_json_value(value)?;
312 }
313 }
314 serde_json::Value::Object(values) => {
315 for value in values.values() {
316 validate_portable_json_value(value)?;
317 }
318 }
319 serde_json::Value::Null | serde_json::Value::Bool(_) | serde_json::Value::String(_) => {}
320 }
321 Ok(())
322}
323
324fn portable_json_error(detail: String) -> serde_json::Error {
325 serde_json::Error::io(std::io::Error::new(std::io::ErrorKind::InvalidData, detail))
326}
327
328pub mod serde {
330 pub fn deserialize_required<'de, D, T>(deserializer: D) -> Result<T, D::Error>
332 where
333 D: ::serde::Deserializer<'de>,
334 T: ::serde::Deserialize<'de>,
335 {
336 <T as ::serde::Deserialize>::deserialize(deserializer)
337 }
338
339 #[allow(clippy::option_option)]
341 pub fn deserialize_optional_value<'de, D, T>(
342 deserializer: D,
343 ) -> Result<Option<Option<T>>, D::Error>
344 where
345 D: ::serde::Deserializer<'de>,
346 T: ::serde::Deserialize<'de>,
347 {
348 Ok(Some(<Option<T> as ::serde::Deserialize>::deserialize(
349 deserializer,
350 )?))
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 #[test]
359 fn bytes_preserve_canonical_padded_base64_wire_behavior() {
360 for length in 0..=258 {
361 let value = Bytes::from(
362 (0..length)
363 .map(|index| u8::try_from(index % 256).unwrap())
364 .collect::<Vec<_>>(),
365 );
366 let wire = serde_json::to_string(&value).unwrap();
367 assert_eq!(serde_json::from_str::<Bytes>(&wire).unwrap(), value);
368 }
369 for wire in [r#""not base64""#, r#""AQI""#, r#""AQJ=""#] {
370 assert!(serde_json::from_str::<Bytes>(wire).is_err());
371 }
372 }
373
374 #[test]
375 fn shared_bytes_round_trip_without_copying() {
376 let source = bytes::Bytes::from_static(b"shared");
377 let contract = Bytes::from(source.clone());
378 assert_eq!(contract.as_slice().as_ptr(), source.as_ptr());
379 let restored = contract.into_shared();
380 assert_eq!(restored.as_ptr(), source.as_ptr());
381 }
382
383 #[test]
384 fn portable_json_rejects_unsafe_integer_values() {
385 let error = decode_portable_json::<serde_json::Value>("9007199254740992").unwrap_err();
386 assert!(error.to_string().contains("unsafe number"));
387 }
388
389 #[test]
390 fn raw_json_validates_content_without_changing_the_string_wire_shape() {
391 let raw = RawJson::new(r#"{"ready":true}"#).unwrap();
392 assert_eq!(
393 serde_json::to_string(&raw).unwrap(),
394 r#""{\"ready\":true}""#
395 );
396 assert_eq!(
397 serde_json::from_str::<RawJson>(r#""{\"ready\":true}""#).unwrap(),
398 raw
399 );
400 assert!(RawJson::new("not JSON").is_err());
401 assert!(RawJson::new("9007199254740992").is_err());
402 }
403
404 #[test]
405 fn typed_json_round_trips_as_one_json_string() {
406 let value = Json::new(BTreeMap::from([("answer".to_owned(), 42_i32)]));
407 let wire = serde_json::to_string(&value).unwrap();
408 assert_eq!(wire, r#""{\"answer\":42}""#);
409 assert_eq!(
410 serde_json::from_str::<Json<BTreeMap<String, i32>>>(&wire).unwrap(),
411 value
412 );
413 }
414}