1use base64::Engine as _;
2use serde::{Deserialize, Serialize};
3use serde_json::{json, Number, Value};
4use thiserror::Error;
5
6use tx3_tir::model::core::{Type, UtxoRef};
7pub use tx3_tir::reduce::ArgValue;
8
9use tx3_tir::reduce::ArgMap;
10
11#[derive(Debug, Error)]
12pub enum Error {
13 #[error("invalid base64: {0}")]
14 InvalidBase64(#[from] base64::DecodeError),
15
16 #[error("invalid hex: {0}")]
17 InvalidHex(#[from] hex::FromHexError),
18
19 #[error("invalid bech32: {0}")]
20 InvalidBech32(#[from] bech32::DecodeError),
21
22 #[error("value is not a valid number: {0}")]
23 InvalidBytesForNumber(String),
24
25 #[error("value is null")]
26 ValueIsNull,
27
28 #[error("can't infer type for value: {0}")]
29 CantInferTypeForValue(Value),
30
31 #[error("value is not a number: {0}")]
32 ValueIsNotANumber(Value),
33
34 #[error("value can't fit: {0}")]
35 NumberCantFit(Number),
36
37 #[error("value is not a bool: {0}")]
38 ValueIsNotABool(Value),
39
40 #[error("value is not a string")]
41 ValueIsNotAString,
42
43 #[error("value is not bytes: {0}")]
44 ValueIsNotBytes(Value),
45
46 #[error("value is not a utxo ref: {0}")]
47 ValueIsNotUtxoRef(Value),
48
49 #[error("invalid bytes envelope: {0}")]
50 InvalidBytesEnvelope(serde_json::Error),
51
52 #[error("value is not an address: {0}")]
53 ValueIsNotAnAddress(Value),
54
55 #[error("invalid utxo ref: {0}")]
56 InvalidUtxoRef(String),
57
58 #[error("target type not supported: {0:?}")]
59 TargetTypeNotSupported(Type),
60}
61
62#[derive(Debug, Deserialize, Serialize, Clone)]
63#[serde(rename_all = "lowercase")]
64pub enum BytesEncoding {
65 Base64,
66 Hex,
67}
68
69#[derive(Debug, Deserialize, Serialize, Clone)]
70pub struct BytesEnvelope {
71 pub content: String,
72 pub encoding: BytesEncoding,
73}
74
75impl BytesEnvelope {
76 pub fn from_hex(hex: &str) -> Result<Self, Error> {
77 Ok(Self {
78 content: hex.to_string(),
79 encoding: BytesEncoding::Hex,
80 })
81 }
82}
83
84impl From<BytesEnvelope> for Vec<u8> {
85 fn from(envelope: BytesEnvelope) -> Self {
86 match envelope.encoding {
87 BytesEncoding::Base64 => base64_to_bytes(&envelope.content).unwrap(),
88 BytesEncoding::Hex => hex_to_bytes(&envelope.content).unwrap(),
89 }
90 }
91}
92
93pub fn string_to_bigint(s: String) -> Result<i128, Error> {
94 let bytes = hex_to_bytes(&s)?;
95 let bytes =
96 <[u8; 16]>::try_from(bytes).map_err(|x| Error::InvalidBytesForNumber(hex::encode(x)))?;
97 Ok(i128::from_be_bytes(bytes))
98}
99
100pub fn hex_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
101 let s = if s.starts_with("0x") {
102 s.trim_start_matches("0x")
103 } else {
104 s
105 };
106
107 let out = hex::decode(s)?;
108
109 Ok(out)
110}
111
112pub fn base64_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
113 let out = base64::engine::general_purpose::STANDARD.decode(s)?;
114
115 Ok(out)
116}
117
118pub fn bech32_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
119 let (_, data) = bech32::decode(s)?;
120
121 Ok(data)
122}
123
124fn number_to_bigint(x: Number) -> Result<i128, Error> {
125 x.as_i128().ok_or(Error::NumberCantFit(x))
126}
127
128fn utxoref_to_value(x: UtxoRef) -> Value {
129 Value::String(format!("{}#{}", hex::encode(x.txid), x.index))
130}
131
132fn bigint_to_value(i: i128) -> Value {
133 if i >= i64::MIN as i128 && i <= i64::MAX as i128 {
134 Value::Number((i as i64).into())
135 } else {
136 let ashex = hex::encode(i.to_be_bytes());
137 Value::String(format!("0x{ashex}"))
138 }
139}
140
141fn value_to_bigint(value: Value) -> Result<i128, Error> {
142 let out = match value {
143 Value::Number(n) => number_to_bigint(n)?,
144 Value::String(s) => string_to_bigint(s)?,
145 Value::Null => return Err(Error::ValueIsNull),
146 x => return Err(Error::ValueIsNotANumber(x)),
147 };
148
149 Ok(out)
150}
151
152fn value_to_bool(value: Value) -> Result<bool, Error> {
153 match value {
154 Value::Bool(b) => Ok(b),
155 Value::Number(n) if n == Number::from(0) => Ok(false),
156 Value::Number(n) if n == Number::from(1) => Ok(true),
157 Value::String(s) if s == "true" => Ok(true),
158 Value::String(s) if s == "false" => Ok(false),
159 x => Err(Error::ValueIsNotABool(x)),
160 }
161}
162
163fn value_to_bytes(value: Value) -> Result<Vec<u8>, Error> {
164 let out = match value {
165 Value::String(s) => hex_to_bytes(&s)?,
166 Value::Object(_) => {
167 let envelope: BytesEnvelope =
168 serde_json::from_value(value).map_err(Error::InvalidBytesEnvelope)?;
169
170 match envelope.encoding {
171 BytesEncoding::Base64 => base64_to_bytes(&envelope.content)?,
172 BytesEncoding::Hex => hex_to_bytes(&envelope.content)?,
173 }
174 }
175 x => return Err(Error::ValueIsNotBytes(x)),
176 };
177
178 Ok(out)
179}
180
181fn value_to_address(value: Value) -> Result<Vec<u8>, Error> {
182 let out = match value {
183 Value::String(s) => match bech32_to_bytes(&s) {
184 Ok(data) => data,
185 Err(_) => hex_to_bytes(&s)?,
186 },
187 x => return Err(Error::ValueIsNotAnAddress(x)),
188 };
189
190 Ok(out)
191}
192
193fn value_to_underfined(value: Value) -> Result<ArgValue, Error> {
194 match value {
195 Value::Bool(b) => Ok(ArgValue::Bool(b)),
196 Value::Number(x) => Ok(ArgValue::Int(number_to_bigint(x)?)),
197 Value::String(s) => Ok(ArgValue::String(s)),
198 x => Err(Error::CantInferTypeForValue(x)),
199 }
200}
201
202fn string_to_utxo_ref(s: &str) -> Result<UtxoRef, Error> {
203 let (txid, index) = s
204 .split_once('#')
205 .ok_or(Error::InvalidUtxoRef(s.to_string()))?;
206
207 let txid = hex::decode(txid).map_err(|_| Error::InvalidUtxoRef(s.to_string()))?;
208 let index = index
209 .parse()
210 .map_err(|_| Error::InvalidUtxoRef(s.to_string()))?;
211
212 Ok(UtxoRef { txid, index })
213}
214
215fn value_to_utxo_ref(value: Value) -> Result<UtxoRef, Error> {
216 match value {
217 Value::String(s) => string_to_utxo_ref(&s),
218 x => Err(Error::ValueIsNotUtxoRef(x)),
219 }
220}
221
222pub fn to_json(value: ArgValue) -> Value {
223 match value {
224 ArgValue::Int(i) => bigint_to_value(i),
225 ArgValue::Bool(b) => Value::Bool(b),
226 ArgValue::String(s) => Value::String(s),
227 ArgValue::Bytes(b) => Value::String(format!("0x{}", hex::encode(b))),
228 ArgValue::Address(a) => Value::String(hex::encode(a)),
229 ArgValue::UtxoSet(x) => {
230 let v = x.into_iter().map(|x| json!(x)).collect();
231 Value::Array(v)
232 }
233 ArgValue::UtxoRef(x) => utxoref_to_value(x),
234 }
235}
236
237pub fn to_json_object(map: ArgMap) -> Value {
238 let v = map
239 .into_iter()
240 .map(|(k, v)| (k.to_lowercase(), to_json(v)))
241 .collect();
242
243 Value::Object(v)
244}
245
246pub fn from_json(value: Value, target: &Type) -> Result<ArgValue, Error> {
247 match target {
248 Type::Int => {
249 let i = value_to_bigint(value)?;
250 Ok(ArgValue::Int(i))
251 }
252 Type::Bool => {
253 let b = value_to_bool(value)?;
254 Ok(ArgValue::Bool(b))
255 }
256 Type::Bytes => {
257 let b = value_to_bytes(value)?;
258 Ok(ArgValue::Bytes(b))
259 }
260 Type::Address => {
261 let a = value_to_address(value)?;
262 Ok(ArgValue::Address(a))
263 }
264 Type::UtxoRef => {
265 let x = value_to_utxo_ref(value)?;
266 Ok(ArgValue::UtxoRef(x))
267 }
268 Type::Undefined => value_to_underfined(value),
269 x => Err(Error::TargetTypeNotSupported(x.clone())),
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 fn partial_eq(a: ArgValue, b: ArgValue) -> bool {
279 match a {
280 ArgValue::Int(a) => match b {
281 ArgValue::Int(b) => dbg!(a) == dbg!(b),
282 _ => false,
283 },
284 ArgValue::Bool(a) => match b {
285 ArgValue::Bool(b) => a == b,
286 _ => false,
287 },
288 ArgValue::String(a) => match b {
289 ArgValue::String(b) => a == b,
290 _ => false,
291 },
292 ArgValue::Bytes(a) => match b {
293 ArgValue::Bytes(b) => a == b,
294 _ => false,
295 },
296 ArgValue::Address(a) => match b {
297 ArgValue::Address(b) => a == b,
298 _ => false,
299 },
300 ArgValue::UtxoSet(hash_set) => match b {
301 ArgValue::UtxoSet(b) => hash_set == b,
302 _ => false,
303 },
304 ArgValue::UtxoRef(utxo_ref) => match b {
305 ArgValue::UtxoRef(b) => utxo_ref == b,
306 _ => false,
307 },
308 }
309 }
310
311 fn json_to_value_test(provided: Value, target: Type, expected: ArgValue) {
312 let value = from_json(provided, &target).unwrap();
313 assert!(partial_eq(value, expected));
314 }
315
316 fn round_trip_test(value: ArgValue, target: Type) {
317 let json = to_json(value.clone());
318 dbg!(&json);
319 let value2 = from_json(json, &target).unwrap();
320 assert!(partial_eq(value, value2));
321 }
322
323 #[test]
324 fn test_round_trip_small_int() {
325 round_trip_test(ArgValue::Int(123456789), Type::Int);
326 }
327
328 #[test]
329 fn test_round_trip_negative_int() {
330 round_trip_test(ArgValue::Int(-123456789), Type::Int);
331 }
332
333 #[test]
334 fn test_round_trip_big_int() {
335 round_trip_test(ArgValue::Int(12345678901234567890), Type::Int);
336 }
337
338 #[test]
339 fn test_round_trip_int_overflow() {
340 round_trip_test(ArgValue::Int(i128::MIN), Type::Int);
341 round_trip_test(ArgValue::Int(i128::MAX), Type::Int);
342 }
343
344 #[test]
345 fn test_round_trip_bool() {
346 round_trip_test(ArgValue::Bool(true), Type::Bool);
347 round_trip_test(ArgValue::Bool(false), Type::Bool);
348 }
349
350 #[test]
351 fn test_round_trip_bool_number() {
352 json_to_value_test(json!(1), Type::Bool, ArgValue::Bool(true));
353 json_to_value_test(json!(0), Type::Bool, ArgValue::Bool(false));
354 }
355
356 #[test]
357 fn test_round_trip_bool_string() {
358 json_to_value_test(json!("true"), Type::Bool, ArgValue::Bool(true));
359 json_to_value_test(json!("false"), Type::Bool, ArgValue::Bool(false));
360 }
361
362 #[test]
363 fn test_round_trip_bytes() {
364 round_trip_test(ArgValue::Bytes(b"hello".to_vec()), Type::Bytes);
365 }
366
367 #[test]
368 fn test_round_trip_bytes_base64() {
369 let json = json!(BytesEnvelope {
370 content: "aGVsbG8=".to_string(),
371 encoding: BytesEncoding::Base64,
372 });
373
374 json_to_value_test(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
375 }
376
377 #[test]
378 fn test_round_trip_bytes_hex() {
379 let json = json!(BytesEnvelope {
380 content: "68656c6c6f".to_string(),
381 encoding: BytesEncoding::Hex,
382 });
383
384 json_to_value_test(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
385 }
386
387 #[test]
388 fn test_round_trip_address() {
389 round_trip_test(ArgValue::Address(b"abc123".to_vec()), Type::Address);
390 }
391
392 #[test]
393 fn test_round_trip_address_bech32() {
394 let json = json!("addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8");
395 let bytes =
396 hex::decode("619493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e").unwrap();
397 json_to_value_test(json, Type::Address, ArgValue::Address(bytes));
398 }
399
400 #[test]
401 fn test_round_trip_utxo_ref() {
402 let json = json!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef#0");
403
404 let utxo_ref = UtxoRef {
405 txid: hex::decode("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
406 .unwrap(),
407 index: 0,
408 };
409
410 json_to_value_test(json, Type::UtxoRef, ArgValue::UtxoRef(utxo_ref));
411 }
412}