diff --git a/compile-tests.mjs b/compile-tests.mjs deleted file mode 100644 index 015ce42..0000000 --- a/compile-tests.mjs +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env zx - -import fs, { createReadStream, existsSync, statSync } from "fs"; -import events from "events"; -import { readFile, stat } from "fs/promises"; -import readline from "readline"; -import { spawn } from "child_process"; -import path from "path"; -// import { chalk } from "zx"; -// import 'zx/globals' - -Array.prototype.last = function () { - return this[this.length - 1]; -}; - -const getImports = async (file) => { - let rl = readline.createInterface({ - input: fs.createReadStream(file), - crlfDelay: Infinity, - }); - - let import_paths = []; - - rl.on("line", (line) => { - if (line.startsWith("import")) { - let path = line.split(" ").last(); - path = path.slice(1, path.length - 2); - import_paths.push(path); - } else if (line.length !== 0 && !line.startsWith("//")) { - rl.close(); - rl.removeAllListeners(); - } - }); - - await events.once(rl, "close"); - - return import_paths; -}; - -const wasm_path = (file) => { - let segments = file.split("/"); - segments.splice(1, 0, ".wasm"); - let basename = segments.last().replace(".mo", ".wasm"); - segments[segments.length - 1] = basename; - return segments.join("/"); -}; - -let test_files = await glob("./test?(s)/**/*.(test|Test).mo"); - -let moc = (await $`dfx cache show`).stdout.toString().trim() + "/moc"; -let mops_sources = (await $`mops sources`).stdout.toString().split("\n"); - -let packages = {}; -let package_args = []; - - -for (const source of mops_sources) { - let segments = source.split(" "); - package_args.push(...segments); - packages[segments[1]] = segments[2]; -} - -const compile_motoko = async (src, dest) => { - await $`${moc} ${package_args} -wasi-system-api ${src} -o ${dest}`; -}; - -if (test_files.length) { - let wasm_dir = test_files[0].split("/")[0] + "/.wasm"; - await $`mkdir -p ${wasm_dir}`; -} - -const last_modified_cache = {}; - -const is_recently_modified = (file, time) => { - let cached_mtime = last_modified_cache[file]; - - if (cached_mtime) { - return cached_mtime > time; - } - - let file_mtime = statSync(file).mtimeMs; - last_modified_cache[file] = file_mtime; - - return file_mtime > time; -}; - -const is_dep_tree_recently_modified = async (file, wasm_mtime, visited) => { - let modified = is_recently_modified(file, wasm_mtime); - - if (modified) { - return true; - } - - let imports = await getImports(file); - - // console.log({file, imports}) - - for (let imp_path of imports) { - if (imp_path.startsWith("mo:")) { - let pkg = imp_path.slice(3).split("/")[0]; - let pkg_path = packages[pkg]; - - pkg_path = (await glob(pkg_path + "/**/*.mo"))[0]; - - if (pkg_path && !visited.has(pkg_path)) { - visited.add(pkg_path); - modified = is_recently_modified(pkg_path, wasm_mtime); - } - - continue; - } - - imp_path = path.resolve(path.dirname(file), imp_path); - - if (existsSync(imp_path.concat(".mo"))) { - imp_path = imp_path.concat(".mo"); - } else { - imp_path = imp_path.concat("/lib.mo"); - } - - if (visited.has(imp_path)) { continue; } - - visited.add(imp_path); - - // console.log({imp_path}) - - if (await is_dep_tree_recently_modified(imp_path, wasm_mtime, visited)) { - return true - }; - - } - - return false; -}; - -const compile_test = async (test_file) => { - const wasm_file = wasm_path(test_file); - - let should_compile = !existsSync(wasm_file); - - if (!should_compile) { - let wasm_mtime = statSync(wasm_file).mtimeMs; - should_compile = await is_dep_tree_recently_modified(test_file, wasm_mtime, new Set()); - } - - if (should_compile) { - console.log(`Compiling ${test_file}`); - await compile_motoko(test_file, wasm_file); - } - - return wasm_file; -} - -const wasm_files = await Promise.all( - test_files.map(compile_test) -); - -for (const wasm_file of wasm_files) { - await $`wasmtime ${wasm_file}`; -} \ No newline at end of file diff --git a/compile-tests.sh b/compile-tests.sh deleted file mode 100644 index 842b85c..0000000 --- a/compile-tests.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/sh -LIBS=$(mops sources) - -TESTS_FILES=`find tests -type f -name '*.Test.mo'` - -if [ -z $1 ] -then - echo "Compiling all test files (*.Test.mo)" -else - echo $1 - TESTS_FILES=`find tests -type f -name '*.Test.mo' | grep $1` -fi - -mkdir -p tests/.wasm - -for TEST in $TESTS_FILES - do - FILE_NAME=`echo ${TEST:6} | awk -F'.' '{print $1}'` - printf "\n\n${FILE_NAME}.Test.mo ...\n" - printf '=%.0s' {1..30} - echo - - WASM=tests/.wasm/$FILE_NAME.Test.wasm - SRC=src/$FILE_NAME - SRC_FILE=$SRC.mo - - IS_COMPILED=0 - - if [ $TEST -nt $WASM ]; - then - echo "Compiling $TEST" - rm -f $WASM - $(vessel bin)/moc $LIBS -wasi-system-api $TEST -o $WASM - IS_COMPILED=1 - fi - - if [ $IS_COMPILED -eq 0 ] && [ -f $SRC_FILE ] && [$SRC_FILE -nt $WASM ]; - then - echo "Compiling because $SRC_FILE changed" - rm -f $WASM - $(vessel bin)/moc $LIBS -wasi-system-api $TEST -o $WASM - IS_COMPILED=1 - fi - - if [ $IS_COMPILED -eq 0 ] && [ -d SRC ] - then - NESTED_FILES=`find $SRC -type f -name '*.mo'` - - for NESTED_FILE in $NESTED_FILES - do - if [ $NESTED_FILE -nt $WASM ] - then - echo "Compiling because $NESTED_FILE changed" - $(vessel bin)/moc $LIBS -wasi-system-api $TEST -o $WASM - IS_COMPILED=1 - break - fi - done - fi - - wasmtime $WASM || exit 1 - - done diff --git a/docs/Candid/Decoder.md b/docs/Candid/Decoder.md deleted file mode 100644 index 6570cf9..0000000 --- a/docs/Candid/Decoder.md +++ /dev/null @@ -1,25 +0,0 @@ -# Candid/Decoder - -## Type `Options` -``` motoko no-repl -type Options = { renameKeys : [(Text, Text)] } -``` - - -## Function `decode` -``` motoko no-repl -func decode(blob : Blob, record_keys : [Text], options : ?Options) : [Candid] -``` - -Decodes a blob encoded in the candid format into a list of the [Candid](./Types.mo#Candid) type in motoko - -### Inputs -- **blob** - A blob encoded in the candid format -**record_keys** - The record keys to use when decoding a record. -**options** - An optional arguement to specify options for decoding. - -## Function `fromArgs` -``` motoko no-repl -func fromArgs(args : [Arg], recordKeyMap : TrieMap.TrieMap) : [Candid] -``` - diff --git a/docs/Candid/Encoder.md b/docs/Candid/Encoder.md deleted file mode 100644 index 64cd7f3..0000000 --- a/docs/Candid/Encoder.md +++ /dev/null @@ -1,19 +0,0 @@ -# Candid/Encoder - -## Function `encode` -``` motoko no-repl -func encode(candid_values : [Candid]) : Blob -``` - - -## Function `encodeOne` -``` motoko no-repl -func encodeOne(candid : Candid) : Blob -``` - - -## Function `toArgs` -``` motoko no-repl -func toArgs(candid_values : [Candid]) : [Arg] -``` - diff --git a/docs/Candid/Parser/Array.md b/docs/Candid/Parser/Array.md deleted file mode 100644 index 57e825b..0000000 --- a/docs/Candid/Parser/Array.md +++ /dev/null @@ -1,7 +0,0 @@ -# Candid/Parser/Array - -## Function `arrayParser` -``` motoko no-repl -func arrayParser(valueParser : () -> Parser) : Parser -``` - diff --git a/docs/Candid/Parser/Blob.md b/docs/Candid/Parser/Blob.md deleted file mode 100644 index 2ae2606..0000000 --- a/docs/Candid/Parser/Blob.md +++ /dev/null @@ -1,7 +0,0 @@ -# Candid/Parser/Blob - -## Function `blobParser` -``` motoko no-repl -func blobParser() : Parser -``` - diff --git a/docs/Candid/Parser/Bool.md b/docs/Candid/Parser/Bool.md deleted file mode 100644 index c75d6c2..0000000 --- a/docs/Candid/Parser/Bool.md +++ /dev/null @@ -1,7 +0,0 @@ -# Candid/Parser/Bool - -## Function `boolParser` -``` motoko no-repl -func boolParser() : Parser -``` - diff --git a/docs/Candid/Parser/Common.md b/docs/Candid/Parser/Common.md deleted file mode 100644 index 5cd7311..0000000 --- a/docs/Candid/Parser/Common.md +++ /dev/null @@ -1,49 +0,0 @@ -# Candid/Parser/Common - -## Function `ignoreSpace` -``` motoko no-repl -func ignoreSpace(parser : P.Parser) : P.Parser -``` - - -## Function `removeUnderscore` -``` motoko no-repl -func removeUnderscore(parser : P.Parser) : P.Parser> -``` - - -## Function `any` -``` motoko no-repl -func any() : Parser -``` - - -## Function `hexChar` -``` motoko no-repl -func hexChar() : Parser -``` - - -## Function `consIf` -``` motoko no-repl -func consIf(parserA : Parser, parserAs : Parser>, cond : (A, List) -> Bool) : Parser> -``` - - -## Function `fromHex` -``` motoko no-repl -func fromHex(char : Char) : Nat8 -``` - - -## Function `toText` -``` motoko no-repl -func toText(chars : List) : Text -``` - - -## Function `listToNat` -``` motoko no-repl -func listToNat(digits : List) : Nat -``` - diff --git a/docs/Candid/Parser/Float.md b/docs/Candid/Parser/Float.md deleted file mode 100644 index 9ead753..0000000 --- a/docs/Candid/Parser/Float.md +++ /dev/null @@ -1,7 +0,0 @@ -# Candid/Parser/Float - -## Function `floatParser` -``` motoko no-repl -func floatParser() : Parser -``` - diff --git a/docs/Candid/Parser/Int.md b/docs/Candid/Parser/Int.md deleted file mode 100644 index 788996b..0000000 --- a/docs/Candid/Parser/Int.md +++ /dev/null @@ -1,13 +0,0 @@ -# Candid/Parser/Int - -## Function `intParser` -``` motoko no-repl -func intParser() : Parser -``` - - -## Function `parseInt` -``` motoko no-repl -func parseInt() : Parser -``` - diff --git a/docs/Candid/Parser/IntX.md b/docs/Candid/Parser/IntX.md deleted file mode 100644 index 38fcb70..0000000 --- a/docs/Candid/Parser/IntX.md +++ /dev/null @@ -1,7 +0,0 @@ -# Candid/Parser/IntX - -## Function `intXParser` -``` motoko no-repl -func intXParser() : Parser -``` - diff --git a/docs/Candid/Parser/Nat.md b/docs/Candid/Parser/Nat.md deleted file mode 100644 index 3cf0548..0000000 --- a/docs/Candid/Parser/Nat.md +++ /dev/null @@ -1,13 +0,0 @@ -# Candid/Parser/Nat - -## Function `natParser` -``` motoko no-repl -func natParser() : Parser -``` - - -## Function `parseNat` -``` motoko no-repl -func parseNat() : Parser -``` - diff --git a/docs/Candid/Parser/NatX.md b/docs/Candid/Parser/NatX.md deleted file mode 100644 index 89ac957..0000000 --- a/docs/Candid/Parser/NatX.md +++ /dev/null @@ -1,7 +0,0 @@ -# Candid/Parser/NatX - -## Function `natXParser` -``` motoko no-repl -func natXParser() : Parser -``` - diff --git a/docs/Candid/Parser/Option.md b/docs/Candid/Parser/Option.md deleted file mode 100644 index efd2a96..0000000 --- a/docs/Candid/Parser/Option.md +++ /dev/null @@ -1,13 +0,0 @@ -# Candid/Parser/Option - -## Function `optionParser` -``` motoko no-repl -func optionParser(candidParser : () -> Parser) : Parser -``` - - -## Function `nullParser` -``` motoko no-repl -func nullParser() : Parser -``` - diff --git a/docs/Candid/Parser/Principal.md b/docs/Candid/Parser/Principal.md deleted file mode 100644 index e5ef0d1..0000000 --- a/docs/Candid/Parser/Principal.md +++ /dev/null @@ -1,7 +0,0 @@ -# Candid/Parser/Principal - -## Function `principalParser` -``` motoko no-repl -func principalParser() : Parser -``` - diff --git a/docs/Candid/Parser/Record.md b/docs/Candid/Parser/Record.md deleted file mode 100644 index 0c9e737..0000000 --- a/docs/Candid/Parser/Record.md +++ /dev/null @@ -1,19 +0,0 @@ -# Candid/Parser/Record - -## Function `recordParser` -``` motoko no-repl -func recordParser(candidParser : () -> Parser) : Parser -``` - - -## Function `fieldParser` -``` motoko no-repl -func fieldParser(valueParser : () -> Parser) : Parser -``` - - -## Function `keyParser` -``` motoko no-repl -func keyParser() : Parser -``` - diff --git a/docs/Candid/Parser/Text.md b/docs/Candid/Parser/Text.md deleted file mode 100644 index 780b1f6..0000000 --- a/docs/Candid/Parser/Text.md +++ /dev/null @@ -1,13 +0,0 @@ -# Candid/Parser/Text - -## Function `textParser` -``` motoko no-repl -func textParser() : Parser -``` - - -## Function `parseText` -``` motoko no-repl -func parseText() : Parser -``` - diff --git a/docs/Candid/Parser/Variant.md b/docs/Candid/Parser/Variant.md deleted file mode 100644 index 3cc3544..0000000 --- a/docs/Candid/Parser/Variant.md +++ /dev/null @@ -1,7 +0,0 @@ -# Candid/Parser/Variant - -## Function `variantParser` -``` motoko no-repl -func variantParser(candidParser : () -> Parser) : Parser -``` - diff --git a/docs/Candid/Parser/lib.md b/docs/Candid/Parser/lib.md deleted file mode 100644 index 6008015..0000000 --- a/docs/Candid/Parser/lib.md +++ /dev/null @@ -1,19 +0,0 @@ -# Candid/Parser/lib - -## Function `parse` -``` motoko no-repl -func parse(text : Text) : [Candid] -``` - - -## Function `multiValueCandidParser` -``` motoko no-repl -func multiValueCandidParser() : Parser -``` - - -## Function `candidParser` -``` motoko no-repl -func candidParser() : Parser -``` - diff --git a/docs/Candid/ToText.md b/docs/Candid/ToText.md deleted file mode 100644 index d13d5ca..0000000 --- a/docs/Candid/ToText.md +++ /dev/null @@ -1,7 +0,0 @@ -# Candid/ToText - -## Function `toText` -``` motoko no-repl -func toText(candid_values : [Candid]) : Text -``` - diff --git a/docs/Candid/Types.md b/docs/Candid/Types.md deleted file mode 100644 index c34cff3..0000000 --- a/docs/Candid/Types.md +++ /dev/null @@ -1,14 +0,0 @@ -# Candid/Types - -## Type `KeyValuePair` -``` motoko no-repl -type KeyValuePair = (Text, Candid) -``` - - -## Type `Candid` -``` motoko no-repl -type Candid = {#Int : Int; #Int8 : Int8; #Int16 : Int16; #Int32 : Int32; #Int64 : Int64; #Nat : Nat; #Nat8 : Nat8; #Nat16 : Nat16; #Nat32 : Nat32; #Nat64 : Nat64; #Bool : Bool; #Float : Float; #Text : Text; #Blob : Blob; #Null; #Empty; #Principal : Principal; #Option : Candid; #Array : [Candid]; #Record : [KeyValuePair]; #Variant : KeyValuePair} -``` - -A standard representation of the Candid type diff --git a/docs/Candid/lib.md b/docs/Candid/lib.md deleted file mode 100644 index 056c6c5..0000000 --- a/docs/Candid/lib.md +++ /dev/null @@ -1,14 +0,0 @@ -# Candid/lib - -## Type `Candid` -``` motoko no-repl -type Candid = T.Candid -``` - -A representation of the Candid format with variants for all possible types. - -## Function `fromText` -``` motoko no-repl -func fromText(t : Text) : [Candid] -``` - diff --git a/docs/JSON/FromText.md b/docs/JSON/FromText.md deleted file mode 100644 index ff37fbd..0000000 --- a/docs/JSON/FromText.md +++ /dev/null @@ -1,15 +0,0 @@ -# JSON/FromText - -## Function `fromText` -``` motoko no-repl -func fromText(rawText : Text) : Blob -``` - -Converts JSON text to a serialized Candid blob that can be decoded to motoko values using `from_candid()` - -## Function `toCandid` -``` motoko no-repl -func toCandid(rawText : Text) : Candid -``` - -Convert JSON text to a Candid value diff --git a/docs/JSON/ToText.md b/docs/JSON/ToText.md deleted file mode 100644 index 9bc7ded..0000000 --- a/docs/JSON/ToText.md +++ /dev/null @@ -1,15 +0,0 @@ -# JSON/ToText - -## Function `toText` -``` motoko no-repl -func toText(blob : Blob, keys : [Text]) : Text -``` - -Converts serialized Candid blob to JSON text - -## Function `fromCandid` -``` motoko no-repl -func fromCandid(candid : Candid) : Text -``` - -Convert a Candid value to JSON text diff --git a/docs/JSON/lib.md b/docs/JSON/lib.md deleted file mode 100644 index 8c2f1fd..0000000 --- a/docs/JSON/lib.md +++ /dev/null @@ -1,8 +0,0 @@ -# JSON/lib -A module for converting between JSON and Motoko values. - -## Type `JSON` -``` motoko no-repl -type JSON = JSON.JSON -``` - diff --git a/docs/UrlEncoded/FromText.md b/docs/UrlEncoded/FromText.md deleted file mode 100644 index bcf41fe..0000000 --- a/docs/UrlEncoded/FromText.md +++ /dev/null @@ -1,15 +0,0 @@ -# UrlEncoded/FromText - -## Function `fromText` -``` motoko no-repl -func fromText(text : Text) : Blob -``` - -Converts a Url-Encoded Text to a serialized Candid Record - -## Function `toCandid` -``` motoko no-repl -func toCandid(text : Text) : Candid -``` - -Converts a Url-Encoded Text to a Candid Record diff --git a/docs/UrlEncoded/Parser.md b/docs/UrlEncoded/Parser.md deleted file mode 100644 index 730b87c..0000000 --- a/docs/UrlEncoded/Parser.md +++ /dev/null @@ -1,7 +0,0 @@ -# UrlEncoded/Parser - -## Function `parseValue` -``` motoko no-repl -func parseValue(text : Text) : Candid -``` - diff --git a/docs/UrlEncoded/ToText.md b/docs/UrlEncoded/ToText.md deleted file mode 100644 index 6b85f22..0000000 --- a/docs/UrlEncoded/ToText.md +++ /dev/null @@ -1,15 +0,0 @@ -# UrlEncoded/ToText - -## Function `toText` -``` motoko no-repl -func toText(blob : Blob, keys : [Text]) : Text -``` - -Converts a serialized Candid blob to a URL-Encoded string. - -## Function `fromCandid` -``` motoko no-repl -func fromCandid(candid : Candid) : Text -``` - -Convert a Candid Record to a URL-Encoded string. diff --git a/docs/UrlEncoded/lib.md b/docs/UrlEncoded/lib.md deleted file mode 100644 index 42640bb..0000000 --- a/docs/UrlEncoded/lib.md +++ /dev/null @@ -1,2 +0,0 @@ -# UrlEncoded/lib -A module for converting between Motoko values and Url-Encoded `Text`. diff --git a/docs/Utils.md b/docs/Utils.md deleted file mode 100644 index c19b496..0000000 --- a/docs/Utils.md +++ /dev/null @@ -1,25 +0,0 @@ -# Utils - -## Function `subText` -``` motoko no-repl -func subText(text : Text, start : Nat, end : Nat) : Text -``` - - -## Function `cmpRecords` -``` motoko no-repl -func cmpRecords(a : (Text, Any), b : (Text, Any)) : Order.Order -``` - - -## Function `stripStart` -``` motoko no-repl -func stripStart(text : Text, prefix : Text.Pattern) : Text -``` - - -## Function `log2` -``` motoko no-repl -func log2(n : Float) : Float -``` - diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 840b6e6..0000000 --- a/docs/index.md +++ /dev/null @@ -1,30 +0,0 @@ -# Index - -* [Candid/Decoder](Candid/Decoder.md) -* [Candid/Encoder](Candid/Encoder.md) -* [Candid/Parser/Array](Candid/Parser/Array.md) -* [Candid/Parser/Blob](Candid/Parser/Blob.md) -* [Candid/Parser/Bool](Candid/Parser/Bool.md) -* [Candid/Parser/Common](Candid/Parser/Common.md) -* [Candid/Parser/Float](Candid/Parser/Float.md) -* [Candid/Parser/Int](Candid/Parser/Int.md) -* [Candid/Parser/IntX](Candid/Parser/IntX.md) -* [Candid/Parser/Nat](Candid/Parser/Nat.md) -* [Candid/Parser/NatX](Candid/Parser/NatX.md) -* [Candid/Parser/Option](Candid/Parser/Option.md) -* [Candid/Parser/Principal](Candid/Parser/Principal.md) -* [Candid/Parser/Record](Candid/Parser/Record.md) -* [Candid/Parser/Text](Candid/Parser/Text.md) -* [Candid/Parser/Variant](Candid/Parser/Variant.md) -* [Candid/Parser/lib](Candid/Parser/lib.md) -* [Candid/ToText](Candid/ToText.md) -* [Candid/Types](Candid/Types.md) -* [Candid/lib](Candid/lib.md) -* [JSON/FromText](JSON/FromText.md) -* [JSON/ToText](JSON/ToText.md) -* [JSON/lib](JSON/lib.md) A module for converting between JSON and Motoko values. -* [UrlEncoded/FromText](UrlEncoded/FromText.md) -* [UrlEncoded/Parser](UrlEncoded/Parser.md) -* [UrlEncoded/ToText](UrlEncoded/ToText.md) -* [UrlEncoded/lib](UrlEncoded/lib.md) A module for converting between Motoko values and Url-Encoded `Text`. -* [Utils](Utils.md) diff --git a/mops.toml b/mops.toml index ea7ac9b..b422557 100644 --- a/mops.toml +++ b/mops.toml @@ -1,27 +1,27 @@ [package] name = "serde" -version = "3.1.0" +version = "3.2.1" description = "A serialisation and deserialisation library for Motoko." repository = "https://github.com/NatLabs/serde" -keywords = [ "json", "candid", "cbor", "urlencoded", "serialization" ] +keywords = ["json", "candid", "cbor", "urlencoded", "serialization"] license = "MIT" [dependencies] base = "0.12.0" itertools = "0.2.1" -candid = "1.0.2" -xtended-numbers = "0.2.1" +candid = "1.1.1" +xtended-numbers = "0.3.1" json-float = "https://github.com/NatLabs/json.mo#float@f3c8e7d418a7a8f2d6c0d7e2d276a0a82c2046ff" parser-combinators = "https://github.com/aviate-labs/parser-combinators.mo#v0.1.2@6a331bf78e9dcd7623977f06c8e561fd1a8c0103" -cbor = "0.1.3" +cbor = "1.0.0" map = "9.0.1" -sha2 = "0.0.6" -rep-indy-hash = "0.1.1" +sha2 = "0.1.0" [dev-dependencies] test = "2.0.0" bench = "1.0.0" fuzz = "0.2.1" +rep-indy-hash = "0.1.1" [toolchain] wasmtime = "14.0.4" diff --git a/readme.md b/readme.md index 51ad2f2..598bb2f 100644 --- a/readme.md +++ b/readme.md @@ -3,32 +3,42 @@ An efficient serialization and deserialization library for Motoko. The library contains four modules: + - **Candid** - - `fromText()` - Converts [Candid text](https://internetcomputer.org/docs/current/tutorials/developer-journey/level-2/2.4-intro-candid/#candid-textual-values) to its serialized form. - - `toText()` - Converts serialized candid to its [textual representation](https://internetcomputer.org/docs/current/tutorials/developer-journey/level-2/2.4-intro-candid/#candid-textual-values). - - `encode()` - Converts the [Candid variant](./src/Candid/Types.mo#L6) to a blob. - - `decode()` - Converts a blob to the [Candid variant](./src/Candid/Types.mo#L6). - > encoding and decoding functions also support conversion between the [`ICRC3` value type](https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3#value) and candid. Checkout the example in the [usage guide](./usage.md#icrc3-value) + + - `fromText()` - Converts [Candid text](https://internetcomputer.org/docs/current/tutorials/developer-journey/level-2/2.4-intro-candid/#candid-textual-values) to its serialized form. + - `toText()` - Converts serialized candid to its [textual representation](https://internetcomputer.org/docs/current/tutorials/developer-journey/level-2/2.4-intro-candid/#candid-textual-values). + - `encode()` - Converts the [Candid variant](./src/Candid/Types.mo#L6) to a blob. + - `decode()` - Converts a blob to the [Candid variant](./src/Candid/Types.mo#L6). + + - encoding and decoding functions also support conversion between the [`ICRC3` value type](https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3#value) and candid. Checkout the example in the [usage guide](./usage.md#icrc3-value) + + - `fromICRC3Value()` - Converts an ICRC3 value to a candid variant. + - `toICRC3Value()` - Converts a candid variant to an ICRC3 value. + - **CBOR** - - `encode()` - Converts serialized candid to CBOR. - - `decode()` - Converts CBOR to a serialized candid. + + - `encode()` - Converts serialized candid to CBOR. + - `decode()` - Converts CBOR to a serialized candid. - **JSON** - - `fromText()` - Converts JSON text to serialized candid. - - `toText()` - Converts serialized candid to JSON text. + + - `fromText()` - Converts JSON text to serialized candid. + - `toText()` - Converts serialized candid to JSON text. - **URL-Encoded Pairs** - - `fromText()` - Converts URL-encoded text to serialized candid. - - `toText()` - Converts serialized candid to URL-encoded text. - + - `fromText()` - Converts URL-encoded text to serialized candid. + - `toText()` - Converts serialized candid to URL-encoded text. ## Getting Started -### Installation +### Installation + [![mops](https://oknww-riaaa-aaaam-qaf6a-cai.raw.ic0.app/badge/mops/serde)](https://mops.one/serde) 1. Install [`mops`](https://j4mwm-bqaaa-aaaam-qajbq-cai.ic0.app/#/docs/install). -2. Inside your project directory, run: +2. Inside your project directory, run: + ```bash mops install serde ``` @@ -36,11 +46,13 @@ mops install serde ### Usage To start, import the necessary modules: + ```motoko import { JSON; Candid; CBOR; UrlEncoded } from "mo:serde"; ``` #### JSON + > The following code can be used for converting data between the other modules (Candid and URL-Encoded Pairs). **Example: JSON to Motoko** @@ -48,22 +60,23 @@ import { JSON; Candid; CBOR; UrlEncoded } from "mo:serde"; 1. **Defining Data Type**: This critical step informs the conversion functions (`from_candid` and `to_candid`) about how to handle the data. Consider the following JSON data: + ```json [ - { - "name": "John", - "id": 123 - }, - { - "name": "Jane", - "id": 456, - "email": "jane@gmail.com" - } + { + "name": "John", + "id": 123 + }, + { + "name": "Jane", + "id": 456, + "email": "jane@gmail.com" + } ] ``` The optional `email` field translates to: - + ```motoko type User = { name: Text; @@ -99,13 +112,13 @@ import { JSON; Candid; CBOR; UrlEncoded } from "mo:serde"; **Example: Motoko to JSON** 1. **Record Keys**: Collect all unique record keys from your data type into an array. This helps the module convert the record keys correctly instead of returning its hash. - + ```motoko let UserKeys = ["name", "id", "email"]; ``` 2. **Conversion**: - + ```motoko let users: [User] = [ { @@ -148,8 +161,8 @@ type Item = { }; let jsonText = "{\"type\": \"bar\", \"label\": \"foo\", \"id\": 112}"; -let options: Serde.Options = { - renameKeys = [("type", "item_type"), ("label", "item_label")] +let options: Serde.Options = { + renameKeys = [("type", "item_type"), ("label", "item_label")] }; let #ok(blob) = Serde.JSON.fromText(jsonText, ?options); @@ -159,6 +172,7 @@ assert renamedKeys == ?{ item_type = "bar"; item_label = "foo"; id = 112 }; ``` Checkout the [usage guide](https://github.com/NatLabs/serde/blob/main/usage.md) for additional examples: + - [Candid](https://github.com/NatLabs/serde/blob/main/usage.md#candid-text) - [URL-Encoded Pairs](https://github.com/NatLabs/serde/blob/main/usage.md#url-encoded-pairs) @@ -173,15 +187,17 @@ Checkout the [usage guide](https://github.com/NatLabs/serde/blob/main/usage.md) ## Running Tests 1. Install dependencies: + - [mops](https://j4mwm-bqaaa-aaaam-qajbq-cai.ic0.app/#/docs/install) - [mocv](https://github.com/ZenVoich/mocv) - [wasmtime](https://github.com/bytecodealliance/wasmtime/blob/main/README.md#wasmtime) 2. Inside the project directory, run: + ```bash mops test ``` --- -Happy coding with `serde`! 🚀 \ No newline at end of file +Happy coding with `serde`! 🚀 diff --git a/src/CBOR/lib.mo b/src/CBOR/lib.mo index c63343e..a020a77 100644 --- a/src/CBOR/lib.mo +++ b/src/CBOR/lib.mo @@ -9,7 +9,7 @@ import Nat64 "mo:base/Nat64"; import Result "mo:base/Result"; import Principal "mo:base/Principal"; -import CBOR_Value "mo:cbor/Value"; +import CBOR_Types "mo:cbor/Types"; import CBOR_Encoder "mo:cbor/Encoder"; import CBOR_Decoder "mo:cbor/Decoder"; import NatX "mo:xtended-numbers/NatX"; @@ -23,12 +23,12 @@ import Utils "../Utils"; module { public type Candid = CandidType.Candid; type Result = Result.Result; - type CBOR = CBOR_Value.Value; + type CBOR = CBOR_Types.Value; public type Options = CandidType.Options; - + /// Converts serialized Candid blob to CBOR blob - public func encode(blob : Blob, keys : [Text], options: ?Options) : Result { + public func encode(blob : Blob, keys : [Text], options : ?Options) : Result { let decoded_res = Candid.decode(blob, keys, options); let #ok(candid) = decoded_res else return Utils.send_error(decoded_res); @@ -38,20 +38,25 @@ module { }; /// Convert a Candid value to CBOR blob - public func fromCandid(candid : Candid, options: CandidType.Options) : Result { + public func fromCandid(candid : Candid, options : CandidType.Options) : Result { let res = transpile_candid_to_cbor(candid, options); let #ok(transpiled_cbor) = res else return Utils.send_error(res); - let cbor_with_self_describe_tag = #majorType6({ tag = 55799 : Nat64; value = transpiled_cbor; }); + let cbor_with_self_describe_tag = #majorType6({ + tag = 55799 : Nat64; + value = transpiled_cbor; + }); - switch(CBOR_Encoder.encode(cbor_with_self_describe_tag)){ - case(#ok(encoded_cbor)){ #ok (Blob.fromArray(encoded_cbor))}; - case(#err(#invalidValue(errMsg))){ #err("Invalid value error while encoding CBOR: " # errMsg) }; + switch (CBOR_Encoder.encode(cbor_with_self_describe_tag)) { + case (#ok(encoded_cbor)) { #ok(Blob.fromArray(encoded_cbor)) }; + case (#err(#invalidValue(errMsg))) { + #err("Invalid value error while encoding CBOR: " # errMsg); + }; }; }; - func transpile_candid_to_cbor(candid : Candid, options: CandidType.Options) : Result { - let transpiled_cbor : CBOR = switch(candid){ + func transpile_candid_to_cbor(candid : Candid, options : CandidType.Options) : Result { + let transpiled_cbor : CBOR = switch (candid) { case (#Empty) #majorType7(#_undefined); case (#Null) #majorType7(#_null); case (#Bool(n)) #majorType7(#bool(n)); @@ -74,7 +79,7 @@ module { case (#Array(arr)) { let buffer = Buffer.Buffer(arr.size()); - for (item in arr.vals()){ + for (item in arr.vals()) { let res = transpile_candid_to_cbor(item, options); let #ok(cbor_val) = res else return Utils.send_error(res); buffer.add(cbor_val); @@ -85,7 +90,7 @@ module { case (#Record(records) or #Map(records)) { let newRecords = Buffer.Buffer<(CBOR, CBOR)>(records.size()); - for ((key, val) in records.vals()){ + for ((key, val) in records.vals()) { let res = transpile_candid_to_cbor(val, options); let #ok(cbor_val) = res else return Utils.send_error(res); newRecords.add((#majorType3(key), cbor_val)); @@ -94,15 +99,15 @@ module { #majorType5(Buffer.toArray(newRecords)); }; - // Candid can make variables optional, when it is decoded using + // Candid can make variables optional, when it is decoded using // `from_candid` if its specified in the type defination // This features allow us to handle optional values when decoding CBOR - // + // // check out "CBOR Tests.options" in the tests folder to see how this in action case (#Option(option)) { let res = transpile_candid_to_cbor(option, options); let #ok(cbor_val) = res else return Utils.send_error(res); - cbor_val + cbor_val; }; case (#Principal(p)) #majorType2(Blob.toArray(Principal.toBlob(p))); @@ -115,42 +120,48 @@ module { #ok(transpiled_cbor); }; - public func decode(blob: Blob, options: ?Options): Result { + public func decode(blob : Blob, options : ?Options) : Result { let candid_res = toCandid(blob, Option.get(options, CandidType.defaultOptions)); let #ok(candid) = candid_res else return Utils.send_error(candid_res); Candid.encodeOne(candid, options); }; - public func toCandid(blob: Blob, options: CandidType.Options): Result { + public func toCandid(blob : Blob, options : CandidType.Options) : Result { let cbor_res = CBOR_Decoder.decode(blob); - + let candid_res = switch (cbor_res) { case (#ok(cbor)) { let #majorType6({ tag = 55799; value }) = cbor else return transpile_cbor_to_candid(cbor, options); transpile_cbor_to_candid(value, options); }; case (#err(cbor_error)) { - switch(cbor_error){ - case (#unexpectedBreak){ return #err("Error decoding CBOR: Unexpected break") }; - case (#unexpectedEndOfBytes) { return #err("Error decoding CBOR: Unexpected end of bytes") }; - case (#invalid(errMsg)) { return #err("Invalid CBOR: " # errMsg) }; + switch (cbor_error) { + case (#unexpectedBreak) { + return #err("Error decoding CBOR: Unexpected break"); + }; + case (#unexpectedEndOfBytes) { + return #err("Error decoding CBOR: Unexpected end of bytes"); + }; + case (#invalid(errMsg)) { + return #err("Invalid CBOR: " # errMsg); + }; }; }; }; - + let #ok(candid) = candid_res else return Utils.send_error(candid_res); #ok(candid); }; - public func transpile_cbor_to_candid(cbor: CBOR, options: CandidType.Options) : Result{ - let transpiled_candid = switch(cbor){ + public func transpile_cbor_to_candid(cbor : CBOR, options : CandidType.Options) : Result { + let transpiled_candid = switch (cbor) { case (#majorType0(n)) #Nat(Nat64.toNat(n)); case (#majorType1(n)) #Int(n); case (#majorType2(n)) #Blob(Blob.fromArray(n)); case (#majorType3(n)) #Text(n); case (#majorType4(arr)) { let buffer = Buffer.Buffer(arr.size()); - for (item in arr.vals()){ + for (item in arr.vals()) { let res = transpile_cbor_to_candid(item, options); let #ok(candid_val) = res else return Utils.send_error(res); buffer.add(candid_val); @@ -159,7 +170,7 @@ module { }; case (#majorType5(records)) { let buffer = Buffer.Buffer<(Text, Candid)>(records.size()); - for ((cbor_text, val) in records.vals()){ + for ((cbor_text, val) in records.vals()) { let #majorType3(key) = cbor_text else return #err("Error decoding CBOR: Unexpected key type"); let res = transpile_cbor_to_candid(val, options); @@ -167,7 +178,7 @@ module { buffer.add((key, candid_val)); }; - if (options.use_icrc_3_value_type){ + if (options.use_icrc_3_value_type) { #Map(Buffer.toArray(buffer)); } else { #Record(Buffer.toArray(buffer)); @@ -189,4 +200,4 @@ module { #ok(transpiled_candid); }; -} \ No newline at end of file +}; diff --git a/src/Candid/ICRC3Value.mo b/src/Candid/ICRC3Value.mo new file mode 100644 index 0000000..4658a2c --- /dev/null +++ b/src/Candid/ICRC3Value.mo @@ -0,0 +1,105 @@ +import Array "mo:base/Array"; +import Nat8 "mo:base/Nat8"; +import Nat16 "mo:base/Nat16"; +import Nat64 "mo:base/Nat64"; +import Nat32 "mo:base/Nat32"; +import Int8 "mo:base/Int8"; +import Int16 "mo:base/Int16"; +import Int64 "mo:base/Int64"; +import Int32 "mo:base/Int32"; +import Debug "mo:base/Debug"; +import Principal "mo:base/Principal"; + +import T "Types"; + +module { + public func toICRC3Value(candid_values : [T.Candid]) : [T.ICRC3Value] { + + func convert(candid : T.Candid) : T.ICRC3Value { + switch (candid) { + case (#Text(t)) #Text(t); + case (#Nat(n)) #Nat(n); + case (#Nat8(n)) #Nat(Nat8.toNat(n)); + case (#Nat16(n)) #Nat(Nat16.toNat(n)); + case (#Nat32(n)) #Nat(Nat32.toNat(n)); + case (#Nat64(n)) #Nat(Nat64.toNat(n)); + case (#Int(n)) #Int(n); + case (#Int8(n)) #Int(Int8.toInt(n)); + case (#Int16(n)) #Int(Int16.toInt(n)); + case (#Int32(n)) #Int(Int32.toInt(n)); + case (#Int64(n)) #Int(Int64.toInt(n)); + case (#Blob(b)) #Blob(b); + case (#Principal(p)) #Blob(Principal.toBlob(p)); + case (#Array(array_vals)) #Array( + Array.tabulate( + array_vals.size(), + func(i : Nat) : T.ICRC3Value { + convert(array_vals.get(i)); + }, + ) + ); + case (#Record(record_vals) or #Map(record_vals)) #Map( + Array.tabulate<(Text, T.ICRC3Value)>( + record_vals.size(), + func(i : Nat) : (Text, T.ICRC3Value) { + let (key, value) = record_vals.get(i); + let icrc3_value = convert(value); + (key, icrc3_value); + }, + ) + ); + case (#Bool(_) or #Option(_) or #Variant(_) or #Tuple(_)) Debug.trap(debug_show candid # " not suppported in ICRC3Value"); + case (#Empty) Debug.trap("Empty not suppported in ICRC3Value"); + case (#Float(f)) Debug.trap("Float not suppported in ICRC3Value"); + case (#Null) Debug.trap("Null not suppported in ICRC3Value"); + + }; + + }; + + Array.tabulate( + candid_values.size(), + func(i : Nat) : T.ICRC3Value { + convert(candid_values.get(i)); + }, + ); + + }; + + public func fromICRC3Value(icrc3_values : [T.ICRC3Value]) : [T.Candid] { + func convert(candid : T.ICRC3Value) : T.Candid { + switch (candid) { + case (#Text(t)) #Text(t); + case (#Nat(n)) #Nat(n); + case (#Int(n)) #Int(n); + case (#Blob(b)) #Blob(b); + case (#Array(array_vals)) #Array( + Array.tabulate( + array_vals.size(), + func(i : Nat) : T.Candid { + convert(array_vals.get(i)); + }, + ) + ); + case (#Map(record_vals)) #Map( + Array.tabulate<(Text, T.Candid)>( + record_vals.size(), + func(i : Nat) : (Text, T.Candid) { + let (key, value) = record_vals.get(i); + let candid_value = convert(value); + (key, candid_value); + }, + ) + ); + }; + }; + + Array.tabulate( + icrc3_values.size(), + func(i : Nat) : T.Candid { + convert(icrc3_values.get(i)); + }, + ) + + }; +}; diff --git a/src/Candid/Types.mo b/src/Candid/Types.mo index 8ccc7f8..2d28850 100644 --- a/src/Candid/Types.mo +++ b/src/Candid/Types.mo @@ -67,10 +67,10 @@ module { // nat values could be either reference pointers to compound types // or actual primitive value codes public type ShallowCandidTypes = { - #OptionRef: Nat; - #ArrayRef: Nat; - #RecordRef: [(Text, Nat)]; - #VariantRef: [(Text, Nat)]; + #OptionRef : Nat; + #ArrayRef : Nat; + #RecordRef : [(Text, Nat)]; + #VariantRef : [(Text, Nat)]; }; public let TypeCode = { @@ -107,7 +107,7 @@ module { /// Encoding and Decoding options public type Options = { - + /// #### Encoding Options /// Contains an array of tuples of the form (old_name, new_name) to rename the record keys. renameKeys : [(Text, Text)]; @@ -119,15 +119,23 @@ module { /// encodes faster if the complete type is known, but not necessary /// fails if types are incorrect - types : ?[CandidType]; + types : ?[CandidType]; /// #### Decoding Options /// When decoding, you have the option to pass in the Candid variant type - /// and omit the type portion of the candid blob and only pass in the + /// and omit the type portion of the candid blob and only pass in the /// serialized values - blob_contains_only_values: Bool; - + blob_contains_only_values : Bool; + + }; + public type ICRC3Value = { + #Blob : Blob; + #Text : Text; + #Nat : Nat; + #Int : Int; + #Array : [ICRC3Value]; + #Map : [(Text, ICRC3Value)]; }; public let defaultOptions : Options = { diff --git a/src/Candid/lib.mo b/src/Candid/lib.mo index 8971562..42ec798 100644 --- a/src/Candid/lib.mo +++ b/src/Candid/lib.mo @@ -11,6 +11,7 @@ import ToText "Text/ToText"; import T "Types"; import Utils "../Utils"; +import ICRC3Value "ICRC3Value"; module { /// A representation of the Candid format with variants for all possible types. @@ -36,4 +37,16 @@ module { public let concatKeys = Utils.concatKeys; + /// Converts an array of ICRC3Value values to [Candid](#Candid) values + public func fromICRC3Value(icrc3_values : [T.ICRC3Value]) : [Candid] { + ICRC3Value.fromICRC3Value(icrc3_values); + }; + + /// Converts an array of [Candid](#Candid) values to ICRC3Value values + public func toICRC3Value(candid_values : [Candid]) : [T.ICRC3Value] { + ICRC3Value.toICRC3Value(candid_values); + }; + + public type ICRC3Value = T.ICRC3Value; + }; diff --git a/src/Utils.mo b/src/Utils.mo index ac76f1a..2f5ed91 100644 --- a/src/Utils.mo +++ b/src/Utils.mo @@ -272,7 +272,6 @@ module { public func clear() { count := 0; - }; public func get(i : Nat) : A { diff --git a/src/lib.mo b/src/lib.mo index 84273a0..9f2c637 100644 --- a/src/lib.mo +++ b/src/lib.mo @@ -1,4 +1,3 @@ - import CandidType "Candid/Types"; import UrlEncodedModule "UrlEncoded"; import JsonModule "JSON"; @@ -16,10 +15,12 @@ module { public type CandidType = CandidType.CandidType; + public type ICRC3Value = CandidType.ICRC3Value; + public let JSON = JsonModule; public let URLEncoded = UrlEncodedModule; public let CBOR = CborModule; public let concatKeys = Utils.concatKeys; public let defaultOptions = CandidType.defaultOptions; -} \ No newline at end of file +}; diff --git a/tests/Candid.ICRC3.Test.mo b/tests/Candid.ICRC3.Test.mo index 27b1310..9371d87 100644 --- a/tests/Candid.ICRC3.Test.mo +++ b/tests/Candid.ICRC3.Test.mo @@ -8,7 +8,9 @@ import Text "mo:base/Text"; import { test; suite } "mo:test"; -import { Candid } "../src"; +import Serde "../src"; + +let { Candid } = Serde; suite( "Candid ICRC3 compatability Test", @@ -42,4 +44,49 @@ suite( ); }, + +); + +suite( + "Connvert between motoko and ICRC3", + func() { + test( + "motoko -> ICRC3", + func() { + + type User = { id : Nat; name : Text }; + + let user : User = { name = "bar"; id = 112 }; + + let blob = to_candid (user); + let #ok(candid_values) = Candid.decode(blob, ["name", "id"], null); + let icrc3_values = Candid.toICRC3Value(candid_values); + + assert icrc3_values[0] == #Map([ + ("id", #Nat(112)), + ("name", #Text("bar")), + ]); + }, + ); + + test( + "ICRC3 -> motoko", + func() { + type User = { name : Text; id : Nat }; + + let icrc3 : Serde.ICRC3Value = #Map([ + ("id", #Nat(112)), + ("name", #Text("bar")), + ]); + + let candid_values = Candid.fromICRC3Value([icrc3]); + + let #ok(blob) = Candid.encode(candid_values, null); + let user : ?User = from_candid (blob); + + assert user == ?{ name = "bar"; id = 112 }; + }, + ); + }, + ); diff --git a/tests/Candid.Large.test.mo b/tests/Candid.Large.test.mo index 0b453f8..b3292ca 100644 --- a/tests/Candid.Large.test.mo +++ b/tests/Candid.Large.test.mo @@ -28,10 +28,7 @@ let candify_store_item = { to_blob = func(c : StoreItem) : Blob { to_candid (c) }; }; -type X = { - name : Text; - x : X; -}; +type X = { name : Text; x : X }; // let x: X = { // name = "yo"; @@ -92,7 +89,7 @@ type StoreItem = { }; }; -let StoreItem : Serde.Candid.CandidType = #Record([ +let StoreItem : Serde.Candid.CandidType = #Record([ ("name", #Text), ("store", #Text), ("customer_reviews", #Array(CustomerReview)), @@ -101,13 +98,9 @@ let StoreItem : Serde.Candid.CandidType = #Record([ ("price", #Float), ("in_stock", #Bool), ("address", #Tuple([#Text, #Text, #Text, #Text])), - ("contact", #Record([ - ("email", #Text), - ("phone", #Option(#Text)), - ])), + ("contact", #Record([("email", #Text), ("phone", #Option(#Text))])), ]); - let cities = ["Toronto", "Ottawa", "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia", "San Antonio", "San Diego", "Dallas", "San Jose"]; let states = ["ON", "QC", "NY", "CA", "IL", "TX", "AZ", "PA", "TX", "CA", "TX", "CA"]; let streets = ["King St", "Queen St", "Yonge St", "Bay St", "Bloor St", "Dundas St", "College St", "Spadina Ave", "St Clair Ave", "Danforth Ave", "Eglinton Ave", "Lawrence Ave"]; @@ -203,7 +196,7 @@ suite( store_items_with_types.add(item); let candid_blob = candify_store_item.to_blob(item); let #ok(split_blob) = CandidDecoder.split(candid_blob, null); - let #ok(candid) = CandidDecoder.one_shot(candid_blob, store_item_keys, ?{Serde.Candid.defaultOptions with types = ?[StoreItem]}); + let #ok(candid) = CandidDecoder.one_shot(candid_blob, store_item_keys, ?{ Serde.Candid.defaultOptions with types = ?[StoreItem] }); candid_buffer_with_types.add(candid); }; }, @@ -213,7 +206,7 @@ suite( func() { for (i in Itertools.range(0, limit)) { let candid = candid_buffer_with_types.get(i); - let res = LegacyCandidEncoder.encode(candid, ?{Serde.Candid.defaultOptions with types = ?[StoreItem]}); + let res = LegacyCandidEncoder.encode(candid, ?{ Serde.Candid.defaultOptions with types = ?[StoreItem] }); let #ok(blob) = res; let item = candify_store_item.from_blob(blob); assert item == store_items_with_types.get(i); diff --git a/usage.md b/usage.md index ebc4f8c..9f1911a 100644 --- a/usage.md +++ b/usage.md @@ -1,7 +1,6 @@ - ## Usage Examples -### CBOR +### CBOR ```motoko @@ -19,7 +18,8 @@ let #ok(cbor) = cbor_res; ``` -#### Candid Variant +#### Candid Variant + ```motoko import { Candid } "mo:serde"; @@ -42,53 +42,56 @@ ``` #### ICRC3 Value + - The [`ICRC3` value type](https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3#value) is a representation of candid types in motoko used for sending information without breaking compatibility between canisters that might change their api/data types over time. - **Converting from ICRC3 to motoko** + ```motoko - import { Candid } "mo:serde"; + import Serde "mo:serde"; - type User = { - name: Text; - id: Nat; - }; + let { Candid } = Serde; - let icrc3 = #Map([ + type User = { name : Text; id : Nat }; + + let icrc3 : Serde.ICRC3Value = #Map([ + ("id", #Nat(112)), ("name", #Text("bar")), - ("id", #Nat(112)) ]); - let options = { Candid.defaultOptions with use_icrc_3_value_type = true }; - let #ok(blob) = Candid.encode(icrc3, ?options); - let user : ?User = from_candid(blob); + let candid_values = Candid.fromICRC3Value([icrc3]); + + let #ok(blob) = Candid.encode(candid_values, null); + let user : ?User = from_candid (blob); assert user == ?{ name = "bar"; id = 112 }; ``` - **Converting from motoko to ICRC3** + ```motoko - import { Candid } "mo:serde"; + import Serde "mo:serde"; - type User = { - name: Text; - id: Nat; - }; + let { Candid } = Serde; + + type User = { id : Nat; name : Text }; let user : User = { name = "bar"; id = 112 }; - let blob = to_candid(user); - let options = { Candid.defaultOptions with use_icrc_3_value_type = true }; - let icrc3 = Candid.encode(blob, ?options); + let blob = to_candid (user); + let #ok(candid_values) = Candid.decode(blob, ["name", "id"], null); + let icrc3_values = Candid.toICRC3Value(candid_values); - assert icrc3 == #Map([ + assert icrc3_values[0] == #Map([ + ("id", #Nat(112)), ("name", #Text("bar")), - ("id", #Nat(112)) ]); ``` ### Candid Text + ```motoko import { Candid } "mo:serde"; @@ -104,20 +107,20 @@ ``` - ### URL-Encoded Pairs + Serialization and deserialization for `application/x-www-form-urlencoded`. This implementation supports URL query strings and URL-encoded pairs, including arrays and nested objects, using the format `items[0]=value&items[1]=value` and `items[subKey]=value`." ```motoko import { URLEncoded } "mo:serde"; - + type User = { name: Text; - id: Nat; + id: Nat; }; - + let payload = "users[0][id]=123&users[0][name]=John&users[1][id]=456&users[1][name]=Jane"; let #ok(blob) = URLEncoded.fromText(payload, null);