From 9d013d9d6e878dc1712ea9fddb523ebbaae995f2 Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Sat, 15 Jul 2023 16:21:27 -0700 Subject: [PATCH] [feat] add new options object to support field key renaming --- .github/workflows/makefile.yml | 29 ++-- .gitignore | 2 + compile-tests.mjs | 160 +++++++++++++++++++++ dfx.json | 1 - docs/Candid/Decoder.md | 25 ++++ docs/Candid/Encoder.md | 19 +++ docs/Candid/Parser/Array.md | 7 + docs/Candid/Parser/Blob.md | 7 + docs/Candid/Parser/Bool.md | 7 + docs/Candid/Parser/Common.md | 49 +++++++ docs/Candid/Parser/Float.md | 7 + docs/Candid/Parser/Int.md | 13 ++ docs/Candid/Parser/IntX.md | 7 + docs/Candid/Parser/Nat.md | 13 ++ docs/Candid/Parser/NatX.md | 7 + docs/Candid/Parser/Option.md | 13 ++ docs/Candid/Parser/Principal.md | 7 + docs/Candid/Parser/Record.md | 19 +++ docs/Candid/Parser/Text.md | 13 ++ docs/Candid/Parser/Variant.md | 7 + docs/Candid/Parser/lib.md | 19 +++ docs/Candid/ToText.md | 7 + docs/Candid/Types.md | 14 ++ docs/Candid/lib.md | 14 ++ docs/JSON/FromText.md | 15 ++ docs/JSON/ToText.md | 15 ++ docs/JSON/lib.md | 8 ++ docs/UrlEncoded/FromText.md | 15 ++ docs/UrlEncoded/Parser.md | 7 + docs/UrlEncoded/ToText.md | 15 ++ docs/UrlEncoded/lib.md | 2 + docs/Utils.md | 25 ++++ docs/index.md | 30 ++++ makefile | 6 +- src/Candid/{ => Blob}/Decoder.mo | 30 +++- src/Candid/{ => Blob}/Encoder.mo | 72 ++++++---- src/Candid/{ => Text}/Parser/Array.mo | 2 +- src/Candid/{ => Text}/Parser/Blob.mo | 2 +- src/Candid/{ => Text}/Parser/Bool.mo | 2 +- src/Candid/{ => Text}/Parser/Common.mo | 2 +- src/Candid/{ => Text}/Parser/Float.mo | 2 +- src/Candid/{ => Text}/Parser/Int.mo | 2 +- src/Candid/{ => Text}/Parser/IntX.mo | 2 +- src/Candid/{ => Text}/Parser/Nat.mo | 2 +- src/Candid/{ => Text}/Parser/NatX.mo | 2 +- src/Candid/{ => Text}/Parser/Option.mo | 2 +- src/Candid/{ => Text}/Parser/Principal.mo | 2 +- src/Candid/{ => Text}/Parser/Record.mo | 2 +- src/Candid/{ => Text}/Parser/Text.mo | 2 +- src/Candid/{ => Text}/Parser/Variant.mo | 2 +- src/Candid/{ => Text}/Parser/lib.mo | 6 +- src/Candid/{ => Text}/ToText.mo | 10 +- src/Candid/Types.mo | 6 + src/Candid/lib.mo | 8 +- src/JSON/FromText.mo | 5 +- src/JSON/ToText.mo | 8 +- src/UrlEncoded/FromText.mo | 6 +- src/UrlEncoded/ToText.mo | 5 +- src/UrlEncoded/lib.mo | 1 + src/lib.mo | 8 ++ tests/Candid.Test.mo | 168 ++++++++++++++-------- tests/JSON.Test.mo | 72 ++++++++-- tests/UrlEncoded.Test.mo | 29 ++-- 63 files changed, 903 insertions(+), 163 deletions(-) create mode 100644 compile-tests.mjs create mode 100644 docs/Candid/Decoder.md create mode 100644 docs/Candid/Encoder.md create mode 100644 docs/Candid/Parser/Array.md create mode 100644 docs/Candid/Parser/Blob.md create mode 100644 docs/Candid/Parser/Bool.md create mode 100644 docs/Candid/Parser/Common.md create mode 100644 docs/Candid/Parser/Float.md create mode 100644 docs/Candid/Parser/Int.md create mode 100644 docs/Candid/Parser/IntX.md create mode 100644 docs/Candid/Parser/Nat.md create mode 100644 docs/Candid/Parser/NatX.md create mode 100644 docs/Candid/Parser/Option.md create mode 100644 docs/Candid/Parser/Principal.md create mode 100644 docs/Candid/Parser/Record.md create mode 100644 docs/Candid/Parser/Text.md create mode 100644 docs/Candid/Parser/Variant.md create mode 100644 docs/Candid/Parser/lib.md create mode 100644 docs/Candid/ToText.md create mode 100644 docs/Candid/Types.md create mode 100644 docs/Candid/lib.md create mode 100644 docs/JSON/FromText.md create mode 100644 docs/JSON/ToText.md create mode 100644 docs/JSON/lib.md create mode 100644 docs/UrlEncoded/FromText.md create mode 100644 docs/UrlEncoded/Parser.md create mode 100644 docs/UrlEncoded/ToText.md create mode 100644 docs/UrlEncoded/lib.md create mode 100644 docs/Utils.md create mode 100644 docs/index.md rename src/Candid/{ => Blob}/Decoder.mo (84%) rename src/Candid/{ => Blob}/Encoder.mo (63%) rename src/Candid/{ => Text}/Parser/Array.mo (97%) rename src/Candid/{ => Text}/Parser/Blob.mo (98%) rename src/Candid/{ => Text}/Parser/Bool.mo (96%) rename src/Candid/{ => Text}/Parser/Common.mo (99%) rename src/Candid/{ => Text}/Parser/Float.mo (97%) rename src/Candid/{ => Text}/Parser/Int.mo (98%) rename src/Candid/{ => Text}/Parser/IntX.mo (98%) rename src/Candid/{ => Text}/Parser/Nat.mo (98%) rename src/Candid/{ => Text}/Parser/NatX.mo (98%) rename src/Candid/{ => Text}/Parser/Option.mo (97%) rename src/Candid/{ => Text}/Parser/Principal.mo (97%) rename src/Candid/{ => Text}/Parser/Record.mo (98%) rename src/Candid/{ => Text}/Parser/Text.mo (98%) rename src/Candid/{ => Text}/Parser/Variant.mo (97%) rename src/Candid/{ => Text}/Parser/lib.mo (95%) rename src/Candid/{ => Text}/ToText.mo (93%) create mode 100644 src/lib.mo diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index 7cd52d3..a704e6c 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -14,28 +14,31 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 14 - - uses: aviate-labs/setup-dfx@v0.2.3 + node-version: 18 + - uses: aviate-labs/setup-dfx@v0.2.5 with: - vessel-version: 0.6.3 - dfx-version: 0.12.1 + dfx-version: 0.14.1 + + - name: install wasmtime + run: | + curl https://wasmtime.dev/install.sh -sSf | bash + echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH + + - name: Install mocv + run: | + npm --yes -g i mocv + + - name: Select mocv version + run: mocv use 0.9.4 - name: install mops run: | npm --yes -g i ic-mops mops i mops sources - - - name: install wasmtime - run: | - curl https://wasmtime.dev/install.sh -sSf | bash - echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH - name: Detect Warnings run: make no-warn - name: Run Tests - run: make compile-tests - - # - name: Generate Docs - # run: make docs + run: mops test diff --git a/.gitignore b/.gitignore index c0b1f14..bde6d28 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ node_modules/ dist/ +# +more-details.md \ No newline at end of file diff --git a/compile-tests.mjs b/compile-tests.mjs new file mode 100644 index 0000000..015ce42 --- /dev/null +++ b/compile-tests.mjs @@ -0,0 +1,160 @@ +#!/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/dfx.json b/dfx.json index 994a73b..9f142cb 100644 --- a/dfx.json +++ b/dfx.json @@ -1,6 +1,5 @@ { "version": 1, - "dfx": "0.12.1", "defaults": { "build": { "packtool": "mops sources", diff --git a/docs/Candid/Decoder.md b/docs/Candid/Decoder.md new file mode 100644 index 0000000..6570cf9 --- /dev/null +++ b/docs/Candid/Decoder.md @@ -0,0 +1,25 @@ +# 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 new file mode 100644 index 0000000..64cd7f3 --- /dev/null +++ b/docs/Candid/Encoder.md @@ -0,0 +1,19 @@ +# 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 new file mode 100644 index 0000000..57e825b --- /dev/null +++ b/docs/Candid/Parser/Array.md @@ -0,0 +1,7 @@ +# 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 new file mode 100644 index 0000000..2ae2606 --- /dev/null +++ b/docs/Candid/Parser/Blob.md @@ -0,0 +1,7 @@ +# 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 new file mode 100644 index 0000000..c75d6c2 --- /dev/null +++ b/docs/Candid/Parser/Bool.md @@ -0,0 +1,7 @@ +# 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 new file mode 100644 index 0000000..5cd7311 --- /dev/null +++ b/docs/Candid/Parser/Common.md @@ -0,0 +1,49 @@ +# 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 new file mode 100644 index 0000000..9ead753 --- /dev/null +++ b/docs/Candid/Parser/Float.md @@ -0,0 +1,7 @@ +# 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 new file mode 100644 index 0000000..788996b --- /dev/null +++ b/docs/Candid/Parser/Int.md @@ -0,0 +1,13 @@ +# 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 new file mode 100644 index 0000000..38fcb70 --- /dev/null +++ b/docs/Candid/Parser/IntX.md @@ -0,0 +1,7 @@ +# 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 new file mode 100644 index 0000000..3cf0548 --- /dev/null +++ b/docs/Candid/Parser/Nat.md @@ -0,0 +1,13 @@ +# 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 new file mode 100644 index 0000000..89ac957 --- /dev/null +++ b/docs/Candid/Parser/NatX.md @@ -0,0 +1,7 @@ +# 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 new file mode 100644 index 0000000..efd2a96 --- /dev/null +++ b/docs/Candid/Parser/Option.md @@ -0,0 +1,13 @@ +# 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 new file mode 100644 index 0000000..e5ef0d1 --- /dev/null +++ b/docs/Candid/Parser/Principal.md @@ -0,0 +1,7 @@ +# 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 new file mode 100644 index 0000000..0c9e737 --- /dev/null +++ b/docs/Candid/Parser/Record.md @@ -0,0 +1,19 @@ +# 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 new file mode 100644 index 0000000..780b1f6 --- /dev/null +++ b/docs/Candid/Parser/Text.md @@ -0,0 +1,13 @@ +# 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 new file mode 100644 index 0000000..3cc3544 --- /dev/null +++ b/docs/Candid/Parser/Variant.md @@ -0,0 +1,7 @@ +# 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 new file mode 100644 index 0000000..6008015 --- /dev/null +++ b/docs/Candid/Parser/lib.md @@ -0,0 +1,19 @@ +# 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 new file mode 100644 index 0000000..d13d5ca --- /dev/null +++ b/docs/Candid/ToText.md @@ -0,0 +1,7 @@ +# 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 new file mode 100644 index 0000000..c34cff3 --- /dev/null +++ b/docs/Candid/Types.md @@ -0,0 +1,14 @@ +# 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 new file mode 100644 index 0000000..056c6c5 --- /dev/null +++ b/docs/Candid/lib.md @@ -0,0 +1,14 @@ +# 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 new file mode 100644 index 0000000..ff37fbd --- /dev/null +++ b/docs/JSON/FromText.md @@ -0,0 +1,15 @@ +# 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 new file mode 100644 index 0000000..9bc7ded --- /dev/null +++ b/docs/JSON/ToText.md @@ -0,0 +1,15 @@ +# 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 new file mode 100644 index 0000000..8c2f1fd --- /dev/null +++ b/docs/JSON/lib.md @@ -0,0 +1,8 @@ +# 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 new file mode 100644 index 0000000..bcf41fe --- /dev/null +++ b/docs/UrlEncoded/FromText.md @@ -0,0 +1,15 @@ +# 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 new file mode 100644 index 0000000..730b87c --- /dev/null +++ b/docs/UrlEncoded/Parser.md @@ -0,0 +1,7 @@ +# UrlEncoded/Parser + +## Function `parseValue` +``` motoko no-repl +func parseValue(text : Text) : Candid +``` + diff --git a/docs/UrlEncoded/ToText.md b/docs/UrlEncoded/ToText.md new file mode 100644 index 0000000..6b85f22 --- /dev/null +++ b/docs/UrlEncoded/ToText.md @@ -0,0 +1,15 @@ +# 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 new file mode 100644 index 0000000..42640bb --- /dev/null +++ b/docs/UrlEncoded/lib.md @@ -0,0 +1,2 @@ +# UrlEncoded/lib +A module for converting between Motoko values and Url-Encoded `Text`. diff --git a/docs/Utils.md b/docs/Utils.md new file mode 100644 index 0000000..c19b496 --- /dev/null +++ b/docs/Utils.md @@ -0,0 +1,25 @@ +# 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 new file mode 100644 index 0000000..840b6e6 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,30 @@ +# 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/makefile b/makefile index 65e93b5..078a134 100644 --- a/makefile +++ b/makefile @@ -4,8 +4,8 @@ compile-tests: bash compile-tests.sh $(file) no-warn: - find src -type f -name '*.mo' -print0 | xargs -0 $(shell vessel bin)/moc -r $(shell mops sources) -Werror -wasi-system-api + find src -type f -name '*.mo' -print0 | xargs -0 $(shell mocv bin)/moc -r $(shell mops sources) -Werror -wasi-system-api docs: - $(shell vessel bin)/mo-doc - $(shell vessel bin)/mo-doc --format plain + $(shell mocv bin)/mo-doc + $(shell mocv bin)/mo-doc --format plain diff --git a/src/Candid/Decoder.mo b/src/Candid/Blob/Decoder.mo similarity index 84% rename from src/Candid/Decoder.mo rename to src/Candid/Blob/Decoder.mo index b8d5241..725bc61 100644 --- a/src/Candid/Decoder.mo +++ b/src/Candid/Blob/Decoder.mo @@ -20,8 +20,8 @@ import Tag "mo:candid/Tag"; import { hashName } "mo:candid/Tag"; -import T "Types"; -import U "../Utils"; +import T "../Types"; +import U "../../Utils"; module { type Arg = Arg.Arg; @@ -30,16 +30,34 @@ module { type RecordFieldType = Type.RecordFieldType; type RecordFieldValue = Value.RecordFieldValue; + type TrieMap = TrieMap.TrieMap; type Candid = T.Candid; type KeyValuePair = T.KeyValuePair; - public func decode(blob : Blob, recordKeys : [Text]) : [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. + + public func decode(blob : Blob, record_keys: [Text], options: ?T.Options) : [Candid] { let res = Decoder.decode(blob); + let renaming_map : TrieMap = switch (options) { + case (?{renameKeys}) TrieMap.fromEntries(renameKeys.vals(), Text.equal, Text.hash); + case (_) TrieMap.TrieMap(Text.equal, Text.hash); + }; + let keyEntries = Iter.map( - recordKeys.vals(), - func(key : Text) : (Nat32, Text) { - (hashName(key), key); + record_keys.vals(), + func(original_key : Text) : (Nat32, Text) { + let new_key = switch(renaming_map.get(original_key)) { + case (?key) key; + case (_) original_key; + }; + + (hashName(original_key), new_key); }, ); diff --git a/src/Candid/Encoder.mo b/src/Candid/Blob/Encoder.mo similarity index 63% rename from src/Candid/Encoder.mo rename to src/Candid/Blob/Encoder.mo index 7e59cce..eb8ff59 100644 --- a/src/Candid/Encoder.mo +++ b/src/Candid/Blob/Encoder.mo @@ -3,6 +3,7 @@ import Blob "mo:base/Blob"; import Debug "mo:base/Debug"; import Result "mo:base/Result"; import Prelude "mo:base/Prelude"; +import Text "mo:base/Text"; import Encoder "mo:candid/Encoder"; import Decoder "mo:candid/Decoder"; @@ -10,8 +11,9 @@ import Arg "mo:candid/Arg"; import Value "mo:candid/Value"; import Type "mo:candid/Type"; -import T "Types"; -import U "../Utils"; +import T "../Types"; +import U "../../Utils"; +import TrieMap "mo:base/TrieMap"; module { type Arg = Arg.Arg; @@ -19,33 +21,42 @@ module { type Value = Value.Value; type RecordFieldType = Type.RecordFieldType; type RecordFieldValue = Value.RecordFieldValue; + type TrieMap = TrieMap.TrieMap; type Candid = T.Candid; type KeyValuePair = T.KeyValuePair; - public func encode(candid_values : [Candid]) : Blob { - let args = toArgs(candid_values); + public func encode(candid_values : [Candid], options: ?T.Options) : Blob { + let renaming_map = TrieMap.TrieMap(Text.equal, Text.hash); + + ignore do ? { + let renameKeys = options!.renameKeys; + for ((k, v) in renameKeys.vals()) { + renaming_map.put(k, v); + }; + }; + + let args = toArgs(candid_values, renaming_map); Encoder.encode(args); }; - public func encodeOne(candid : Candid) : Blob { - let args = toArgs([candid]); - Encoder.encode(args); + public func encodeOne(candid : Candid, options: ?T.Options) : Blob { + encode([candid], options); }; - public func toArgs(candid_values : [Candid]) : [Arg] { + public func toArgs(candid_values : [Candid], renaming_map: TrieMap) : [Arg] { Array.map( candid_values, func(candid : Candid) : Arg { { - _type = toArgType(candid); - value = toArgValue(candid); + _type = toArgType(candid, renaming_map); + value = toArgValue(candid, renaming_map); }; }, ); }; - func toArgType(candid : Candid) : Type { + func toArgType(candid : Candid, renaming_map: TrieMap) : Type { switch (candid) { case (#Nat(_)) #nat; case (#Nat8(_)) #nat8; @@ -71,11 +82,11 @@ module { case (#Null) #_null; case (#Option(optType)) { - #opt(toArgType(optType)); + #opt(toArgType(optType, renaming_map)); }; case (#Array(arr)) { if (arr.size() > 0) { - #vector(toArgType(arr[0])); + #vector(toArgType(arr[0], renaming_map)); } else { #vector(#empty); }; @@ -85,9 +96,11 @@ module { let newRecords = Array.map( Array.sort(records, U.cmpRecords), func((key, val) : KeyValuePair) : RecordFieldType { + let renamed_key = get_renamed_key(renaming_map, key); + { - tag = #name(key); - _type = toArgType(val); + tag = #name(renamed_key); + _type = toArgType(val, renaming_map); }; }, ); @@ -96,10 +109,11 @@ module { }; case (#Variant((key, val))) { + let renamed_key = get_renamed_key(renaming_map, key); #variant([{ - tag = #name(key); - _type = toArgType(val); + tag = #name(renamed_key); + _type = toArgType(val, renaming_map); }]); }; @@ -107,7 +121,7 @@ module { }; }; - func toArgValue(candid : Candid) : Value { + func toArgValue(candid : Candid, renaming_map: TrieMap) : Value { switch (candid) { case (#Nat(n)) #nat(n); case (#Nat8(n)) #nat8(n); @@ -132,13 +146,13 @@ module { case (#Null) #_null; case (#Option(optVal)) { - #opt(?toArgValue(optVal)); + #opt(?toArgValue(optVal, renaming_map)); }; case (#Array(arr)) { let transformedArr = Array.map( arr, func(elem : Candid) : Value { - toArgValue(elem); + toArgValue(elem, renaming_map); }, ); @@ -162,9 +176,11 @@ module { let newRecords = Array.map( records, func((key, val) : KeyValuePair) : RecordFieldValue { + let renamed_key = get_renamed_key(renaming_map, key); + { - tag = #name(key); - value = toArgValue(val); + tag = #name(renamed_key); + value = toArgValue(val, renaming_map); }; }, ); @@ -173,14 +189,22 @@ module { }; case (#Variant((key, val))) { + let renamed_key = get_renamed_key(renaming_map, key); #variant({ - tag = #name(key); - value = toArgValue(val); + tag = #name(renamed_key); + value = toArgValue(val, renaming_map); }); }; case (c) Prelude.unreachable(); }; }; + + func get_renamed_key(renaming_map: TrieMap, key: Text) : Text { + switch (renaming_map.get(key)) { + case (?v) v; + case (_) key; + }; + } }; diff --git a/src/Candid/Parser/Array.mo b/src/Candid/Text/Parser/Array.mo similarity index 97% rename from src/Candid/Parser/Array.mo rename to src/Candid/Text/Parser/Array.mo index 11aa74f..207e909 100644 --- a/src/Candid/Parser/Array.mo +++ b/src/Candid/Text/Parser/Array.mo @@ -4,7 +4,7 @@ import List "mo:base/List"; import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; -import Candid "../Types"; +import Candid "../../Types"; import { ignoreSpace } "Common"; diff --git a/src/Candid/Parser/Blob.mo b/src/Candid/Text/Parser/Blob.mo similarity index 98% rename from src/Candid/Parser/Blob.mo rename to src/Candid/Text/Parser/Blob.mo index 1615ebe..1338cfd 100644 --- a/src/Candid/Parser/Blob.mo +++ b/src/Candid/Text/Parser/Blob.mo @@ -5,7 +5,7 @@ import List "mo:base/List"; import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; -import Candid "../Types"; +import Candid "../../Types"; import { ignoreSpace; hexChar; fromHex } "Common"; module { diff --git a/src/Candid/Parser/Bool.mo b/src/Candid/Text/Parser/Bool.mo similarity index 96% rename from src/Candid/Parser/Bool.mo rename to src/Candid/Text/Parser/Bool.mo index 017e44a..e6da61a 100644 --- a/src/Candid/Parser/Bool.mo +++ b/src/Candid/Text/Parser/Bool.mo @@ -5,7 +5,7 @@ import List "mo:base/List"; import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; -import Candid "../Types"; +import Candid "../../Types"; import { ignoreSpace; hexChar; fromHex } "Common"; module { diff --git a/src/Candid/Parser/Common.mo b/src/Candid/Text/Parser/Common.mo similarity index 99% rename from src/Candid/Parser/Common.mo rename to src/Candid/Text/Parser/Common.mo index 1ffc11e..d533dd5 100644 --- a/src/Candid/Parser/Common.mo +++ b/src/Candid/Text/Parser/Common.mo @@ -8,7 +8,7 @@ import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; import NatX "mo:xtended-numbers/NatX"; -import Candid "../Types"; +import Candid "../../Types"; module { type Candid = Candid.Candid; diff --git a/src/Candid/Parser/Float.mo b/src/Candid/Text/Parser/Float.mo similarity index 97% rename from src/Candid/Parser/Float.mo rename to src/Candid/Text/Parser/Float.mo index 688c4d1..b9db4aa 100644 --- a/src/Candid/Parser/Float.mo +++ b/src/Candid/Text/Parser/Float.mo @@ -4,7 +4,7 @@ import List "mo:base/List"; import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; -import Candid "../Types"; +import Candid "../../Types"; import { listToNat } "Common"; import { parseInt } "Int"; diff --git a/src/Candid/Parser/Int.mo b/src/Candid/Text/Parser/Int.mo similarity index 98% rename from src/Candid/Parser/Int.mo rename to src/Candid/Text/Parser/Int.mo index 761b0d0..4618698 100644 --- a/src/Candid/Parser/Int.mo +++ b/src/Candid/Text/Parser/Int.mo @@ -5,7 +5,7 @@ import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; import NatX "mo:xtended-numbers/NatX"; -import Candid "../Types"; +import Candid "../../Types"; import { listToNat } "Common"; import { parseNat } "Nat"; diff --git a/src/Candid/Parser/IntX.mo b/src/Candid/Text/Parser/IntX.mo similarity index 98% rename from src/Candid/Parser/IntX.mo rename to src/Candid/Text/Parser/IntX.mo index 0fa6a27..4246214 100644 --- a/src/Candid/Parser/IntX.mo +++ b/src/Candid/Text/Parser/IntX.mo @@ -9,7 +9,7 @@ import Int64 "mo:base/Int64"; import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; -import Candid "../Types"; +import Candid "../../Types"; import { ignoreSpace; toText } "Common"; import { parseInt; intParser } "Int"; diff --git a/src/Candid/Parser/Nat.mo b/src/Candid/Text/Parser/Nat.mo similarity index 98% rename from src/Candid/Parser/Nat.mo rename to src/Candid/Text/Parser/Nat.mo index 516d42c..5980150 100644 --- a/src/Candid/Parser/Nat.mo +++ b/src/Candid/Text/Parser/Nat.mo @@ -7,7 +7,7 @@ import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; import NatX "mo:xtended-numbers/NatX"; -import Candid "../Types"; +import Candid "../../Types"; import { ignoreSpace; hexChar; fromHex; removeUnderscore; listToNat } "Common"; module { diff --git a/src/Candid/Parser/NatX.mo b/src/Candid/Text/Parser/NatX.mo similarity index 98% rename from src/Candid/Parser/NatX.mo rename to src/Candid/Text/Parser/NatX.mo index 7778ae3..f916c6e 100644 --- a/src/Candid/Parser/NatX.mo +++ b/src/Candid/Text/Parser/NatX.mo @@ -9,7 +9,7 @@ import Nat64 "mo:base/Nat64"; import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; -import Candid "../Types"; +import Candid "../../Types"; import { ignoreSpace; toText } "Common"; import { parseNat; natParser } "Nat"; diff --git a/src/Candid/Parser/Option.mo b/src/Candid/Text/Parser/Option.mo similarity index 97% rename from src/Candid/Parser/Option.mo rename to src/Candid/Text/Parser/Option.mo index fba8414..338ff3c 100644 --- a/src/Candid/Parser/Option.mo +++ b/src/Candid/Text/Parser/Option.mo @@ -4,7 +4,7 @@ import List "mo:base/List"; import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; -import Candid "../Types"; +import Candid "../../Types"; import { ignoreSpace } "Common"; diff --git a/src/Candid/Parser/Principal.mo b/src/Candid/Text/Parser/Principal.mo similarity index 97% rename from src/Candid/Parser/Principal.mo rename to src/Candid/Text/Parser/Principal.mo index f077b57..be12212 100644 --- a/src/Candid/Parser/Principal.mo +++ b/src/Candid/Text/Parser/Principal.mo @@ -6,7 +6,7 @@ import Principal "mo:base/Principal"; import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; -import Candid "../Types"; +import Candid "../../Types"; import { ignoreSpace; toText } "Common"; module { diff --git a/src/Candid/Parser/Record.mo b/src/Candid/Text/Parser/Record.mo similarity index 98% rename from src/Candid/Parser/Record.mo rename to src/Candid/Text/Parser/Record.mo index 0129ba5..84658f0 100644 --- a/src/Candid/Parser/Record.mo +++ b/src/Candid/Text/Parser/Record.mo @@ -4,7 +4,7 @@ import List "mo:base/List"; import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; -import Candid "../Types"; +import Candid "../../Types"; import { ignoreSpace; hexChar; fromHex; toText } "Common"; import { parseText } "Text"; diff --git a/src/Candid/Parser/Text.mo b/src/Candid/Text/Parser/Text.mo similarity index 98% rename from src/Candid/Parser/Text.mo rename to src/Candid/Text/Parser/Text.mo index 540f3b3..4d015cf 100644 --- a/src/Candid/Parser/Text.mo +++ b/src/Candid/Text/Parser/Text.mo @@ -6,7 +6,7 @@ import Text "mo:base/Text"; import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; -import Candid "../Types"; +import Candid "../../Types"; module{ type Candid = Candid.Candid; diff --git a/src/Candid/Parser/Variant.mo b/src/Candid/Text/Parser/Variant.mo similarity index 97% rename from src/Candid/Parser/Variant.mo rename to src/Candid/Text/Parser/Variant.mo index 37818e4..442ee6d 100644 --- a/src/Candid/Parser/Variant.mo +++ b/src/Candid/Text/Parser/Variant.mo @@ -4,7 +4,7 @@ import List "mo:base/List"; import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; -import Candid "../Types"; +import Candid "../../Types"; import { ignoreSpace } "Common"; import { keyParser; fieldParser } = "Record"; diff --git a/src/Candid/Parser/lib.mo b/src/Candid/Text/Parser/lib.mo similarity index 95% rename from src/Candid/Parser/lib.mo rename to src/Candid/Text/Parser/lib.mo index 38b4ebc..3686b3e 100644 --- a/src/Candid/Parser/lib.mo +++ b/src/Candid/Text/Parser/lib.mo @@ -2,11 +2,12 @@ import Char "mo:base/Char"; import Debug "mo:base/Debug"; import Iter "mo:base/Iter"; import List "mo:base/List"; +import TrieMap "mo:base/TrieMap"; import C "mo:parser-combinators/Combinators"; import P "mo:parser-combinators/Parser"; -import Candid "../Types"; +import Candid "../../Types"; import { ignoreSpace; any } "Common"; @@ -27,6 +28,7 @@ import { variantParser } "Variant"; module CandidParser { type Candid = Candid.Candid; type List = List.List; + type TrieMap = TrieMap.TrieMap; type Parser = P.Parser; @@ -35,7 +37,7 @@ module CandidParser { switch (parseCandid(chars)) { case (?candid) candid; - case (null) Debug.trap("Failed to parse Candid text for input: " # debug_show (chars)); + case (null) Debug.trap("Failed to parse Candid text from input: " # debug_show (chars)); }; }; diff --git a/src/Candid/ToText.mo b/src/Candid/Text/ToText.mo similarity index 93% rename from src/Candid/ToText.mo rename to src/Candid/Text/ToText.mo index 0a3fd67..062ebbb 100644 --- a/src/Candid/ToText.mo +++ b/src/Candid/Text/ToText.mo @@ -1,15 +1,17 @@ import Float "mo:base/Float"; import Text "mo:base/Text"; import Principal "mo:base/Principal"; +import TrieMap "mo:base/TrieMap"; import Itertools "mo:itertools/Iter"; -import Candid "Types"; +import CandidTypes "../Types"; -import U "../Utils"; +import U "../../Utils"; module { - type Candid = Candid.Candid; + type Candid = CandidTypes.Candid; + type TrieMap = TrieMap.TrieMap; public func toText(candid_values : [Candid]) : Text { var text = ""; @@ -18,7 +20,7 @@ module { for (val in candid_iter) { if (candid_iter.peek() == null){ - text #= candidToText(val); + text #= candidToText(val, ); } else { text #= candidToText(val) # ", "; }; diff --git a/src/Candid/Types.mo b/src/Candid/Types.mo index b7e7a29..0b95131 100644 --- a/src/Candid/Types.mo +++ b/src/Candid/Types.mo @@ -32,4 +32,10 @@ module { }; + /// Encoding and Decoding options + public type Options = { + /// Contains an array of tuples of the form (old_name, new_name) to rename the record keys. + renameKeys : [(Text, Text)]; + }; + }; diff --git a/src/Candid/lib.mo b/src/Candid/lib.mo index db78496..d6da587 100644 --- a/src/Candid/lib.mo +++ b/src/Candid/lib.mo @@ -5,10 +5,10 @@ import Debug "mo:base/Debug"; import Result "mo:base/Result"; import Prelude "mo:base/Prelude"; -import Encoder "Encoder"; -import Decoder "Decoder"; -import Parser "Parser"; -import ToText "ToText"; +import Encoder "Blob/Encoder"; +import Decoder "Blob/Decoder"; +import Parser "Text/Parser"; +import ToText "Text/ToText"; import T "Types"; diff --git a/src/JSON/FromText.mo b/src/JSON/FromText.mo index 7117f76..8015ff2 100644 --- a/src/JSON/FromText.mo +++ b/src/JSON/FromText.mo @@ -14,15 +14,16 @@ import JSON "mo:json/JSON"; import Candid "../Candid"; import U "../Utils"; +import CandidTypes "../Candid/Types"; module { type JSON = JSON.JSON; type Candid = Candid.Candid; /// Converts JSON text to a serialized Candid blob that can be decoded to motoko values using `from_candid()` - public func fromText(rawText : Text) : Blob { + public func fromText(rawText : Text, options: ?CandidTypes.Options) : Blob { let candid = toCandid(rawText); - Candid.encodeOne(candid); + Candid.encodeOne(candid, options); }; /// Convert JSON text to a Candid value diff --git a/src/JSON/ToText.mo b/src/JSON/ToText.mo index 252d90c..0f890ab 100644 --- a/src/JSON/ToText.mo +++ b/src/JSON/ToText.mo @@ -14,14 +14,15 @@ import NatX "mo:xtended-numbers/NatX"; import IntX "mo:xtended-numbers/IntX"; import Candid "../Candid"; +import CandidTypes "../Candid/Types"; module { type JSON = JSON.JSON; type Candid = Candid.Candid; /// Converts serialized Candid blob to JSON text - public func toText(blob : Blob, keys : [Text]) : Text { - let candid = Candid.decode(blob, keys); + public func toText(blob : Blob, keys : [Text], options: ?CandidTypes.Options) : Text { + let candid = Candid.decode(blob, keys, options); fromCandid(candid[0]); }; @@ -84,7 +85,8 @@ module { let (key, val) = variant; #Object([("#" # key, candidToJSON(val))]); }; - + + // #Blob(_), #Empty and #Principal(_) are not supported case (_) Prelude.unreachable(); }; }; diff --git a/src/UrlEncoded/FromText.mo b/src/UrlEncoded/FromText.mo index 48c9f88..e251662 100644 --- a/src/UrlEncoded/FromText.mo +++ b/src/UrlEncoded/FromText.mo @@ -17,6 +17,7 @@ import Prelude "mo:base/Prelude"; import itertools "mo:itertools/Iter"; import Candid "../Candid"; +import CandidTypes "../Candid/Types"; import { parseValue } "./Parser"; import U "../Utils"; @@ -39,9 +40,9 @@ module { func newMap() : NestedTrieMap = TrieMap.TrieMap(Text.equal, Text.hash); /// Converts a Url-Encoded Text to a serialized Candid Record - public func fromText(text : Text) : Blob { + public func fromText(text : Text, options: ?CandidTypes.Options) : Blob { let candid = toCandid(text); - Candid.encodeOne(candid); + Candid.encodeOne(candid, options); }; /// Converts a Url-Encoded Text to a Candid Record @@ -203,6 +204,7 @@ module { return #Array(array); }; + // check if single value is a variant if (triemap.size() == 1) { let (variant_key, value) = switch (triemap.entries().next()) { case (?(k, v))(k, v); diff --git a/src/UrlEncoded/ToText.mo b/src/UrlEncoded/ToText.mo index e786a93..aad3ead 100644 --- a/src/UrlEncoded/ToText.mo +++ b/src/UrlEncoded/ToText.mo @@ -20,14 +20,15 @@ import itertools "mo:itertools/Iter"; import Candid "../Candid"; import U "../Utils"; +import CandidTypes "../Candid/Types"; module { type Candid = Candid.Candid; type TrieMap = TrieMap.TrieMap; /// Converts a serialized Candid blob to a URL-Encoded string. - public func toText(blob : Blob, keys : [Text]) : Text { - let candid = Candid.decode(blob, keys); + public func toText(blob : Blob, keys : [Text], options: ?CandidTypes.Options) : Text { + let candid = Candid.decode(blob, keys, options); fromCandid(candid[0]); }; diff --git a/src/UrlEncoded/lib.mo b/src/UrlEncoded/lib.mo index 90c7d15..25c88dc 100644 --- a/src/UrlEncoded/lib.mo +++ b/src/UrlEncoded/lib.mo @@ -22,4 +22,5 @@ module { public let { fromText; toCandid } = FromText; public let { toText; fromCandid } = ToText; + }; diff --git a/src/lib.mo b/src/lib.mo new file mode 100644 index 0000000..a3149a6 --- /dev/null +++ b/src/lib.mo @@ -0,0 +1,8 @@ +import CandidTypes "Candid/Types"; + +module { + + public type Options = CandidTypes.Options; + + public type Candid = CandidTypes.Candid; +} \ No newline at end of file diff --git a/tests/Candid.Test.mo b/tests/Candid.Test.mo index 2c90528..15d1ebb 100644 --- a/tests/Candid.Test.mo +++ b/tests/Candid.Test.mo @@ -1,3 +1,4 @@ +// @testmode wasi import Blob "mo:base/Blob"; import Debug "mo:base/Debug"; import Iter "mo:base/Iter"; @@ -7,7 +8,7 @@ import Principal "mo:base/Principal"; import ActorSpec "./utils/ActorSpec"; import Candid "../src/Candid"; -import Encoder "../src/Candid/Encoder"; +import Encoder "../src/Candid/Blob/Encoder"; let { assertTrue; @@ -20,6 +21,8 @@ let { run; } = ActorSpec; +type Candid = Candid.Candid; + let success = run( [ describe( @@ -28,12 +31,38 @@ let success = run( describe( "decode()", [ + it( + "renaming keys", + do { + let motoko = [{ name = "candid"; arr = [1, 2, 3, 4] }, { name = "motoko"; arr = [5, 6, 7, 8] }, { name = "rust"; arr = [9, 10, 11, 12] }]; + let blob = to_candid (motoko); + let options = { renameKeys = [("arr", "array"), ("name", "username")] }; + let candid = Candid.decode(blob, ["name", "arr"], ?options); + + candid == [ + #Array([ + #Record([ + ("array", #Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)])), + ("username", #Text("candid")), + ]), + #Record([ + ("array", #Array([#Nat(5), #Nat(6), #Nat(7), #Nat(8)])), + ("username", #Text("motoko")), + ]), + #Record([ + ("array", #Array([#Nat(9), #Nat(10), #Nat(11), #Nat(12)])), + ("username", #Text("rust")), + ]), + ]) + ]; + } + ), it( "record type: {name: Text}", do { let motoko = { name = "candid" }; let blob = to_candid (motoko); - let candid = Candid.decode(blob, ["name"]); + let candid = Candid.decode(blob, ["name"], null); candid == [#Record([("name", #Text("candid"))])]; }, @@ -43,7 +72,7 @@ let success = run( do { let arr = [1, 2, 3, 4]; let blob = to_candid (arr); - let candid = Candid.decode(blob, []); + let candid = Candid.decode(blob, [], null); candid == [#Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)])]; }, @@ -57,8 +86,8 @@ let success = run( let bytes_array = to_candid (motoko_blob); let bytes_blob = to_candid (motoko_blob); - let candid_array = Candid.decode(bytes_array, []); - let candid_blob = Candid.decode(bytes_blob, []); + let candid_array = Candid.decode(bytes_array, [], null); + let candid_blob = Candid.decode(bytes_blob, [], null); assertAllTrue([ // All [Nat8] types are decoded as #Blob @@ -91,11 +120,11 @@ let success = run( let record_blob = to_candid (record); let array_blob = to_candid (array); - let text_candid = Candid.decode(text_blob, ["text"]); - let nat_candid = Candid.decode(nat_blob, ["nat"]); - let bool_candid = Candid.decode(bool_blob, ["bool"]); - let record_candid = Candid.decode(record_blob, ["record", "site"]); - let array_candid = Candid.decode(array_blob, ["array"]); + let text_candid = Candid.decode(text_blob, ["text"], null); + let nat_candid = Candid.decode(nat_blob, ["nat"], null); + let bool_candid = Candid.decode(bool_blob, ["bool"], null); + let record_candid = Candid.decode(record_blob, ["record", "site"], null); + let array_candid = Candid.decode(array_blob, ["array"], null); assertAllTrue([ text_candid == [#Variant("text", #Text("hello"))], @@ -139,7 +168,7 @@ let success = run( ]; let blob = to_candid (users); - let candid = Candid.decode(blob, record_keys); + let candid = Candid.decode(blob, record_keys, null); candid == [ #Array([ @@ -171,6 +200,33 @@ let success = run( describe( "encode()", [ + it("renaming keys", do { + let candid : Candid = #Array([ + #Record([ + ("array", #Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)])), + ("name", #Text("candid")), + ]), + #Record([ + ("array", #Array([#Nat(5), #Nat(6), #Nat(7), #Nat(8)])), + ("name", #Text("motoko")), + ]), + #Record([ + ("array", #Array([#Nat(9), #Nat(10), #Nat(11), #Nat(12)])), + ("name", #Text("rust")), + ]), + ]); + + type Data = { + language: Text; + daily_downloads: [Nat] + }; + + let options = { renameKeys = [("array", "daily_downloads"), ("name", "language")] }; + let blob = Candid.encodeOne(candid, ?options); + let motoko : ?[Data] = from_candid (blob); + // true + motoko == ?[{ language = "candid"; daily_downloads = [1, 2, 3, 4] }, { language = "motoko"; daily_downloads = [5, 6, 7, 8] }, { language = "rust"; daily_downloads = [9, 10, 11, 12] }]; + }), it( "record type {name: Text}", do { @@ -179,7 +235,7 @@ let success = run( name : Text; }; - let blob = Candid.encodeOne(candid); + let blob = Candid.encodeOne(candid, null); let user : ?User = from_candid (blob); user == ?{ name = "candid" }; @@ -193,8 +249,8 @@ let success = run( let candid_1 = #Array([#Nat8(1 : Nat8), #Nat8(2 : Nat8), #Nat8(3 : Nat8), #Nat8(4 : Nat8)]); let candid_2 = #Blob(motoko_blob); - let serialized_1 = Candid.encodeOne(candid_1); - let serialized_2 = Candid.encodeOne(candid_2); + let serialized_1 = Candid.encodeOne(candid_1, null); + let serialized_2 = Candid.encodeOne(candid_2, null); let blob_1 : ?Blob = from_candid (serialized_1); let blob_2 : ?Blob = from_candid (serialized_2); @@ -210,47 +266,47 @@ let success = run( ]); }, ), - it( - "variant", - do { - - type Variant = { - #text : Text; - #nat : Nat; - #bool : Bool; - #record : { site : Text }; - #array : [Nat]; - }; - - let text = #Variant("text", #Text("hello")); - let nat = #Variant("nat", #Nat(123)); - let bool = #Variant("bool", #Bool(true)); - let record = #Variant("record", #Record([("site", #Text("github"))])); - let array = #Variant("array", #Array([#Nat(1), #Nat(2), #Nat(3)])); - - let text_blob = Candid.encodeOne(text); - let nat_blob = Candid.encodeOne(nat); - let bool_blob = Candid.encodeOne(bool); - let record_blob = Candid.encodeOne(record); - let array_blob = Candid.encodeOne(array); - - let text_val : ?Variant = from_candid (text_blob); - let nat_val : ?Variant = from_candid (nat_blob); - let bool_val : ?Variant = from_candid (bool_blob); - let record_val : ?Variant = from_candid (record_blob); - let array_val : ?Variant = from_candid (array_blob); - - assertAllTrue([ - text_val == ?#text("hello"), - nat_val == ?#nat(123), - bool_val == ?#bool(true), - record_val == ?#record({ - site = "github"; - }), - array_val == ?#array([1, 2, 3]), - ]); - }, - ), + // it( + // "variant", + // do { + + // type Variant = { + // #text : Text; + // #nat : Nat; + // #bool : Bool; + // #record : { site : Text }; + // #array : [Nat]; + // }; + + // let text = #Variant("text", #Text("hello")); + // let nat = #Variant("nat", #Nat(123)); + // let bool = #Variant("bool", #Bool(true)); + // let record = #Variant("record", #Record([("site", #Text("github"))])); + // let array = #Variant("array", #Array([#Nat(1), #Nat(2), #Nat(3)])); + + // let text_blob = Candid.encodeOne(text, null); + // let nat_blob = Candid.encodeOne(nat, null); + // let bool_blob = Candid.encodeOne(bool, null); + // let record_blob = Candid.encodeOne(record, null); + // let array_blob = Candid.encodeOne(array, null); + + // let text_val : ?Variant = from_candid (text_blob); + // let nat_val : ?Variant = from_candid (nat_blob); + // let bool_val : ?Variant = from_candid (bool_blob); + // let record_val : ?Variant = from_candid (record_blob); + // let array_val : ?Variant = from_candid (array_blob); + + // assertAllTrue([ + // text_val == ?#text("hello"), + // nat_val == ?#nat(123), + // bool_val == ?#bool(true), + // record_val == ?#record({ + // site = "github"; + // }), + // array_val == ?#array([1, 2, 3]), + // ]); + // }, + // ), ], ), @@ -271,7 +327,7 @@ let success = run( ("details", #Record([("age", #Nat(32)), ("email", #Option(#Text("example@gmail.com"))), ("registered", #Bool(true))])), ]); - let blob = Candid.encodeOne(candid); + let blob = Candid.encodeOne(candid, null); let mo : ?User = from_candid (blob); mo == ?{ diff --git a/tests/JSON.Test.mo b/tests/JSON.Test.mo index 87afd6b..5ccc9ab 100644 --- a/tests/JSON.Test.mo +++ b/tests/JSON.Test.mo @@ -1,3 +1,4 @@ +// @testmode wasi import Blob "mo:base/Blob"; import Debug "mo:base/Debug"; import Iter "mo:base/Iter"; @@ -33,10 +34,10 @@ let success = run( "fromText()", [ it( - "fromText()", + "record type", do { let text = "{\"name\": \"Tomi\", \"id\": 32}"; - let blob = JSON.fromText(text); + let blob = JSON.fromText(text, null); let user : ?User = from_candid (blob); user == ?{ name = "Tomi"; id = ?32 }; @@ -61,11 +62,11 @@ let success = run( let record = "{\"#record\": {\"site\": \"github\"}}"; let array = "{\"#array\": [1, 2, 3] }"; - let text_blob = JSON.fromText(text); - let nat_blob = JSON.fromText(nat); - let bool_blob = JSON.fromText(bool); - let record_blob = JSON.fromText(record); - let array_blob = JSON.fromText(array); + let text_blob = JSON.fromText(text, null); + let nat_blob = JSON.fromText(nat, null); + let bool_blob = JSON.fromText(bool, null); + let record_blob = JSON.fromText(record, null); + let array_blob = JSON.fromText(array, null); let text_val : ?Variant = from_candid (text_blob); let nat_val : ?Variant = from_candid (nat_blob); @@ -84,6 +85,28 @@ let success = run( ]); }, ), + it( + "renaming record fields", + do { + // type Original = { + // label : Nat; + // query : Text; + // }; + + type UserData = { + account_label : Nat; + user_query : Text; + }; + + let text = "{\"label\": 123, \"query\": \"?user_id=12&address=2014%20Forest%20Hill%20Drive\"}"; + let options = { renameKeys = [("label", "account_label"), ("query", "user_query")] }; + let blob = JSON.fromText(text, ?options); + + let user : ?UserData = from_candid (blob); + + user == ?{ account_label = 123; user_query = "?user_id=12&address=2014%20Forest%20Hill%20Drive" }; + }, + ), ], ), describe( @@ -94,7 +117,7 @@ let success = run( do { let user = { name = "Tomi"; id = null }; let blob = to_candid (user); - let jsonText = JSON.toText(blob, ["name", "id"]); + let jsonText = JSON.toText(blob, ["name", "id"], null); jsonText == "{\"id\": null, \"name\": \"Tomi\"}"; }, @@ -122,11 +145,11 @@ let success = run( let record_blob = to_candid (record); let array_blob = to_candid (array); - let text_json = JSON.toText(text_blob, ["text"]); - let nat_json = JSON.toText(nat_blob, ["nat"]); - let bool_json = JSON.toText(bool_blob, ["bool"]); - let record_json = JSON.toText(record_blob, ["record", "site"]); - let array_json = JSON.toText(array_blob, ["array"]); + let text_json = JSON.toText(text_blob, ["text"], null); + let nat_json = JSON.toText(nat_blob, ["nat"], null); + let bool_json = JSON.toText(bool_blob, ["bool"], null); + let record_json = JSON.toText(record_blob, ["record", "site"], null); + let array_json = JSON.toText(array_blob, ["array"], null); assertAllTrue([ text_json == "{\"#text\": \"hello\"}", @@ -137,6 +160,29 @@ let success = run( ]); }, ), + it( + "renaming record fields", + do { + // type Original = { + // label : Nat; // reserved keyword that is renamed to account_label + // query : Text; // reserved keyword that is renamed to user_query + // }; + + type UserData = { + account_label : Nat; + user_query : Text; + }; + + let UserDataKeys = ["account_label", "user_query"]; + let options = { renameKeys = [("account_label", "label"), ("user_query", "query")] }; + + let data : UserData = { account_label = 123; user_query = "?user_id=12&address=2014%20Forest%20Hill%20Drive" }; + let blob = to_candid (data); + let jsonText = JSON.toText(blob, UserDataKeys, ?options); + + jsonText == "{\"label\": 123, \"query\": \"?user_id=12&address=2014%20Forest%20Hill%20Drive\"}"; + }, + ), ], ), ], diff --git a/tests/UrlEncoded.Test.mo b/tests/UrlEncoded.Test.mo index a175e7a..0f3ebef 100644 --- a/tests/UrlEncoded.Test.mo +++ b/tests/UrlEncoded.Test.mo @@ -1,3 +1,4 @@ +// @testmode wasi import Debug "mo:base/Debug"; import Iter "mo:base/Iter"; @@ -32,7 +33,7 @@ let success = run([ "single record", do { - let blob = UrlEncoded.fromText("msg=Hello World&name=John"); + let blob = UrlEncoded.fromText("msg=Hello World&name=John", null); let res : ?User = from_candid (blob); @@ -47,9 +48,9 @@ let success = run([ it( "record with array", do { - let blob = UrlEncoded.fromText( - "users[0][name]=John&users[0][msg]=Hello World&users[1][name]=Jane&users[1][msg]=testing", - ); + + let text = "users[0][name]=John&users[0][msg]=Hello World&users[1][name]=Jane&users[1][msg]=testing"; + let blob = UrlEncoded.fromText(text, null); let res : ?{ users : [User] } = from_candid (blob); assertTrue( @@ -91,14 +92,14 @@ let success = run([ let user = "variant[#user][name]=John&variant[#user][msg]=Hello World"; let array = "variant[#array][0]=1&variant[#array][1]=2&variant[#array][2]=3"; - let text_blob = UrlEncoded.fromText(text); - let nat_blob = UrlEncoded.fromText(nat); - let int_blob = UrlEncoded.fromText(int); - let float_blob = UrlEncoded.fromText(float); - let bool_blob = UrlEncoded.fromText(bool); - let record_blob = UrlEncoded.fromText(record); - let user_blob = UrlEncoded.fromText(user); - let array_blob = UrlEncoded.fromText(array); + let text_blob = UrlEncoded.fromText(text, null); + let nat_blob = UrlEncoded.fromText(nat, null); + let int_blob = UrlEncoded.fromText(int, null); + let float_blob = UrlEncoded.fromText(float, null); + let bool_blob = UrlEncoded.fromText(bool, null); + let record_blob = UrlEncoded.fromText(record, null); + let user_blob = UrlEncoded.fromText(user, null); + let array_blob = UrlEncoded.fromText(array, null); let text_val : ?{ variant : Variant } = from_candid (text_blob); let nat_val : ?{ variant : Variant } = from_candid (nat_blob); @@ -145,7 +146,7 @@ let success = run([ }; let blob = to_candid (info); - let text = UrlEncoded.toText(blob, ["name", "msg"]); + let text = UrlEncoded.toText(blob, ["name", "msg"], null); assertTrue(text == "msg=Hello World&name=John"); }, ), @@ -165,7 +166,7 @@ let success = run([ let blob = to_candid ({ users }); - let text = UrlEncoded.toText(blob, ["users", "name", "msg"]); + let text = UrlEncoded.toText(blob, ["users", "name", "msg"], null); assertTrue( text == "users[0][msg]=Hello World&users[0][name]=John&users[1][msg]=testing&users[1][name]=Jane",