From ec8333e538672719f19bf067823320e23baa5819 Mon Sep 17 00:00:00 2001 From: skilesare Date: Sat, 5 Mar 2022 15:38:52 -0600 Subject: [PATCH 1/2] Refactoring --- README.md | 5 + dfx.json | 23 + package-set.dhall | 25 + src/clone.mo | 50 ++ src/{lib.mo => conversion.mo} | 1248 ++------------------------------- src/properties.mo | 346 +++++++++ src/types.mo | 354 ++++++++++ src/workspace.mo | 545 ++++++++++++++ test_runner.sh | 22 + tests/test_runner.mo | 58 ++ vessel.dhall | 4 + 11 files changed, 1471 insertions(+), 1209 deletions(-) create mode 100644 dfx.json create mode 100644 package-set.dhall create mode 100644 src/clone.mo rename src/{lib.mo => conversion.mo} (55%) create mode 100644 src/properties.mo create mode 100644 src/types.mo create mode 100644 src/workspace.mo create mode 100644 test_runner.sh create mode 100644 tests/test_runner.mo create mode 100644 vessel.dhall diff --git a/README.md b/README.md index d2ea3d3..5970137 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,8 @@ Todo: Tests, examples Note: This project was a part of the [ARAMAKME expirament](https://hwqwz-ryaaa-aaaai-aasoa-cai.raw.ic0.app/) and an example of how to integrate an ARAMAKME license into a library can be found in the /Example_Aramakme_License folder. The ARAMAKME license has since been removed from this library and it is licensed under the MIT License. Until they are all distributed, the ARAMAKME NFTs are still for sale as a nastalgoic piece of memorobilia, and all profits will be locked into an 8 year neuron benifiting [ICDevs.org](https://icdevs.org) who are sheparding this library as it moves forward and improves. + +## Testing + + printf "yes" | bash test_runner.sh + diff --git a/dfx.json b/dfx.json new file mode 100644 index 0000000..8b7b8fe --- /dev/null +++ b/dfx.json @@ -0,0 +1,23 @@ +{ + "canisters": { + + "test_runner": { + "main": "tests/test_runner.mo", + "type": "motoko" + } + }, + "defaults": { + "build": { + "args": "", + "packtool": "vessel sources" + } + }, + "dfx": "0.9.2", + "networks": { + "local": { + "bind": "127.0.0.1:8000", + "type": "ephemeral" + } + }, + "version": 1 +} \ No newline at end of file diff --git a/package-set.dhall b/package-set.dhall new file mode 100644 index 0000000..072bbde --- /dev/null +++ b/package-set.dhall @@ -0,0 +1,25 @@ +let upstream = https://github.com/dfinity/vessel-package-set/releases/download/mo-0.6.21-20220215/package-set.dhall sha256:b46f30e811fe5085741be01e126629c2a55d4c3d6ebf49408fb3b4a98e37589b +let Package = + { name : Text, version : Text, repo : Text, dependencies : List Text } + +let + -- This is where you can add your own packages to the package-set + additions = + [] : List Package + +let + {- This is where you can override existing packages in the package-set + + For example, if you wanted to use version `v2.0.0` of the foo library: + let overrides = [ + { name = "foo" + , version = "v2.0.0" + , repo = "https://github.com/bar/foo" + , dependencies = [] : List Text + } + ] + -} + overrides = + [] : List Package + +in upstream # additions # overrides diff --git a/src/clone.mo b/src/clone.mo new file mode 100644 index 0000000..8d7011f --- /dev/null +++ b/src/clone.mo @@ -0,0 +1,50 @@ +/////////////////////////////// +// +// ©2021 @aramakme +// +//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +/////////////////////////////// + + +import Types "types"; +import Array "mo:base/Array"; + +module { + + type CandyValue = Types.CandyValue; + type CandyValueUnstable = Types.CandyValueUnstable; + type PropertyUnstable = Types.PropertyUnstable; + + + public func cloneValueUnstable(val : CandyValueUnstable) : CandyValueUnstable{ + switch(val){ + case(#Class(val)){ + + return #Class(Array.tabulate(val.size(), func(idx){ + {name= val[idx].name; value=cloneValueUnstable(val[idx].value); immutable = val[idx].immutable}; + })); + }; + case(#Bytes(val)){ + switch(val){ + case(#frozen(val)){ + + #Bytes(#frozen(val)); + }; + case(#thawed(val)){ + + #Bytes(#thawed(val.clone())); + }; + } + }; + case(_){ + + val; + } + }; + }; + +} \ No newline at end of file diff --git a/src/lib.mo b/src/conversion.mo similarity index 55% rename from src/lib.mo rename to src/conversion.mo index 12ee3b1..e5534b7 100644 --- a/src/lib.mo +++ b/src/conversion.mo @@ -9,336 +9,40 @@ //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////// -import Array "mo:base/Array"; -import Blob "mo:base/Blob"; import Buffer "mo:base/Buffer"; +import Blob "mo:base/Blob"; import Char "mo:base/Char"; -import Debug "mo:base/Debug"; -import Hash "mo:base/Hash"; +import Iter "mo:base/Iter"; +import Text "mo:base/Text"; import HashMap "mo:base/HashMap"; +import Array "mo:base/Array"; +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 Iter "mo:base/Iter"; -import Float "mo:base/Float"; -import List "mo:base/List"; -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 Principal "mo:base/Principal"; import Prelude "mo:base/Prelude"; -import Principal "mo:principal/Principal"; -import Result "mo:base/Result"; -import Text "mo:base/Text"; -import Time "mo:base/Time"; -import Cycles "mo:base/ExperimentalCycles"; -import Hex "./hex"; - -module { - - public type PropertiesUnstable = [PropertyUnstable]; - public type PropertyUnstable = {name : Text; value : CandyValueUnstable; immutable : Bool}; - - public type UpdateRequestUnstable = { - id : Text; - update : [UpdateUnstable]; - }; - - public type UpdateUnstable = { - name : Text; - mode : UpdateModeUnstable; - }; - - public type UpdateModeUnstable = { - #Set : CandyValueUnstable; - #Next : [UpdateUnstable]; - }; - - //a data chunk should be no larger than 2MB so that it can be shipped to other canisters - public type DataChunk = CandyValueUnstable; - public type DataZone = Buffer.Buffer; - public type Workspace = Buffer.Buffer; - - public type AddressedChunk = (Nat, Nat, CandyValue); - public type AddressedChunkArray = [AddressedChunk]; - public type AddressedChunkBuffer = Buffer.Buffer; - - //stable - public type CandyValue = { - #Int : Int; - #Int8: Int8; - #Int16: Int16; - #Int32: Int32; - #Int64: Int64; - #Nat : Nat; - #Nat8 : Nat8; - #Nat16 : Nat16; - #Nat32 : Nat32; - #Nat64 : Nat64; - #Float : Float; - #Text : Text; - #Bool : Bool; - #Blob : Blob; - #Class : [Property]; - #Principal : Principal; - #Option : ?CandyValue; - #Array : { - #frozen: [CandyValue]; - #thawed: [CandyValue]; //need to thaw when going to CandyValueUnstable - }; - #Nats: { - #frozen: [Nat]; - #thawed: [Nat]; //need to thaw when going to TrixValueUnstable - }; - #Floats: { - #frozen: [Float]; - #thawed: [Float]; //need to thaw when going to CandyValueUnstable - }; - #Bytes : { - #frozen: [Nat8]; - #thawed: [Nat8]; //need to thaw when going to CandyValueUnstable - }; - #Empty; - }; - - //unstable - public type CandyValueUnstable = { - #Int : Int; - #Int8: Int8; - #Int16: Int16; - #Int32: Int32; - #Int64: Int64; - #Nat : Nat; - #Nat8 : Nat8; - #Nat16 : Nat16; - #Nat32 : Nat32; - #Nat64 : Nat64; - #Float : Float; - #Text : Text; - #Bool : Bool; - #Blob : Blob; - #Class : [PropertyUnstable]; - #Principal : Principal; - #Floats : { - #frozen: [Float]; - #thawed: Buffer.Buffer; - }; - #Nats: { - #frozen: [Nat]; - #thawed: Buffer.Buffer; //need to thaw when going to TrixValueUnstable - }; - #Array : { - #frozen: [CandyValueUnstable]; - #thawed: Buffer.Buffer; //need to thaw when going to CandyValueUnstable - }; - #Option : ?CandyValueUnstable; - #Bytes : { - #frozen: [Nat8]; - #thawed: Buffer.Buffer; //need to thaw when going to CandyValueUnstable - }; - #Empty; - }; - - //////////////////////////////////// - // - // The the following stable types were copied from departurelabs' property.mo. They work as a plug and play - // here with CandyValue. - // - // https://github.com/DepartureLabsIC/non-fungible-token/blob/main/src/property.mo - // - // The following section is issued under the MIT License Copyright (c) 2021 Departure Labs: - /////////////////////////////////// - - public type Property = {name : Text; value : CandyValue; immutable : Bool}; - - public type Properties = [Property]; - - public type PropertyError = { - #Unauthorized; - #NotFound; - #InvalidRequest; - #AuthorizedPrincipalLimitReached : Nat; - #Immutable; - }; - - public type Query = { - name : Text; // Target property name. - next : [Query]; // Optional sub-properties in the case of a class value. - }; - - public type QueryMode = { - #All; // Returns all properties. - #Some : [Query]; // Returns a select set of properties based on the name. - }; - - // Specifies the properties that should be updated to a certain value. - public type UpdateRequest = { - id : Text; - update : [Update]; - }; - - public type Update = { - name : Text; - mode : UpdateMode; - }; - - public type UpdateMode = { - #Set : CandyValue; - #Next : [Update]; - }; - - /////////////////////////////////// - // - // End departurelabs' property.mo types - // - // Code below resumes the Copyright ARAMAKME - // - /////////////////////////////////// - - - - - - - private func toPropertyUnstableMap(ps : PropertiesUnstable) : HashMap.HashMap { - - let m = HashMap.HashMap(ps.size(), Text.equal, Text.hash); - for (property in ps.vals()) { - m.put(property.name, property); - }; - m; - }; - - private func fromPropertyUnstableMap(m : HashMap.HashMap) : PropertiesUnstable { - - var ps : PropertiesUnstable = []; - for ((_, p) in m.entries()) { - ps := Array.append(ps, [p]); - }; - ps; - }; +import List "mo:base/List"; +import Types "types"; +import Hex "hex"; +import Properties "properties"; - // Returns a subset of from properties based on the given query. - // NOTE: ignores unknown properties. - public func getPropertiesUnstable(properties : PropertiesUnstable, qs : [Query]) : Result.Result { - let m = toPropertyUnstableMap(properties); - var ps : PropertiesUnstable = []; - for (q in qs.vals()) { - switch (m.get(q.name)) { - case (null) { - // Query contained an unknown property. - return #err(#NotFound); - }; - case (? p) { - switch (p.value) { - case (#Class(c)) { - if (q.next.size() == 0) { - // Return every sub-attribute attribute. - ps := Array.append(ps, [p]); - } else { - let sps = switch (getPropertiesUnstable(c, q.next)) { - case (#err(e)) { return #err(e); }; - case (#ok(v)) { v; }; - }; - - ps := Array.append(ps, [{ - name = p.name; - value = #Class(sps); - immutable = p.immutable; - }]); - }; - }; - case (other) { - // Not possible to get sub-attribute of a non-class property. - if (q.next.size() != 0) { - return #err(#NotFound); - }; - ps := Array.append(ps, [p]); - }; - } - }; - }; - }; - #ok(ps); - }; - - // Updates the given properties based on the given update query. - // NOTE: creates unknown properties. - public func updatePropertiesUnstable(properties : PropertiesUnstable, us : [UpdateUnstable]) : Result.Result { - let m = toPropertyUnstableMap(properties); - for (u in us.vals()) { - switch (m.get(u.name)) { - case (null) { - // Update contained an unknown property, so it gets created. - switch (u.mode) { - case (#Next(sus)) { - let sps = switch(updatePropertiesUnstable([], sus)) { - case (#err(e)) { return #err(e); }; - case (#ok(v)) { v; }; - }; - - m.put(u.name, { - name = u.name; - value = #Class(sps); - immutable = false; - }); - }; - case (#Set(v)) { - m.put(u.name, { - name = u.name; - value = v; - immutable = false; - }); - }; - }; - }; - case (? p) { - // Can not update immutable property. - if (p.immutable) { - return #err(#Immutable); - }; - switch (u.mode) { - case (#Next(sus)) { - switch (p.value) { - case (#Class(c)) { - let sps = switch(updatePropertiesUnstable(c, sus)) { - case (#err(e)) { return #err(e); }; - case (#ok(v)) { v; }; - }; - - m.put(u.name, { - name = p.name; - value = #Class(sps); - immutable = false; - }); - }; - case (other) { - // Not possible to update sub-attribute of a non-class property. - return #err(#NotFound); - }; - }; - return #err(#NotFound); - }; - case (#Set(v)) { - m.put(u.name, { - name = p.name; - value = v; - immutable = false; - }); - }; - }; - }; - }; - }; - - #ok(fromPropertyUnstableMap(m)); - }; +module { + type CandyValue = Types.CandyValue; + type CandyValueUnstable = Types.CandyValueUnstable; + type DataZone = Types.DataZone; + type Property = Types.Property; + type PropertyUnstable = Types.PropertyUnstable; //todo: generic accesors @@ -1302,201 +1006,9 @@ module { }; }; + - public func cloneValueUnstable(val : CandyValueUnstable) : CandyValueUnstable{ - switch(val){ - case(#Class(val)){ - - return #Class(Array.tabulate(val.size(), func(idx){ - {name= val[idx].name; value=cloneValueUnstable(val[idx].value); immutable = val[idx].immutable}; - })); - }; - case(#Bytes(val)){ - switch(val){ - case(#frozen(val)){ - - #Bytes(#frozen(val)); - }; - case(#thawed(val)){ - - #Bytes(#thawed(val.clone())); - }; - } - }; - case(_){ - - val; - } - }; - }; - - - public func unwrapOptionValue(val : CandyValue): CandyValue{ - - switch(val){ - case(#Option(val)){ - switch(val){ - case(null){ - return #Empty; - }; - case(?val){ - return val; - } - }; - }; - case(_){val}; - }; - }; - - public func unwrapOptionValueUnstable(val : CandyValueUnstable): CandyValueUnstable{ - - switch(val){ - case(#Option(val)){ - switch(val){ - case(null){ - return #Empty; - }; - case(?val){ - return val; - } - }; - }; - case(_){val}; - }; - }; - - public func stabalizeProperty(item : PropertyUnstable) : Property{ - return { - name = item.name; - value = stabalizeValue(item.value); - immutable = item.immutable; - } - }; - - public func destabalizeProperty(item : Property) : PropertyUnstable{ - return { - name = item.name; - value = destabalizeValue(item.value); - immutable = item.immutable; - } - }; - - public func stabalizeValue(item : CandyValueUnstable) : CandyValue{ - 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(#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.tabulate(val.size(), func(idx){ - stabalizeProperty(val[idx]); - })); - }; - case(#Principal(val)){ #Principal(val)}; - case(#Array(val)){ - switch(val){ - case(#frozen(val)){#Array(#frozen(stabalizeValueArray(val)))}; - case(#thawed(val)){#Array(#thawed(stabalizeValueArray(val.toArray())))}; - }; - }; - case(#Option(val)){ - switch(val){ - case(null){ #Option(null)}; - case(?val){#Option(?stabalizeValue(val))}; - }; - }; - case(#Bytes(val)){ - switch(val){ - case(#frozen(val)){ #Bytes(#frozen(val))}; - case(#thawed(val)){ #Bytes(#thawed(val.toArray()))}; - }; - }; - case(#Floats(val)){ - switch(val){ - case(#frozen(val)){ #Floats(#frozen(val))}; - case(#thawed(val)){ #Floats(#thawed(val.toArray()))}; - }; - }; - case(#Nats(val)){ - switch(val){ - case(#frozen(val)){ #Nats(#frozen(val))}; - case(#thawed(val)){ #Nats(#thawed(val.toArray()))}; - }; - }; - case(#Empty){ #Empty}; - } - }; - - public func destabalizeValue(item : CandyValue) : CandyValueUnstable{ - 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(#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.tabulate(val.size(), func(idx){ - destabalizeProperty(val[idx]); - })); - }; - case(#Principal(val)){#Principal(val)}; - case(#Array(val)){ - switch(val){ - case(#frozen(val)){#Array(#frozen(destabalizeValueArray(val)))}; - case(#thawed(val)){#Array(#thawed(toBuffer(destabalizeValueArray(val))))}; - }; - }; - case(#Option(val)){ - switch(val){ - case(null){ #Option(null)}; - case(?val){#Option(?destabalizeValue(val))}; - }; - }; - case(#Bytes(val)){ - switch(val){ - case(#frozen(val)){ #Bytes(#frozen(val))}; - case(#thawed(val)){#Bytes(#thawed(toBuffer(val)))}; - }; - }; - case(#Floats(val)){ - switch(val){ - case(#frozen(val)){ #Floats(#frozen(val))}; - case(#thawed(val)){#Floats(#thawed(toBuffer(val)))}; - }; - }; - case(#Nats(val)){ - switch(val){ - case(#frozen(val)){ #Nats(#frozen(val))}; - case(#thawed(val)){#Nats(#thawed(toBuffer(val)))}; - }; - }; - case(#Empty){ #Empty}; - } - }; - ////////////////////////////////////////////////////////////////////// // The following functions easily creates a buffer from an arry of any type ////////////////////////////////////////////////////////////////////// @@ -1691,722 +1203,40 @@ module { return n; }; - ////////////////////////////////////////////////////////////////////// - // The following functions enable the inspection and manipulation of workspaces - // Workspaces are valueble when using orthogonal persistance to keep track of data - // in a format that is easily transmitable across the wire given IC restrictions - ////////////////////////////////////////////////////////////////////// - - public func countAddressedChunksInWorkspace(x : Workspace) : Nat{ - - var chunks = 0; - for (thisZone in Iter.range(0, x.size() - 1)){ - chunks += x.get(thisZone).size(); - }; - chunks; + - }; - - public func emptyWorkspace() : Workspace { - - return Buffer.Buffer(1); - }; - - public func initWorkspace(size : Nat) : Workspace { - - return Buffer.Buffer(size); - }; - - //variants take up 2 bytes as long as there are fewer than 32 item in the enum - public func getValueSize(item : CandyValue) : Nat{ + public func unwrapOptionValue(val : CandyValue): CandyValue{ - let varSize = switch(item){ - case(#Int(val)){ - var a : Nat = 0; - var b : Nat = Int.abs(val); - var test = true; - while test { - a += 1; - b := b / 256; - test := b > 0; - }; - a + 1;//add the sign - }; - case(#Int8(val)){1}; - case(#Int16(val)){2}; - case(#Int32(val)){3}; - case(#Int64(val)){4}; - case(#Nat(val)){ - var a : Nat = 0; - var b = val; - var test = true; - while test { - a += 1; - b := b / 256; - test := b > 0; - }; - a; - }; - case(#Nat8(val)){1}; - case(#Nat16(val)){2}; - case(#Nat32(val)){3}; - case(#Nat64(val)){4}; - case(#Float(val)){4}; - case(#Text(val)){(val.size()*4)}; - case(#Bool(val)){1}; - case(#Blob(val)){val.size()}; - case(#Class(val)){ - var size = 0; - for(thisItem in val.vals()){ - size += 1 + (thisItem.name.size() * 4) + getValueSize(thisItem.value); - }; - - return size; - }; - case(#Principal(val)){principalToBytes(val).size()};//don't like this but need to confirm it is constant + switch(val){ case(#Option(val)){ - switch val{ - case(null){0}; - case(?val){getValueSize(val)} - } - }; - case(#Array(val)){ switch(val){ - case(#frozen(val)){ - var size = 0; - for(thisItem in val.vals()){ - size += 1 + getValueSize(thisItem); - }; - - return size; - }; - case(#thawed(val)){ - var size = 0; - for(thisItem in val.vals()){ - size += 1 + getValueSize(thisItem); - }; - - return size; - }; - }; - }; - case(#Bytes(val)){ - switch(val){ - case(#frozen(val)){val.size() + 2}; - case(#thawed(val)){val.size() + 2}; - }; - }; - case(#Floats(val)){ - switch(val){ - case(#frozen(val)){(val.size() * 4) + 2}; - case(#thawed(val)){(val.size() * 4) + 2}; - }; - }; - case(#Nats(val)){ - switch(val){ - case(#frozen(val)){ - var size = 0; - for(thisItem in val.vals()){ - size += 1 + getValueSize(#Nat(thisItem)); - }; - - return size; - }; - case(#thawed(val)){ - var size = 0; - for(thisItem in val.vals()){ - size += 1 + getValueSize(#Nat(thisItem)); - }; - - return size; + case(null){ + return #Empty; }; + case(?val){ + return val; + } }; }; - case(#Empty){0}; + case(_){val}; }; - - return varSize; }; - public func getValueUnstableSize(item : CandyValueUnstable) : Nat{ + public func unwrapOptionValueUnstable(val : CandyValueUnstable): CandyValueUnstable{ - let varSize = switch(item){ - case(#Int(val)){ - var a : Nat = 0; - var b : Nat = Int.abs(val); - var test = true; - while test { - a += 1; - b := b / 256; - test := b > 0; - }; - a + 1;//add the sign - }; - case(#Int8(val)){1}; - case(#Int16(val)){2}; - case(#Int32(val)){3}; - case(#Int64(val)){4}; - case(#Nat(val)){ - var a : Nat = 0; - var b = val; - var test = true; - while test { - a += 1; - b := b / 256; - test := b > 0; - }; - a; - }; - case(#Nat8(val)){1}; - case(#Nat16(val)){2}; - case(#Nat32(val)){3}; - case(#Nat64(val)){4}; - case(#Float(val)){4}; - case(#Text(val)){val.size()*4}; - case(#Bool(val)){1}; - case(#Blob(val)){val.size()}; - case(#Class(val)){ - var size = 0; - for(thisItem in val.vals()){ - size += 1 + (thisItem.name.size() * 4) + getValueUnstableSize(thisItem.value); - }; - - return size; - }; - case(#Principal(val)){principalToBytes(val).size()};//don't like this but need to confirm it is constant + switch(val){ case(#Option(val)){ - switch val{ - case(null){0}; - case(?val){ getValueUnstableSize(val)} - } - }; - case(#Array(val)){ - switch(val){ - case(#frozen(val)){ - var size = 0; - for(thisItem in val.vals()){ - size += 1 + getValueUnstableSize(thisItem); - }; - - return size; - }; - case(#thawed(val)){ - var size = 0; - for(thisItem in val.vals()){ - size += 1 + getValueUnstableSize(thisItem); - }; - - return size; - }; - }; - }; - - case(#Bytes(val)){ switch(val){ - case(#frozen(val)){val.size() + 2}; - case(#thawed(val)){val.size() + 2}; - }; - }; - case(#Floats(val)){ - switch(val){ - case(#frozen(val)){(val.size() * 4) + 2}; - case(#thawed(val)){(val.size() * 4) + 2}; - }; - }; - case(#Nats(val)){ - switch(val){ - case(#frozen(val)){ - var size = 0; - for(thisItem in val.vals()){ - size += 1 + getValueUnstableSize(#Nat(thisItem)); - }; - - return size; - }; - case(#thawed(val)){ - var size = 0; - for(thisItem in val.vals()){ - size += 1 + getValueUnstableSize(#Nat(thisItem)); - }; - - return size; - }; - }; - }; - case(#Empty){0}; - }; - return varSize + 2; - }; - - public func stabalizeValueArray(items : [CandyValueUnstable]) : [CandyValue]{ - - let finalItems = Buffer.Buffer(items.size()); - for(thisItem in items.vals()){ - finalItems.add(stabalizeValue(thisItem)); - }; - - return finalItems.toArray(); - - - }; - - public func destabalizeValueArray(items : [CandyValue]) : [CandyValueUnstable]{ - - let finalItems = Buffer.Buffer(items.size()); - for(thisItem in items.vals()){ - finalItems.add(destabalizeValue(thisItem)); - }; - - return finalItems.toArray(); - }; - - public func stabalizeValueBuffer(items : DataZone) : [CandyValue]{ - - let finalItems = Buffer.Buffer(items.size()); - for(thisItem in items.vals()){ - finalItems.add(stabalizeValue(thisItem)); - }; - - return finalItems.toArray(); - - }; - - public func workspaceToAddressedChunkArray(x : Workspace) : AddressedChunkArray { - - var currentZone = 0; - var currentChunk = 0; - let result = Array.tabulate(countAddressedChunksInWorkspace(x), func(thisChunk){ - let thisChunk = (currentZone, currentChunk, stabalizeValue(x.get(currentZone).get(currentChunk))); - if(currentChunk == Nat.sub(x.get(currentZone).size(),1)){ - currentZone += 1; - currentChunk := 0; - } else { - currentChunk += 1; - }; - thisChunk; - }); - - return result; - }; - - public func workspaceDeepClone(x : Workspace) : Workspace { - - var currentZone = 0; - var currentChunk = 0; - let ws = Buffer.Buffer(x.size()); - for(thisZone in x.vals()){ - - let tz = Buffer.Buffer(thisZone.size()); - ws.add(tz); - for(thisDataChunk in thisZone.vals()){ - tz.add(cloneValueUnstable(thisDataChunk)); - }; - - }; - return ws; - }; - - public func fromAddressedChunks(x : AddressedChunkArray) : Workspace{ - let result = Buffer.Buffer(x.size()); - fileAddressedChunks(result, x); - return result; - }; - - public func fileAddressedChunks(workspace: Workspace, x : AddressedChunkArray) { - - for (thisChunk : AddressedChunk in Array.vals(x)){ - - let resultSize : Nat = workspace.size(); - let targetZone = thisChunk.0 + 1; - - if(targetZone <= resultSize){ - //zone exist - } else { - //append zone - - for (thisIndex in Iter.range(resultSize, targetZone-1)){ - workspace.add(Buffer.Buffer(1)); - }; - - }; - - let thisZone = workspace.get(thisChunk.0); - - if(thisChunk.1 + 1 <= thisZone.size()){ - //zone exists - thisZone.put(thisChunk.1, destabalizeValue(thisChunk.2)); - } else { - //append zone - - for (newChunk in Iter.range(thisZone.size(), thisChunk.1)){ - - let newBuffer = if(thisChunk.1 == newChunk){ - //we know the size - - destabalizeValue(thisChunk.2); - } else { - #Empty; - }; - thisZone.add(newBuffer); - }; - //return thisZone.get(thisChunk.1); - }; - - - }; - - return ; - }; - - public func getDataZoneSize(dz: DataZone) : Nat { - - var size : Nat = 0; - for(thisChunk in dz.vals()){ - size += getValueUnstableSize(thisChunk); - }; - - return size; - }; - - public func getWorkspaceChunkSize(_workspace: Workspace, _maxChunkSize : Nat) : Nat{ - - var currentChunk : Nat = 0; - var handBrake = 0; - var zoneTracker = 0; - var chunkTracker = 0; - - - label chunking while (1==1){ - handBrake += 1; - if(handBrake > 10000){ break chunking;}; - var foundBytes = 0; - //calc bytes - for(thisZone in Iter.range(zoneTracker, _workspace.size()-1)){ - for(thisChunk in Iter.range(chunkTracker, _workspace.get(thisZone).size()-1)){ - - let thisItem = _workspace.get(thisZone).get(thisChunk); - - let newSize = foundBytes + getValueUnstableSize(thisItem); - - if( newSize > _maxChunkSize) - { - //went over bytes - - currentChunk += 1; - zoneTracker := thisZone; - chunkTracker := thisChunk; - - continue chunking; - }; - //adding some bytes - foundBytes := newSize; - }; - }; - - }; - - currentChunk += 1; - - return currentChunk; - - - }; - - public func getWorkspaceChunk(_workspace: Workspace, _chunkID : Nat, _maxChunkSize : Nat) : ({#eof; #chunk} , AddressedChunkBuffer){ - var currentChunk : Nat = 0; - var handBrake = 0; - var zoneTracker = 0; - var chunkTracker = 0; - - let resultBuffer = Buffer.Buffer(1); - label chunking while (1==1){ - handBrake += 1; - if(handBrake > 10000){ break chunking;}; - var foundBytes = 0; - //calc bytes - for(thisZone in Iter.range(zoneTracker, _workspace.size()-1)){ - for(thisChunk in Iter.range(chunkTracker, _workspace.get(thisZone).size()-1)){ - - let thisItem = _workspace.get(thisZone).get(thisChunk); - - let newSize = foundBytes + getValueUnstableSize(thisItem); - if( newSize > _maxChunkSize) - { - //went over bytes - if(currentChunk == _chunkID){ - return (#chunk, resultBuffer); - }; - currentChunk += 1; - zoneTracker := thisZone; - chunkTracker := thisChunk; - continue chunking; - }; - if(currentChunk == _chunkID){ - //add it to our return - resultBuffer.add((thisZone, thisChunk, stabalizeValue(thisItem))); - - }; - - foundBytes := newSize; - }; - }; - - return (#eof, resultBuffer); - }; - - return (#eof, resultBuffer); - }; - - public func getAddressedChunkArraySize(item : AddressedChunkArray) : Nat{ - - var size : Nat = 0; - for(thisItem in item.vals()){ - size += getValueSize(thisItem.2) + 4 + 4; //only works for up to 32 byte adresess...should be fine but verify and document. - }; - - return size; - }; - - public func getDataChunkFromAddressedChunkArray(item : AddressedChunkArray, dataZone: Nat, dataChunk: Nat) : CandyValue{ - - var size : Nat = 0; - for(thisItem in item.vals()){ - if(thisItem.0 == dataZone and thisItem.1 == dataChunk){ - return thisItem.2; - } - }; - return #Empty; - }; - - - - public func getClassProperty(val: CandyValue, name : Text) : Property{ - - switch(val){ - case(#Class(val)){ - for(thisItem in val.vals()){ - if(thisItem.name == name){ - return thisItem; + case(null){ + return #Empty; }; - }; - //couldnt find name in class - assert(false); - //unreachable - return {name=""; value=#Empty; immutable=true}; - - }; - case(_){ - - assert(false); - //unreachable - return {name=""; value=#Empty; immutable=true}; - } - - }; - - }; - - public func byteBufferDataZoneToBuffer(dz : DataZone): Buffer.Buffer>{ - - let result = Buffer.Buffer>(dz.size()); - for(thisItem in dz.vals()){ - result.add(valueUnstableToBytesBuffer(thisItem)); - }; - - return result; - }; - - public func byteBufferChunksToValueUnstableBufferDataZone(buffer : Buffer.Buffer>): DataZone{ - - let result = Buffer.Buffer(buffer.size()); - for(thisItem in buffer.vals()){ - result.add(#Bytes(#thawed(thisItem))); - }; - - return result; - }; - - public func initDataZone(val : CandyValueUnstable) : DataZone{ - - let result = Buffer.Buffer(1); - result.add(val); - return result; - }; - - public func flattenAddressedChunkArray(data : AddressedChunkArray) : [Nat8]{ - //note loses integrity after 256 Zones or 256 chunks - - let accumulator : Buffer.Buffer = Buffer.Buffer(getAddressedChunkArraySize(data)); - for(thisItem in data.vals()){ - - for(thisbyte in natToBytes(thisItem.0).vals()){ - accumulator.add(thisbyte); - }; - for(thisbyte in natToBytes(thisItem.1).vals()){ - accumulator.add(thisbyte); - }; - - for(thisbyte in valueToBytes(thisItem.2).vals()){ - accumulator.add(thisbyte); - }; - - }; - return accumulator.toArray(); - - - }; - - //////////////////////////////////// - // - // The following functions were copied from departurelabs' property.mo. They work as a plug and play - // here with CandyValue and CandyValueUnstable. - // - // https://github.com/DepartureLabsIC/non-fungible-token/blob/main/src/property.mo - // - // The following lines are issued under the MIT License Copyright (c) 2021 Departure Labs: - // - /////////////////////////////////// - - private func toPropertyMap(ps : Properties) : HashMap.HashMap { - - let m = HashMap.HashMap(ps.size(), Text.equal, Text.hash); - for (property in ps.vals()) { - m.put(property.name, property); - }; - m; - }; - - private func fromPropertyMap(m : HashMap.HashMap) : Properties { - - var ps : Properties = []; - for ((_, p) in m.entries()) { - ps := Array.append(ps, [p]); - }; - ps; - }; - - // Returns a subset of from properties based on the given query. - // NOTE: ignores unknown properties. - public func getProperties(properties : Properties, qs : [Query]) : Result.Result { - let m = toPropertyMap(properties); - var ps : Properties = []; - for (q in qs.vals()) { - switch (m.get(q.name)) { - case (null) { - // Query contained an unknown property. - return #err(#NotFound); - }; - case (? p) { - switch (p.value) { - case (#Class(c)) { - if (q.next.size() == 0) { - // Return every sub-attribute attribute. - ps := Array.append(ps, [p]); - } else { - let sps = switch (getProperties(c, q.next)) { - case (#err(e)) { return #err(e); }; - case (#ok(v)) { v; }; - }; - - ps := Array.append(ps, [{ - name = p.name; - value = #Class(sps); - immutable = p.immutable; - }]); - }; - }; - case (other) { - // Not possible to get sub-attribute of a non-class property. - if (q.next.size() != 0) { - return #err(#NotFound); - }; - ps := Array.append(ps, [p]); - }; + case(?val){ + return val; } }; }; + case(_){val}; }; - #ok(ps); - }; - - // Updates the given properties based on the given update query. - // NOTE: creates unknown properties. - public func updateProperties(properties : Properties, us : [Update]) : Result.Result { - let m = toPropertyMap(properties); - for (u in us.vals()) { - switch (m.get(u.name)) { - case (null) { - // Update contained an unknown property, so it gets created. - switch (u.mode) { - case (#Next(sus)) { - let sps = switch(updateProperties([], sus)) { - case (#err(e)) { return #err(e); }; - case (#ok(v)) { v; }; - }; - - m.put(u.name, { - name = u.name; - value = #Class(sps); - immutable = false; - }); - }; - case (#Set(v)) { - m.put(u.name, { - name = u.name; - value = v; - immutable = false; - }); - }; - }; - }; - case (? p) { - // Can not update immutable property. - if (p.immutable) { - return #err(#Immutable); - }; - switch (u.mode) { - case (#Next(sus)) { - switch (p.value) { - case (#Class(c)) { - let sps = switch(updateProperties(c, sus)) { - case (#err(e)) { return #err(e); }; - case (#ok(v)) { v; }; - }; - - m.put(u.name, { - name = p.name; - value = #Class(sps); - immutable = false; - }); - }; - case (other) { - // Not possible to update sub-attribute of a non-class property. - return #err(#NotFound); - }; - }; - return #err(#NotFound); - }; - case (#Set(v)) { - m.put(u.name, { - name = p.name; - value = v; - immutable = false; - }); - }; - }; - }; - }; - }; - - #ok(fromPropertyMap(m)); }; - //////////////////////////////////// - // - // End code from Departure labs property.mo - // - /////////////////////////////////// - - - -}; +} \ No newline at end of file diff --git a/src/properties.mo b/src/properties.mo new file mode 100644 index 0000000..3ea2264 --- /dev/null +++ b/src/properties.mo @@ -0,0 +1,346 @@ +/////////////////////////////// +// +// ©2021 @aramakme +// +//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +/////////////////////////////// + +import Buffer "mo:base/Buffer"; +import HashMap "mo:base/HashMap"; +import Text "mo:base/Text"; +import Array "mo:base/Array"; +import Result "mo:base/Result"; +import Types "types"; + +module { + + type PropertiesUnstable = Types.PropertiesUnstable; + type Query = Types.Query; + type PropertyError = Types.PropertyError; + type UpdateUnstable = Types.UpdateUnstable; + type Property = Types.Property; + type PropertyUnstable = Types.PropertyUnstable; + type CandyValue = Types.CandyValue; + type Properties = Types.Properties; + type Update = Types.Update; + + private func toPropertyUnstableMap(ps : PropertiesUnstable) : HashMap.HashMap { + + let m = HashMap.HashMap(ps.size(), Text.equal, Text.hash); + for (property in ps.vals()) { + m.put(property.name, property); + }; + m; + }; + + private func fromPropertyUnstableMap(m : HashMap.HashMap) : PropertiesUnstable { + + var ps : PropertiesUnstable = []; + for ((_, p) in m.entries()) { + ps := Array.append(ps, [p]); + }; + ps; + }; + + + // Returns a subset of from properties based on the given query. + // NOTE: ignores unknown properties. + public func getPropertiesUnstable(properties : PropertiesUnstable, qs : [Query]) : Result.Result { + let m = toPropertyUnstableMap(properties); + var ps : PropertiesUnstable = []; + for (q in qs.vals()) { + switch (m.get(q.name)) { + case (null) { + // Query contained an unknown property. + return #err(#NotFound); + }; + case (? p) { + switch (p.value) { + case (#Class(c)) { + if (q.next.size() == 0) { + // Return every sub-attribute attribute. + ps := Array.append(ps, [p]); + } else { + let sps = switch (getPropertiesUnstable(c, q.next)) { + case (#err(e)) { return #err(e); }; + case (#ok(v)) { v; }; + }; + + ps := Array.append(ps, [{ + name = p.name; + value = #Class(sps); + immutable = p.immutable; + }]); + }; + }; + case (other) { + // Not possible to get sub-attribute of a non-class property. + if (q.next.size() != 0) { + return #err(#NotFound); + }; + ps := Array.append(ps, [p]); + }; + } + }; + }; + }; + #ok(ps); + }; + + // Updates the given properties based on the given update query. + // NOTE: creates unknown properties. + public func updatePropertiesUnstable(properties : PropertiesUnstable, us : [UpdateUnstable]) : Result.Result { + let m = toPropertyUnstableMap(properties); + for (u in us.vals()) { + switch (m.get(u.name)) { + case (null) { + // Update contained an unknown property, so it gets created. + switch (u.mode) { + case (#Next(sus)) { + let sps = switch(updatePropertiesUnstable([], sus)) { + case (#err(e)) { return #err(e); }; + case (#ok(v)) { v; }; + }; + + m.put(u.name, { + name = u.name; + value = #Class(sps); + immutable = false; + }); + }; + case (#Set(v)) { + m.put(u.name, { + name = u.name; + value = v; + immutable = false; + }); + }; + }; + }; + case (? p) { + // Can not update immutable property. + if (p.immutable) { + return #err(#Immutable); + }; + switch (u.mode) { + case (#Next(sus)) { + switch (p.value) { + case (#Class(c)) { + let sps = switch(updatePropertiesUnstable(c, sus)) { + case (#err(e)) { return #err(e); }; + case (#ok(v)) { v; }; + }; + + m.put(u.name, { + name = p.name; + value = #Class(sps); + immutable = false; + }); + }; + case (other) { + // Not possible to update sub-attribute of a non-class property. + return #err(#NotFound); + }; + }; + return #err(#NotFound); + }; + case (#Set(v)) { + m.put(u.name, { + name = p.name; + value = v; + immutable = false; + }); + }; + }; + }; + }; + }; + + #ok(fromPropertyUnstableMap(m)); + }; + + + + + public func getClassProperty(val: CandyValue, name : Text) : Property{ + + switch(val){ + case(#Class(val)){ + for(thisItem in val.vals()){ + if(thisItem.name == name){ + return thisItem; + }; + }; + //couldnt find name in class + assert(false); + //unreachable + return {name=""; value=#Empty; immutable=true}; + + }; + case(_){ + + assert(false); + //unreachable + return {name=""; value=#Empty; immutable=true}; + } + + }; + + }; + + //////////////////////////////////// + // + // The following functions were copied from departurelabs' property.mo. They work as a plug and play + // here with CandyValue and CandyValueUnstable. + // + // https://github.com/DepartureLabsIC/non-fungible-token/blob/main/src/property.mo + // + // The following lines are issued under the MIT License Copyright (c) 2021 Departure Labs: + // + /////////////////////////////////// + + private func toPropertyMap(ps : Properties) : HashMap.HashMap { + + let m = HashMap.HashMap(ps.size(), Text.equal, Text.hash); + for (property in ps.vals()) { + m.put(property.name, property); + }; + m; + }; + + private func fromPropertyMap(m : HashMap.HashMap) : Properties { + + var ps : Properties = []; + for ((_, p) in m.entries()) { + ps := Array.append(ps, [p]); + }; + ps; + }; + + // Returns a subset of from properties based on the given query. + // NOTE: ignores unknown properties. + public func getProperties(properties : Properties, qs : [Query]) : Result.Result { + let m = toPropertyMap(properties); + var ps : Properties = []; + for (q in qs.vals()) { + switch (m.get(q.name)) { + case (null) { + // Query contained an unknown property. + return #err(#NotFound); + }; + case (? p) { + switch (p.value) { + case (#Class(c)) { + if (q.next.size() == 0) { + // Return every sub-attribute attribute. + ps := Array.append(ps, [p]); + } else { + let sps = switch (getProperties(c, q.next)) { + case (#err(e)) { return #err(e); }; + case (#ok(v)) { v; }; + }; + + ps := Array.append(ps, [{ + name = p.name; + value = #Class(sps); + immutable = p.immutable; + }]); + }; + }; + case (other) { + // Not possible to get sub-attribute of a non-class property. + if (q.next.size() != 0) { + return #err(#NotFound); + }; + ps := Array.append(ps, [p]); + }; + } + }; + }; + }; + #ok(ps); + }; + + // Updates the given properties based on the given update query. + // NOTE: creates unknown properties. + public func updateProperties(properties : Properties, us : [Update]) : Result.Result { + let m = toPropertyMap(properties); + for (u in us.vals()) { + switch (m.get(u.name)) { + case (null) { + // Update contained an unknown property, so it gets created. + switch (u.mode) { + case (#Next(sus)) { + let sps = switch(updateProperties([], sus)) { + case (#err(e)) { return #err(e); }; + case (#ok(v)) { v; }; + }; + + m.put(u.name, { + name = u.name; + value = #Class(sps); + immutable = false; + }); + }; + case (#Set(v)) { + m.put(u.name, { + name = u.name; + value = v; + immutable = false; + }); + }; + }; + }; + case (? p) { + // Can not update immutable property. + if (p.immutable) { + return #err(#Immutable); + }; + switch (u.mode) { + case (#Next(sus)) { + switch (p.value) { + case (#Class(c)) { + let sps = switch(updateProperties(c, sus)) { + case (#err(e)) { return #err(e); }; + case (#ok(v)) { v; }; + }; + + m.put(u.name, { + name = p.name; + value = #Class(sps); + immutable = false; + }); + }; + case (other) { + // Not possible to update sub-attribute of a non-class property. + return #err(#NotFound); + }; + }; + return #err(#NotFound); + }; + case (#Set(v)) { + m.put(u.name, { + name = p.name; + value = v; + immutable = false; + }); + }; + }; + }; + }; + }; + + #ok(fromPropertyMap(m)); + }; + + //////////////////////////////////// + // + // End code from Departure labs property.mo + // + /////////////////////////////////// + +} \ No newline at end of file diff --git a/src/types.mo b/src/types.mo new file mode 100644 index 0000000..14d06cb --- /dev/null +++ b/src/types.mo @@ -0,0 +1,354 @@ +/////////////////////////////// +// +// ©2021 @aramakme +// +//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +/////////////////////////////// + +import Buffer "mo:base/Buffer"; +import Array "mo:base/Array"; + + +module { + + public type PropertiesUnstable = [PropertyUnstable]; + public type PropertyUnstable = {name : Text; value : CandyValueUnstable; immutable : Bool}; + + public type UpdateRequestUnstable = { + id : Text; + update : [UpdateUnstable]; + }; + + public type UpdateUnstable = { + name : Text; + mode : UpdateModeUnstable; + }; + + public type UpdateModeUnstable = { + #Set : CandyValueUnstable; + #Next : [UpdateUnstable]; + }; + + //////////////////////////////////// + // + // The the following stable types were copied from departurelabs' property.mo. They work as a plug and play + // here with CandyValue. + // + // https://github.com/DepartureLabsIC/non-fungible-token/blob/main/src/property.mo + // + // The following section is issued under the MIT License Copyright (c) 2021 Departure Labs: + /////////////////////////////////// + + public type Property = {name : Text; value : CandyValue; immutable : Bool}; + + public type Properties = [Property]; + + public type PropertyError = { + #Unauthorized; + #NotFound; + #InvalidRequest; + #AuthorizedPrincipalLimitReached : Nat; + #Immutable; + }; + + public type Query = { + name : Text; // Target property name. + next : [Query]; // Optional sub-properties in the case of a class value. + }; + + public type QueryMode = { + #All; // Returns all properties. + #Some : [Query]; // Returns a select set of properties based on the name. + }; + + // Specifies the properties that should be updated to a certain value. + public type UpdateRequest = { + id : Text; + update : [Update]; + }; + + public type Update = { + name : Text; + mode : UpdateMode; + }; + + public type UpdateMode = { + #Set : CandyValue; + #Next : [Update]; + }; + + /////////////////////////////////// + // + // End departurelabs' property.mo types + // + // Code below resumes the Copyright ARAMAKME + // + /////////////////////////////////// + +//stable + public type CandyValue = { + #Int : Int; + #Int8: Int8; + #Int16: Int16; + #Int32: Int32; + #Int64: Int64; + #Nat : Nat; + #Nat8 : Nat8; + #Nat16 : Nat16; + #Nat32 : Nat32; + #Nat64 : Nat64; + #Float : Float; + #Text : Text; + #Bool : Bool; + #Blob : Blob; + #Class : [Property]; + #Principal : Principal; + #Option : ?CandyValue; + #Array : { + #frozen: [CandyValue]; + #thawed: [CandyValue]; //need to thaw when going to CandyValueUnstable + }; + #Nats: { + #frozen: [Nat]; + #thawed: [Nat]; //need to thaw when going to TrixValueUnstable + }; + #Floats: { + #frozen: [Float]; + #thawed: [Float]; //need to thaw when going to CandyValueUnstable + }; + #Bytes : { + #frozen: [Nat8]; + #thawed: [Nat8]; //need to thaw when going to CandyValueUnstable + }; + #Empty; + }; + + //unstable + public type CandyValueUnstable = { + #Int : Int; + #Int8: Int8; + #Int16: Int16; + #Int32: Int32; + #Int64: Int64; + #Nat : Nat; + #Nat8 : Nat8; + #Nat16 : Nat16; + #Nat32 : Nat32; + #Nat64 : Nat64; + #Float : Float; + #Text : Text; + #Bool : Bool; + #Blob : Blob; + #Class : [PropertyUnstable]; + #Principal : Principal; + #Floats : { + #frozen: [Float]; + #thawed: Buffer.Buffer; + }; + #Nats: { + #frozen: [Nat]; + #thawed: Buffer.Buffer; //need to thaw when going to TrixValueUnstable + }; + #Array : { + #frozen: [CandyValueUnstable]; + #thawed: Buffer.Buffer; //need to thaw when going to CandyValueUnstable + }; + #Option : ?CandyValueUnstable; + #Bytes : { + #frozen: [Nat8]; + #thawed: Buffer.Buffer; //need to thaw when going to CandyValueUnstable + }; + #Empty; + }; + + //a data chunk should be no larger than 2MB so that it can be shipped to other canisters + public type DataChunk = CandyValueUnstable; + public type DataZone = Buffer.Buffer; + public type Workspace = Buffer.Buffer; + + public type AddressedChunk = (Nat, Nat, CandyValue); + public type AddressedChunkArray = [AddressedChunk]; + public type AddressedChunkBuffer = Buffer.Buffer; + + public func stabalizeValue(item : CandyValueUnstable) : CandyValue{ + 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(#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.tabulate(val.size(), func(idx){ + stabalizeProperty(val[idx]); + })); + }; + case(#Principal(val)){ #Principal(val)}; + case(#Array(val)){ + switch(val){ + case(#frozen(val)){#Array(#frozen(stabalizeValueArray(val)))}; + case(#thawed(val)){#Array(#thawed(stabalizeValueArray(val.toArray())))}; + }; + }; + case(#Option(val)){ + switch(val){ + case(null){ #Option(null)}; + case(?val){#Option(?stabalizeValue(val))}; + }; + }; + case(#Bytes(val)){ + switch(val){ + case(#frozen(val)){ #Bytes(#frozen(val))}; + case(#thawed(val)){ #Bytes(#thawed(val.toArray()))}; + }; + }; + case(#Floats(val)){ + switch(val){ + case(#frozen(val)){ #Floats(#frozen(val))}; + case(#thawed(val)){ #Floats(#thawed(val.toArray()))}; + }; + }; + case(#Nats(val)){ + switch(val){ + case(#frozen(val)){ #Nats(#frozen(val))}; + case(#thawed(val)){ #Nats(#thawed(val.toArray()))}; + }; + }; + case(#Empty){ #Empty}; + } + }; + + public func destabalizeValue(item : CandyValue) : CandyValueUnstable{ + 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(#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.tabulate(val.size(), func(idx){ + destabalizeProperty(val[idx]); + })); + }; + case(#Principal(val)){#Principal(val)}; + case(#Array(val)){ + switch(val){ + case(#frozen(val)){#Array(#frozen(destabalizeValueArray(val)))}; + case(#thawed(val)){#Array(#thawed(toBuffer(destabalizeValueArray(val))))}; + }; + }; + case(#Option(val)){ + switch(val){ + case(null){ #Option(null)}; + case(?val){#Option(?destabalizeValue(val))}; + }; + }; + case(#Bytes(val)){ + switch(val){ + case(#frozen(val)){ #Bytes(#frozen(val))}; + case(#thawed(val)){#Bytes(#thawed(toBuffer(val)))}; + }; + }; + case(#Floats(val)){ + switch(val){ + case(#frozen(val)){ #Floats(#frozen(val))}; + case(#thawed(val)){#Floats(#thawed(toBuffer(val)))}; + }; + }; + case(#Nats(val)){ + switch(val){ + case(#frozen(val)){ #Nats(#frozen(val))}; + case(#thawed(val)){#Nats(#thawed(toBuffer(val)))}; + }; + }; + case(#Empty){ #Empty}; + } + }; + + public func stabalizeProperty(item : PropertyUnstable) : Property{ + return { + name = item.name; + value = stabalizeValue(item.value); + immutable = item.immutable; + } + }; + + public func destabalizeProperty(item : Property) : PropertyUnstable{ + return { + name = item.name; + value = destabalizeValue(item.value); + immutable = item.immutable; + } + }; + + public func stabalizeValueArray(items : [CandyValueUnstable]) : [CandyValue]{ + + let finalItems = Buffer.Buffer(items.size()); + for(thisItem in items.vals()){ + finalItems.add(stabalizeValue(thisItem)); + }; + + return finalItems.toArray(); + + + }; + + public func destabalizeValueArray(items : [CandyValue]) : [CandyValueUnstable]{ + + let finalItems = Buffer.Buffer(items.size()); + for(thisItem in items.vals()){ + finalItems.add(destabalizeValue(thisItem)); + }; + + return finalItems.toArray(); + }; + + public func stabalizeValueBuffer(items : DataZone) : [CandyValue]{ + + let finalItems = Buffer.Buffer(items.size()); + for(thisItem in items.vals()){ + finalItems.add(stabalizeValue(thisItem)); + }; + + return finalItems.toArray(); + + }; + + ////////////////////////////////////////////////////////////////////// + // The following functions easily creates a buffer from an arry of any type + ////////////////////////////////////////////////////////////////////// + + public func toBuffer(x :[T]) : Buffer.Buffer{ + + let thisBuffer = Buffer.Buffer(x.size()); + for(thisItem in x.vals()){ + thisBuffer.add(thisItem); + }; + return thisBuffer; + }; + +} \ No newline at end of file diff --git a/src/workspace.mo b/src/workspace.mo new file mode 100644 index 0000000..0712e6b --- /dev/null +++ b/src/workspace.mo @@ -0,0 +1,545 @@ +/////////////////////////////// +// +// ©2021 @aramakme +// +//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +/////////////////////////////// + + +import Array "mo:base/Array"; +import Buffer "mo:base/Buffer"; + +import Int "mo:base/Int"; +import Iter "mo:base/Iter"; +import Nat "mo:base/Nat"; +import Types "types"; +import Conversion "conversion"; +import Clone "clone"; + +module { + + type CandyValue = Types.CandyValue; + type CandyValueUnstable = Types.CandyValueUnstable; + type Workspace = Types.Workspace; + type DataZone = Types.DataZone; + type AddressedChunkArray = Types.AddressedChunkArray; + type AddressedChunk = Types.AddressedChunk; + type AddressedChunkBuffer = Types.AddressedChunkBuffer; + type DataChunk = Types.DataChunk; + + + + ////////////////////////////////////////////////////////////////////// + // The following functions enable the inspection and manipulation of workspaces + // Workspaces are valueble when using orthogonal persistance to keep track of data + // in a format that is easily transmitable across the wire given IC restrictions + ////////////////////////////////////////////////////////////////////// + + public func countAddressedChunksInWorkspace(x : Workspace) : Nat{ + + var chunks = 0; + for (thisZone in Iter.range(0, x.size() - 1)){ + chunks += x.get(thisZone).size(); + }; + chunks; + + }; + + public func emptyWorkspace() : Workspace { + + return Buffer.Buffer(1); + }; + + public func initWorkspace(size : Nat) : Workspace { + + return Buffer.Buffer(size); + }; + + //variants take up 2 bytes as long as there are fewer than 32 item in the enum + public func getValueSize(item : CandyValue) : Nat{ + + let varSize = switch(item){ + case(#Int(val)){ + var a : Nat = 0; + var b : Nat = Int.abs(val); + var test = true; + while test { + a += 1; + b := b / 256; + test := b > 0; + }; + a + 1;//add the sign + }; + case(#Int8(val)){1}; + case(#Int16(val)){2}; + case(#Int32(val)){3}; + case(#Int64(val)){4}; + case(#Nat(val)){ + var a : Nat = 0; + var b = val; + var test = true; + while test { + a += 1; + b := b / 256; + test := b > 0; + }; + a; + }; + case(#Nat8(val)){1}; + case(#Nat16(val)){2}; + case(#Nat32(val)){3}; + case(#Nat64(val)){4}; + case(#Float(val)){4}; + case(#Text(val)){(val.size()*4)}; + case(#Bool(val)){1}; + case(#Blob(val)){val.size()}; + case(#Class(val)){ + var size = 0; + for(thisItem in val.vals()){ + size += 1 + (thisItem.name.size() * 4) + getValueSize(thisItem.value); + }; + + return size; + }; + case(#Principal(val)){Conversion.principalToBytes(val).size()};//don't like this but need to confirm it is constant + case(#Option(val)){ + switch val{ + case(null){0}; + case(?val){getValueSize(val)} + } + }; + case(#Array(val)){ + switch(val){ + case(#frozen(val)){ + var size = 0; + for(thisItem in val.vals()){ + size += 1 + getValueSize(thisItem); + }; + + return size; + }; + case(#thawed(val)){ + var size = 0; + for(thisItem in val.vals()){ + size += 1 + getValueSize(thisItem); + }; + + return size; + }; + }; + }; + case(#Bytes(val)){ + switch(val){ + case(#frozen(val)){val.size() + 2}; + case(#thawed(val)){val.size() + 2}; + }; + }; + case(#Floats(val)){ + switch(val){ + case(#frozen(val)){(val.size() * 4) + 2}; + case(#thawed(val)){(val.size() * 4) + 2}; + }; + }; + case(#Nats(val)){ + switch(val){ + case(#frozen(val)){ + var size = 0; + for(thisItem in val.vals()){ + size += 1 + getValueSize(#Nat(thisItem)); + }; + + return size; + }; + case(#thawed(val)){ + var size = 0; + for(thisItem in val.vals()){ + size += 1 + getValueSize(#Nat(thisItem)); + }; + + return size; + }; + }; + }; + case(#Empty){0}; + }; + + return varSize; + }; + + public func getValueUnstableSize(item : CandyValueUnstable) : Nat{ + + let varSize = switch(item){ + case(#Int(val)){ + var a : Nat = 0; + var b : Nat = Int.abs(val); + var test = true; + while test { + a += 1; + b := b / 256; + test := b > 0; + }; + a + 1;//add the sign + }; + case(#Int8(val)){1}; + case(#Int16(val)){2}; + case(#Int32(val)){3}; + case(#Int64(val)){4}; + case(#Nat(val)){ + var a : Nat = 0; + var b = val; + var test = true; + while test { + a += 1; + b := b / 256; + test := b > 0; + }; + a; + }; + case(#Nat8(val)){1}; + case(#Nat16(val)){2}; + case(#Nat32(val)){3}; + case(#Nat64(val)){4}; + case(#Float(val)){4}; + case(#Text(val)){val.size()*4}; + case(#Bool(val)){1}; + case(#Blob(val)){val.size()}; + case(#Class(val)){ + var size = 0; + for(thisItem in val.vals()){ + size += 1 + (thisItem.name.size() * 4) + getValueUnstableSize(thisItem.value); + }; + + return size; + }; + case(#Principal(val)){Conversion.principalToBytes(val).size()};//don't like this but need to confirm it is constant + case(#Option(val)){ + switch val{ + case(null){0}; + case(?val){ getValueUnstableSize(val)} + } + }; + case(#Array(val)){ + switch(val){ + case(#frozen(val)){ + var size = 0; + for(thisItem in val.vals()){ + size += 1 + getValueUnstableSize(thisItem); + }; + + return size; + }; + case(#thawed(val)){ + var size = 0; + for(thisItem in val.vals()){ + size += 1 + getValueUnstableSize(thisItem); + }; + + return size; + }; + }; + }; + + case(#Bytes(val)){ + switch(val){ + case(#frozen(val)){val.size() + 2}; + case(#thawed(val)){val.size() + 2}; + }; + }; + case(#Floats(val)){ + switch(val){ + case(#frozen(val)){(val.size() * 4) + 2}; + case(#thawed(val)){(val.size() * 4) + 2}; + }; + }; + case(#Nats(val)){ + switch(val){ + case(#frozen(val)){ + var size = 0; + for(thisItem in val.vals()){ + size += 1 + getValueUnstableSize(#Nat(thisItem)); + }; + + return size; + }; + case(#thawed(val)){ + var size = 0; + for(thisItem in val.vals()){ + size += 1 + getValueUnstableSize(#Nat(thisItem)); + }; + + return size; + }; + }; + }; + case(#Empty){0}; + }; + return varSize + 2; + }; + + + + public func workspaceToAddressedChunkArray(x : Workspace) : AddressedChunkArray { + + var currentZone = 0; + var currentChunk = 0; + let result = Array.tabulate(countAddressedChunksInWorkspace(x), func(thisChunk){ + let thisChunk = (currentZone, currentChunk, Types.stabalizeValue(x.get(currentZone).get(currentChunk))); + if(currentChunk == Nat.sub(x.get(currentZone).size(),1)){ + currentZone += 1; + currentChunk := 0; + } else { + currentChunk += 1; + }; + thisChunk; + }); + + return result; + }; + + public func workspaceDeepClone(x : Workspace) : Workspace { + + var currentZone = 0; + var currentChunk = 0; + let ws = Buffer.Buffer(x.size()); + for(thisZone in x.vals()){ + + let tz = Buffer.Buffer(thisZone.size()); + ws.add(tz); + for(thisDataChunk in thisZone.vals()){ + tz.add(Clone.cloneValueUnstable(thisDataChunk)); + }; + + }; + return ws; + }; + + public func fromAddressedChunks(x : AddressedChunkArray) : Workspace{ + let result = Buffer.Buffer(x.size()); + fileAddressedChunks(result, x); + return result; + }; + + public func fileAddressedChunks(workspace: Workspace, x : AddressedChunkArray) { + + for (thisChunk : AddressedChunk in Array.vals(x)){ + + let resultSize : Nat = workspace.size(); + let targetZone = thisChunk.0 + 1; + + if(targetZone <= resultSize){ + //zone exist + } else { + //append zone + + for (thisIndex in Iter.range(resultSize, targetZone-1)){ + workspace.add(Buffer.Buffer(1)); + }; + + }; + + let thisZone = workspace.get(thisChunk.0); + + if(thisChunk.1 + 1 <= thisZone.size()){ + //zone exists + thisZone.put(thisChunk.1, Types.destabalizeValue(thisChunk.2)); + } else { + //append zone + + for (newChunk in Iter.range(thisZone.size(), thisChunk.1)){ + + let newBuffer = if(thisChunk.1 == newChunk){ + //we know the size + + Types.destabalizeValue(thisChunk.2); + } else { + #Empty; + }; + thisZone.add(newBuffer); + }; + //return thisZone.get(thisChunk.1); + }; + + + }; + + return ; + }; + + public func getDataZoneSize(dz: DataZone) : Nat { + + var size : Nat = 0; + for(thisChunk in dz.vals()){ + size += getValueUnstableSize(thisChunk); + }; + + return size; + }; + + public func getWorkspaceChunkSize(_workspace: Workspace, _maxChunkSize : Nat) : Nat{ + + var currentChunk : Nat = 0; + var handBrake = 0; + var zoneTracker = 0; + var chunkTracker = 0; + + + label chunking while (1==1){ + handBrake += 1; + if(handBrake > 10000){ break chunking;}; + var foundBytes = 0; + //calc bytes + for(thisZone in Iter.range(zoneTracker, _workspace.size()-1)){ + for(thisChunk in Iter.range(chunkTracker, _workspace.get(thisZone).size()-1)){ + + let thisItem = _workspace.get(thisZone).get(thisChunk); + + let newSize = foundBytes + getValueUnstableSize(thisItem); + + if( newSize > _maxChunkSize) + { + //went over bytes + + currentChunk += 1; + zoneTracker := thisZone; + chunkTracker := thisChunk; + + continue chunking; + }; + //adding some bytes + foundBytes := newSize; + }; + }; + + }; + + currentChunk += 1; + + return currentChunk; + + + }; + + public func getWorkspaceChunk(_workspace: Workspace, _chunkID : Nat, _maxChunkSize : Nat) : ({#eof; #chunk} , AddressedChunkBuffer){ + var currentChunk : Nat = 0; + var handBrake = 0; + var zoneTracker = 0; + var chunkTracker = 0; + + let resultBuffer = Buffer.Buffer(1); + label chunking while (1==1){ + handBrake += 1; + if(handBrake > 10000){ break chunking;}; + var foundBytes = 0; + //calc bytes + for(thisZone in Iter.range(zoneTracker, _workspace.size()-1)){ + for(thisChunk in Iter.range(chunkTracker, _workspace.get(thisZone).size()-1)){ + + let thisItem = _workspace.get(thisZone).get(thisChunk); + + let newSize = foundBytes + getValueUnstableSize(thisItem); + if( newSize > _maxChunkSize) + { + //went over bytes + if(currentChunk == _chunkID){ + return (#chunk, resultBuffer); + }; + currentChunk += 1; + zoneTracker := thisZone; + chunkTracker := thisChunk; + continue chunking; + }; + if(currentChunk == _chunkID){ + //add it to our return + resultBuffer.add((thisZone, thisChunk, Types.stabalizeValue(thisItem))); + + }; + + foundBytes := newSize; + }; + }; + + return (#eof, resultBuffer); + }; + + return (#eof, resultBuffer); + }; + + public func getAddressedChunkArraySize(item : AddressedChunkArray) : Nat{ + + var size : Nat = 0; + for(thisItem in item.vals()){ + size += getValueSize(thisItem.2) + 4 + 4; //only works for up to 32 byte adresess...should be fine but verify and document. + }; + + return size; + }; + + public func getDataChunkFromAddressedChunkArray(item : AddressedChunkArray, dataZone: Nat, dataChunk: Nat) : CandyValue{ + + var size : Nat = 0; + for(thisItem in item.vals()){ + if(thisItem.0 == dataZone and thisItem.1 == dataChunk){ + return thisItem.2; + } + }; + return #Empty; + }; + + + + public func byteBufferDataZoneToBuffer(dz : DataZone): Buffer.Buffer>{ + + let result = Buffer.Buffer>(dz.size()); + for(thisItem in dz.vals()){ + result.add(Conversion.valueUnstableToBytesBuffer(thisItem)); + }; + + return result; + }; + + public func byteBufferChunksToValueUnstableBufferDataZone(buffer : Buffer.Buffer>): DataZone{ + + let result = Buffer.Buffer(buffer.size()); + for(thisItem in buffer.vals()){ + result.add(#Bytes(#thawed(thisItem))); + }; + + return result; + }; + + public func initDataZone(val : CandyValueUnstable) : DataZone{ + + let result = Buffer.Buffer(1); + result.add(val); + return result; + }; + + public func flattenAddressedChunkArray(data : AddressedChunkArray) : [Nat8]{ + //note loses integrity after 256 Zones or 256 chunks + + let accumulator : Buffer.Buffer = Buffer.Buffer(getAddressedChunkArraySize(data)); + for(thisItem in data.vals()){ + + for(thisbyte in Conversion.natToBytes(thisItem.0).vals()){ + accumulator.add(thisbyte); + }; + for(thisbyte in Conversion.natToBytes(thisItem.1).vals()){ + accumulator.add(thisbyte); + }; + + for(thisbyte in Conversion.valueToBytes(thisItem.2).vals()){ + accumulator.add(thisbyte); + }; + + }; + return accumulator.toArray(); + + + }; + + +} \ No newline at end of file diff --git a/test_runner.sh b/test_runner.sh new file mode 100644 index 0000000..b6ba344 --- /dev/null +++ b/test_runner.sh @@ -0,0 +1,22 @@ +set -ex + +dfx identity new test_candy || true +dfx identity use test_candy + +ADMIN_PRINCIPAL=$(dfx identity get-principal) +ADMIN_ACCOUNTID=$(dfx ledger account-id) + +echo $ADMIN_PRINCIPAL +echo $ADMIN_ACCOUNTID + +dfx canister create --all +dfx build --all +dfx canister install test_runner --mode=reinstall +echo "yes" + +TEST_RUNNER_ID=$(dfx canister id test_runner) + +echo $TEST_RUNNER_ID + +dfx canister call test_runner test + diff --git a/tests/test_runner.mo b/tests/test_runner.mo new file mode 100644 index 0000000..4f1ce89 --- /dev/null +++ b/tests/test_runner.mo @@ -0,0 +1,58 @@ + +import C "mo:matchers/Canister"; +import M "mo:matchers/Matchers"; +import T "mo:matchers/Testable"; +import S "mo:matchers/Suite"; +import Debug "mo:base/Debug"; +import Principal "mo:base/Principal"; + +import Types "../src/types"; +import Clone "../src/clone"; +import Conversion "../src/conversion"; +import Properties "../src/properties"; +import Workspace "../src/workspace"; + + + + +shared (deployer) actor class test_runner() = this { + let it = C.Tester({ batchSize = 8 }); + + + + public shared func test() : async {#success; #fail : Text} { + + let suite = S.suite("test nft", [ + //test getting witness returns empty if no witness + S.test("testOwner", switch(await testConversions()){case(#success){true};case(_){false};}, M.equals(T.bool(true))), + + ]); + S.run(suite); + + return #success; + }; + + // US.2 + // US.3 + public shared func testConversions() : async {#success; #fail : Text} { + Debug.print("running testOwner"); + + let owner = Principal.toText(deployer.caller); + + let suite = S.suite("test conversion", [ + + S.test("Nat32 is Nat", Conversion.valueUnstableToNat(#Nat32(10)), M.equals(T.nat(10))) + ]); + + S.run(suite); + + return #success; + + + return #success; + + + }; + + +} \ No newline at end of file diff --git a/vessel.dhall b/vessel.dhall new file mode 100644 index 0000000..19be4cd --- /dev/null +++ b/vessel.dhall @@ -0,0 +1,4 @@ +{ + dependencies = [ "base", "matchers" ], + compiler = None Text +} From 266185edcffa9731f301cfa7c68d646a3095b1de Mon Sep 17 00:00:00 2001 From: skilesare Date: Sat, 5 Mar 2022 16:46:49 -0600 Subject: [PATCH 2/2] update readme --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 5970137..e74efdd 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,18 @@ Library for Converting Types and Creating Workable Motoko Collections This library provides for both Stable and Unstable collections and conversions. These methods help with keeping data in unstable workable runtime memory while providing methods to convert those objects to stable collections that can be put into upgrade variables for persistence across upgrades or for shipping the objects to other canisters and returning them as async functions. +I have refactored the files into separate libraries to silo some of the functionality. + +type.mo - holds most types and few conversion functions to stabilize/destabilize candy values, properties, and workspace types. + +conversion.mo - holds most of the conversion functions + +clone.mo - has some clone functions for deep cloning classes + +properties.mo - property and class functions for updating and manipulating classes + +workspace.mo - useful for keeping workable data in chunks that can be moved around canisters. + CandyValue and CandyValueUnstable allow you to specify your variables in a variant class that makes keeping arrays and buffers of different types managable. ie stable var myCollection : [CandyValueStable] = [#Int(0), #Text("second value"), #Float(3.14)] The property objects (adapted from https://github.com/DepartureLabsIC/non-fungible-token/blob/main/src/property.mo with copyright DepartureLabs and under MIT License, included here for compilability reasons.) allow for Key/Value collections that can be easily created, queried, and updated. We include conversions between stable and unstable properties.