Repository: ergoplatform/sigma-rust (language: rust)

Issue:
Try parse long from string before treating array as a tuple.
>Hi. I try to create Coll[Coll[Coll[Long]]] with ergo-lib-wasm-nodejs
const values = [[['10', '100'], ['10', '50']], [['9']]]
const const = wasm.Constant.from_js(values)


I got error:

Error [ConvError]: Invalid tuple encoding, expected the first array item to be 'Tuple''
    at module.exports.__wbg_new_15d3966e9981a196 (.../node_modules/ergo-lib-wasm-nodejs/ergo_lib_wasm.js:7002:17)
    at wasm://wasm/008867b2:wasm-function[8909]:0x1d9c3f
    at wasm://wasm/008867b2:wasm-function[2538]:0x16c9f0
    at wasm://wasm/008867b2:wasm-function[7269]:0x1d0ec3
    at wasm://wasm/008867b2:wasm-function[7048]:0x1ceb76
    at wasm://wasm/008867b2:wasm-function[3080]:0x1801ca
    at wasm://wasm/008867b2:wasm-function[4539]:0x1a787e
    at Function.from_js (.../node_modules/ergo-lib-wasm-nodejs/ergo_lib_wasm.js:1855:18)


Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-lib-wasm/src/ast/js_conv.rs b/bindings/ergo-lib-wasm/src/ast/js_conv.rs
index 2603404a8..ef5d1a149 100644
--- a/bindings/ergo-lib-wasm/src/ast/js_conv.rs
+++ b/bindings/ergo-lib-wasm/src/ast/js_conv.rs
@@ -44,8 +44,10 @@ pub fn array_as_tuple(items: Vec<JsValue>) -> JsValue {
 pub enum ConvError {
     #[error("not supported: {0:?}")]
     NotSupported(JsValue),
-    #[error("unexpected: {0:?}")]
-    Unexpected(Constant),
+    #[error("unexpected constant: {0:?}")]
+    UnexpectedConst(Constant),
+    #[error("unexpected js value: {0:?}, expected {1}")]
+    UnexpectedJs(JsValue, &'static str),
     #[error("IO error: {0}")]
     TryExtractFromError(#[from] TryExtractFromError),
     #[error("Failed to parse Long from string: {0}")]
@@ -69,28 +71,13 @@ pub(crate) fn constant_from_js(val: &JsValue) -> Result<Constant, ConvError> {
         })
     } else if let Ok(arr) = val.clone().dyn_into::<Array>() {
         if let Ok(str) = arr.get(0).dyn_into::<JsString>() {
-            // tuple
-            if str != TUPLE_TOKEN {
-                return Err(ConvError::InvalidTupleEncoding);
+            if str == TUPLE_TOKEN {
+                tuple_from_js(&arr)
+            } else if let Ok(coll_longs) = coll_long_from_js(&arr) {
+                Ok(coll_longs)
+            } else {
+                return Err(ConvError::NotSupported(val.clone()));
             }
-            let mut v: Vec<Constant> = Vec::new();
-            for i in 1..arr.length() {
-                let elem_const = constant_from_js(&arr.get(i))?;
-                v.push(elem_const);
-            }
-            #[allow(clippy::unwrap_used)]
-            Ok(Constant {
-                tpe: SType::STuple(STuple {
-                    items: TupleItems::try_from(
-                        v.clone().into_iter().map(|c| c.tpe).collect::<Vec<SType>>(),
-                    )
-                    .unwrap(),
-                }),
-                v: Literal::Tup(
-                    TupleItems::try_from(v.into_iter().map(|c| c.v).collect::<Vec<Literal>>())
-                        .unwrap(),
-                ),
-            })
         } else {
             // regular array
             let mut cs: Vec<Constant> = Vec::new();
@@ -125,6 +112,44 @@ pub(crate) fn constant_from_js(val: &JsValue) -> Result<Constant, ConvError> {
     }
 }
 
+fn tuple_from_js(arr: &Array) -> Result<Constant, ConvError> {
+    // tuple
+    let mut v: Vec<Constant> = Vec::new();
+    for i in 1..arr.length() {
+        let elem_const = constant_from_js(&arr.get(i))?;
+        v.push(elem_const);
+    }
+    #[allow(clippy::unwrap_used)]
+    Ok(Constant {
+        tpe: SType::STuple(STuple {
+            items: TupleItems::try_from(
+                v.clone().into_iter().map(|c| c.tpe).collect::<Vec<SType>>(),
+            )
+            .unwrap(),
+        }),
+        v: Literal::Tup(
+            TupleItems::try_from(v.into_iter().map(|c| c.v).collect::<Vec<Literal>>()).unwrap(),
+        ),
+    })
+}
+
+fn coll_long_from_js(arr: &Array) -> Result<Constant, ConvError> {
+    // try array of longs
+    let mut longs: Vec<i64> = Vec::new();
+    for i in 0..arr.length() {
+        let arr_elem = arr.get(i);
+        let js_string = arr_elem
+            .clone()
+            .dyn_into::<JsString>()
+            .map_err(|_| ConvError::UnexpectedJs(arr_elem, "JsString"))?;
+        let parsed_i64 = String::from(js_string.clone())
+            .parse::<i64>()
+            .map_err(|e| ConvError::FailedToParseLongFromString(js_string.into()))?;
+        longs.push(parsed_i64);
+    }
+    Ok(longs.into())
+}
+
 pub(crate) fn constant_to_js(c: Constant) -> Result<JsValue, ConvError> {
     Ok(match c.tpe {
         SType::SBoolean => c.v.try_extract_into::<bool>()?.into(),
@@ -147,7 +172,7 @@ pub(crate) fn constant_to_js(c: Constant) -> Result<JsValue, ConvError> {
                 }
                 arr.into()
             }
-            _ => return Err(ConvError::Unexpected(c)),
+            _ => return Err(ConvError::UnexpectedConst(c)),
         },
         SType::STuple(ref item_tpes) => {
             let vec: Vec<JsValue> = match c.v {
@@ -156,7 +181,7 @@ pub(crate) fn constant_to_js(c: Constant) -> Result<JsValue, ConvError> {
                     .zip(item_tpes.clone().items.into_iter())
                     .map(|(v, tpe)| constant_to_js(Constant { tpe, v }))
                     .collect::<Result<Vec<JsValue>, _>>()?,
-                _ => return Err(ConvError::Unexpected(c.clone())),
+                _ => return Err(ConvError::UnexpectedConst(c.clone())),
             };
             let arr = Array::new();
             for item in vec {
@@ -164,7 +189,7 @@ pub(crate) fn constant_to_js(c: Constant) -> Result<JsValue, ConvError> {
             }
             arr.into()
         }
-        _ => return Err(ConvError::Unexpected(c.clone())),
+        _ => return Err(ConvError::UnexpectedConst(c.clone())),
     })
 }
 


Test patch:
diff --git a/bindings/ergo-lib-wasm/tests/test_constant.js b/bindings/ergo-lib-wasm/tests/test_constant.js
index ccee33d5f..11fe702f4 100644
--- a/bindings/ergo-lib-wasm/tests/test_constant.js
+++ b/bindings/ergo-lib-wasm/tests/test_constant.js
@@ -186,5 +186,15 @@ it("roundtrip BigInt", async () => {
 
 it("too big BigInt fail", async () => {
   let bigint = BigInt(92233720368547758071111111111111111111111111111111111111111111111111111111111111111111111111n);
-  expect(function () {ergo_wasm.Constant.from_js(bigint);}).to.throw();
+  expect(function () { ergo_wasm.Constant.from_js(bigint); }).to.throw();
+});
+
+it("roundtrip Coll[Coll[Long]]", async () => {
+  let array1 = ["9223372036854775807", "1", "2"]; // i64 max value
+  let array2 = ["5", "3", "4"];
+  let js_value = [array1, array2];
+  let c_js = ergo_wasm.Constant.from_js(js_value);
+  expect(c_js != null);
+  expect(c_js.dbg_tpe()).equal("SColl(SColl(SLong))");
+  assert.deepEqual(c_js.to_js(), js_value);
 });