From 49778ecd876c9bbb4b623e28a38ce556bc694993 Mon Sep 17 00:00:00 2001 From: ZhenyaUsenko <98zusenko@gmail.com> Date: Thu, 24 Aug 2023 11:48:01 +0300 Subject: [PATCH 1/9] updated Map dependency name to map7 to avoid conflicts in projects that use other versions --- mops.toml | 2 +- old_package-set.dhall | 2 +- old_vessel.dhall | 2 +- src/candid.mo | 2 +- src/clone.mo | 4 ++-- src/conversion.mo | 2 +- src/properties.mo | 2 +- src/types.mo | 4 ++-- src/upgrade.mo | 2 +- src/workspace.mo | 4 ++-- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/mops.toml b/mops.toml index 603c75f..b5ba100 100644 --- a/mops.toml +++ b/mops.toml @@ -4,7 +4,7 @@ matchers = "https://github.com/kritzcreek/motoko-matchers#v1.3.0" candid = "https://github.com/gekctek/motoko_candid#v1.0.1" xtendedNumbers = "https://github.com/gekctek/motoko_numbers#v1.0.2" stablebuffer = "https://github.com/skilesare/StableBuffer#v0.2.0" -map = "https://github.com/ZhenyaUsenko/motoko-hash-map#v7.0.0" +map7 = "https://github.com/ZhenyaUsenko/motoko-hash-map#v7.0.0" candy_0_1_12 = "https://github.com/icdevs/candy_library#v0.1.12" [package] diff --git a/old_package-set.dhall b/old_package-set.dhall index df8c71d..15f286b 100644 --- a/old_package-set.dhall +++ b/old_package-set.dhall @@ -27,7 +27,7 @@ let , dependencies = [ "base"] }, { name = "base", repo = "https://github.com/dfinity/motoko-base.git", version = "moc-0.8.1", dependencies = []: List Text }, - { name = "map" + { name = "map7" , repo = "https://github.com/ZhenyaUsenko/motoko-hash-map" , version = "v7.0.0" , dependencies = [ "base"] diff --git a/old_vessel.dhall b/old_vessel.dhall index 9c2f3cd..248dfcb 100644 --- a/old_vessel.dhall +++ b/old_vessel.dhall @@ -1,4 +1,4 @@ { - dependencies = [ "base", "matchers", "candid", "xtendedNumbers", "stablebuffer", "map","candy_0_1_12"], + dependencies = [ "base", "matchers", "candid", "xtendedNumbers", "stablebuffer", "map7","candy_0_1_12"], compiler = Some "0.8.1", } diff --git a/src/candid.mo b/src/candid.mo index f14d2fc..a425ee5 100644 --- a/src/candid.mo +++ b/src/candid.mo @@ -18,7 +18,7 @@ import Blob "mo:base/Blob"; import Principal "mo:base/Principal"; import Text "mo:base/Text"; -import Map "mo:map/Map"; +import Map "mo:map7/Map"; import Types "types"; import CandyHex "hex"; diff --git a/src/clone.mo b/src/clone.mo index bcd5019..e6007bb 100644 --- a/src/clone.mo +++ b/src/clone.mo @@ -16,8 +16,8 @@ import Types "types"; import Array "mo:base/Array"; import StableBuffer "mo:stablebuffer/StableBuffer"; -import Map "mo:map/Map"; -import Set "mo:map/Set"; +import Map "mo:map7/Map"; +import Set "mo:map7/Set"; module { diff --git a/src/conversion.mo b/src/conversion.mo index d97d712..6a30d8d 100644 --- a/src/conversion.mo +++ b/src/conversion.mo @@ -40,7 +40,7 @@ import Types "types"; import Hex "hex"; import Properties "properties"; import StableBuffer "mo:stablebuffer/StableBuffer"; -import Map "mo:map/Map"; +import Map "mo:map7/Map"; module { diff --git a/src/properties.mo b/src/properties.mo index 89f40a9..f996a8f 100644 --- a/src/properties.mo +++ b/src/properties.mo @@ -15,7 +15,7 @@ /// manipulating classes. import Buffer "mo:base/Buffer"; -import Map "mo:map/Map"; +import Map "mo:map7/Map"; import Text "mo:base/Text"; import Array "mo:base/Array"; import Result "mo:base/Result"; diff --git a/src/types.mo b/src/types.mo index 136a3be..e2fe475 100644 --- a/src/types.mo +++ b/src/types.mo @@ -20,8 +20,8 @@ import Buffer "mo:base/Buffer"; import StableBuffer "mo:stablebuffer/StableBuffer"; import Array "mo:base/Array"; import Iter "mo:base/Iter"; -import Map "mo:map/Map"; -import Set "mo:map/Set"; +import Map "mo:map7/Map"; +import Set "mo:map7/Set"; import Text "mo:base/Text"; import Blob "mo:base/Blob"; import Nat "mo:base/Nat"; diff --git a/src/upgrade.mo b/src/upgrade.mo index ac2cd74..c0f8244 100644 --- a/src/upgrade.mo +++ b/src/upgrade.mo @@ -18,7 +18,7 @@ import CandyTypes "types"; import Array "mo:base/Array"; import Iter "mo:base/Iter"; import Buffer "mo:base/Buffer"; -import Map "mo:map/Map"; +import Map "mo:map7/Map"; module { diff --git a/src/workspace.mo b/src/workspace.mo index ab6cf47..74643e7 100644 --- a/src/workspace.mo +++ b/src/workspace.mo @@ -18,8 +18,8 @@ import Array "mo:base/Array"; import Buffer "mo:base/Buffer"; import StableBuffer "mo:stablebuffer/StableBuffer"; -import Map "mo:map/Map"; -import Set "mo:map/Set"; +import Map "mo:map7/Map"; +import Set "mo:map7/Set"; import Int "mo:base/Int"; import Iter "mo:base/Iter"; From d67fc25d0c8e237318044240f1dcda74fc6a9b08 Mon Sep 17 00:00:00 2001 From: Austin Fatheree Date: Mon, 2 Oct 2023 21:19:26 -0500 Subject: [PATCH 2/9] Initial 0.3.0 commit --- mops.toml | 6 +- src/candid.mo | 57 ++++++++++++++- src/clone.mo | 6 +- src/conversion.mo | 4 +- src/json.mo.orig | 108 ++++++++++++++++++++++++++++ src/properties.mo | 6 +- src/types.mo | 176 ++++++++++++++++++++++++++++------------------ src/upgrade.mo | 158 +++++++++++++++++++++++++++++++++++++---- src/workspace.mo | 24 +++++-- 9 files changed, 448 insertions(+), 97 deletions(-) create mode 100644 src/json.mo.orig diff --git a/mops.toml b/mops.toml index b5ba100..4c58d80 100644 --- a/mops.toml +++ b/mops.toml @@ -1,14 +1,16 @@ [dependencies] -base = "https://github.com/dfinity/motoko-base#moc-0.8.1" +base = "https://github.com/dfinity/motoko-base#moc-0.10.0" matchers = "https://github.com/kritzcreek/motoko-matchers#v1.3.0" candid = "https://github.com/gekctek/motoko_candid#v1.0.1" xtendedNumbers = "https://github.com/gekctek/motoko_numbers#v1.0.2" stablebuffer = "https://github.com/skilesare/StableBuffer#v0.2.0" map7 = "https://github.com/ZhenyaUsenko/motoko-hash-map#v7.0.0" +map9 = "https://github.com/ZhenyaUsenko/motoko-hash-map#v9.0.1" candy_0_1_12 = "https://github.com/icdevs/candy_library#v0.1.12" +candy_0_2_0 = "https://github.com/icdevs/candy_library#0.2.0" [package] name = "candy" -version = "0.2.0" +version = "0.3.0" description = "Library for Converting Types and Creating Workable Motoko Collections" repository = "https://github.com/icdevs/candy_library" \ No newline at end of file diff --git a/src/candid.mo b/src/candid.mo index a425ee5..da2157d 100644 --- a/src/candid.mo +++ b/src/candid.mo @@ -18,7 +18,7 @@ import Blob "mo:base/Blob"; import Principal "mo:base/Principal"; import Text "mo:base/Text"; -import Map "mo:map7/Map"; +import Map "mo:map9/Map"; import Types "types"; import CandyHex "hex"; @@ -174,6 +174,7 @@ module { case(#Int32(val))buffer.add({_type = #int32; value = #int32(val)}); case(#Int16(val))buffer.add({_type = #int16; value = #int16(val)}); case(#Int8(val))buffer.add({_type = #int8; value = #int8(val)}); + case(#Map(val)){ let list = val; @@ -191,6 +192,60 @@ module { let body: Buffer.Buffer = Buffer.Buffer(list.size()); + let localBody: Buffer.Buffer = Buffer.Buffer(2); + + var tracker : Nat32 = 0; + for(this_item in list.vals()){ + let key = (this_item.0); + let value = (value_to_candid(this_item.1))[0]; + + switch(lastValueType){ + case(null) lastValueType := ?value._type; + case(?lastValueType){ + if(CandidTypes.equal(lastValueType, value._type)){ + + } else { + bFoundMultipleValueTypes := true; + }; + }; + }; + + localTypes.add({_type = #text; tag = #hash(0)}); + localTypes.add({_type = value._type; tag = #hash(1)}); + + localBody.add({tag = #hash(0); value = #text(key)}); + localBody.add({tag = #hash(1); value = value.value}); + + let thisItem = {_type=#record(Buffer.toArray(localTypes)); value = #record(Buffer.toArray(localBody))}; + + types.add({_type = thisItem._type; tag = #hash(tracker)}); + body.add({tag = #hash(tracker); value = thisItem.value}); + values.add(thisItem.value); + tracker += 1; + }; + + + buffer.add({_type=#record(Buffer.toArray(types)); value = #record(Buffer.toArray(body))}) + + }; + + case(#ValueMap(val)){ + let list = val; + + var bFoundMultipleKeyTypes = false; + var bFoundMultipleValueTypes = false; + var lastKeyType : ?CandidTypes.Type = null; + var lastValueType : ?CandidTypes.Type = null; + let values: Buffer.Buffer = Buffer.Buffer(list.size()); + let types: Buffer.Buffer = Buffer.Buffer(list.size()); + + let localValues: Buffer.Buffer = Buffer.Buffer(2); + let localTypes: Buffer.Buffer = Buffer.Buffer(2); + + + let body: Buffer.Buffer = Buffer.Buffer(list.size()); + + let localBody: Buffer.Buffer = Buffer.Buffer(2); var tracker : Nat32 = 0; diff --git a/src/clone.mo b/src/clone.mo index e6007bb..925f227 100644 --- a/src/clone.mo +++ b/src/clone.mo @@ -16,8 +16,8 @@ import Types "types"; import Array "mo:base/Array"; import StableBuffer "mo:stablebuffer/StableBuffer"; -import Map "mo:map7/Map"; -import Set "mo:map7/Set"; +import Map "mo:map9/Map"; +import Set "mo:map9/Set"; module { @@ -46,7 +46,7 @@ module { case(#Ints(val)){#Ints(StableBuffer.clone(val))}; case(#Floats(val)){#Floats(StableBuffer.clone(val))}; case(#Array(val)){#Array(StableBuffer.clone(val))}; - case(#Map(val)){#Map(Map.fromIter(Map.entries(val), Types.candyMapHashTool))}; + case(#ValueMap(val)){#ValueMap(Map.fromIter(Map.entries(val), Types.candyMapHashTool))}; case(#Set(val)){#Set(Set.fromIter(Set.keys(val), Types.candyMapHashTool))}; case(_) val; }; diff --git a/src/conversion.mo b/src/conversion.mo index 6a30d8d..9566b62 100644 --- a/src/conversion.mo +++ b/src/conversion.mo @@ -40,7 +40,7 @@ import Types "types"; import Hex "hex"; import Properties "properties"; import StableBuffer "mo:stablebuffer/StableBuffer"; -import Map "mo:map7/Map"; +import Map "mo:map9/Map"; module { @@ -610,6 +610,7 @@ module { case(#Floats(val)){Prelude.nyi()}; case(#Nats(val)){Prelude.nyi()}; case(#Ints(val)){Prelude.nyi()}; + case(#ValueMap(val)){Prelude.nyi()}; case(#Map(val)){Prelude.nyi()}; case(#Set(val)){Prelude.nyi()}; } @@ -1233,6 +1234,7 @@ module { case(#Floats(val)){Prelude.nyi()}; case(#Nats(val)){Prelude.nyi()}; case(#Ints(val)){Prelude.nyi()}; + case(#ValueMap(val)){Prelude.nyi()}; case(#Map(val)){Prelude.nyi()}; case(#Set(val)){Prelude.nyi()}; } diff --git a/src/json.mo.orig b/src/json.mo.orig new file mode 100644 index 0000000..468aa93 --- /dev/null +++ b/src/json.mo.orig @@ -0,0 +1,108 @@ +/// JSON support for the candy library. +/// +/// This module contains the utilities useful for handling JSON values. + +import Buffer "mo:base/Buffer"; +import Nat "mo:base/Nat"; +import Nat16 "mo:base/Nat16"; +import Nat32 "mo:base/Nat32"; +import Nat64 "mo:base/Nat64"; +import Nat8 "mo:base/Nat8"; +import Float "mo:base/Float"; +import Int "mo:base/Int"; +import Int8 "mo:base/Int8"; +import Int16 "mo:base/Int16"; +import Int32 "mo:base/Int32"; +import Int64 "mo:base/Int64"; +import Bool "mo:base/Bool"; +import Blob "mo:base/Blob"; +import Principal "mo:base/Principal"; +import Text "mo:base/Text"; + +import Types "types"; +import CandyHex "hex"; + +module { + /// Convert `CandyShared` to JSON format as `Text`. + /// + /// Example: + /// ```motoko include=import + /// let val: CandyShared = #Option(?#Principal(Principal.fromText("xyz"))); + /// let val_as_json = Json.value_to_json(val); + /// ``` + public func value_to_json(val: Types.CandyShared): Text { + switch(val){ + //nat + case(#Nat(val)){ Nat.toText(val)}; + case(#Nat64(val)){ Nat64.toText(val)}; + case(#Nat32(val)){ Nat32.toText(val)}; + case(#Nat16(val)){ Nat16.toText(val)}; + case(#Nat8(val)){ Nat8.toText(val)}; + //text + case(#Text(val)){ "\"" # val # "\""; }; + //class + case(#Class(val)){ + var body: Buffer.Buffer = Buffer.Buffer(1); + for(this_item in val.vals()){ + body.add("\"" # this_item.name # "\"" # ":" # value_to_json(this_item.value)); + }; + return "{" # Text.join(",", body.vals()) # "}"; + }; + //array + case(#Array(val)){ + var body: Buffer.Buffer = Buffer.Buffer(1); + for(this_item in val.vals()){ + body.add(value_to_json(this_item)); + }; + return "[" # Text.join(",", body.vals()) # "]"; + }; + case(#Option(val)){ + switch(val){ + case(null){"null";}; + case(?val){value_to_json(val);} + } + }; + case(#Nats(val)){ + var body: Buffer.Buffer = Buffer.Buffer(1); + for(this_item in val.vals()){ + body.add(Nat.toText(this_item)); + }; + return "[" # Text.join(",", body.vals()) # "]"; + }; + case(#Ints(val)){ + var body: Buffer.Buffer = Buffer.Buffer(1); + for(this_item in val.vals()){ + body.add(Int.toText(this_item)); + }; + return "[" # Text.join(",", body.vals()) # "]"; + }; + case(#Floats(val)){ + var body: Buffer.Buffer = Buffer.Buffer(1); + for(this_item in val.vals()){ + body.add(Float.toText(this_item)); + }; + return "[" # Text.join(",", body.vals()) # "]"; + }; + //bytes + case(#Bytes(val)){ + return "\"" # CandyHex.encode(val) # "\"";//CandyHex.encode(val); + }; + //bytes + case(#Blob(val)){ + return "\"" # CandyHex.encode(Blob.toArray(val)) # "\"";//CandyHex.encode(val); + }; + //principal + case(#Principal(val)){ "\"" # Principal.toText(val) # "\"";}; + //bool + case(#Bool(val)){ "\"" # Bool.toText(val) # "\"";}; + //float + case(#Float(val)){ Float.format(#fix 8, val)}; + case(#Int(val)){Int.toText(val);}; + case(#Int64(val)){Int64.toText(val);}; + case(#Int32(val)){Int32.toText(val);}; + case(#Int16(val)){Int16.toText(val);}; + case(#Int8(val)){Int8.toText(val);}; + case(_){"";}; + }; + }; +}; diff --git a/src/properties.mo b/src/properties.mo index f996a8f..ba48234 100644 --- a/src/properties.mo +++ b/src/properties.mo @@ -15,7 +15,7 @@ /// manipulating classes. import Buffer "mo:base/Buffer"; -import Map "mo:map7/Map"; +import Map "mo:map9/Map"; import Text "mo:base/Text"; import Array "mo:base/Array"; import Result "mo:base/Result"; @@ -318,9 +318,9 @@ module { // /////////////////////////////////// - private func toPropertySharedMap(ps : Properties) : Map.Map { + private func toPropertySharedMap(ps : Properties) : Map.Map { let m = Map.new(); - for (property in Map.vals(ps)) { + for (property in Map.vals(ps)) { ignore Map.put(m, Map.thash, property.name, Types.shareProperty(property)); }; m; diff --git a/src/types.mo b/src/types.mo index e2fe475..87c4ef7 100644 --- a/src/types.mo +++ b/src/types.mo @@ -20,8 +20,8 @@ import Buffer "mo:base/Buffer"; import StableBuffer "mo:stablebuffer/StableBuffer"; import Array "mo:base/Array"; import Iter "mo:base/Iter"; -import Map "mo:map7/Map"; -import Set "mo:map7/Set"; +import Map "mo:map9/Map"; +import Set "mo:map9/Set"; import Text "mo:base/Text"; import Blob "mo:base/Blob"; import Nat "mo:base/Nat"; @@ -152,7 +152,8 @@ module { #Nats: [Nat]; #Floats: [Float]; #Bytes : [Nat8]; - #Map : [(CandyShared, CandyShared)]; + #ValueMap : [(CandyShared, CandyShared)]; + #Map : [(Text, CandyShared)]; #Set : [CandyShared]; }; @@ -180,7 +181,8 @@ module { #Array : StableBuffer.StableBuffer; #Option : ?Candy; #Bytes : StableBuffer.StableBuffer; - #Map : Map.Map; + #ValueMap : Map.Map; + #Map : Map.Map; #Set : Set.Set; }; @@ -236,7 +238,7 @@ module { case(#Bytes(val)){ #Bytes(StableBuffer.toArray(val))}; case(#Floats(val)){#Floats(StableBuffer.toArray(val))}; case(#Nats(val)){#Nats(StableBuffer.toArray(val))}; - case(#Map(val)){ + case(#ValueMap(val)){ let entries = Map.entries(val); let stableEntries = Iter.map<(Candy, Candy), (CandyShared, CandyShared)>( entries, @@ -244,7 +246,17 @@ module { (shareCandy(x.0), shareCandy(x.1)) }); - #Map(Iter.toArray<(CandyShared,CandyShared)>(stableEntries)); + #ValueMap(Iter.toArray<(CandyShared,CandyShared)>(stableEntries)); + }; + case(#Map(val)){ + let entries = Map.entries(val); + let stableEntries = Iter.map<(Text, Candy), (Text, CandyShared)>( + entries, + func (x : (Text, Candy)){ + (x.0, shareCandy(x.1)) + }); + + #Map(Iter.toArray<(Text,CandyShared)>(stableEntries)); }; case(#Set(val)){ let entries = Set.keys(val); @@ -301,7 +313,7 @@ module { case(#Floats(val)){#Floats(toBuffer(val))}; case(#Nats(val)){#Nats(toBuffer(val))}; case(#Ints(val)){#Ints(toBuffer(val))}; - case(#Map(val)){ + case(#ValueMap(val)){ //let entries = Map.entries(val); let unstableEntries = Iter.map<(CandyShared, CandyShared), (Candy, Candy)>( val.vals(), @@ -309,7 +321,17 @@ module { (unshare(x.0), unshare(x.1)) }); - #Map(Map.fromIter(unstableEntries, candyMapHashTool)); + #ValueMap(Map.fromIter(unstableEntries, candyMapHashTool)); + }; + case(#Map(val)){ + //let entries = Map.entries(val); + let unstableEntries = Iter.map<(Text, CandyShared), (Text, Candy)>( + val.vals(), + func (x : (Text, CandyShared)){ + (x.0, unshare(x.1)) + }); + + #Map(Map.fromIter(unstableEntries, Map.thash)); }; case(#Set(val)){ //let entries = Set.keys(val); @@ -432,19 +454,19 @@ module { /// let x: CandyShared = #Principal(Principal.fromText("abc")); /// let h = Types.hash(x); /// ``` - public func hash(x :Candy) : Nat { + public func hash(x :Candy) : Nat32 { let thisHash = switch(x){ case(#Int(val)) Map.ihash.0(val); - case(#Int8(val)) Map.ihash.0(Int8.toInt(val)); - case(#Int16(val)) Map.ihash.0(Int16.toInt(val)); - case(#Int32(val)) Map.ihash.0(Int32.toInt(val)); - case(#Int64(val)) Map.ihash.0(Int64.toInt(val)); + case(#Int8(val)) Map.i8hash.0(val); + case(#Int16(val)) Map.i16hash.0(val); + case(#Int32(val)) Map.i32hash.0(val); + case(#Int64(val)) Map.i64hash.0(val); case(#Nat(val)) Map.nhash.0(val); - case(#Nat8(val)) Map.nhash.0(Nat8.toNat(val)); - case(#Nat16(val)) Map.nhash.0(Nat16.toNat(val)); - case(#Nat32(val)) Map.nhash.0(Nat32.toNat(val)); - case(#Nat64(val)) Map.nhash.0(Nat64.toNat(val)); + case(#Nat8(val)) Map.n8hash.0(val); + case(#Nat16(val)) Map.n16hash.0(val); + case(#Nat32(val)) Map.n32hash.0(val); + case(#Nat64(val)) Map.n64hash.0(val); case(#Float(val)) Map.thash.0(Float.format(#exact, Float.nearest(val * 100000000) / 100000000)); case(#Text(val)) Map.thash.0(val); case(#Bool(val)) Map.lhash.0(val); @@ -452,20 +474,20 @@ module { case(#Class(val)){ var accumulator = 0 : Nat32; for(thisItem in Map.vals(val)){ - accumulator +%= Nat32.fromNat(Map.thash.0(thisItem.name)); - accumulator +%= Nat32.fromNat(hash(thisItem.value)); - accumulator +%= Nat32.fromNat(Map.lhash.0(thisItem.immutable)); + accumulator +%= Map.thash.0(thisItem.name); + accumulator +%= hash(thisItem.value); + accumulator +%= Map.lhash.0(thisItem.immutable); }; - Nat32.toNat(accumulator); + accumulator; }; case(#Principal(val)) Map.phash.0(val); case(#Array(val)){ var accumulator = 0 : Nat32; for(thisItem in StableBuffer.vals(val)){ - let ahash = Nat32.fromNat(hash(thisItem)); + let ahash = hash(thisItem); accumulator := (accumulator *% 3) +% ahash; }; - Nat32.toNat(accumulator); + accumulator; }; case(#Option(val)){ @@ -473,7 +495,7 @@ module { switch(val){ case(null){ result -%= 1 : Nat32; - return Nat32.toNat(result); + return result; }; case(?val){hash(val)}; }; @@ -483,44 +505,53 @@ module { case(#Floats(val)){ //arrays must be in the same order so we add index var accumulator = 0 : Nat32; for(thisItem in StableBuffer.vals(val)){ - let ahash = Nat32.fromNat(hash(#Float(thisItem))); + let ahash = hash(#Float(thisItem)); accumulator := (accumulator *% 3) +% ahash; }; - Nat32.toNat(accumulator); + accumulator; }; case(#Nats(val)){ var accumulator = 0 : Nat32; for(thisItem in StableBuffer.vals(val)){ - let ahash = Nat32.fromNat(hash(#Nat(thisItem))); + let ahash = hash(#Nat(thisItem)); accumulator := (accumulator *% 3) +% ahash; }; - Nat32.toNat(accumulator); + accumulator; }; case(#Ints(val)){ var accumulator = 0 : Nat32; for(thisItem in StableBuffer.vals(val)){ - let ahash = Nat32.fromNat(hash(#Int(thisItem))); + let ahash = hash(#Int(thisItem)); accumulator := (accumulator *% 3) +% ahash; }; - Nat32.toNat(accumulator); + accumulator; + }; + case(#ValueMap(val)){ + //this map ignores insertion order + var accumulator = 0 : Nat32; + for(thisItem in Map.entries(val)){ + accumulator +%= hash(thisItem.0); + accumulator +%= hash(thisItem.1); + }; + accumulator; }; case(#Map(val)){ //this map ignores insertion order var accumulator = 0 : Nat32; for(thisItem in Map.entries(val)){ - accumulator +%= Nat32.fromNat(hash(thisItem.0)); - accumulator +%= Nat32.fromNat(hash(thisItem.1)); + accumulator +%= Map.thash.0(thisItem.0); + accumulator +%= hash(thisItem.1); }; - Nat32.toNat(accumulator); + accumulator; }; case(#Set(val)){ //this set ignores insertion order var accumulator = 0 : Nat32; for(thisItem in Set.keys(val)){ - accumulator +%= Nat32.fromNat(hash(thisItem)); + accumulator +%= hash(thisItem); }; - Nat32.toNat(accumulator); + accumulator; }; }; }; @@ -658,7 +689,7 @@ module { }; return true; }; - case(#Map(x), #Map(y)){ + case(#ValueMap(x), #ValueMap(y)){ //this map ignores insertion order if(Map.size(x) != Map.size(y)) return false; @@ -705,19 +736,19 @@ module { /// let x: Candy = #Principal(Principal.fromText("abc")); /// let h = Types.hashShared(x); /// ``` - public func hashShared(x :CandyShared) : Nat { + public func hashShared(x :CandyShared) : Nat32 { let thisHash = switch(x){ case(#Int(val)) Map.ihash.0(val); - case(#Int8(val)) Map.ihash.0(Int8.toInt(val)); - case(#Int16(val)) Map.ihash.0(Int16.toInt(val)); - case(#Int32(val)) Map.ihash.0(Int32.toInt(val)); - case(#Int64(val)) Map.ihash.0(Int64.toInt(val)); + case(#Int8(val)) Map.i8hash.0(val); + case(#Int16(val)) Map.i16hash.0(val); + case(#Int32(val)) Map.i32hash.0(val); + case(#Int64(val)) Map.i64hash.0(val); case(#Nat(val)) Map.nhash.0(val); - case(#Nat8(val)) Map.nhash.0(Nat8.toNat(val)); - case(#Nat16(val)) Map.nhash.0(Nat16.toNat(val)); - case(#Nat32(val)) Map.nhash.0(Nat32.toNat(val)); - case(#Nat64(val)) Map.nhash.0(Nat64.toNat(val)); + case(#Nat8(val)) Map.n8hash.0(val); + case(#Nat16(val)) Map.n16hash.0(val); + case(#Nat32(val)) Map.n32hash.0(val); + case(#Nat64(val)) Map.n64hash.0(val); case(#Float(val)) Map.thash.0(Float.format(#exact, Float.nearest(val * 100000000)/ 100000000)); case(#Text(val)) Map.thash.0(val); case(#Bool(val)) Map.lhash.0(val); @@ -725,11 +756,11 @@ module { case(#Class(val)){ var accumulator = 0 : Nat32; for(thisItem in val.vals()){ - accumulator +%= Nat32.fromNat(Map.thash.0(thisItem.name)); - accumulator +%= Nat32.fromNat(hashShared(thisItem.value)); - accumulator +%= Nat32.fromNat(Map.lhash.0(thisItem.immutable)); + accumulator +%= Map.thash.0(thisItem.name); + accumulator +%= hashShared(thisItem.value); + accumulator +%= Map.lhash.0(thisItem.immutable); }; - Nat32.toNat(accumulator); + accumulator; }; case(#Principal(val)) Map.phash.0(val); @@ -737,17 +768,17 @@ module { var accumulator = 0 : Nat32; for(thisItem in val.vals()){ - let ahash = Nat32.fromNat(hashShared(thisItem)); + let ahash = hashShared(thisItem); accumulator := (accumulator *% 3) +% ahash; }; - Nat32.toNat(accumulator); + accumulator; }; case(#Option(val)){ var result : Nat32 = 0; switch(val){ case(null){ result -%= 1 : Nat32; - return Nat32.toNat(result); + return result; }; case(?val){hashShared(val)}; }; @@ -758,43 +789,52 @@ module { var accumulator = 0 : Nat32; for(thisItem in val.vals()){ - let ahash = Nat32.fromNat(hashShared(#Float(thisItem))); + let ahash = hashShared(#Float(thisItem)); accumulator := (accumulator *% 3) +% ahash; }; - Nat32.toNat(accumulator); + accumulator; }; case(#Nats(val)){ var accumulator = 0 : Nat32; for(thisItem in val.vals()){ - let ahash = Nat32.fromNat(hashShared(#Nat(thisItem))); + let ahash =hashShared(#Nat(thisItem)); accumulator := (accumulator *% 3) +% ahash; }; - Nat32.toNat(accumulator); + accumulator; }; case(#Ints(val)){ var accumulator = 0 : Nat32; for(thisItem in val.vals()){ - let ahash = Nat32.fromNat(hashShared(#Int(thisItem))); + let ahash =hashShared(#Int(thisItem)); accumulator := (accumulator *% 3) +% ahash; }; - Nat32.toNat(accumulator); + accumulator; + }; + case(#ValueMap(val)){ + //this map takes insertion order into account + var accumulator = 0 : Nat32; + for(thisItem in val.vals()){ + accumulator +%= hashShared(thisItem.0); + accumulator +%= hashShared(thisItem.1); + }; + accumulator; }; case(#Map(val)){ //this map takes insertion order into account var accumulator = 0 : Nat32; for(thisItem in val.vals()){ - accumulator +%= Nat32.fromNat(hashShared(thisItem.0)); - accumulator +%= Nat32.fromNat(hashShared(thisItem.1)); + accumulator +%= Map.thash.0(thisItem.0); + accumulator +%= hashShared(thisItem.1); }; - Nat32.toNat(accumulator); + accumulator; }; case(#Set(val)){ //this set takes insertion order into account var accumulator = 0 : Nat32; for(thisItem in val.vals()){ - accumulator +%= Nat32.fromNat(hashShared(thisItem)); + accumulator +%= hashShared(thisItem); }; - Nat32.toNat(accumulator); + accumulator; }; }; }; @@ -909,14 +949,14 @@ module { }; return true; }; - case(#Map(x), #Map(y)){ + case(#ValueMap(x), #ValueMap(y)){ //this map IGNORES insertion order if(x.size() != y.size()) return false; - let yit = Iter.sort<(CandyShared, CandyShared)>(y.vals(), func(x, y){Nat.compare(hashShared(x.0), hashShared(y.0))}); + let yit = Iter.sort<(CandyShared, CandyShared)>(y.vals(), func(x, y){Nat32.compare(hashShared(x.0), hashShared(y.0))}); - for(thisItem in Iter.sort<(CandyShared, CandyShared)>(x.vals(), func(x, y){Nat.compare(hashShared(x.0), hashShared(y.0))})){ + for(thisItem in Iter.sort<(CandyShared, CandyShared)>(x.vals(), func(x, y){Nat32.compare(hashShared(x.0), hashShared(y.0))})){ switch(yit.next()){ case(null) return false; case(?val){ @@ -933,9 +973,9 @@ module { }; case(#Set(x), #Set(y)){ //this set takes insertion order into account - let yit = Iter.sort(y.vals(), func(x, y){Nat.compare(hashShared(x), hashShared(y))}); + let yit = Iter.sort(y.vals(), func(x, y){Nat32.compare(hashShared(x), hashShared(y))}); - for(thisItem in Iter.sort(x.vals(), func(x, y){Nat.compare(hashShared(x), hashShared(y))})){ + for(thisItem in Iter.sort(x.vals(), func(x, y){Nat32.compare(hashShared(x), hashShared(y))})){ switch(yit.next()){ case(null) return false; case(?val){ diff --git a/src/upgrade.mo b/src/upgrade.mo index c0f8244..3888d41 100644 --- a/src/upgrade.mo +++ b/src/upgrade.mo @@ -14,16 +14,21 @@ /// This module contains the utility functions to upgrade candy values from version 1 to version 2. import CandyOld "mo:candy_0_1_12/types"; +import Candy0_2_0 "mo:candy_0_2_0/types"; import CandyTypes "types"; import Array "mo:base/Array"; import Iter "mo:base/Iter"; import Buffer "mo:base/Buffer"; -import Map "mo:map7/Map"; +import Map7 "mo:map7/Map"; +import Set7 "mo:map7/Set"; +import Map "mo:map9/Map"; +import Set "mo:map9/Set"; +import StableBuffer "mo:stablebuffer/StableBuffer"; module { /// Upgrade from V1 representation of `CandyShared` to the V2 representation. - public func upgradeCandy(item : CandyOld.CandyValueUnstable) : CandyTypes.Candy { + public func upgradeCandy0_1_2_to_0_2_0(item : CandyOld.CandyValueUnstable) : Candy0_2_0.Candy { switch (item) { case (#Int(val)) { #Int(val) }; case (#Int8(val)) { #Int8(val) }; @@ -40,27 +45,27 @@ module { case (#Bool(val)) { #Bool(val) }; case (#Blob(val)) { #Blob(val) }; case (#Class(val)) { - let iter = Iter.map( + let iter = Iter.map( val.vals(), func(x) {(x.name, { - x with value = upgradeCandy(x.value) + x with value = upgradeCandy0_1_2_to_0_2_0(x.value) }); }, ); #Class( - Map.fromIter(iter, - Map.thash)); + Map7.fromIter(iter, + Map7.thash)); }; case (#Principal(val)) { #Principal(val) }; case (#Array(val)) { switch (val) { case (#frozen(val)) { - #Array(CandyTypes.toBuffer(Array.map(val, upgradeCandy))); + #Array(Candy0_2_0.toBuffer(Array.map(val, upgradeCandy0_1_2_to_0_2_0))); }; case (#thawed(val)) { - #Array(CandyTypes.toBuffer(Array.map(Buffer.toArray(val), upgradeCandy))); + #Array(Candy0_2_0.toBuffer(Array.map(Buffer.toArray(val), upgradeCandy0_1_2_to_0_2_0))); }; }; }; @@ -86,7 +91,7 @@ module { case (#Option(val)) { switch (val) { case (null) { #Option(null) }; - case (?val) { #Option(?upgradeCandy(val)) }; + case (?val) { #Option(?upgradeCandy0_1_2_to_0_2_0(val)) }; }; }; @@ -95,7 +100,7 @@ module { }; /// Upgrade from V1 representation of `CandySharedUnstable` to the V2 representation 'CandyValudShared'. - public func upgradeCandyShared(item : CandyOld.CandyValue) : CandyTypes.CandyShared { + public func upgradeCandyShared0_1_2_to_0_2_0(item : CandyOld.CandyValue) : Candy0_2_0.CandyShared { switch (item) { case (#Int(val)) { #Int(val) }; case (#Int8(val)) { #Int8(val) }; @@ -113,11 +118,11 @@ module { case (#Blob(val)) { #Blob(val) }; case (#Class(val)) { #Class( - Array.map( + Array.map( val, func(x) { { - x with value = upgradeCandyShared(x.value) + x with value = upgradeCandyShared0_1_2_to_0_2_0(x.value) }; }, ), @@ -127,17 +132,17 @@ module { case (#Array(val)) { switch (val) { case (#frozen(val)) { - #Array(Array.map(val, upgradeCandyShared)); + #Array(Array.map(val, upgradeCandyShared0_1_2_to_0_2_0)); }; case (#thawed(val)) { - #Array(Array.map(Iter.toArray(val.vals()), upgradeCandyShared)); + #Array(Array.map(Iter.toArray(val.vals()), upgradeCandyShared0_1_2_to_0_2_0)); }; }; }; case (#Option(val)) { switch (val) { case (null) { #Option(null) }; - case (?val) { #Option(?upgradeCandyShared(val)) }; + case (?val) { #Option(?upgradeCandyShared0_1_2_to_0_2_0(val)) }; }; }; case (#Bytes(val)) { @@ -162,4 +167,127 @@ module { case (#Empty) { #Option(null) }; }; }; + + + /// Upgrade from V2 representation of `CandyShared` to the V3 representation. + public func upgradeCandy0_2_0_to_0_3_0(item : Candy0_2_0.Candy) : CandyTypes.Candy { + switch (item) { + case (#Int(val)) { #Int(val) }; + case (#Int8(val)) { #Int8(val) }; + case (#Int16(val)) { #Int16(val) }; + case (#Int32(val)) { #Int32(val) }; + case (#Int64(val)) { #Int64(val) }; + case (#Ints(val)) { #Ints(val) }; + case (#Nat(val)) { #Nat(val) }; + case (#Nat8(val)) { #Nat8(val) }; + case (#Nat16(val)) { #Nat16(val) }; + case (#Nat32(val)) { #Nat32(val) }; + case (#Nat64(val)) { #Nat64(val) }; + case (#Float(val)) { #Float(val) }; + case (#Text(val)) { #Text(val) }; + case (#Bool(val)) { #Bool(val) }; + case (#Blob(val)) { #Blob(val) }; + case (#Class(val)) { + let iter = Iter.map( + Map7.vals(val), + func(x) {(x.name, + { + x with value = upgradeCandy0_2_0_to_0_3_0(x.value) + }); + }, + ); + + #Class( + Map.fromIter(iter, + Map.thash)); + }; + case (#Principal(val)) { #Principal(val) }; + case (#Array(val)) { + #Array(CandyTypes.toBuffer(Array.map(StableBuffer.toArray(val), upgradeCandy0_2_0_to_0_3_0))); + }; + case (#Bytes(val)) {#Bytes(val)}; + case (#Floats(val)) {#Floats(val)}; + case (#Nats(val)) {#Nats(val)}; + case (#Option(val)) { + switch(val){ + case(null) #Option(null); + case(?val){ + #Option(?upgradeCandy0_2_0_to_0_3_0(val)); + }; + }; + }; + case (#Map(val)) { + let iter = Iter.map<(Candy0_2_0.Candy, Candy0_2_0.Candy), (CandyTypes.Candy, CandyTypes.Candy)>( + Map7.entries(val), + func(x) {(upgradeCandy0_2_0_to_0_3_0(x.0), upgradeCandy0_2_0_to_0_3_0(x.1)); + }, + ); + #ValueMap(Map.fromIter(iter, + CandyTypes.candyMapHashTool))}; + case (#Set(val)) { + #Set(Set.fromIter(Iter.map(Set7.keys(val), upgradeCandy0_2_0_to_0_3_0), CandyTypes.candyMapHashTool)); + }; + }; + }; + + /// Upgrade from V1 representation of `CandySharedUnstable` to the V2 representation 'CandyValudShared'. + public func upgradeCandyShared0_2_0_to_0_3_0(item : Candy0_2_0.CandyShared) : CandyTypes.CandyShared { + + switch (item) { + case (#Int(val)) { #Int(val) }; + case (#Int8(val)) { #Int8(val) }; + case (#Int16(val)) { #Int16(val) }; + case (#Int32(val)) { #Int32(val) }; + case (#Int64(val)) { #Int64(val) }; + case (#Ints(val)) { #Ints(val) }; + case (#Nat(val)) { #Nat(val) }; + case (#Nat8(val)) { #Nat8(val) }; + case (#Nat16(val)) { #Nat16(val) }; + case (#Nat32(val)) { #Nat32(val) }; + case (#Nat64(val)) { #Nat64(val) }; + case (#Float(val)) { #Float(val) }; + case (#Text(val)) { #Text(val) }; + case (#Bool(val)) { #Bool(val) }; + case (#Blob(val)) { #Blob(val) }; + case (#Class(val)) { + #Class( + Array.map( + val, + func(x) { + { + x with value = upgradeCandyShared0_2_0_to_0_3_0(x.value) + }; + }, + )); + }; + case (#Principal(val)) { #Principal(val) }; + case (#Array(val)) { + #Array(Array.map(val, upgradeCandyShared0_2_0_to_0_3_0)); + }; + case (#Bytes(val)) {#Bytes(val)}; + case (#Floats(val)) {#Floats(val)}; + case (#Nats(val)) {#Nats(val)}; + case (#Option(val)) { + switch(val){ + case(null) #Option(null); + case(?val){ + #Option(?upgradeCandyShared0_2_0_to_0_3_0(val)); + }; + }; + }; + case (#Map(val)) { + let iter = Iter.map<(Candy0_2_0.CandyShared, Candy0_2_0.CandyShared), (CandyTypes.CandyShared, CandyTypes.CandyShared)>( + val.vals(), + func(x) {(upgradeCandyShared0_2_0_to_0_3_0(x.0), upgradeCandyShared0_2_0_to_0_3_0(x.1)); + }, + ); + #ValueMap(Iter.toArray< (CandyTypes.CandyShared, CandyTypes.CandyShared)>(iter))}; + case (#Set(val)) { + let iter = Iter.map<(Candy0_2_0.CandyShared), (CandyTypes.CandyShared)>( + val.vals(), + upgradeCandyShared0_2_0_to_0_3_0, + ); + #Set(Iter.toArray< CandyTypes.CandyShared>(iter))}; + }; + }; }; diff --git a/src/workspace.mo b/src/workspace.mo index 74643e7..c44e209 100644 --- a/src/workspace.mo +++ b/src/workspace.mo @@ -18,8 +18,8 @@ import Array "mo:base/Array"; import Buffer "mo:base/Buffer"; import StableBuffer "mo:stablebuffer/StableBuffer"; -import Map "mo:map7/Map"; -import Set "mo:map7/Set"; +import Map "mo:map9/Map"; +import Set "mo:map9/Set"; import Int "mo:base/Int"; import Iter "mo:base/Iter"; @@ -144,13 +144,20 @@ module { }; return size; }; - case (#Map(val)) { + case (#ValueMap(val)) { var size = 0; for (thisItem in Map.entries(val)) { size += getCandySize(thisItem.0) + getCandySize(thisItem.1) + 2; }; return size; }; + case (#Map(val)) { + var size = 0; + for (thisItem in Map.entries(val)) { + size += (thisItem.0.size() * 4) + getCandySize(thisItem.1) + 2; + }; + return size; + }; case (#Set(val)) { var size = 0; for (thisItem in Set.keys(val)) { @@ -245,7 +252,7 @@ module { }; return size; }; - case (#Map(val)) { + case (#ValueMap(val)) { var size = 0; for (thisItem in val.vals()) { size += getCandySharedSize(thisItem.0) + getCandySharedSize(thisItem.1) + 2; @@ -253,6 +260,15 @@ module { return size; + }; + case (#Map(val)) { + var size = 0; + for (thisItem in val.vals()) { + size += (thisItem.0.size() *4) + getCandySharedSize(thisItem.1) + 2; + }; + + return size; + }; case (#Set(val)) { var size = 0; From cd373ae95ce081d827793f6262c7495626dbe9e8 Mon Sep 17 00:00:00 2001 From: Austin Fatheree Date: Fri, 27 Oct 2023 11:32:49 -0500 Subject: [PATCH 3/9] adding map, set, value map conversions --- dfx.json | 2 +- src/conversion.mo | 130 +++++++++++++++++++++++++++++++++++++++++++++- src/types.mo | 42 +++++++++++++++ 3 files changed, 171 insertions(+), 3 deletions(-) diff --git a/dfx.json b/dfx.json index c158633..be3e7fb 100644 --- a/dfx.json +++ b/dfx.json @@ -11,7 +11,7 @@ "packtool": "mops sources" } }, - "dfx": "0.14.0", + "dfx": "0.15.1", "networks": { "local": { "bind": "127.0.0.1:8000", diff --git a/src/conversion.mo b/src/conversion.mo index 9566b62..e8db2e4 100644 --- a/src/conversion.mo +++ b/src/conversion.mo @@ -41,6 +41,7 @@ import Hex "hex"; import Properties "properties"; import StableBuffer "mo:stablebuffer/StableBuffer"; import Map "mo:map9/Map"; +import Set "mo:map9/Set"; module { @@ -484,7 +485,40 @@ module { return Hex.encode(StableBuffer.toArray(val)); }; - case(_){assert(false);/*unreachable*/"";}; + case(#Set(val)){ //this is currently not parseable and should probably just be used for debuging. It would be nice to output candid. + + var t = "["; + for(thisItem in Set.keys(val)){ + t := t # "{" # candyToText(thisItem) # "} "; + }; + + return Text.trimEnd(t, #text(" ")) # "]"; + + + }; + case(#Map(val)){ //this is currently not parseable and should probably just be used for debuging. It would be nice to output candid. + + var t = "{"; + for(thisItem in Map.entries(val)){ + t := t # thisItem.0 # ":" # candyToText(thisItem.1) # "; "; + }; + + return Text.trimEnd(t, #text(" ")) # "}"; + + + }; + case(#ValueMap(val)){ //this is currently not parseable and should probably just be used for debuging. It would be nice to output candid. + + var t = "{"; + for(thisItem in Map.entries(val)){ + t := t # candyToText(thisItem.0) # ":" # candyToText(thisItem.1) # "; "; + }; + + return Text.trimEnd(t, #text(" ")) # "}"; + + + }; + //case(_){assert(false);/*unreachable*/"";}; }; }; @@ -672,6 +706,65 @@ module { }; }; + /// Convert a `Candy` to `Map` + /// + /// Example: + /// ```motoko include=import + /// let map = Map.new(); + /// Map.put(map, thash, "akey", #Text("value")); + /// let value: Candy = #Map(map); + /// let value_as_nats_buffer = Conversion.candyToMap(value); + /// ``` + /// Note: Throws if the underlying value isn't convertible. + public func candyToMap(val : Candy) : Map.Map{ + switch (val){ + case(#Map(val)){ + return val; + }; + case(_){ + Prelude.nyi(); //will throw for unconvertable types + }; + }; + }; + + /// Example: + /// ```motoko include=import + /// let map = Map.new(); + /// Map.put(map, candyHashTool, #Text("akey"), #Text("value")); + /// let value: Candy = #ValueMapMap(map); + /// let value_as_nats_buffer = Conversion.candyToValueMap(value); + /// ``` + /// Note: Throws if the underlying value isn't convertible. + public func candyToValueMap(val : Candy) : Map.Map{ + switch (val){ + case(#ValueMap(val)){ + return val; + }; + case(_){ + Prelude.nyi(); //will throw for unconvertable types + }; + }; + }; + + /// Example: + /// ```motoko include=import + /// let map = Set.new(); + /// Set.put(map, candyHashTool, #Text("akey")); + /// let value: Candy = #Set(map); + /// let value_as_nats_buffer = Conversion.candyToSet(value); + /// ``` + /// Note: Throws if the underlying value isn't convertible. + public func candyToSet(val : Candy) : Set.Set{ + switch (val){ + case(#Set(val)){ + return val; + }; + case(_){ + Prelude.nyi(); //will throw for unconvertable types + }; + }; + }; + /// Convert a `CandyShared` to `Nat`. /// /// Example: @@ -1107,7 +1200,40 @@ module { return Hex.encode(val); }; - case(_){assert(false);/*unreachable*/"";}; + case(#Set(val)){ //this is currently not parseable and should probably just be used for debuging. It would be nice to output candid. + + var t = "["; + for(thisItem in val.vals()){ + t := t # "{" # candySharedToText(thisItem) # "} "; + }; + + return Text.trimEnd(t, #text(" ")) # "]"; + + + }; + case(#Map(val)){ //this is currently not parseable and should probably just be used for debuging. It would be nice to output candid. + + var t = "{"; + for(thisItem in val.vals()){ + t := t # thisItem.0 # ": " # candySharedToText(thisItem.1) # "; "; + }; + + return Text.trimEnd(t, #text(" ")) # "}"; + + + }; + case(#ValueMap(val)){ //this is currently not parseable and should probably just be used for debuging. It would be nice to output candid. + + var t = "{"; + for(thisItem in val.vals()){ + t := t # candySharedToText(thisItem.0) # ": " # candySharedToText(thisItem.1) # "; "; + }; + + return Text.trimEnd(t, #text(" ")) # "}"; + + + }; + //case(_){assert(false);/*unreachable*/"";}; }; }; diff --git a/src/types.mo b/src/types.mo index 87c4ef7..9052f33 100644 --- a/src/types.mo +++ b/src/types.mo @@ -710,6 +710,26 @@ module { }; return true; }; + case(#Map(x), #Map(y)){ + //this map ignores insertion order + + if(Map.size(x) != Map.size(y)) return false; + + for(thisItem in Map.entries(x)){ + switch(Map.get(y, Map.thash, thisItem.0)){ + + case(null){ + return false; + }; + case(?val){ + if(eq(val, thisItem.1) == false){ + return false; + }; + } + }; + }; + return true; + }; case(#Set(x), #Set(y)){ //this set takes insertion order into account if(Set.size(x) != Set.size(y)) return false; @@ -971,6 +991,28 @@ module { }; return true; }; + case(#Map(x), #Map(y)){ + //this map IGNORES insertion order + + if(x.size() != y.size()) return false; + + let yit = Iter.sort<(Text, CandyShared)>(y.vals(), func(x, y){Nat32.compare(Text.hash(x.0), Text.hash(y.0))}); + + for(thisItem in Iter.sort<(Text, CandyShared)>(x.vals(), func(x, y){Nat32.compare(Text.hash(x.0), Text.hash(y.0))})){ + switch(yit.next()){ + case(null) return false; + case(?val){ + if(Text.equal(val.0, thisItem.0) == false){ + return false; + }; + if(eqShared(val.1, thisItem.1) == false){ + return false; + }; + }; + }; + }; + return true; + }; case(#Set(x), #Set(y)){ //this set takes insertion order into account let yit = Iter.sort(y.vals(), func(x, y){Nat32.compare(hashShared(x), hashShared(y))}); From a34f3f9ab11077d59afe707e1f747cb27efaf579 Mon Sep 17 00:00:00 2001 From: Austin Fatheree Date: Tue, 7 Nov 2023 15:09:35 -0600 Subject: [PATCH 4/9] added candyToPropertyMap --- src/conversion.mo | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/conversion.mo b/src/conversion.mo index e8db2e4..1489aa6 100644 --- a/src/conversion.mo +++ b/src/conversion.mo @@ -764,6 +764,27 @@ module { }; }; }; + + /// Convert a `Candy` to `Map` + /// + /// Example: + /// ```motoko include=import + /// let map = Map.new(); + /// Map.put(map, thash, "akey", {name="test";value=#Text("value"); immutable=true;); + /// let value: Candy = #Class(map); + /// let value_as_nats_buffer = Conversion.candyToPropertyMap(value); + /// ``` + /// Note: Throws if the underlying value isn't convertible. + public func candyToPropertyMap(val : Candy) : Map.Map{ + switch (val){ + case(#Class(val)){ + return val; + }; + case(_){ + Prelude.nyi(); //will throw for unconvertable types + }; + }; + }; /// Convert a `CandyShared` to `Nat`. /// From 22ffae877ac1d7d1082537f3701f028782728f42 Mon Sep 17 00:00:00 2001 From: Austin Fatheree Date: Wed, 8 Nov 2023 15:31:22 -0600 Subject: [PATCH 5/9] Adding ValueShared --- src/conversion.mo | 66 +++++++++++++++++++++++++++++++++++++++++++++++ src/types.mo | 9 +++++++ 2 files changed, 75 insertions(+) diff --git a/src/conversion.mo b/src/conversion.mo index 1489aa6..93f9cbf 100644 --- a/src/conversion.mo +++ b/src/conversion.mo @@ -48,6 +48,7 @@ module { type CandyShared = Types.CandyShared; type Candy = Types.Candy; + type ValueShared = Types.ValueShared; type DataZone = Types.DataZone; type PropertyShared = Types.PropertyShared; type Property = Types.Property; @@ -1958,4 +1959,69 @@ module { case(_){val}; }; }; + + ///converts a candyshared value to the reduced set of ValueShared used in many places like ICRC3. Some types not recoverable + public func CandySharedToValue(x: CandyShared) : ValueShared { + switch(x){ + case(#Text(x)) #Text(x); + case(#Map(x)) { + let buf = Buffer.Buffer<(Text, ValueShared)>(1); + for(thisItem in x.vals()){ + buf.add((thisItem.0, CandySharedToValue(thisItem.1))); + }; + #Map(Buffer.toArray(buf)); + }; + case(#Class(x)) { + let buf = Buffer.Buffer<(Text, ValueShared)>(1); + for(thisItem in x.vals()){ + buf.add((thisItem.name, CandySharedToValue(thisItem.value))); + }; + #Map(Buffer.toArray(buf)); + }; + case(#Int(x)) #Int(x); + case(#Int8(x)) #Int(Int8.toInt(x)); + case(#Int16(x)) #Int(Int16.toInt(x)); + case(#Int32(x)) #Int(Int32.toInt(x)); + case(#Int64(x)) #Int(Int64.toInt(x)); + case(#Ints(x)){ + #Array(Array.map(x, func(x: Int) : ValueShared { #Int(x)})); + }; + case(#Nat(x)) #Nat(x); + case(#Nat8(x)) #Nat(Nat8.toNat(x)); + case(#Nat16(x)) #Nat(Nat16.toNat(x)); + case(#Nat32(x)) #Nat(Nat32.toNat(x)); + case(#Nat64(x)) #Nat(Nat64.toNat(x)); + case(#Nats(x)){ + #Array(Array.map(x, func(x: Nat) : ValueShared { #Nat(x)})); + }; + case(#Bytes(x)){ + #Blob(Blob.fromArray(x)); + }; + case(#Array(x)) { + #Array(Array.map(x, CandySharedToValue)); + }; + case(#Blob(x)) #Blob(x); + case(#Bool(x)) #Blob(Blob.fromArray([if(x==true){1 : Nat8} else {0: Nat8}])); + case(#Float(x)){#Text(Float.format(#exact, x))}; + case(#Floats(x)){ + #Array(Array.map(x, func(x: Float) : ValueShared { CandySharedToValue(#Float(x))})); + }; + case(#Option(x)){ //empty array is null + switch(x){ + case(null) #Array([]); + case(?x) #Array([CandySharedToValue(x)]); + }; + }; + case(#Principal(x)){ + #Blob(Principal.toBlob(x)); + }; + case(#Set(x)) { + #Array(Array.map(x, func(x: CandyShared) : ValueShared { CandySharedToValue(x)})); + }; + case(#ValueMap(x)) { + #Array(Array.map<(CandyShared,CandyShared),ValueShared>(x, func(x: (CandyShared,CandyShared)) : ValueShared { #Array([CandySharedToValue(x.0), CandySharedToValue(x.1)])})); + }; + //case(_){assert(false);/*unreachable*/#Nat(0);}; + }; + }; } diff --git a/src/types.mo b/src/types.mo index 9052f33..8e52827 100644 --- a/src/types.mo +++ b/src/types.mo @@ -157,6 +157,15 @@ module { #Set : [CandyShared]; }; + public type ValueShared = { + #Int : Int; + #Nat : Nat; + #Text : Text; + #Blob : Blob; + #Array : [ValueShared]; + #Map : [(Text, ValueShared)]; + }; + /// The Shared CandyShared. public type Candy = { #Int : Int; From f82d0eb9b9baafcc1a08f1941bcb89c622b59ea2 Mon Sep 17 00:00:00 2001 From: Austin Fatheree Date: Fri, 17 Nov 2023 12:59:35 -0600 Subject: [PATCH 6/9] Adding Tests. Updating Value Conversion for ICRC3 --- mops.toml | 27 ++-- src/candid.mo | 85 ++++------- src/clone.mo | 2 +- src/conversion.mo | 73 ++++++++- src/hex.mo | 100 ------------- src/json.mo | 10 +- src/json.mo.orig | 108 -------------- src/types.mo | 2 +- src/upgrade.mo | 33 ++-- src/workspace.mo | 2 +- test/candid.test.mo | 323 ++++++++++++++++++++++++++++++++++++++++ test/clone.test.mo | 221 +++++++++++++++++++++++++++ test/json.test.mo | 162 ++++++++++++++++++++ test/properties.test.mo | 190 +++++++++++++++++++++++ test/types.test.mo | 175 ++++++++++++++++++++++ test/upgrade.test.mo | 119 +++++++++++++++ test/workspaces.test.mo | 217 +++++++++++++++++++++++++++ 17 files changed, 1551 insertions(+), 298 deletions(-) delete mode 100644 src/hex.mo delete mode 100644 src/json.mo.orig create mode 100644 test/candid.test.mo create mode 100644 test/clone.test.mo create mode 100644 test/json.test.mo create mode 100644 test/properties.test.mo create mode 100644 test/types.test.mo create mode 100644 test/upgrade.test.mo create mode 100644 test/workspaces.test.mo diff --git a/mops.toml b/mops.toml index 4c58d80..7d3050c 100644 --- a/mops.toml +++ b/mops.toml @@ -1,16 +1,23 @@ [dependencies] -base = "https://github.com/dfinity/motoko-base#moc-0.10.0" -matchers = "https://github.com/kritzcreek/motoko-matchers#v1.3.0" -candid = "https://github.com/gekctek/motoko_candid#v1.0.1" -xtendedNumbers = "https://github.com/gekctek/motoko_numbers#v1.0.2" -stablebuffer = "https://github.com/skilesare/StableBuffer#v0.2.0" -map7 = "https://github.com/ZhenyaUsenko/motoko-hash-map#v7.0.0" -map9 = "https://github.com/ZhenyaUsenko/motoko-hash-map#v9.0.1" -candy_0_1_12 = "https://github.com/icdevs/candy_library#v0.1.12" -candy_0_2_0 = "https://github.com/icdevs/candy_library#0.2.0" +base = "https://github.com/dfinity/motoko-base#moc-0.10.0@a15ca7cd26335505e103288654f480a44463d844" +matchers = "https://github.com/kritzcreek/motoko-matchers#v1.3.0@3dac8a071b69e4e651b25a7d9683fe831eb7cffd" +candid = "https://github.com/gekctek/motoko_candid#v1.0.1@4f0c445a4f998d4d07b616fafd463eea8f52e4bc" +xtendedNumbers = "https://github.com/gekctek/motoko_numbers#v1.0.2@775995c49c55f7c53e5c9e5a52dc3536201064b2" +stablebuffer = "https://github.com/skilesare/StableBuffer#v0.2.0@110660769d11ba93c618dc4712525d20503bdc37" +stablebuffer_1_3_0 = "https://github.com/canscale/StableBuffer#v1.3.0@acdde6bb5b939227997cebdbb8919d2e6da8691c" +map7 = "https://github.com/ZhenyaUsenko/motoko-hash-map#v7.0.0@f0e25632e5da80118274e78ceeb5bec95f3c2b81" +map9 = "https://github.com/ZhenyaUsenko/motoko-hash-map#v9.0.1@10b68f6ea8df5e72dfa4c07a50c8bb60a916c233" +candy_0_1_12 = "https://github.com/icdevs/candy_library#v0.1.12@20db7a8a74258bb07c7d354ea477bf95121747a3" +candy_0_2_0 = "https://github.com/icdevs/candy_library#0.2.0@4fc5aebec44355da94a4d3ebef87623e4545d89a" +encoding_0_4_1 = "https://github.com/aviate-labs/encoding.mo#v0.4.1@2711d18727e954b11afc0d37945608512b5fbce2" +vector = "0.2.0" [package] name = "candy" version = "0.3.0" description = "Library for Converting Types and Creating Workable Motoko Collections" -repository = "https://github.com/icdevs/candy_library" \ No newline at end of file +repository = "https://github.com/icdevs/candy_library" + +[dev-dependencies] +test = "1.1.0" +fuzz = "0.1.1" \ No newline at end of file diff --git a/src/candid.mo b/src/candid.mo index da2157d..71512c4 100644 --- a/src/candid.mo +++ b/src/candid.mo @@ -21,7 +21,6 @@ import Text "mo:base/Text"; import Map "mo:map9/Map"; import Types "types"; -import CandyHex "hex"; import CandidTypes "mo:candid/Type"; import Arg "mo:candid/Arg"; @@ -177,118 +176,88 @@ module { case(#Map(val)){ let list = val; - - var bFoundMultipleKeyTypes = false; - var bFoundMultipleValueTypes = false; - var lastKeyType : ?CandidTypes.Type = null; - var lastValueType : ?CandidTypes.Type = null; + let values: Buffer.Buffer = Buffer.Buffer(list.size()); + let types: Buffer.Buffer = Buffer.Buffer(list.size()); let localValues: Buffer.Buffer = Buffer.Buffer(2); - let localTypes: Buffer.Buffer = Buffer.Buffer(2); + let body: Buffer.Buffer = Buffer.Buffer(list.size()); - - let localBody: Buffer.Buffer = Buffer.Buffer(2); + var tracker : Nat32 = 0; + let localTypes: Buffer.Buffer = Buffer.Buffer(2); + let localBody: Buffer.Buffer = Buffer.Buffer(2); for(this_item in list.vals()){ let key = (this_item.0); let value = (value_to_candid(this_item.1))[0]; + - switch(lastValueType){ - case(null) lastValueType := ?value._type; - case(?lastValueType){ - if(CandidTypes.equal(lastValueType, value._type)){ - } else { - bFoundMultipleValueTypes := true; - }; - }; - }; + localTypes.add({_type = value._type; tag = #name(key)}); - localTypes.add({_type = #text; tag = #hash(0)}); - localTypes.add({_type = value._type; tag = #hash(1)}); + localBody.add({tag = #name(key); value = value.value}); - localBody.add({tag = #hash(0); value = #text(key)}); - localBody.add({tag = #hash(1); value = value.value}); + - let thisItem = {_type=#record(Buffer.toArray(localTypes)); value = #record(Buffer.toArray(localBody))}; + //buffer.add(thisItem); - types.add({_type = thisItem._type; tag = #hash(tracker)}); - body.add({tag = #hash(tracker); value = thisItem.value}); - values.add(thisItem.value); + //types.add({_type = thisItem._type; tag = #hash(tracker)}); + //body.add({tag = #hash(tracker); value = thisItem.value}); + //values.add(thisItem.value); tracker += 1; }; - buffer.add({_type=#record(Buffer.toArray(types)); value = #record(Buffer.toArray(body))}) + buffer.add({_type=#record(Buffer.toArray(localTypes)); value = #record(Buffer.toArray(localBody))}) }; case(#ValueMap(val)){ let list = val; - - var bFoundMultipleKeyTypes = false; - var bFoundMultipleValueTypes = false; - var lastKeyType : ?CandidTypes.Type = null; - var lastValueType : ?CandidTypes.Type = null; + let values: Buffer.Buffer = Buffer.Buffer(list.size()); + let types: Buffer.Buffer = Buffer.Buffer(list.size()); let localValues: Buffer.Buffer = Buffer.Buffer(2); - let localTypes: Buffer.Buffer = Buffer.Buffer(2); + let body: Buffer.Buffer = Buffer.Buffer(list.size()); - - let localBody: Buffer.Buffer = Buffer.Buffer(2); + var tracker : Nat32 = 0; for(this_item in list.vals()){ let key = (value_to_candid(this_item.0))[0]; let value = (value_to_candid(this_item.1))[0]; - switch(lastKeyType){ - case(null) lastKeyType := ?key._type; - case(?lastKeyType){ - if(CandidTypes.equal(lastKeyType, key._type)){ - } else { - bFoundMultipleKeyTypes := true; - }; - }; - }; - switch(lastValueType){ - case(null) lastValueType := ?value._type; - case(?lastValueType){ - if(CandidTypes.equal(lastValueType, value._type)){ - - } else { - bFoundMultipleValueTypes := true; - }; - }; - }; + let localTypes: Buffer.Buffer = Buffer.Buffer(2); + let localBody: Buffer.Buffer = Buffer.Buffer(2); + localTypes.add({_type = key._type; tag = #hash(0)}); localTypes.add({_type = value._type; tag = #hash(1)}); + localBody.add({tag = #hash(0); value = key.value}); localBody.add({tag = #hash(1); value = value.value}); let thisItem = {_type=#record(Buffer.toArray(localTypes)); value = #record(Buffer.toArray(localBody))}; - types.add({_type = thisItem._type; tag = #hash(tracker)}); - body.add({tag = #hash(tracker); value = thisItem.value}); - values.add(thisItem.value); + types.add({_type = #record(Buffer.toArray(localTypes)); tag = #hash(tracker)}); + body.add({tag = #hash(tracker); value = #record(Buffer.toArray(localBody))}); + tracker += 1; }; - buffer.add({_type=#record(Buffer.toArray(types)); value = #record(Buffer.toArray(body))}) + //buffer.add({_type=#record(Buffer.toArray(types)); value = #record(Buffer.toArray(body))}) }; //array diff --git a/src/clone.mo b/src/clone.mo index 925f227..9571247 100644 --- a/src/clone.mo +++ b/src/clone.mo @@ -15,7 +15,7 @@ import Types "types"; import Array "mo:base/Array"; -import StableBuffer "mo:stablebuffer/StableBuffer"; +import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; import Map "mo:map9/Map"; import Set "mo:map9/Set"; diff --git a/src/conversion.mo b/src/conversion.mo index 93f9cbf..5f3655e 100644 --- a/src/conversion.mo +++ b/src/conversion.mo @@ -37,9 +37,9 @@ import Principal "mo:base/Principal"; import Prelude "mo:base/Prelude"; import List "mo:base/List"; import Types "types"; -import Hex "hex"; +import Hex "mo:encoding_0_4_1/Hex"; import Properties "properties"; -import StableBuffer "mo:stablebuffer/StableBuffer"; +import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; import Map "mo:map9/Map"; import Set "mo:map9/Set"; @@ -2023,5 +2023,74 @@ module { }; //case(_){assert(false);/*unreachable*/#Nat(0);}; }; + + + }; + + ///converts a candy value to the reduced set of ValueShared used in many places like ICRC3. Some types not recoverable + public func CandyToValue(x: Candy) : ValueShared { + switch(x){ + case(#Text(x)) #Text(x); + case(#Map(x)) { + let buf = Buffer.Buffer<(Text, ValueShared)>(1); + for(thisItem in Map.entries(x)){ + buf.add((thisItem.0, CandyToValue(thisItem.1))); + }; + #Map(Buffer.toArray(buf)); + }; + case(#Class(x)) { + let buf = Buffer.Buffer<(Text, ValueShared)>(1); + for(thisItem in Map.entries(x)){ + buf.add((thisItem.1.name, CandyToValue(thisItem.1.value))); + }; + #Map(Buffer.toArray(buf)); + }; + case(#Int(x)) #Int(x); + case(#Int8(x)) #Int(Int8.toInt(x)); + case(#Int16(x)) #Int(Int16.toInt(x)); + case(#Int32(x)) #Int(Int32.toInt(x)); + case(#Int64(x)) #Int(Int64.toInt(x)); + case(#Ints(x)){ + #Array(StableBuffer.toArray(StableBuffer.map(x, func(x: Int) : ValueShared { #Int(x)}))); + }; + case(#Nat(x)) #Nat(x); + case(#Nat8(x)) #Nat(Nat8.toNat(x)); + case(#Nat16(x)) #Nat(Nat16.toNat(x)); + case(#Nat32(x)) #Nat(Nat32.toNat(x)); + case(#Nat64(x)) #Nat(Nat64.toNat(x)); + case(#Nats(x)){ + #Array(StableBuffer.toArray(StableBuffer.map(x, func(x: Nat) : ValueShared { #Nat(x)}))); + }; + case(#Bytes(x)){ + #Blob(Blob.fromArray(StableBuffer.toArray(x))); + }; + case(#Array(x)) { + #Array(StableBuffer.toArray(StableBuffer.map(x, CandyToValue))); + }; + case(#Blob(x)) #Blob(x); + case(#Bool(x)) #Blob(Blob.fromArray([if(x==true){1 : Nat8} else {0: Nat8}])); + case(#Float(x)){#Text(Float.format(#exact, x))}; + case(#Floats(x)){ + #Array(StableBuffer.toArray(StableBuffer.map(x, func(x: Float) : ValueShared { CandyToValue(#Float(x))}))); + }; + case(#Option(x)){ //empty array is null + switch(x){ + case(null) #Array([]); + case(?x) #Array([CandyToValue(x)]); + }; + }; + case(#Principal(x)){ + #Blob(Principal.toBlob(x)); + }; + case(#Set(x)) { + #Array(Iter.toArray(Iter.map(Set.keys(x), func(x: Candy) : ValueShared { CandyToValue(x)}))); + }; + case(#ValueMap(x)) { + #Array(Iter.toArray(Iter.map<(Candy,Candy),ValueShared>(Map.entries(x), func(x: (Candy,Candy)) : ValueShared { #Array([CandyToValue(x.0), CandyToValue(x.1)])}))); + }; + //case(_){assert(false);/*unreachable*/#Nat(0);}; + }; + + }; } diff --git a/src/hex.mo b/src/hex.mo deleted file mode 100644 index 52be30f..0000000 --- a/src/hex.mo +++ /dev/null @@ -1,100 +0,0 @@ -/// Hexadecimal support for the candy library. -/// -/// This module contains the utilities useful for handling hexadecimal values. - -/////////////////////////////// -// ** -// * Module : Hex.mo -// * Description : Hexadecimal encoding and decoding routines. -// * Copyright : 2020 Enzo Haussecker -// * License : Apache 2.0 with LLVM Exception -// * Maintainer : Enzo Haussecker -// * Stability : Stable -// */ -/////////////////////////////// - -import Array "mo:base/Array"; -import Iter "mo:base/Iter"; -import Option "mo:base/Option"; -import Nat8 "mo:base/Nat8"; -import Char "mo:base/Char"; -import Result "mo:base/Result"; - -module { - private type Result = Result.Result; - - private let base : Nat8 = 0x10; - - private let symbols = [ - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', - ]; - - /// Defines a type to indicate that the decoder has failed. - public type DecodeError = { - #msg : Text; - }; - - /// Encode an array of unsigned 8-bit integers in hexadecimal format. - public func encode(array : [Nat8]) : Text { - Array.foldLeft(array, "", func (accum, w8) { - accum # encodeW8(w8); - }); - }; - - /// Encode an unsigned 8-bit integer in hexadecimal format. - private func encodeW8(w8 : Nat8) : Text { - let c1 = symbols[Nat8.toNat(w8 / base)]; - let c2 = symbols[Nat8.toNat(w8 % base)]; - Char.toText(c1) # Char.toText(c2); - }; - - /// Decode an array of unsigned 8-bit integers in hexadecimal format. - /// - /// Returns a DecodeError if the decoding is unsuccessful. - public func decode(text : Text) : Result<[Nat8], DecodeError> { - let next = text.chars().next; - func parse() : Result { - Option.get>( - do ? { - let c1 = next()!; - let c2 = next()!; - Result.chain(decodeW4(c1), func (x1) { - Result.chain(decodeW4(c2), func (x2) { - #ok (x1 * base + x2); - }) - }) - }, - #err (#msg "Not enough input!"), - ); - }; - var i = 0; - let n = text.size() / 2 + text.size() % 2; - let array = Array.init(n, 0); - while (i != n) { - switch (parse()) { - case (#ok w8) { - array[i] := w8; - i += 1; - }; - case (#err err) { - return #err err; - }; - }; - }; - #ok (Array.freeze(array)); - }; - - /// Decode an unsigned 4-bit integer in hexadecimal format. - /// - /// Returns a DecodeError if the decoding is unsuccessful. - private func decodeW4(char : Char) : Result { - for (i in Iter.range(0, 15)) { - if (symbols[i] == char) { - return #ok (Nat8.fromNat(i)); - }; - }; - let str = "Unexpected character: " # Char.toText(char); - #err (#msg str); - }; -}; diff --git a/src/json.mo b/src/json.mo index 468aa93..327dc24 100644 --- a/src/json.mo +++ b/src/json.mo @@ -20,7 +20,7 @@ import Principal "mo:base/Principal"; import Text "mo:base/Text"; import Types "types"; -import CandyHex "hex"; +import Hex "mo:encoding_0_4_1/Hex"; module { /// Convert `CandyShared` to JSON format as `Text`. @@ -79,24 +79,24 @@ module { case(#Floats(val)){ var body: Buffer.Buffer = Buffer.Buffer(1); for(this_item in val.vals()){ - body.add(Float.toText(this_item)); + body.add(Float.format(#exact, this_item)); }; return "[" # Text.join(",", body.vals()) # "]"; }; //bytes case(#Bytes(val)){ - return "\"" # CandyHex.encode(val) # "\"";//CandyHex.encode(val); + return "\"" # Hex.encode(val) # "\"";//CandyHex.encode(val); }; //bytes case(#Blob(val)){ - return "\"" # CandyHex.encode(Blob.toArray(val)) # "\"";//CandyHex.encode(val); + return "\"" # Hex.encode(Blob.toArray(val)) # "\"";//CandyHex.encode(val); }; //principal case(#Principal(val)){ "\"" # Principal.toText(val) # "\"";}; //bool case(#Bool(val)){ "\"" # Bool.toText(val) # "\"";}; //float - case(#Float(val)){ Float.format(#fix 8, val)}; + case(#Float(val)){ Float.format(#exact, val)}; case(#Int(val)){Int.toText(val);}; case(#Int64(val)){Int64.toText(val);}; case(#Int32(val)){Int32.toText(val);}; diff --git a/src/json.mo.orig b/src/json.mo.orig deleted file mode 100644 index 468aa93..0000000 --- a/src/json.mo.orig +++ /dev/null @@ -1,108 +0,0 @@ -/// JSON support for the candy library. -/// -/// This module contains the utilities useful for handling JSON values. - -import Buffer "mo:base/Buffer"; -import Nat "mo:base/Nat"; -import Nat16 "mo:base/Nat16"; -import Nat32 "mo:base/Nat32"; -import Nat64 "mo:base/Nat64"; -import Nat8 "mo:base/Nat8"; -import Float "mo:base/Float"; -import Int "mo:base/Int"; -import Int8 "mo:base/Int8"; -import Int16 "mo:base/Int16"; -import Int32 "mo:base/Int32"; -import Int64 "mo:base/Int64"; -import Bool "mo:base/Bool"; -import Blob "mo:base/Blob"; -import Principal "mo:base/Principal"; -import Text "mo:base/Text"; - -import Types "types"; -import CandyHex "hex"; - -module { - /// Convert `CandyShared` to JSON format as `Text`. - /// - /// Example: - /// ```motoko include=import - /// let val: CandyShared = #Option(?#Principal(Principal.fromText("xyz"))); - /// let val_as_json = Json.value_to_json(val); - /// ``` - public func value_to_json(val: Types.CandyShared): Text { - switch(val){ - //nat - case(#Nat(val)){ Nat.toText(val)}; - case(#Nat64(val)){ Nat64.toText(val)}; - case(#Nat32(val)){ Nat32.toText(val)}; - case(#Nat16(val)){ Nat16.toText(val)}; - case(#Nat8(val)){ Nat8.toText(val)}; - //text - case(#Text(val)){ "\"" # val # "\""; }; - //class - case(#Class(val)){ - var body: Buffer.Buffer = Buffer.Buffer(1); - for(this_item in val.vals()){ - body.add("\"" # this_item.name # "\"" # ":" # value_to_json(this_item.value)); - }; - return "{" # Text.join(",", body.vals()) # "}"; - }; - //array - case(#Array(val)){ - var body: Buffer.Buffer = Buffer.Buffer(1); - for(this_item in val.vals()){ - body.add(value_to_json(this_item)); - }; - return "[" # Text.join(",", body.vals()) # "]"; - }; - case(#Option(val)){ - switch(val){ - case(null){"null";}; - case(?val){value_to_json(val);} - } - }; - case(#Nats(val)){ - var body: Buffer.Buffer = Buffer.Buffer(1); - for(this_item in val.vals()){ - body.add(Nat.toText(this_item)); - }; - return "[" # Text.join(",", body.vals()) # "]"; - }; - case(#Ints(val)){ - var body: Buffer.Buffer = Buffer.Buffer(1); - for(this_item in val.vals()){ - body.add(Int.toText(this_item)); - }; - return "[" # Text.join(",", body.vals()) # "]"; - }; - case(#Floats(val)){ - var body: Buffer.Buffer = Buffer.Buffer(1); - for(this_item in val.vals()){ - body.add(Float.toText(this_item)); - }; - return "[" # Text.join(",", body.vals()) # "]"; - }; - //bytes - case(#Bytes(val)){ - return "\"" # CandyHex.encode(val) # "\"";//CandyHex.encode(val); - }; - //bytes - case(#Blob(val)){ - return "\"" # CandyHex.encode(Blob.toArray(val)) # "\"";//CandyHex.encode(val); - }; - //principal - case(#Principal(val)){ "\"" # Principal.toText(val) # "\"";}; - //bool - case(#Bool(val)){ "\"" # Bool.toText(val) # "\"";}; - //float - case(#Float(val)){ Float.format(#fix 8, val)}; - case(#Int(val)){Int.toText(val);}; - case(#Int64(val)){Int64.toText(val);}; - case(#Int32(val)){Int32.toText(val);}; - case(#Int16(val)){Int16.toText(val);}; - case(#Int8(val)){Int8.toText(val);}; - case(_){"";}; - }; - }; -}; diff --git a/src/types.mo b/src/types.mo index 8e52827..c738489 100644 --- a/src/types.mo +++ b/src/types.mo @@ -17,7 +17,7 @@ /// & stabilize/destabilize candy values. import Buffer "mo:base/Buffer"; -import StableBuffer "mo:stablebuffer/StableBuffer"; +import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; import Array "mo:base/Array"; import Iter "mo:base/Iter"; import Map "mo:map9/Map"; diff --git a/src/upgrade.mo b/src/upgrade.mo index 3888d41..861af86 100644 --- a/src/upgrade.mo +++ b/src/upgrade.mo @@ -23,10 +23,19 @@ import Map7 "mo:map7/Map"; import Set7 "mo:map7/Set"; import Map "mo:map9/Map"; import Set "mo:map9/Set"; -import StableBuffer "mo:stablebuffer/StableBuffer"; +import StableBuffer_Old "mo:stablebuffer/StableBuffer"; +import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; module { + public func toBufferOld(x :[T]) : StableBuffer_Old.StableBuffer{ + let thisBuffer = StableBuffer_Old.initPresized(x.size()); + for(thisItem in x.vals()){ + StableBuffer_Old.add(thisBuffer,thisItem); + }; + return thisBuffer; + }; + /// Upgrade from V1 representation of `CandyShared` to the V2 representation. public func upgradeCandy0_1_2_to_0_2_0(item : CandyOld.CandyValueUnstable) : Candy0_2_0.Candy { switch (item) { @@ -71,20 +80,20 @@ module { }; case (#Bytes(val)) { switch (val) { - case (#frozen(val)) { #Bytes(CandyTypes.toBuffer(val)) }; - case (#thawed(val)) { #Bytes(CandyTypes.toBuffer(Buffer.toArray(val))) }; + case (#frozen(val)) { #Bytes(toBufferOld(val)) }; + case (#thawed(val)) { #Bytes(toBufferOld(Buffer.toArray(val))) }; }; }; case (#Floats(val)) { switch (val) { - case (#frozen(val)) { #Floats(CandyTypes.toBuffer(val)) }; - case (#thawed(val)) { #Floats(CandyTypes.toBuffer(Buffer.toArray(val))) }; + case (#frozen(val)) { #Floats(toBufferOld(val)) }; + case (#thawed(val)) { #Floats(toBufferOld(Buffer.toArray(val))) }; }; }; case (#Nats(val)) { switch (val) { - case (#frozen(val)) { #Nats(CandyTypes.toBuffer(val)) }; - case (#thawed(val)) { #Nats(CandyTypes.toBuffer(Buffer.toArray(val))) }; + case (#frozen(val)) { #Nats(toBufferOld(val)) }; + case (#thawed(val)) { #Nats(toBufferOld(Buffer.toArray(val))) }; }; }; @@ -177,7 +186,7 @@ module { case (#Int16(val)) { #Int16(val) }; case (#Int32(val)) { #Int32(val) }; case (#Int64(val)) { #Int64(val) }; - case (#Ints(val)) { #Ints(val) }; + case (#Ints(val)) { #Ints(StableBuffer.fromArray(StableBuffer_Old.toArray(val))) }; case (#Nat(val)) { #Nat(val) }; case (#Nat8(val)) { #Nat8(val) }; case (#Nat16(val)) { #Nat16(val) }; @@ -203,11 +212,11 @@ module { }; case (#Principal(val)) { #Principal(val) }; case (#Array(val)) { - #Array(CandyTypes.toBuffer(Array.map(StableBuffer.toArray(val), upgradeCandy0_2_0_to_0_3_0))); + #Array(CandyTypes.toBuffer(Array.map(StableBuffer_Old.toArray(val), upgradeCandy0_2_0_to_0_3_0))); }; - case (#Bytes(val)) {#Bytes(val)}; - case (#Floats(val)) {#Floats(val)}; - case (#Nats(val)) {#Nats(val)}; + case (#Bytes(val)) {#Bytes(StableBuffer.fromArray(StableBuffer_Old.toArray(val)))}; + case (#Floats(val)) {#Floats(StableBuffer.fromArray(StableBuffer_Old.toArray(val)))}; + case (#Nats(val)) {#Nats(StableBuffer.fromArray(StableBuffer_Old.toArray(val)))}; case (#Option(val)) { switch(val){ case(null) #Option(null); diff --git a/src/workspace.mo b/src/workspace.mo index c44e209..6a9ab76 100644 --- a/src/workspace.mo +++ b/src/workspace.mo @@ -17,7 +17,7 @@ import Array "mo:base/Array"; import Buffer "mo:base/Buffer"; -import StableBuffer "mo:stablebuffer/StableBuffer"; +import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; import Map "mo:map9/Map"; import Set "mo:map9/Set"; diff --git a/test/candid.test.mo b/test/candid.test.mo new file mode 100644 index 0000000..1a348f9 --- /dev/null +++ b/test/candid.test.mo @@ -0,0 +1,323 @@ +import D "mo:base/Debug"; +import {test} "mo:test"; +import Candid "../src/candid"; +import Types "../src/types"; +import Conv "../src/conversion"; +import Principal "mo:base/Principal"; +import Nat "mo:base/Nat"; +import Int "mo:base/Int"; +import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; +import Array "mo:base/Array"; +import Nat8 "mo:base/Nat8"; +import Text "mo:base/Text"; +import CandidTypes "mo:candid/Type"; +import Arg "mo:candid/Arg"; +import Value "mo:candid/Value"; + +type CandyShared = Types.CandyShared; + +// Helper function to assert equality on Arg values. +func assertArgEq(expected: Arg.Arg, actual: Arg.Arg) { + D.print("in assert equal" # debug_show(expected, actual)); + assert(CandidTypes.equal(expected._type, actual._type)); + switch (expected.value, actual.value) { + case (#text(e), #text(a)) { assert(e == a); }; + case (#principal(e), #principal(a)) { assert(e == a); }; + case (#int(e), #int(a)) { assert(e == a); }; + case (#nat(e), #nat(a)) { assert(e == a); }; + case (#int8(e), #int8(a)) { assert(e == a); }; + case (#int16(e), #int16(a)) { assert(e == a); }; + case (#int32(e), #int32(a)) { assert(e == a); }; + case (#int64(e), #int64(a)) { assert(e == a); }; + case (#nat8(e), #nat8(a)) { assert(e == a); }; + case (#nat16(e), #nat16(a)) { assert(e == a); }; + case (#nat32(e), #nat32(a)) { assert(e == a); }; + case (#nat64(e), #nat64(a)) { assert(e == a); }; + case (#bool(e), #bool(a)) { assert(e == a); }; + case (#float32(e), #float32(a)) { assert(e == a); }; + case (#float64(e), #float64(a)) { assert(e == a); }; + case (#opt(?e), #opt(?a)) { assert(Value.equal(e,a)) }; + case (#vector(e), #vector(a)) { assert(Array.equal((e,a, Value.equal))) }; + case (#record(e), #record(a)) { assert(Value.equal((expected.value, actual.value))) }; + case (#_null(e), #_null(a)) { assert(true); }; + // ... Add other cases as needed for complete coverage of Value variants. + case (_, _) { assert(false);//, "Types of Arg values do not match."); + }; + }; +}; + +// Helper function to construct Arg values for testing. +func makeArg(t: CandidTypes.Type, v: Value.Value): Arg.Arg { + {_type = t; value = v} +}; + +// Test cases for value_to_candid +test("should convert CandyShared #Nat to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Nat(42); + let expectedArg = makeArg(#nat, #nat(42)); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +test("should convert CandyShared #Nat64 to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Nat64(42); + let expectedArg = makeArg(#nat64, #nat64(42: Nat64)); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +// ... Similar tests for #Nat32, #Nat16, #Nat8, #Text, #Principal, #Bool, #Float, and so on. + +test("should convert CandyShared #Text to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Text("Hello"); + let expectedArg = makeArg(#text, #text("Hello")); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +test("should convert CandyShared #Bool to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Bool(true); + let expectedArg = makeArg(#bool, #bool(true)); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +test("should convert CandyShared #Principal to Arg with appropriate Candid type", func() { + let principalValue = Principal.fromText("rrkah-fqaaa-aaaaa-aaaaq-cai"); + let candy: CandyShared = #Principal(principalValue); + let expectedArg = makeArg(#principal, #principal(#transparent(principalValue))); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +test("should convert CandyShared #Option(null) to proper Arg with appropriate Candid type", func() { + let candy: CandyShared = #Option(null); + let expectedArg = makeArg(#opt(#_null), #_null); + let result = Candid.value_to_candid(candy); + D.print(debug_show(expectedArg, result)); + assertArgEq(expectedArg, result[0]); +}); + +test("should convert CandyShared #Option(value) to proper Arg with appropriate Candid type", func() { + let candy: CandyShared = #Option(?#Nat(42)); + let expectedArg = makeArg(#opt(#nat), #opt(?#nat(42))); + let result = Candid.value_to_candid(candy); + D.print(debug_show(expectedArg, result)); + assertArgEq(expectedArg, result[0]); +}); + +test("should convert CandyShared #Set to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Set([#Nat(1), #Nat(2), #Nat(3)]); + let expectedArg = makeArg(#vector(#nat), + #vector([ + #nat(1), + #nat(2), + #nat(3) + ]) + ); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +// Test conversion of CandyShared to Candid for the #Map type. + test("should convert CandyShared #Map to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Map([ + ("key1", #Nat(1)), + ("key2", #Text("two")), + ("key3", #Bool(true)) + ]); + let expectedArg = makeArg(#record([ + // Need to sort fields in lexicographical order for #record type + {tag = #name("key1"); _type = #nat}, + {tag = #name("key2"); _type = #text}, + {tag = #name("key3"); _type = #bool} + ]), + #record([ + {tag = #name("key1"); value = #nat(1)}, + {tag = #name("key2"); value = #text("two")}, + {tag = #name("key3"); value = #bool(true)} + ])); + let result = Candid.value_to_candid(candy); + D.print(debug_show(result)); + D.print(debug_show(expectedArg)); + assertArgEq(expectedArg, result[0]); +}); + +// Test conversion of CandyShared to Candid for the #ValueMap type. +test("should convert CandyShared #ValueMap to Arg with appropriate Candid type", func() { + let keyVal1: (CandyShared, CandyShared) = (#Nat(10), #Nat(30)); + let keyVal2: (CandyShared, CandyShared) = (#Nat(20), #Nat(40)); + let candy: CandyShared = #ValueMap([keyVal1, keyVal2]); + let expectedArg = makeArg(#record([ + {tag = #hash(0 : Nat32); _type = #record([{tag = #hash(0 : Nat32); _type = #nat;},{tag = #hash(1 : Nat32); _type = #nat;}])}, + {tag = #hash(1 : Nat32); _type = #record([{tag = #hash(0 : Nat32); _type = #nat;},{tag = #hash(1 : Nat32); _type = #nat;}])}, + ]), + #record([ + {tag = #hash(0 : Nat32); value = #record([ {tag = #hash(0 : Nat32);value=#nat(10)}, {tag = #hash(1 : Nat32); value=#nat(30)}])}, + {tag = #hash(1 : Nat32); value = #record([ {tag = #hash(0 : Nat32);value=#nat(20)}, {tag = #hash(1 : Nat32); value=#nat(40)}])}, + ])); + let result = Candid.value_to_candid(candy); + + D.print(debug_show(result)); + D.print(debug_show(expectedArg)); + assertArgEq(expectedArg, result[0]); + // Repeat this pattern for each pair in the ValueMap. +}); + +// Test conversion of CandyShared to Candid for the #Class type. +test("should convert CandyShared #Class to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Class([ + {name = "age"; value = #Nat(30); immutable = true}, + {name = "name"; value = #Text("Alice"); immutable = true}, + {name = "verified"; value = #Bool(true); immutable = true} + ]); + let expectedArg = makeArg(#record([ + {tag = #name("age"); _type = #nat}, + {tag = #name("name"); _type = #text}, + {tag = #name("verified"); _type = #bool} + ]), + #record([ + {tag = #name("age"); value = #nat(30)}, + {tag = #name("name"); value = #text("Alice")}, + {tag = #name("verified"); value = #bool(true)} + ])); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +// Test conversion of CandyShared to Candid for the #Array type. +test("should convert CandyShared #Array to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Array([#Nat(1), #Nat(2), #Nat(3)]); + let expectedArg = makeArg(#vector(#nat), + #vector([ + #nat(1), + #nat(2), + #nat(3) + ]) + ); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +// Test conversion of CandyShared to Candid for the #Bytes type. +test("should convert CandyShared #Bytes to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Bytes([0x01, 0x02, 0x03]); + let expectedArg = makeArg(#vector(#nat8), + #vector([ + #nat8(1:Nat8), + #nat8(2:Nat8), + #nat8(3:Nat8) + ]) + ); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + + +// Test conversion of CandyShared #Float to Arg with appropriate Candid type +test("should convert CandyShared #Float to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Float(42.42); + let expectedArg = makeArg(#float64, #float64(42.42)); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +// Test conversion of CandyShared #Int to Arg with appropriate Candid type +test("should convert CandyShared #Int to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Int(42); + let expectedArg = makeArg(#int, #int(42)); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +// Test conversion of CandyShared #Int8 to Arg with appropriate Candid type +test("should convert CandyShared #Int8 to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Int8(42); + let expectedArg = makeArg(#int8, #int8(42 : Int8)); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +// Test conversion of CandyShared #Int16 to Arg with appropriate Candid type +test("should convert CandyShared #Int16 to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Int16(42); + let expectedArg = makeArg(#int16, #int16(42 : Int16)); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +// Test conversion of CandyShared #Int32 to Arg with appropriate Candid type +test("should convert CandyShared #Int32 to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Int32(42); + let expectedArg = makeArg(#int32, #int32(42:Int32)); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +// Test conversion of CandyShared #Int64 to Arg with appropriate Candid type +test("should convert CandyShared #Int64 to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Int64(42); + let expectedArg = makeArg(#int64, #int64(42:Int64)); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +// Test conversion of CandyShared #Class with mixed types to Arg with appropriate Candid type +test("should convert CandyShared #Class with mixed types to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Class([ + {name = "age"; value = #Nat(30); immutable = true}, + {name = "name"; value = #Text("Alice"); immutable = true}, + {name = "verified"; value = #Bool(true); immutable = true}, + {name = "balance"; value = #Float(100.50); immutable = false} + ]); + let expectedArg = makeArg(#record([ + {tag = #name("age"); _type = #nat}, + {tag = #name("name"); _type = #text}, + {tag = #name("verified"); _type = #bool}, + {tag = #name("balance"); _type = #float64} + ]), + #record([ + {tag = #name("age"); value = #nat(30)}, + {tag = #name("name"); value = #text("Alice")}, + {tag = #name("verified"); value = #bool(true)}, + {tag = #name("balance"); value = #float64(100.50)} + ])); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); + +/* // Test conversion of CandyShared #Array with mixed types to Arg with appropriate Candid type +test("should convert CandyShared #Array with mixed types to Arg with appropriate Candid type", func() { + let candy: CandyShared = #Array([ + #Nat(1), + #Text("Alice"), + #Bool(true), + #Float(100.50) + ]); + let expectedArg = makeArg(#vector, + #vector([ + #nat(1 : Nat), + #text("Alice"), + #bool(true), + #float64(100.50 : Float) + ]) + ); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); */ + +/* // Test conversion of CandyShared #ValueMap with different value types to Arg with appropriate Candid type +test("should convert CandyShared #ValueMap with different value types to Arg with appropriate Candid type", func() { + let candy: CandyShared = #ValueMap([ + (#Nat(10), #Text("ten")), + (#Bool(false), #Float(200.25)) + ]); + let expectedArg = makeArg(#record([{_type = #any; tag = #hash(0)}]), + #record([ + {tag = #hash(0); value = #record([{tag = #hash(0); value = #nat(10)}, {tag = #hash(1); value = #text("ten")}])}, + {tag = #hash(1); value = #record([{tag = #hash(0); value = #bool(false)}, {tag = #hash(1); value = #float64(200.25)}])} + ]) + ); + let result = Candid.value_to_candid(candy); + assertArgEq(expectedArg, result[0]); +}); */ \ No newline at end of file diff --git a/test/clone.test.mo b/test/clone.test.mo new file mode 100644 index 0000000..0c0ceeb --- /dev/null +++ b/test/clone.test.mo @@ -0,0 +1,221 @@ +import D "mo:base/Debug"; +import {test} "mo:test"; +import Types "../src/types"; +import Clone "../src/clone"; +import Principal "mo:base/Principal"; +import Nat "mo:base/Nat"; +import Nat8 "mo:base/Nat8"; +import Nat16 "mo:base/Nat16"; +import Nat32 "mo:base/Nat32"; +import Nat64 "mo:base/Nat64"; +import Int "mo:base/Int"; +import Int8 "mo:base/Int8"; +import Int16 "mo:base/Int16"; +import Int32 "mo:base/Int32"; +import Int64 "mo:base/Int64"; +import Iter "mo:base/Iter"; +import Float "mo:base/Float"; +import Bool "mo:base/Bool"; +import Text "mo:base/Text"; +import Blob "mo:base/Blob"; +import Array "mo:base/Array"; +import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; +import Map "mo:map9/Map"; +import Set "mo:map9/Set"; + +type Candy = Types.Candy; +type CandyShared = Types.CandyShared; +type Property = Types.Property; + +// Helper function for deep equality check of Candy type +func deepEqualCandy(x : Candy, y : Candy) : Bool { + switch (x, y) { + case (#Int(a), #Int(b)) Int.equal(a, b); + case (#Int8(a), #Int8(b)) Int8.equal(a, b); + case (#Int16(a), #Int16(b)) Int16.equal(a, b); + case (#Int32(a), #Int32(b)) Int32.equal(a, b); + case (#Int64(a), #Int64(b)) Int64.equal(a, b); + case (#Nat(a), #Nat(b)) Nat.equal(a, b); + case (#Nat8(a), #Nat8(b)) Nat8.equal(a, b); + case (#Nat16(a), #Nat16(b)) Nat16.equal(a, b); + case (#Nat32(a), #Nat32(b)) Nat32.equal(a, b); + case (#Nat64(a), #Nat64(b)) Nat64.equal(a, b); + case (#Float(a), #Float(b)) Float.equal(a, b); + case (#Text(a), #Text(b)) Text.equal(a, b); + case (#Bool(a), #Bool(b)) a == b; + case (#Blob(a), #Blob(b)) Blob.equal(a, b); + case (#Principal(a), #Principal(b)) Principal.equal(a, b); + case (#Array(a), #Array(b)) { + if (StableBuffer.size(a) != StableBuffer.size(b)) { + return false; + }; + for (index in Iter.range(0, StableBuffer.size(a) - 1)) { + if (not (deepEqualCandy(StableBuffer.get(a, index), StableBuffer.get(b, index)))) { + return false; + }; + }; + true; + }; + case (#Class(a), #Class(b)) { + if (Map.size(a) != Map.size(b)) { + return false; + }; + for ((name, propA) in Map.entries(a)) { + switch (Map.get(b, Map.thash, name)) { + case (null) { return false; }; + case (?propB) { + if (not (propA.immutable == propB.immutable and deepEqualCandy(propA.value, propB.value))) { + return false; + }; + }; + }; + }; + true; + }; + case (#Set(a), #Set(b)) { + if (Set.size(a) != Set.size(b)) { + return false; + }; + for (elem in Set.keys(a)) { + if (not (Set.has(b, Types.candyMapHashTool, elem))) { + return false; + }; + }; + true; + }; + case (#Map(a), #Map(b)) { + if (Map.size(a) != Map.size(b)) { + return false; + }; + for ((key, valueA) in Map.entries(a)) { + switch (Map.get(b, Map.thash, key)) { + case (null) { return false; }; + case (?valueB) { + if (not(deepEqualCandy(valueA, valueB))) { + return false; + }; + }; + }; + }; + true; + }; + case (#ValueMap(a), #ValueMap(b)) { + if (Map.size(a) != Map.size(b)) { + return false; + }; + for ((keyA, valueA) in Map.entries(a)) { + var found = false; + for ((keyB, valueB) in Map.entries(b)) { + // Since keys can be any candy, we cannot use Map.get here and have to iterate + if (deepEqualCandy(keyA, keyB) and deepEqualCandy(valueA, valueB)) { + found := true; + }; + }; + if (not found) { + return false; + }; + }; + true; + }; + case (#Option(aOpt), #Option(bOpt)) { + switch (aOpt, bOpt) { + case (null, null) true; + case (?a, ?b) deepEqualCandy(a, b); + case (_, _) false; + } + }; + case (_, _) false; + }; +}; + +test("cloneCandy should deep clone #Int", func() { + let value: Candy = #Int(42); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); + +test("cloneCandy should deep clone #Int64", func() { + let value: Candy = #Int64(42); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); + +test("cloneCandy should deep clone #Nat literals", func() { + let value: Candy = #Nat(123); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); + +test("cloneCandy should deep clone #Float", func() { + let value: Candy = #Float(42.0); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); + +test("cloneCandy should deep clone #Text", func() { + let value: Candy = #Text("Hello, world!"); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); + +test("cloneCandy should deep clone #Blob", func() { + let value: Candy = #Blob(Blob.fromArray([1, 2])); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); + +test("cloneCandy should deep clone #Principal", func() { + let value: Candy = #Principal(Principal.fromText("2vxsx-fae")); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); + +test("cloneCandy should deep clone #Class", func() { + let prop1: Candy = #Int(42); + let prop2: Candy = #Text("Hello, world!"); + let value: Candy = #Class(Map.fromIter([ + ("int_prop", {name = "int_prop"; value = prop1; immutable = false}), + ("text_prop", {name = "text_prop"; value = prop2; immutable = false}) + ].vals(), Map.thash)); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); + +test("cloneCandy should deep clone #Array", func() { + let value: Candy = #Array(StableBuffer.fromArray([#Int(42), #Text("Hello, world!")])); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); + +test("cloneCandy should deep clone #Map", func() { + let value: Candy = #Map(Map.fromIter( + [("key1", #Int(42)), ("key2", #Text("Hello, world!"))].vals(), + Map.thash + )); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); + +test("cloneCandy should deep clone #ValueMap", func() { + let value: Candy = #ValueMap(Map.fromIter( + [(#Int(1), #Int(42)), (#Int(2), #Text("Hello, world!"))].vals(), + Types.candyMapHashTool + )); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); + +test("cloneCandy should deep clone #Set", func() { + let value: Candy = #Set(Set.fromIter( + [#Int(42), #Text("Hello, world!")].vals(), + Types.candyMapHashTool + )); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); + +test("cloneCandy should deep clone #Option", func() { + let value: Candy = #Option(?#Int(42)); + let cloned = Clone.cloneCandy(value); + assert(deepEqualCandy(value, cloned)); +}); \ No newline at end of file diff --git a/test/json.test.mo b/test/json.test.mo new file mode 100644 index 0000000..61461cf --- /dev/null +++ b/test/json.test.mo @@ -0,0 +1,162 @@ +import D "mo:base/Debug"; +import {test} "mo:test"; +import Json "../src/json"; +import Types "../src/types"; +import Nat "mo:base/Nat"; +import Nat8 "mo:base/Nat8"; +import Text "mo:base/Text"; +import Principal "mo:base/Principal"; + +type CandyShared = Types.CandyShared; + +test("value_to_json should convert Nat to JSON", func() { + let value: CandyShared = #Nat(123); + let json = Json.value_to_json(value); + assert(json == "123"); +}); + +test("value_to_json should convert Nat8 to JSON", func() { + let value: CandyShared = #Nat8(45); + let json = Json.value_to_json(value); + assert(json == "45"); +}); + +test("value_to_json should convert Nat16 to JSON", func() { + let value: CandyShared = #Nat16(400); + let json = Json.value_to_json(value); + assert(json == "400"); +}); + +test("value_to_json should convert Nat32 to JSON", func() { + let value: CandyShared = #Nat32(50000); + let json = Json.value_to_json(value); + assert(json == "50000"); +}); + +test("value_to_json should convert Nat64 to JSON", func() { + let value: CandyShared = #Nat64(70000000); + let json = Json.value_to_json(value); + assert(json == "70000000"); +}); + +test("value_to_json should convert Int to JSON", func() { + let value: CandyShared = #Int(-123); + let json = Json.value_to_json(value); + assert(json == "-123"); +}); + +test("value_to_json should convert Int8 to JSON", func() { + let value: CandyShared = #Int8(-45); + let json = Json.value_to_json(value); + assert(json == "-45"); +}); + +test("value_to_json should convert Int16 to JSON", func() { + let value: CandyShared = #Int16(-400); + let json = Json.value_to_json(value); + assert(json == "-400"); +}); + +test("value_to_json should convert Int32 to JSON", func() { + let value: CandyShared = #Int32(-50000); + let json = Json.value_to_json(value); + assert(json == "-50000"); +}); + +test("value_to_json should convert Int64 to JSON", func() { + let value: CandyShared = #Int64(-70000000); + let json = Json.value_to_json(value); + assert(json == "-70000000"); +}); + +test("value_to_json should convert Float to JSON", func() { + let value: CandyShared = #Float(3.1415); + let json = Json.value_to_json(value); + D.print(debug_show(json)); + assert(Text.startsWith(json, #text("3.1415"))); // Handle float precision +}); + +test("value_to_json should convert Text to JSON", func() { + let value: CandyShared = #Text("Hello, World!"); + let json = Json.value_to_json(value); + assert(json == "\"Hello, World!\""); +}); + +test("value_to_json should convert Bool to JSON", func() { + let value: CandyShared = #Bool(true); + let json = Json.value_to_json(value); + assert(json == "\"true\""); +}); + +test("value_to_json should convert Blob to JSON", func() { + let value: CandyShared = #Blob(Text.encodeUtf8("Blob")); + let json = Json.value_to_json(value); + D.print(debug_show(json)); + assert(json == "\"426c6f62\""); +}); + +test("value_to_json should convert Principal to JSON", func() { + let principalValue = Principal.fromText("rrkah-fqaaa-aaaaa-aaaaq-cai"); + let value: CandyShared = #Principal(principalValue); + let json = Json.value_to_json(value); + assert(json == "\"" # Principal.toText(principalValue) # "\""); +}); + +test("value_to_json should convert Array to JSON", func() { + let value: CandyShared = #Array([#Nat(1), #Nat(2), #Nat(3)]); + let json = Json.value_to_json(value); + assert(json == "[1,2,3]"); +}); + +test("value_to_json should convert empty Array to JSON", func() { + let value: CandyShared = #Array([]); + let json = Json.value_to_json(value); + assert(json == "[]"); +}); + +test("value_to_json should convert Class to JSON", func() { + let value: CandyShared = #Class([ + { + name = "age"; + value = #Nat(30); + immutable = true; + }, + { + name = "name"; + value = #Text("John Doe"); + immutable = true; + } + ]); + let json = Json.value_to_json(value); + assert(json == "{\"age\":30,\"name\":\"John Doe\"}"); +}); + +test("value_to_json should convert Nats to JSON", func() { + let value: CandyShared = #Nats([Nat8.toNat(1), Nat8.toNat(2), Nat8.toNat(3)]); + let json = Json.value_to_json(value); + assert(json == "[1,2,3]"); +}); + +test("value_to_json should convert Floats to JSON", func() { + let value: CandyShared = #Floats([1.1, 2.2, 3.3]); + let json = Json.value_to_json(value); + // Note: Adjust the test for potential loss of precision in the float conversion + D.print(debug_show(json)); + assert(Text.startsWith(json, #text("[1.10000"))); + assert(Text.contains(json, #text(",2"))); + assert(Text.contains(json, #text(",3"))); +}); + +test("value_to_json should convert Option to JSON", func() { + let value: CandyShared = #Option(?#Bool(true)); + let json = Json.value_to_json(value); + assert(json == "\"true\""); +}); + +test("value_to_json should convert none Option to JSON", func() { + let value: CandyShared = #Option(null); + let json = Json.value_to_json(value); + assert(json == "null"); +}); + +// Additional tests for Map, Set, ValueMap, and any other types can follow the pattern above. \ No newline at end of file diff --git a/test/properties.test.mo b/test/properties.test.mo new file mode 100644 index 0000000..f0fcd09 --- /dev/null +++ b/test/properties.test.mo @@ -0,0 +1,190 @@ +import D "mo:base/Debug"; +import {test} "mo:test"; +import Properties "../src/properties"; +import Types "../src/types"; +import Principal "mo:base/Principal"; +import Nat "mo:base/Nat"; +import Text "mo:base/Text"; + +type Property = Types.Property; +type UpdateShared = Types.UpdateShared; +type Query = Types.Query; + +type CandyShared = Types.CandyShared; + +// Shared Test Instances +let principal = Principal.fromText("rrkah-fqaaa-aaaaa-aaaaq-cai"); +let natValue: Nat = 42; + +// Utility function to create properties +func createPropertiesShared() : Types.PropertiesShared { + return [ + {name = "prop1"; value = #Nat(natValue); immutable = true}, + {name = "prop2"; value = #Principal(principal); immutable = false}, + {name = "prop3"; value = #Class([ + {name = "subclass_prop1"; value = #Nat(natValue); immutable = false} + ]); immutable = false} + ]; +}; + + + // Test getPropertiesShared + test("getPropertiesShared should return specified shared properties", func() { + let properties = createPropertiesShared(); + let queries: [Query] = [{name="prop1"; next=[];}]; + let result = Properties.getPropertiesShared(properties, queries); + switch(result) { + case(#ok(res)) { + assert(res.size() == 1); + assert(res[0].name == "prop1"); + }; + case(_) { assert(false); }; + }; + }); + + // Test getPropertiesShared with nested queries + test("getPropertiesShared should return nested shared properties", func() { + let properties = createPropertiesShared(); + let queries: [Query] = [{name = "prop3"; next = [{name = "subclass_prop1"; next=[]}]}]; + let result = Properties.getPropertiesShared(properties, queries); + switch(result) { + case(#ok(res)) { + assert(res.size() == 1); + switch(res[0].value) { + case (#Class(props)) { assert(props.size() == 1); assert(props[0].name == "subclass_prop1"); }; + case (_) { assert(false); }; + }; + }; + case(_) { assert(false); }; + }; + }); + + // Test updatePropertiesShared + test("updatePropertiesShared should update mutable properties", func() { + let properties = createPropertiesShared(); + let updates: [UpdateShared] = [{name = "prop2"; mode = #Set(#Nat(999))}]; + let result = Properties.updatePropertiesShared(properties, updates); + switch(result) { + case(#ok(res)) { + assert(res.size() == 3); + assert((res[1]).value == #Nat(999)); + }; + case(_) { assert(false); }; + }; + }); + + // Test updatePropertiesShared on immutable property + test("updatePropertiesShared should not update immutable properties", func() { + let properties = createPropertiesShared(); + let updates: [UpdateShared] = [{name = "prop1"; mode = #Set(#Nat(999))}]; + let result = Properties.updatePropertiesShared(properties, updates); + switch(result) { + case(#err(err)) { + switch(err) { + case(#Immutable) { assert(true); }; + case(_) { assert(false); }; + }; + }; + case(_) { assert(false); }; + }; + }); + + // Test updatePropertiesShared with non-existent property + test("updatePropertiesShared should add non-existent properties", func() { + let properties = createPropertiesShared(); + let updates: [UpdateShared] = [{name = "non_existent"; mode = #Set(#Nat(999))}]; + let result = Properties.updatePropertiesShared(properties, updates); + switch(result) { + case(#err(err)) { + switch(err) { + case(#NotFound) { assert(false); }; + case(_) { assert(false); }; + }; + }; + case(#ok(val)) { + switch(val[3].value){ + case(#Nat(val)){ + assert(val == 999); + }; + case(_) return assert(false); + };}; + }; + }); + + let nestedProperty: Types.PropertyShared = { + name = "nestedProp"; + value = #Class([ // Nested class inside the property + { + name = "subProp1"; + value = #Nat(123); + immutable = true + } + ]); + immutable = false +}; + +// Test getProperties uses the same `properties` setup as getPropertiesShared, so omitted for brevity +// ... + +// Test getClassPropertyShared +test("getClassPropertyShared should return a specific property from a class if available", func() { + let classValue: CandyShared = #Class([nestedProperty]); + let propertyName = "nestedProp"; + let property = Properties.getClassPropertyShared(classValue, propertyName); + switch (property) { + case (null) { assert(false); }; + case (?p) { + assert(p.name == propertyName); + assert(p.immutable == false); + }; + }; +}); + +// Test updateProperties +test("updateProperties should update a mutable property within a Candy class", func() { + var properties = createPropertiesShared(); + let mutablePropName = "prop2"; + let newMutableValue: CandyShared = #Nat(100); + let updates: [Types.UpdateShared] = [{name = mutablePropName; mode = #Set(newMutableValue)}]; + let result = Properties.updatePropertiesShared(properties, updates); + switch (result) { + case (#ok(updatedProperties)) { + switch(Properties.getClassPropertyShared(#Class(updatedProperties), mutablePropName)){ + case(?value){ + assert(value.value == newMutableValue); + }; + case(null){ + return assert(false); + }; + } + + }; + case (#err(_)) { + assert(false); + }; + }; +}); + +// Test updating an immutable property +test("updateProperties should not update an immutable property", func() { + let properties = createPropertiesShared(); + let immutablePropertyName = "prop1"; + let updates: [UpdateShared] = [{name = immutablePropertyName; mode = #Set(#Nat(999))}]; + let result = Properties.updatePropertiesShared(properties, updates); + switch (result) { + case (#ok(_)) { + assert(false); // Should not occur + }; + case (#err(e)) { + switch (e) { + case (#Immutable) { + assert(true); // Correct error + }; + case (_) { + assert(false); // Incorrect error type + }; + }; + }; + }; +}); + diff --git a/test/types.test.mo b/test/types.test.mo new file mode 100644 index 0000000..e9eeef6 --- /dev/null +++ b/test/types.test.mo @@ -0,0 +1,175 @@ +import D "mo:base/Debug"; +import {test} "mo:test"; +import Types "../src/types"; +import Conv "../src/conversion"; +import Principal "mo:base/Principal"; +import Nat "mo:base/Nat"; +import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; +import Array "mo:base/Array"; +import Nat8 "mo:base/Nat8"; + +type Candy = Types.Candy; +type CandyShared = Types.CandyShared; + +// Test Instances +let principal = Principal.fromText("rrkah-fqaaa-aaaaa-aaaaq-cai"); +let natValue: Nat = 42; +let arrayValue: [Candy] = [#Nat(natValue)]; +let bufferValue: StableBuffer.StableBuffer = StableBuffer.fromArray([natValue]); + +// Test `Types` Module +test("shareCandy should convert Candy to CandyShared preserving the structure", func() { + let original: Candy = #Principal(principal); + let shared_: CandyShared = Types.shareCandy(original); + switch(shared_) { + case (#Principal(p)) { + assert(p == principal); + }; + case (_) { + return assert(false); + }; + }; +}); + +test("unshare should convert CandyShared to Candy preserving the structure", func() { + let shared_: CandyShared = #Principal(principal); + let original: Candy = Types.unshare(shared_); + switch(original) { + case (#Principal(p)) { + assert(p == principal); + }; + case (_) { + return assert(false); + }; + }; +}); + +test("shareProperty should convert Property to PropertyShared preserving the structure", func() { + let property: Types.Property = {name = "testProp"; value = #Nat(natValue); immutable = true}; + let sharedProperty: Types.PropertyShared = Types.shareProperty(property); + assert(sharedProperty.name ==property.name); + assert(sharedProperty.immutable == property.immutable); + switch(sharedProperty.value) { + case (#Nat(n)) { + assert(n == natValue); + }; + case (_) { + return assert(false); + }; + }; +}); + +test("unshareProperty should convert PropertyShared to Property preserving the structure", func() { + let sharedProperty: Types.PropertyShared = {name = "testProp"; value = #Nat(natValue); immutable = true}; + let property: Types.Property = Types.unshareProperty(sharedProperty); + assert(property.name ==sharedProperty.name); + assert(property.immutable ==sharedProperty.immutable); + switch(property.value) { + case (#Nat(n)) { + assert(n == natValue); + }; + case (_) { + return assert(false); + }; + }; +}); + +test("shareCandyArray should convert list of Candy to list of CandyShared", func() { + let sharedArray: [CandyShared] = Types.shareCandyArray(arrayValue); + for (value in Array.vals(sharedArray)) { + switch(value) { + case (#Nat(n)) { + assert(n == natValue); + }; + case (_) { + return assert(false); + }; + }; + }; +}); + +test("unshareArray should convert list of CandyShared to list of Candy", func() { + let sharedArray: [CandyShared] = Types.shareCandyArray(arrayValue); + let originalArray: [Candy] = Types.unshareArray(sharedArray); + for (value in Array.vals(originalArray)) { + switch(value) { + case (#Nat(n)) { + assert(n == natValue); + }; + case (_) { + return assert(false); + }; + }; + }; +}); + +test("shareCandyBuffer should convert a DataZone to an array of CandyShared", func() { + let dataZone: Types.DataZone = StableBuffer.fromArray(arrayValue); + let sharedBuffer: [CandyShared] = Types.shareCandyBuffer(dataZone); + for (value in sharedBuffer.vals()) { + switch(value) { + case (#Nat(n)) { + assert(n == natValue); + }; + case (_) { + return assert(false); + }; + }; + }; +}); + +test("toBuffer should convert an array to a stable buffer of the same elements", func() { + let buffer: StableBuffer.StableBuffer = Types.toBuffer([natValue]); + assert(StableBuffer.get(buffer, 0) == natValue); +}); + +test("hash function should produce consistent hash codes for Candy", func() { + let original: Candy = #Nat(natValue); + let hashCode1: Nat32 = Types.hash(original); + let hashCode2: Nat32 = Types.hash(original); + assert(hashCode1 == hashCode2); +}); + +test("nat32ToBytes should convert Nat32 to a byte array representation", func() { + let value = 42 : Nat32; + let bytes = Conv.nat32ToBytes(value); + let expectedBytes: [Nat8] = [0, 0, 0, 42]; + assert(Array.equal(bytes, expectedBytes, Nat8.equal)); +}); + +test("eq function should compare two Candy for equality", func() { + let candy1: Candy = #Nat(natValue); + let candy2: Candy = #Nat(natValue); + let unequalCandy: Candy = #Nat(natValue + 1); + assert(Types.eq(candy1, candy2) == true); + assert(Types.eq(candy1, unequalCandy) == false); +}); + +test("candyMapHashTool should provide hash and equality functions for Candy", func() { + let candy: Candy = #Nat(natValue); + let hashCode: Nat32 = Types.candyMapHashTool.0(candy); + let equality: Bool = Types.candyMapHashTool.1(candy, candy); + assert(equality == true); +}); + +test("hashShared function should produce consistent hash codes for CandyShared", func() { + let shared_: CandyShared = #Nat(natValue); + let hashCode1: Nat32 = Types.hashShared(shared_); + let hashCode2: Nat32 = Types.hashShared(shared_); + assert(hashCode1 == hashCode2); +}); + +test("eqShared function should compare two CandyShared for equality", func() { + let shared1: CandyShared = #Nat(natValue); + let shared2: CandyShared = #Nat(natValue); + let unequalShared: CandyShared = #Nat(natValue + 1); + assert(Types.eqShared(shared1, shared2) == true); + assert(Types.eqShared(shared1, unequalShared) == false); +}); + +test("candySharedMapHashTool should provide hash and equality functions for CandyShared", func() { + let shared_: CandyShared = #Nat(natValue); + let hashCode: Nat32 = Types.candySharedMapHashTool.0(shared_); + let equality: Bool = Types.candySharedMapHashTool.1(shared_, shared_); + assert(equality == true); +}); \ No newline at end of file diff --git a/test/upgrade.test.mo b/test/upgrade.test.mo new file mode 100644 index 0000000..b505cab --- /dev/null +++ b/test/upgrade.test.mo @@ -0,0 +1,119 @@ +import D "mo:base/Debug"; +import {test} "mo:test"; +import Types "../src/types"; +import Upgrade "../src/upgrade"; +import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; +import Nat8 "mo:base/Nat8"; +import Principal "mo:base/Principal"; +import Array "mo:base/Array"; +import Blob "mo:base/Blob"; +import CandyOld "mo:candy_0_1_12/types"; +import Candy0_2_0 "mo:candy_0_2_0/types"; + +type CandyV1 = CandyOld.CandyValueUnstable; +type CandySharedV1 = CandyOld.CandyValue; +type CandyV2 = Candy0_2_0.Candy; +type CandySharedV2 = Candy0_2_0.CandyShared; +type CandyV3 = Types.Candy; +type CandySharedV3 = Types.CandyShared; + +let testPrincipalV1 = Principal.fromText("rrkah-fqaaa-aaaaa-aaaaq-cai"); +let testPrincipalV2 = testPrincipalV1; +let testPrincipalV3 = testPrincipalV1; + +// Sample data for various types +let testInt: Int = 1; +let testNat: Nat = 42; +let testBool: Bool = true; +let testText: Text = "test"; +let testFloat: Float = 3.14; +let testBlob: Blob = Blob.fromArray([0, 1, 2, 3]); +let testArrayV1: [CandyV1] = [#Int(testInt)]; // V1 only supports an array of CandyV1 +let testArrayV2: [CandySharedV2] = Array.tabulate(3, func(_){#Int(testInt)}); // V2 supports long array of CandyV2 +// Helpers for constructing V3 test values +let testSharedArrayV3: [CandySharedV3] = [#Int(testInt), #Text(testText), #Bool(testBool)]; + + +// Begin Test Suite + +// Test Cases for Upgrade from V1 representation of Candy to V2 +test("upgradeCandy0_1_2_to_0_2_0 - Int", func() { + let originalCandyV1: CandyV1 = #Int(testInt); + let upgradedCandyV2: CandyV2 = Upgrade.upgradeCandy0_1_2_to_0_2_0(originalCandyV1); + let #Int(val) = upgradedCandyV2; + assert(val == testInt); +}); + +// Add more test cases to cover all Candy types for upgrade function `upgradeCandy0_1_2_to_0_2_0` +// ... + +// Test Cases for Upgrade from V1 representation of CandyShared to V2 representations CandyShared +test("upgradeCandyShared0_1_2_to_0_2_0 - Bool", func() { + let originalCandySharedV1: CandySharedV1 = #Bool(testBool); + let upgradedCandySharedV2: CandySharedV2 = Upgrade.upgradeCandyShared0_1_2_to_0_2_0(originalCandySharedV1); + assert(upgradedCandySharedV2 == #Bool(testBool)); +}); + +// Add more test cases to cover all CandyShared types for upgrade function `upgradeCandyShared0_1_2_to_0_2_0` +// ... + +// Test Cases for Upgrade from V2 representation of Candy to V3 +test("upgradeCandy0_2_0_to_0_3_0 - Text", func() { + let originalCandyV2: CandyV2 = #Text(testText); + let upgradedCandyV3: CandyV3 = Upgrade.upgradeCandy0_2_0_to_0_3_0(originalCandyV2); + let #Text(val) = upgradedCandyV3; + assert(upgradedCandyV3 == testText); +}); + +test("upgradeCandy0_1_2_to_0_2_0 - Nat", func() { + let originalCandyV1: CandyV1 = #Nat(testNat); + let upgradedCandyV2: CandyV2 = Upgrade.upgradeCandy0_1_2_to_0_2_0(originalCandyV1); + let #Nat(val) = upgradedCandyV2; + assert(val == testNat); +}); + +test("upgradeCandy0_1_2_to_0_2_0 - Bool", func() { + let originalCandyV1: CandyV1 = #Bool(testBool); + let upgradedCandyV2: CandyV2 = Upgrade.upgradeCandy0_1_2_to_0_2_0(originalCandyV1); + let #Bool(val) = upgradedCandyV2; + assert(val == testBool); +}); + +test("upgradeCandy0_1_2_to_0_2_0 - Float", func() { + let originalCandyV1: CandyV1 = #Float(testFloat); + let upgradedCandyV2: CandyV2 = Upgrade.upgradeCandy0_1_2_to_0_2_0(originalCandyV1); + let #Float(val) = upgradedCandyV2; + assert(val == testFloat); +}); + +test("upgradeCandy0_1_2_to_0_2_0 - Principal", func() { + let originalCandyV1: CandyV1 = #Principal(testPrincipalV1); + let upgradedCandyV2: CandyV2 = Upgrade.upgradeCandy0_1_2_to_0_2_0(originalCandyV1); + let #Principal(val) = upgradedCandyV2; + assert(val == testPrincipalV2); +}); + +test("upgradeCandy0_1_2_to_0_2_0 - Null", func() { + let originalCandyV1: CandyV1 = #Empty; + let upgradedCandyV2: CandyV2 = Upgrade.upgradeCandy0_1_2_to_0_2_0(originalCandyV1); + let #Option(val) = upgradedCandyV2; + switch(val){ + case(null){assert(true)}; + case(_) assert(false); + }; +}); + + +// Test Cases for Upgrade from V2 representation of CandyShared to V3 representations CandyShared +test("upgradeCandyShared0_2_0_to_0_3_0 - Array", func() { + let originalCandySharedV2: CandySharedV2 = #Array(testArrayV2); + let upgradedCandySharedV3: CandySharedV3 = Upgrade.upgradeCandyShared0_2_0_to_0_3_0(originalCandySharedV2); + D.print(debug_show(upgradedCandySharedV3, testSharedArrayV3)); + assert(Types.eqShared(upgradedCandySharedV3, #Array([#Int(1), #Int(1), #Int(1)]))); // Convert upgraded array to V3 format +}); + +// Add more test cases to cover all CandyShared types for upgrade function `upgradeCandyShared0_2_0_to_0_3_0` +// ... + +// End Test Suite + diff --git a/test/workspaces.test.mo b/test/workspaces.test.mo new file mode 100644 index 0000000..10de545 --- /dev/null +++ b/test/workspaces.test.mo @@ -0,0 +1,217 @@ +// Assuming workspace.mo contains the Workspace module functions. +import Workspace "../src/workspace"; +import Types "../src/types"; +import {test} "mo:test"; +import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; +import Array "mo:base/Array"; +import Iter "mo:base/Iter"; +import Buffer "mo:base/Buffer"; +import Nat8 "mo:base/Nat8"; +import Blob "mo:base/Blob"; +import D "mo:base/Debug"; + +type Workspace = Types.Workspace; +type DataZone = Types.DataZone; +type Candy = Types.Candy; +type CandyShared = Types.CandyShared; +type AddressedChunkArray = Types.AddressedChunkArray; +type DataChunk = Types.DataChunk; + +// Helper function to create a data chunk with an array of Nats up to the size specified +func buildDataChunk(size: Nat) : DataChunk { + #Array(StableBuffer.fromArray(Array.tabulate(size, func (i :Nat): Candy{#Nat(i)}))) +}; + +func buildDataZone(size: Nat) : DataZone { + StableBuffer.fromArray(Array.tabulate(size, func (i :Nat): Candy{#Nat(i)})) +}; + +// Testing countAddressedChunksInWorkspace +test("countAddressedChunksInWorkspace should correctly count chunks in workspace", func() { + let testDataChunkSizes = [1, 3, 5, 2]; // Array size represents chunk setup + let ws: Workspace = Workspace.initWorkspace(testDataChunkSizes.size()); + for (size in testDataChunkSizes.vals()) { + StableBuffer.add(ws, buildDataZone(size)); // Using helper to build chunks + }; + assert(Workspace.countAddressedChunksInWorkspace(ws) == 11); +}); + +// Testing emptyWorkspace +test("emptyWorkspace should create an empty workspace", func() { + let ws = Workspace.emptyWorkspace(); + assert(StableBuffer.size(ws) == 0); +}); + +// Testing initWorkspace +test("initWorkspace should initialize a workspace with a given capacity", func() { + let ws = Workspace.initWorkspace(5); + D.print(debug_show(StableBuffer.size(ws))); + assert(StableBuffer.size(ws) == 0); //init is only presized +}); + +// Testing getCandySize +test("getCandySize should return correct byte size of a Candy", func() { + let candyInt: Candy = #Int(255); + assert(Workspace.getCandySize(candyInt) == 2); // Test for a simple Int Candy, use others as needed +}); + +// Testing getCandySharedSize +test("getCandySharedSize should return correct byte size of a CandyShared", func() { + let candySharedInt: CandyShared = #Int(255); + D.print(debug_show(Workspace.getCandySharedSize(candySharedInt))); + assert(Workspace.getCandySharedSize(candySharedInt) == 4); // Similar to getCandySize but with CandyShared +}); + +// Testing workspaceToAddressedChunkArray +test("workspaceToAddressedChunkArray should convert workspace to addressed chunk array", func() { + let ws: Workspace = Workspace.initWorkspace(2); + StableBuffer.add(ws, buildDataZone(1)); + StableBuffer.add(ws, buildDataZone(1)); + let addressedChunks = Workspace.workspaceToAddressedChunkArray(ws); + assert(addressedChunks.size() == 2); + // Test individual chunks and positions accurately +}); + +// Testing workspaceDeepClone +test("workspaceDeepClone should produce an identical but independent copy of workspace", func() { + let ws: Workspace = Workspace.initWorkspace(3); + StableBuffer.add(ws, buildDataZone(1)); + StableBuffer.add(ws, buildDataZone(2)); + let wsClone = Workspace.workspaceDeepClone(ws); + // Assert deep equality of workspaces + assert(StableBuffer.size(ws) == StableBuffer.size(wsClone)); + // Assert that modifying one does not change the other + let updatedChunk = buildDataZone(5); + StableBuffer.put(wsClone, 1, updatedChunk); + assert(StableBuffer.size(StableBuffer.get(ws, 1)) != StableBuffer.size(StableBuffer.get(wsClone, 1))); +}); + +// Testing fromAddressedChunks + + +test("fromAddressedChunks should create a new workspace from an addressed chunk array", func() { + let addressedChunks: AddressedChunkArray = [(0, 0, #Int(42)), (1, 0, #Int(43))]; + let ws = Workspace.fromAddressedChunks(addressedChunks); + assert(StableBuffer.size(ws) == 2); + // Assert that chunks are correctly configured in the new workspace + assert(Types.eq(StableBuffer.get(StableBuffer.get(ws, 0),0),#Int(42))); + assert(Types.eq(StableBuffer.get(StableBuffer.get(ws, 1),0), #Int(43))); +}); + +// Testing getDataZoneSize +test("getDataZoneSize should return size of DataZone in bytes", func() { + let dz = buildDataZone(356); + let size = Workspace.getDataZoneSize(dz); + D.print(debug_show(size)); + assert(size > 0); + let expectedSize: Nat = 456; // 1 byte for the variant tag, plus size of Nat + assert(size == expectedSize); +}); + +// Testing getWorkspaceChunkSize +test("getWorkspaceChunkSize should return the number of chunks after partitioning", func() { + let ws: Workspace = Workspace.initWorkspace(3); + for (x in Iter.range(0, 2)) { // Initialize with 3 chunks + StableBuffer.add(ws, buildDataZone(64)); // Assuming max chunk size is smaller than 64 + }; + let chunkSize = Workspace.getWorkspaceChunkSize(ws, 32); // Assuming max chunk size is 32 + assert(chunkSize > 3); +}); + +// Testing getWorkspaceChunk +test("getWorkspaceChunk should return a specific chunk data", func() { + let ws: Workspace = Workspace.initWorkspace(1); + StableBuffer.add(ws, buildDataZone(4)); + let chunk = Workspace.getWorkspaceChunk(ws, 0, 32); // Assuming max chunk size is 32 and we want the first chunk + // Verify chunk content +}); + +// Testing getAddressedChunkArraySize +test("getAddressedChunkArraySize should return accurate size of the addressed chunks", func() { + + let addressedChunks: AddressedChunkArray = [(0, 0, #Int(42)), (1, 0, #Nat(15))]; + let size = Workspace.getAddressedChunkArraySize(addressedChunks); + D.print(debug_show(size)); + let expectedSize: Nat = 23; // Assuming each zone chunk is one Nat size plus tag + assert(size == expectedSize); +}); + +// Testing getDataChunkFromAddressedChunkArray +test("getDataChunkFromAddressedChunkArray should return correct chunk data", func() { + let addressedChunks: AddressedChunkArray = [(0, 0, #Int(42)), (1, 1, #Nat(15))]; + let dataChunk = Workspace.getDataChunkFromAddressedChunkArray(addressedChunks, 1, 1); + // Verify returned `dataChunk` is #Nat(15) from the addressedChunks setup +}); + +// Testing byteBufferDataZoneToBuffer +test("byteBufferDataZoneToBuffer should convert data zone to buffer of bytes buffers", func() { + let dz = buildDataZone(2); // Build your DataZone with the helper function + let byteBuffer = Workspace.byteBufferDataZoneToBuffer(dz); + // Verify that the byteBuffer contains expected byte arrays +}); + +// Testing byteBufferChunksToCandyBufferDataZone +test("byteBufferChunksToCandyBufferDataZone should convert buffer of bytes buffers to data zone", func() { + let byteBuffer = Buffer.Buffer(2); + byteBuffer.add( Nat8.fromNat(42)); + byteBuffer.add( Nat8.fromNat(43)); + let byteBuffers = Buffer.Buffer>(1); + byteBuffers.add(byteBuffer); + let dz = Workspace.byteBufferChunksToCandyBufferDataZone(byteBuffers); + // Verify that the DataZone 'dz' correctly contains the converted data +}); + +// Testing initDataZone +test("initDataZone should create a DataZone with initial value", func() { + let dz = Workspace.initDataZone(#Nat(42)); + assert(StableBuffer.size(dz) == 1); + // Test initialized DataZone has the expected content +}); + +// Testing flattenAddressedChunkArray +test("flattenAddressedChunkArray should return a flattened byte array for AddressedChunkArray", func() { + let addressedChunks: AddressedChunkArray = [(0, 0, #Int(42))]; + let flattenedBytes = Workspace.flattenAddressedChunkArray(addressedChunks); + assert(Blob.fromArray(flattenedBytes).size() > 0); // Replace with expected array size + // Test that bytes are flattened as expected +}); + +// Testing workspaceToAddressedChunkArray +test("workspaceToAddressedChunkArray should convert workspace to addressed chunk array", func() { + let ws: Workspace = Workspace.initWorkspace(3); + StableBuffer.add(ws, buildDataZone(1)); + StableBuffer.add(ws, buildDataZone(2)); + StableBuffer.add(ws, buildDataZone(3)); + let addressedChunks = Workspace.workspaceToAddressedChunkArray(ws); + assert(addressedChunks.size() == 6); + // Test individual chunks and positions accurately + assert(addressedChunks[0].0 == 0); + assert(addressedChunks[0].1 == 0); + assert(Types.eqShared(addressedChunks[0].2, #Nat(0))); + + assert(addressedChunks[1].0 == 1); + assert(addressedChunks[1].1 == 0); + assert(Types.eqShared(addressedChunks[1].2, #Nat(0))); + + assert(addressedChunks[2].0 == 1); + assert(addressedChunks[2].1 == 1); + assert(Types.eqShared(addressedChunks[02].2, #Nat(1))); + + assert(addressedChunks[3].0 == 2); + assert(addressedChunks[3].1 == 0); + assert(Types.eqShared(addressedChunks[3].2, #Nat(0))); + + assert(addressedChunks[4].0 == 2); + assert(addressedChunks[4].1 == 1); + assert(Types.eqShared(addressedChunks[4].2, #Nat(1))); + + assert(addressedChunks[5].0 == 2); + assert(addressedChunks[5].1 == 2); + assert(Types.eqShared(addressedChunks[5].2, #Nat(2))); + + + +}); + + + From 2ef1a5d8ed1d69e3e00b618288c8417e56ae7259 Mon Sep 17 00:00:00 2001 From: Austin Fatheree Date: Fri, 17 Nov 2023 13:01:40 -0600 Subject: [PATCH 7/9] Adding Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ac0355d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +v0.3.0 + +- Added the ValueShared type for dumping a Candy and CandyShared to an ICRC3 style Value type. +- Added many tests. \ No newline at end of file From bbf1102e79618ab4465472f98a6dce2ebf1e7819 Mon Sep 17 00:00:00 2001 From: Austin Fatheree Date: Fri, 17 Nov 2023 13:04:45 -0600 Subject: [PATCH 8/9] bumping yaml --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f5dbc40..22b5cb4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,7 +22,7 @@ jobs: node-version: 14 - uses: aviate-labs/setup-dfx@v0.2.3 with: - dfx-version: 0.13.1 + dfx-version: 0.15.1 - name: "install Motoko binaries" run: | From 24197f1e76143aba0fecf6a5cdbf9d73a085ba95 Mon Sep 17 00:00:00 2001 From: Austin Fatheree Date: Fri, 17 Nov 2023 13:08:05 -0600 Subject: [PATCH 9/9] update yaml --- .github/workflows/ci.yaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 22b5cb4..45f98b3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -31,19 +31,12 @@ jobs: tar -xzf motoko-linux64-${{ env.moc_version }}.tar.gz -C /home/runner/bin echo "/home/runner/bin" >> $GITHUB_PATH - - name: "install vessel" - run: | - wget --output-document /home/runner/bin/vessel https://github.com/kritzcreek/vessel/releases/download/${{ env.vessel_version }}/vessel-linux64 - chmod +x /home/runner/bin/vessel - name: "install mops" run: | npm --yes -g i ic-mops mops i - - name: "check" - run: make check - - name: "check-mops" run: make check-mops