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