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