├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── CHANGELOG.md ├── ICRC16.md ├── LICENSE ├── Makefile ├── README.md ├── dfx.json ├── docs ├── candid.html ├── clone.html ├── conversion.html ├── hex.html ├── index.html ├── json.html ├── properties.html ├── styles.css ├── types.html ├── upgrade.html └── workspace.html ├── mops.toml ├── old_package-set.dhall ├── old_vessel.dhall ├── src ├── candid.mo ├── clone.mo ├── conversion copy.mo ├── conversion.mo ├── icrc16 │ └── conversion.mo ├── json.mo ├── properties.mo ├── types.mo ├── upgrade.mo └── workspace.mo ├── test ├── bool_to_bytes.test.mo ├── bytes_to_bool.test.mo ├── candid.test.mo ├── clone.test.mo ├── conversion.test.mo ├── json.test.mo ├── properties.test.mo ├── types.test.mo ├── upgrade.test.mo └── workspaces.test.mo ├── test_runner.sh └── tests └── test_runner.mo /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 0.2.0 8 | pull_request: 9 | 10 | # Remember to update me in package-set.yml as well 11 | env: 12 | vessel_version: "v0.6.4" 13 | moc_version: "0.8.7" 14 | 15 | jobs: 16 | tests: 17 | runs-on: ubuntu-22.04 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions/setup-node@v3 21 | with: 22 | node-version: 14 23 | - uses: aviate-labs/setup-dfx@v0.2.3 24 | with: 25 | dfx-version: 0.15.1 26 | 27 | - name: "install Motoko binaries" 28 | run: | 29 | wget https://github.com/dfinity/motoko/releases/download/${{ env.moc_version }}/motoko-linux64-${{ env.moc_version }}.tar.gz 30 | mkdir -p /home/runner/bin 31 | tar -xzf motoko-linux64-${{ env.moc_version }}.tar.gz -C /home/runner/bin 32 | echo "/home/runner/bin" >> $GITHUB_PATH 33 | 34 | 35 | - name: "install mops" 36 | run: | 37 | npm --yes -g i ic-mops 38 | mops i 39 | 40 | - name: "check-mops" 41 | run: make check-mops 42 | 43 | - name: "docs" 44 | run: make docs 45 | 46 | - name: Deploy to GH Pages 47 | if: ${{ github.event_name != 'pull_request' }} 48 | uses: peaceiris/actions-gh-pages@v3 49 | with: 50 | github_token: ${{ secrets.GITHUB_TOKEN }} 51 | publish_dir: docs 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dfx/ 2 | .vessel/ 3 | .mops/ 4 | *.wasm -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | v0.3.1 2 | 3 | - Added the /icrc16 folder and the conversions.mo file there to begin conversion to icrc16 reference. 4 | - Conversions in /icrc16/conversion.mo generally return result types and should not throw. 5 | - Added tests for /icrc16/conversion.mo 6 | - Update Base 7 | 8 | v0.3.0 9 | 10 | - Added the ValueShared type for dumping a Candy and CandyShared to an ICRC3 style Value type. 11 | - Added many tests. -------------------------------------------------------------------------------- /ICRC16.md: -------------------------------------------------------------------------------- 1 | # ICRC16 CandyShared 2 | 3 | ## Context 4 | 5 | The proposed ICRC16 CandyShared standard defines a Candid interface for unstructured data that canisters can use to exchange document-style data in a standardized way. This standard aims to facilitate the exchange of unstructured data between canisters and improve interoperability between different systems. 6 | 7 | ## Data details 8 | 9 | * icrc: 16 10 | * title: ICRC16 CandyShared 11 | * author: Austin Fatheree - austin dot fatheree at gmail dot come or @afat on twitter 12 | * status: Draft 13 | * category: ICRC 14 | * requires: None 15 | * created: 2023-Mar-10 16 | * updated: [Current date] 17 | 18 | ## Summary 19 | 20 | The ICRC16 standard proposes a Candid interface for unstructured data to facilitate data exchange between canisters in a standardized way. 21 | 22 | ## Introduction 23 | 24 | The proposed standard describes the Candid interface for unstructured data that canisters can use to exchange data in a flexible and interoperable way. This interface is built upon the [Candid serialization format](https://github.com/dfinity/candid) and defines a set of types can be used to handle various types of unstructured data. 25 | 26 | ## Goals 27 | 28 | The main goals of this standard are to: 29 | 30 | * Establish a standard interface for exchanging unstructured data between canisters 31 | * Facilitate the development of standard libraries in Rust, Motoko, Azel, and Kybra that can convert unstructured data into optimized objects 32 | * Improve the interoperability of different systems by enabling a standardized approach to unstructured data exchange 33 | * Simplify the certification and serving of unstructured data, such as JSON data that needs to be served from an Internet Computer canister 34 | 35 | ## Candid Interface Definition 36 | 37 | The ICRC16 CandyShared standard defines a Candid interface for unstructured data that includes the following type: 38 | 39 | ``` 40 | type CandyShared = 41 | variant { 42 | Array: vec CandyShared; 43 | Blob: blob; 44 | Bool: bool; 45 | Bytes: vec nat8; 46 | Class: vec PropertyShared; 47 | Float: float64; 48 | Floats: vec float64; 49 | Int: int; 50 | Int16: int16; 51 | Int32: int32; 52 | Int64: int64; 53 | Int8: int8; 54 | Ints: vec int; 55 | Map: vec record { 56 | CandyShared; 57 | CandyShared; 58 | }; 59 | Nat: nat; 60 | Nat16: nat16; 61 | Nat32: nat32; 62 | Nat64: nat64; 63 | Nat8: nat8; 64 | Nats: vec nat; 65 | Option: opt CandyShared; 66 | Principal: principal; 67 | Set: vec CandyShared; 68 | Text: text; 69 | }; 70 | ``` 71 | 72 | This type defines a set of variants that can be used to represent different types of unstructured data, including arrays, blobs, booleans, bytes, classes, floats, integers, maps, naturals, options, principals, sets, and text. 73 | 74 | 75 | ## Complementary standards 76 | 77 | This standard can be used by other ICRC standards that require metadata or unstructured data exchange, such as: 78 | 79 | * ICRC-12 - Event Publishers can specify that their data - vec Nat8 - is ICRC16 compliant and can be deserialized using from_candid. 80 | * ICRC-14 for game stats - The Value type is already very close to CandyShared. 81 | * ICRC-7 for NFT and other Token standards for metadata. By using ICRC16, these standards would make them selves future compatible. 82 | 83 | ## Possible Extensions and Use Cases 84 | 85 | * ICDevs has developed a motoko library that uses CandyShared and unshares these values into useful structures that can improve the data access and conversion for varius types. These values are stable and can survive upgrades without having to implement pre or post upgrade. https://github.com/icdevs/candy_library/tree/0.2.0 86 | * The Origyn_NFT standard uses the this format for its metadata. It allows the NFT creator maximum freedom in defining the fields they want in their NFT metadata fields. see https://github.com/ORIGYN-SA/origyn_nft/blob/f3d50ec079ec113932d8f67450d67da5df9993fd/src/tests/test_utils.mo#L83 for an example. 87 | * [Zhenya Usenko](https://github.com/ZhenyaUsenko) has the beginning of a library for querying the data structures called CandyPath which could become an addon standard. We should make an ICRC called CandyPath to standardize this and it should be as close to GraphQL as possible. https://github.com/ZhenyaUsenko/motoko-candy-utils 88 | * We should create an ICRC called CandySchema that allows a service to provide a schema for their CandyShared structures that can be validated. 89 | 90 | 91 | ## Implementation 92 | 93 | The ICRC16 standard can be implemented in any language that supports Candid serialization, such as Rust, Motoko, Azel, or Kybra. Implementers can use the standard type and service method to handle unstructured data in a consistent and efficient way. The ICRC16 standard also encourages the development of standard libraries that can convert unstructured data into optimized objects, such as the Candy_Library example provided in the use case section. 94 | 95 | ## Rationale 96 | The need for a standard Candid interface for unstructured data arises from the fact that unstructured data is ubiquitous in many systems, including the Internet Computer. Unstructured data can come in many forms, such as JSON, XML, YAML, or even binary data, and can be used for various purposes, such as exchanging documents, files, or metadata. However, the lack of a standardized approach to unstructured data exchange can create interoperability issues and make it difficult for developers to handle unstructured data in a consistent and efficient way. 97 | 98 | By defining a Candid interface for unstructured data, the ICRC16 standard aims to provide a common ground for canisters to exchange unstructured data in a flexible and interoperable way. This standard defines a set of types that can be used to represent and access different types of unstructured data, including arrays, blobs, maps, and text. The standard also complements other Candid-related standards, such as ICRC-12 for Candid extensions, and can be used by other ICRC standards that require metadata or unstructured data exchange. 99 | 100 | 101 | ## Security Considerations 102 | 103 | The ICRC16 standard defines a Candid interface for unstructured data that can be used to exchange data between canisters. However, care should be taken to ensure that the exchanged data is secure and does not pose a security risk to the system. In particular, canisters should validate the data they receive from other canisters to ensure that it conforms to the expected format and does not contain malicious code or data. 104 | 105 | Implementers of the ICRC16 standard should also consider the security implications of their implementation and follow best practices for secure software development. This includes using secure coding practices, validating user input, sanitizing data, and following the principle of least privilege. Implementers should also consider the potential impact of denial-of-service attacks or other forms of attacks that can exploit vulnerabilities in the system. 106 | 107 | In particular, the size of a CandyShared object could be used in an attack. Depending on your use case, you may want to check the size of the object before storing or processing it to make sure it doesn't violate rational use cases. 108 | 109 | # Conclusion 110 | 111 | The proposed ICRC16 CandyShared standard defines a Candid interface for unstructured data that canisters can use to exchange data in a flexible and interoperable way. This standard aims to simplify the exchange of unstructured data and improve interoperability between different systems. We believe that this standard will be useful for developers who need to handle unstructured data in a consistent and efficient way and that it will facilitate the development of standard libraries and tools that can work with unstructured data. 112 | 113 | We welcome feedback and contributions from the community to help refine and improve this standard. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 - ARAMAKME 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: check docs test 2 | 3 | install-dfx-cache: 4 | dfx cache install 5 | 6 | check: install-dfx-cache 7 | find src -type f -name '*.mo' -print0 | xargs -0 $(shell dfx cache show)/moc $(shell vessel sources) --check 8 | 9 | check-mops: install-dfx-cache 10 | find src -type f -name '*.mo' -print0 | xargs -0 $(shell dfx cache show)/moc $(shell mops sources) --check 11 | 12 | all: check-strict check-strict-mops docs test 13 | 14 | check-strict: install-dfx-cache 15 | find src -type f -name '*.mo' -print0 | xargs -0 $(shell dfx cache show)/moc $(shell vessel sources) -Werror --check 16 | 17 | check-strict-mops: install-dfx-cache 18 | find src -type f -name '*.mo' -print0 | xargs -0 $(shell dfx cache show)/moc $(shell mops sources) -Werror --check 19 | 20 | docs: install-dfx-cache 21 | $(shell dfx cache show)/mo-doc 22 | 23 | test: 24 | printf "yes" | bash test_runner.sh 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # candy_library 2 | 3 | v0.2.0 4 | 5 | Library for Converting Types and Creating Workable Motoko Collections 6 | 7 | This library provides for both Stable and Shared 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. 8 | 9 | #### Todo 10 | - [ ] Tests 11 | - [ ] Examples 12 | 13 | ## Installation 14 | 15 | ``` 16 | mops candy 17 | ``` 18 | 19 | ## Structure 20 | The provided functionality is divided into separate libraries to silo some of the functionality. 21 | 22 | #### type.mo 23 | Holds most types and few conversion functions to stabilize/destabilize candy values, properties, and workspace types. 24 | 25 | `CandyShared` and `Candy` allow you to specify your variables in a variant class that makes keeping arrays and buffers of different types managable. i.e. stable var `myCollection : [CandySharedStable] = [#Int(0), #Text("second value"), #Float(3.14)]`. 26 | 27 | Stabalize and Destablaize functions are provided for both `CandyShared <> Candy`, `Properties <> Property`, `[CandyShared] <> [Candy]`. 28 | 29 | #### conversion.mo 30 | Holds most of the conversion functions. 31 | 32 | We provide the following conversion methods. Note that most of these will `assert(false)` if you try to output an impossible conversion. 33 | 34 | * CandyShared -> Nat 35 | * CandyShared -> Nat8 36 | * CandyShared -> Nat16 37 | * CandyShared -> Nat32 38 | * CandyShared -> Nat64 39 | * CandyShared -> Int 40 | * CandyShared -> Int8 41 | * CandyShared -> Int16 42 | * CandyShared -> Int32 43 | * CandyShared -> Int64 44 | * CandyShared -> Float 45 | * CandyShared -> Text 46 | * CandyShared -> Principal 47 | * CandyShared -> Bool 48 | * CandyShared -> Blob 49 | * CandyShared -> CandyShared Array 50 | * CandyShared -> Byte Array [Nat8] 51 | 52 | * Candy -> Nat 53 | * Candy -> Nat8 54 | * Candy -> Nat16 55 | * Candy -> Nat32 56 | * Candy -> Nat64 57 | * Candy -> Int 58 | * Candy -> Int8 59 | * Candy -> Int16 60 | * Candy -> Int32 61 | * Candy -> Int64 62 | * Candy -> Float 63 | * Candy -> Text 64 | * Candy -> Principal 65 | * Candy -> Bool 66 | * Candy -> Blob 67 | * Candy -> Candy Array 68 | * Candy -> Byte Array [Nat8] 69 | * Candy -> Byte Buffer Buffer.Buffer 70 | * Candy -> Float Buffer Buffer.Buffer 71 | 72 | `#Option` variant types can be unwrapped with `unwrapOptionCandy` and `unwrapOptionCandyShared` and will return `#Empty` if the option was null. 73 | 74 | A toBuffer function will convert an Array to a Buffer of the same type. The is a n on n function and will iterate through each item in the array. 75 | 76 | We provide a number of functions that convert base types to byte arrays and back that can easily be converted to blobs using the Blob base library. Supports: Nat, Nat[64,32,16], Text, Principal, Bool, Int 77 | 78 | #### clone.mo 79 | Has some clone functions for deep cloning classes. The clone functions exist to clone unstable values into new variables. 80 | 81 | #### properties.mo 82 | Property and class functions for updating and manipulating classes. 83 | 84 | 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. 85 | 86 | #### workspace.mo 87 | Useful for keeping workable data in chunks that can be moved around canisters. 88 | 89 | Workspace collections help manage data and can help chunk data into chunks sized to send across another canister or return to a calling client. These calls are limited to ~2MB and this library can help you keep chunks of data together for easy monitoring. Workspaces can be deconstructed into `AddressedChunckArrays` that can be shipped elsewhere and reassembled using the address of the chunks on the other side of an async call. For more information and helper libraries see the pipelinify project. 90 | 91 | * `workspaceToAddressedChunkArray` - convert a workspace to an `AddressedChunkArray` 92 | * `workspaceDeepClone` - clone a workspace 93 | * `fromAddressedChunks` - reconsitute a workspace from and `AddressedChunkArray` 94 | * `getDataZoneSize` - inspects the size of the datazone. 95 | * `getWorkspaceChunkSize` - Gets the number of chunks a workspace will be split into given a max chunk size 96 | * `getWorkspaceChunk` - gets the nth chunk given a max chunk size(recomended 2MB) 97 | * `getAddressedChunkArraySize` - returns the size of an `AddressedChunkArray` 98 | * `getDataChunkFromAddressedChunkArray` - returns the addressed chunk out of an `AddresedChunkArray` 99 | * `byteBufferDataZoneToBuffer` - specifically converts a `DataZone` containing a `ByteBuffer` into a `ByteBuffer` 100 | * `byteBufferChunksToValueSharedBufferDataZone` - converts a `ByteBuffer` into a `DataZone` 101 | * `initDataZone` - initializes a `Datazone` 102 | * `flattenAddressedChunkArray` - Converts an addressed chunk array into a pure byte array. Breaks after 256 Zones or 256 Chunks. 103 | 104 | ## Testing 105 | From the root directory of the project, execute the following command: 106 | 107 | ```bash 108 | printf "yes" | bash test_runner.sh 109 | ``` 110 | 111 | ## Releases 112 | 113 | #### v0.3.1 114 | 115 | - Added the /icrc16 folder and the conversions.mo file there to begin conversion to icrc16 reference. 116 | - Conversions in /icrc16/conversion.mo generally return result types and should not throw. 117 | - Added tests for /icrc16/conversion.mo 118 | - Update Base 119 | 120 | #### v0.3.0 121 | 122 | - Added the ValueShared type for dumping a Candy and CandyShared to an ICRC3 style Value type. 123 | - Added many tests. 124 | 125 | #### v0.2.0 126 | 127 | * Major breaking changes 128 | * Value is now CandyShared 129 | * ValueUnstable is now Candy 130 | * Added a stable hash 131 | * Todo: fund a better eq function than to_candid, from_candid 132 | 133 | #### v0.1.12 134 | 135 | * Refactor - Cleaned up code 136 | * JSON - added a library to dump values to JSON 137 | 138 | ## Note 139 | 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. -------------------------------------------------------------------------------- /dfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "canisters": { 3 | "test_runner": { 4 | "main": "tests/test_runner.mo", 5 | "type": "motoko" 6 | } 7 | }, 8 | "defaults": { 9 | "build": { 10 | "args": "", 11 | "packtool": "mops sources" 12 | } 13 | }, 14 | "networks": { 15 | "local": { 16 | "bind": "127.0.0.1:8000", 17 | "type": "ephemeral" 18 | } 19 | }, 20 | "version": 1 21 | } -------------------------------------------------------------------------------- /docs/candid.html: -------------------------------------------------------------------------------- 1 | 2 |

candid

Candid support for the candy values.

3 |

public func value_to_candid(val : Types.CandyShared) : [Arg.Arg]

Convert a CandyShared to an Array of Candid Arg.

4 |

Example:

5 |
motoko include=import
6 | let val: CandyShared = #Option(?#Principal(Principal.fromText("xyz")));
7 | let candid = Candid.value_to_candid(val);

-------------------------------------------------------------------------------- /docs/clone.html: -------------------------------------------------------------------------------- 1 | 2 |

clone

Cloning support for the candy values.

3 |

This module contains a few utilities for deep cloning candy values.

4 |

public func cloneCandy(val : Candy) : Candy

Deep clone a Candy.

5 |

Example:

6 |
motoko include=import
7 | let val: Candy = #Option(?#Principal(Principal.fromText("xyz")));
8 | let cloned_val = Clone.cloneCandy(val);

-------------------------------------------------------------------------------- /docs/hex.html: -------------------------------------------------------------------------------- 1 | 2 |

hex

Hexadecimal support for the candy library.

3 |

This module contains the utilities useful for handling hexadecimal values.

4 |

type DecodeError = {#msg : Text}

Defines a type to indicate that the decoder has failed.

5 |

public func encode(array : [Nat8]) : Text

Encode an array of unsigned 8-bit integers in hexadecimal format.

6 |

public func decode(text : Text) : Result<[Nat8], DecodeError>

Decode an array of unsigned 8-bit integers in hexadecimal format.

7 |

Returns a DecodeError if the decoding is unsuccessful.

8 |

-------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 |

Index of modules

  • candid

    Candid support for the candy values.

    3 |
  • clone

    Cloning support for the candy values.

    4 |

    This module contains a few utilities for deep cloning candy values.

    5 |
  • conversion

    Conversion utilities for the candy library.

    6 |

    This module contains the conversion functions to convert values to & from 7 | candy values.

    8 |
  • hex

    Hexadecimal support for the candy library.

    9 |

    This module contains the utilities useful for handling hexadecimal values.

    10 |
  • json

    JSON support for the candy library.

    11 |

    This module contains the utilities useful for handling JSON values.

    12 |
  • properties

    Properties for the candy library.

    13 |

    This module contains the properties and class functions for updating and 14 | manipulating classes.

    15 |
  • types

    Types for the candy library.

    16 |

    This module contains the types that denote candy values, properties 17 | and workspace. 18 | It also contains a few converstion functions to serialize/deserialize 19 | & stabilize/destabilize candy values.

    20 |
  • upgrade

    Upgrade utilities for the candy library.

    21 |

    This module contains the utility functions to upgrade candy values from version 1 to version 2.

    22 |
  • workspace

    Workspace utilities for the candy library.

    23 |

    This module contains the utilities useful for keeping workable data in 24 | chunks that can be moved around canisters. They enable the inspection and 25 | manipulation of workspaces.

    26 |
-------------------------------------------------------------------------------- /docs/json.html: -------------------------------------------------------------------------------- 1 | 2 |

json

JSON support for the candy library.

3 |

This module contains the utilities useful for handling JSON values.

4 |

public func value_to_json(val : Types.CandyShared) : Text

Convert CandyShared to JSON format as Text.

5 |

Example:

6 |
motoko include=import
7 | let val: CandyShared = #Option(?#Principal(Principal.fromText("xyz")));
8 | let val_as_json = Json.value_to_json(val);

-------------------------------------------------------------------------------- /docs/properties.html: -------------------------------------------------------------------------------- 1 | 2 |

properties

Properties for the candy library.

3 |

This module contains the properties and class functions for updating and 4 | manipulating classes.

5 |

public func getPropertiesShared(properties : PropertiesShared, qs : [Query]) : Result.Result<PropertiesShared, PropertySharedError>

Get a subset of fields from the PropertiesShared based on the given query.

6 |

Example:

7 |
motoko include=import
  8 | let properties: PropertiesShared = [
  9 |   {
 10 |    name = "prop1";
 11 |    value = #Principal(Principal.fromText("abc"));
 12 |    immutable = false;
 13 |   },
 14 |   {
 15 |    name = "prop2";
 16 |    value = #Nat8(44);
 17 |    immutable = true;
 18 |   },
 19 |   {
 20 |    name = "prop3";
 21 |    value = #Class(
 22 |     {
 23 |       name = "class_field1";
 24 |       value = #Nat(222);
 25 |       immutable = false;
 26 |     },
 27 |     {
 28 |       name = "class_field2";
 29 |       value = #Text("sample");
 30 |       immutable = true;
 31 |     }
 32 |    );
 33 |    immutable = false;
 34 |   }
 35 | ];
 36 | let qs = [
 37 |  {
 38 |    name = "prop2"
 39 |  },
 40 |  {
 41 |    name = "prop3";
 42 |    next = [
 43 |      {
 44 |        name = "class_field2";
 45 |      }
 46 |    ];
 47 |  }
 48 | ];
 49 | // Will return prop2 and the class_field2 from prop3.
 50 | let subset_result = Properties.getPropertiesShared(properties, qs);

Note: Ignores unknown properties.

51 |

public func updatePropertiesShared(properties : PropertiesShared, us : [UpdateShared]) : Result.Result<PropertiesShared, PropertySharedError>

Updates the given properties based on the given update query.

52 |

Example:

53 |
motoko include=import
 54 | let properties: PropertiesShared = [
 55 |   {
 56 |    name = "prop1";
 57 |    value = #Principal(Principal.fromText("abc"));
 58 |    immutable = true;
 59 |   },
 60 |   {
 61 |    name = "prop2";
 62 |    value = #Nat8(44);
 63 |    immutable = false;
 64 |   },
 65 |   {
 66 |    name = "prop3";
 67 |    value = #Class(
 68 |     {
 69 |       name = "class_field1";
 70 |       value = #Nat(222);
 71 |       immutable = false;
 72 |     },
 73 |     {
 74 |       name = "class_field2";
 75 |       value = #Text("sample");
 76 |       immutable = true;
 77 |     }
 78 |    );
 79 |    immutable = false;
 80 |   }
 81 | ];
 82 | let us = [
 83 |  {
 84 |    name = "prop1",
 85 |    mode = #Set(#Nat8(66))
 86 |  },
 87 |  {
 88 |    name = "prop3";
 89 |    mode = #Next([
 90 |     {
 91 |       name = "class_field1";
 92 |       mode = #Lock(#Nat(333)); 
 93 |     }
 94 |    ])
 95 |  }
 96 | ];
 97 | // Will update prop1 and the class_field1 from prop3 to new values.
 98 | let updated_properties = Properties.updatePropertiesShared(properties, us);

Note:

99 |
  • Creates unknown properties.
  • Returns error if the query tries to update an immutable property.

public func getClassPropertyShared(val : CandyShared, name : Text) : ?PropertyShared

Updates the given properties based on the given update query.

100 |

Example:

101 |
motoko include=import
102 | let c  = #Class(
103 |  {
104 |    name = "class_field1";
105 |    value = #Nat(222);
106 |    immutable = false;
107 |  },
108 |  {
109 |    name = "class_field2";
110 |    value = #Text("sample");
111 |    immutable = true;
112 |  }
113 | );
114 | let prop = Properties.getClassPropertyShared(c, "class_field1");

Note: Returns null if:

115 |
  • The underlying value isn't a #Class.
  • The property with the given name wasn't found inside the class.

public func getProperties(properties : Properties, qs : [Query]) : Result.Result<Properties, PropertySharedError>

Get a subset of fields from the Properties based on the given query.

116 |

Example:

117 |
motoko include=import
118 | let properties: Properties = [
119 |   {
120 |    name = "prop1";
121 |    value = #Principal(Principal.fromText("abc"));
122 |    immutable = false;
123 |   },
124 |   {
125 |    name = "prop2";
126 |    value = #Nat8(44);
127 |    immutable = true;
128 |   },
129 |   {
130 |    name = "prop3";
131 |    value = #Class(
132 |     {
133 |       name = "class_field1";
134 |       value = #Nat(222);
135 |       immutable = false;
136 |     },
137 |     {
138 |       name = "class_field2";
139 |       value = #Text("sample");
140 |       immutable = true;
141 |     }
142 |    );
143 |    immutable = false;
144 |   }
145 | ];
146 | let qs = [
147 |  {
148 |    name = "prop2"
149 |  },
150 |  {
151 |    name = "prop3";
152 |    next = [
153 |      {
154 |        name = "class_field2";
155 |      }
156 |    ];
157 |  }
158 | ];
159 | // Will return prop2 and the class_field2 from prop3.
160 | let subset_result = Properties.getProperties(properties, qs);

Note: Ignores unknown properties.

161 |

public func updateProperties(properties : Properties, us : [Update]) : Result.Result<Properties, PropertySharedError>

Updates the given properties based on the given update query.

162 |

Example:

163 |
motoko include=import
164 | let properties: Properties = [
165 |   {
166 |    name = "prop1";
167 |    value = #Principal(Principal.fromText("abc"));
168 |    immutable = true;
169 |   },
170 |   {
171 |    name = "prop2";
172 |    value = #Nat8(44);
173 |    immutable = false;
174 |   },
175 |   {
176 |    name = "prop3";
177 |    value = #Class(
178 |     {
179 |       name = "class_field1";
180 |       value = #Nat(222);
181 |       immutable = false;
182 |     },
183 |     {
184 |       name = "class_field2";
185 |       value = #Text("sample");
186 |       immutable = true;
187 |     }
188 |    );
189 |    immutable = false;
190 |   }
191 | ];
192 | let us = [
193 |  {
194 |    name = "prop1",
195 |    mode = #Set(#Nat8(66))
196 |  },
197 |  {
198 |    name = "prop3";
199 |    mode = #Next([
200 |     {
201 |       name = "class_field1";
202 |       mode = #Lock(#Nat(333)); 
203 |     }
204 |    ])
205 |  }
206 | ];
207 | // Will update prop1 and the class_field1 from prop3 to new values.
208 | let updated_properties = Properties.updateProperties(properties, us);

Note:

209 |
  • Creates unknown properties.
  • Returns error if the query tries to update an immutable property.

-------------------------------------------------------------------------------- /docs/styles.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | box-sizing: border-box; 4 | } 5 | 6 | body { 7 | background: #fff; 8 | color: #222; 9 | font-family: Circular Std, sans-serif; 10 | line-height: 1.15; 11 | -webkit-font-smoothing: antialiased; 12 | margin: 0; 13 | font-size: 1.0625rem; 14 | } 15 | 16 | .keyword { 17 | color: #264059; 18 | } 19 | 20 | .type { 21 | color: #ad448e; 22 | } 23 | 24 | .parameter { 25 | color: #264059; 26 | } 27 | 28 | .classname { 29 | color: #2c8093; 30 | } 31 | 32 | .fnname { 33 | color: #9a6e31; 34 | } 35 | 36 | .sidebar { 37 | width: 200px; 38 | position: fixed; 39 | left: 0; 40 | top: 0; 41 | bottom: 0; 42 | overflow: auto; 43 | 44 | background-color: #F1F1F1; 45 | } 46 | 47 | .documentation { 48 | margin-left: 230px; 49 | max-width: 960px; 50 | } 51 | 52 | .sidebar > ul { 53 | margin: 0 10px; 54 | padding: 0; 55 | list-style: none; 56 | } 57 | 58 | .sidebar a { 59 | display: block; 60 | text-overflow: ellipsis; 61 | overflow: hidden; 62 | line-height: 15px; 63 | padding: 7px 5px; 64 | font-size: 14px; 65 | font-weight: 400; 66 | transition: border 500ms ease-out; 67 | color: #000; 68 | text-decoration: none; 69 | } 70 | 71 | .sidebar h3 { 72 | border-bottom: 1px #dddddd solid; 73 | font-weight: 500; 74 | margin: 20px 0 15px 0; 75 | padding-bottom: 6px; 76 | text-align: center; 77 | } 78 | 79 | .declaration { 80 | border-bottom: 1px solid #f0f0f0; 81 | } 82 | .declaration:last-of-type { 83 | border-bottom: none; 84 | } 85 | 86 | .declaration :last-child { 87 | border: none; 88 | } 89 | 90 | h1 { 91 | font-weight: 500; 92 | padding-bottom: 6px; 93 | border-bottom: 1px #D5D5D5 dashed; 94 | } 95 | 96 | h4.function-declaration { 97 | font-weight: 600; 98 | margin-top: 16px; 99 | } 100 | 101 | h4.value-declaration { 102 | font-weight: 600; 103 | margin-top: 16px; 104 | } 105 | 106 | h4.type-declaration { 107 | font-weight: 600; 108 | margin-top: 16px; 109 | } 110 | 111 | h4.class-declaration { 112 | font-weight: 600; 113 | margin-top: 16px; 114 | } 115 | 116 | .class-declaration ~ div { 117 | margin-left: 20px; 118 | } 119 | 120 | .index-container { 121 | display: flex; 122 | flex-direction: column; 123 | align-items: center; 124 | font-size: 1.2rem; 125 | } 126 | 127 | .index-header { 128 | font-weight: 400; 129 | } 130 | 131 | .index-listing { 132 | padding: 0; 133 | list-style: none; 134 | max-width: 960px; 135 | } 136 | 137 | .index-item { 138 | margin-bottom: 5px; 139 | } 140 | 141 | .index-item-link { 142 | text-decoration: none; 143 | font-weight: 400; 144 | } 145 | 146 | .index-item-link::after { 147 | content: " \2014\00A0"; 148 | } 149 | 150 | .index-item-comment { 151 | display: inline; 152 | } 153 | 154 | .index-item-comment > * { 155 | display: none; 156 | } 157 | 158 | .index-item-comment > *:first-child { 159 | display: inline; 160 | white-space: nowrap; 161 | } 162 | 163 | -------------------------------------------------------------------------------- /docs/types.html: -------------------------------------------------------------------------------- 1 | 2 |

types

Types for the candy library.

3 |

This module contains the types that denote candy values, properties 4 | and workspace. 5 | It also contains a few converstion functions to serialize/deserialize 6 | & stabilize/destabilize candy values.

7 |

type Properties = Map.Map<Text, Property>

A collection of Property.

8 |

type Property = { name : Text; value : Candy; immutable : Bool }

Specifies a single unstable property.

9 |

type UpdateRequestShared = { id : Text; update : [UpdateShared] }

Specifies the unstable properties that should be updated to a certain value.

10 |

type UpdateShared = { name : Text; mode : UpdateModeShared }

Update information for a single property.

11 |

type UpdateModeShared = {#Set : CandyShared; #Lock : CandyShared; #Next : [UpdateShared]}

Mode for the update operation.

12 |

type PropertyShared = { name : Text; value : CandyShared; immutable : Bool }

Specifies a single property.

13 |

type PropertiesShared = [PropertyShared]

A collection of PropertyShared.

14 |

type PropertySharedError = {#Unauthorized; #NotFound; #InvalidRequest; #AuthorizedPrincipalLimitReached : Nat; #Immutable}

Specifies an error which occurred during an operation on a PropertyShared.

15 |

type Query = { name : Text; next : [Query] }

Specifies the properties that should be queried.

16 |

type QueryMode = {#All; #Some : [Query]}

Mode for the query operation.

17 |

type UpdateRequest = { id : Text; update : [Update] }

Specifies the properties that should be updated to a certain value.

18 |

type Update = { name : Text; mode : UpdateMode }

Update information for a single property.

19 |

type UpdateMode = {#Set : Candy; #Lock : Candy; #Next : [Update]}

Mode for the update operation.

20 |

type CandyShared = {#Int : Int; #Int8 : Int8; #Int16 : Int16; #Int32 : Int32; #Int64 : Int64; #Ints : [Int]; #Nat : Nat; #Nat8 : Nat8; #Nat16 : Nat16; #Nat32 : Nat32; #Nat64 : Nat64; #Float : Float; #Text : Text; #Bool : Bool; #Blob : Blob; #Class : [PropertyShared]; #Principal : Principal; #Option : ?CandyShared; #Array : [CandyShared]; #Nats : [Nat]; #Floats : [Float]; #Bytes : [Nat8]; #Map : [(CandyShared, CandyShared)]; #Set : [CandyShared]}

The Stable CandyShared.

21 |

type Candy = {#Int : Int; #Int8 : Int8; #Int16 : Int16; #Int32 : Int32; #Int64 : Int64; #Ints : StableBuffer.StableBuffer<Int>; #Nat : Nat; #Nat8 : Nat8; #Nat16 : Nat16; #Nat32 : Nat32; #Nat64 : Nat64; #Float : Float; #Text : Text; #Bool : Bool; #Blob : Blob; #Class : Map.Map<Text, Property>; #Principal : Principal; #Floats : StableBuffer.StableBuffer<Float>; #Nats : StableBuffer.StableBuffer<Nat>; #Array : StableBuffer.StableBuffer<Candy>; #Option : ?Candy; #Bytes : StableBuffer.StableBuffer<Nat8>; #Map : Map.Map<Candy, Candy>; #Set : Set.Set<Candy>}

The Shared CandyShared.

22 |

type DataChunk = Candy

Note: A DataChunk should be no larger than 2MB so that it can be shipped to other canisters.

23 |

type DataZone = StableBuffer.StableBuffer<DataChunk>

type Workspace = StableBuffer.StableBuffer<DataZone>

Workspaces are valueble when using orthogonal persistance to keep track of data in a format 24 | that is easily transmitable across the wire given IC restrictions

25 |

type AddressedChunk = (Nat, Nat, CandyShared)

type AddressedChunkArray = [AddressedChunk]

type AddressedChunkBuffer = StableBuffer.StableBuffer<AddressedChunk>

public func shareCandy(item : Candy) : CandyShared

Convert a Candy to CandyShared.

26 |

Example:

27 |
motoko include=import
28 | let unstable: Candy = #Principal(Principal.fromText("abc"));
29 | let shareCandy = Types.shareCandy(unstable);

public func unshare(item : CandyShared) : Candy

Convert a CandyShared to Candy.

30 |

Example:

31 |
motoko include=import
32 | let stable: CandyShared = #Principal(Principal.fromText("abc"));
33 | let unsharedValue = Types.unshare(unstable);

public func shareProperty(item : Property) : PropertyShared

Convert a Property to PropertyShared.

34 |

Example:

35 |
motoko include=import
36 | let unstable: Property = {
37 |    name = "name";
38 |    value = #Principal(Principal.fromText("abc"));
39 |    immutable = false;
40 |  };
41 | let stablePropertyShared = Types.shareProperty(unstable);

public func unshareProperty(item : PropertyShared) : Property

Convert a PropertyShared to Property.

42 |

Example:

43 |
motoko include=import
44 | let stablePropertyShared: PropertyShared = {
45 |    name = "name";
46 |    value = #Principal(Principal.fromText("abc"));
47 |    immutable = true;
48 |  };
49 | let unstablePropertyShared = Types.unshareProperty(stablePropertyShared);

public func shareCandyArray(items : [Candy]) : [CandyShared]

Convert a [Candy] to [CandyShared].

50 |

Example:

51 |
motoko include=import
52 |  let array: [Candy] = [#Principal(Principal.fromText("abc")), #Int(1)];
53 |  let arrayStable = Types.shareCandyArray(array);

public func unshareArray(items : [CandyShared]) : [Candy]

Convert a [CandyShared] to [Candy].

54 |

Example:

55 |
motoko include=import
56 |  let arrayStable: [CandyShared] = [#Principal(Principal.fromText("abc")), #Int(1)];
57 |  let array = Types.unshareArray(arrayStable);

public func shareCandyBuffer(items : DataZone) : [CandyShared]

Convert a DataZone to [CandyShared].

58 |

Example:

59 |
motoko include=import
60 |  let dataZone = Types.toBuffer<DataChunk>([#Int32(5), #Int(1)]);
61 |  let sharedValues = Types.shareCandyBuffer(dataZone);

public func toBuffer<T>(x : [T]) : StableBuffer.StableBuffer<T>

Create a Buffer from [T] where T can be of any type.

62 |

Example:

63 |
motoko include=import
64 |  let array = [1, 2, 3];
65 |  let buf = Types.toBuffer<Nat>(array);  

public func hash(x : Candy) : Nat

Get the hash of the CandyShared.

66 |

Example:

67 |
motoko include=import
68 | let x: CandyShared = #Principal(Principal.fromText("abc"));
69 | let h = Types.hash(x);

public func eq(x : Candy, y : Candy) : Bool

Checks the two CandyShared params for equality.

70 |

Example:

71 |
motoko include=import
72 | let x: CandyShared = #Int(1);
73 | let y: CandyShared = #Int(2);
74 | let z: CandyShared = #Int(1);
75 | let x_y = Types.eq(x, y); // false
76 | let x_z = Types.eq(x, z); // true

public let candyMapHashTool :

public func hashShared(x : CandyShared) : Nat

Get the hash of the Candy.

77 |

Example:

78 |
motoko include=import
79 | let x: Candy = #Principal(Principal.fromText("abc"));
80 | let h = Types.hashShared(x);  

public func eqShared(x : CandyShared, y : CandyShared) : Bool

Checks the two CandyShared params for equality.

81 |

Example:

82 |
motoko include=import
83 | let x: Candy = #Int(1);
84 | let y: Candy = #Int(2);
85 | let z: Candy = #Int(1);
86 | let x_y = Types.eq(x, y); // false
87 | let x_z = Types.eq(x, z); // true

public let candySharedMapHashTool :

-------------------------------------------------------------------------------- /docs/upgrade.html: -------------------------------------------------------------------------------- 1 | 2 |

upgrade

Upgrade utilities for the candy library.

3 |

This module contains the utility functions to upgrade candy values from version 1 to version 2.

4 |

public func upgradeCandy(item : CandyOld.CandyValueUnstable) : CandyTypes.Candy

Upgrade from V1 representation of CandyShared to the V2 representation.

5 |

public func upgradeCandyShared(item : CandyOld.CandyValue) : CandyTypes.CandyShared

Upgrade from V1 representation of CandySharedUnstable to the V2 representation 'CandyValudShared'.

6 |

-------------------------------------------------------------------------------- /docs/workspace.html: -------------------------------------------------------------------------------- 1 | 2 |

workspace

Workspace utilities for the candy library.

3 |

This module contains the utilities useful for keeping workable data in 4 | chunks that can be moved around canisters. They enable the inspection and 5 | manipulation of workspaces.

6 |

public func countAddressedChunksInWorkspace(x : Workspace) : Nat

Get the count of addressed chunks in the given Workspace.

7 |

Example:

8 |
motoko include=import
 9 | let ws = Workspace.initWorkspace(3);
10 | StableBuffer.add<DataZone>(ws, StableBuffer.initPresized<DataChunk>(1));
11 | StableBuffer.add<DataZone>(ws, StableBuffer.initPresized<DataChunk>(5));
12 | StableBuffer.add<DataZone>(ws, StableBuffer.initPresized<DataChunk>(2));
13 | let count = Workspace.countAddressedChunksInWorkspace(ws); // 8.

public func emptyWorkspace() : Workspace

Create an empty Workspace.

14 |

public func initWorkspace(size : Nat) : Workspace

Initialize a Workspace with the given capacity.

15 |

public func getCandySize(item : Candy) : Nat

Get the size in bytes taken up by the Candy.

16 |

public func getCandySharedSize(item : CandyShared) : Nat

Get the size in bytes taken up by the CandyShared.

17 |

public func workspaceToAddressedChunkArray(x : Workspace) : AddressedChunkArray

Convert Workspace to AddressedChunkArray.

18 |

Example:

19 |
motoko include=import
20 | let ws = Workspace.initWorkspace(3);
21 | StableBuffer.add<DataZone>(ws, StableBuffer.initPresized<DataChunk>(1));
22 | StableBuffer.add<DataZone>(ws, StableBuffer.initPresized<DataChunk>(5));
23 | StableBuffer.add<DataZone>(ws, StableBuffer.initPresized<DataChunk>(2));
24 | let addressed_chunk_array = Workspace.workspaceToAddressedChunkArray(ws);

public func workspaceDeepClone(x : Workspace) : Workspace

Get a deep clone of the given Workspace.

25 |

Example:

26 |
motoko include=import
27 | let ws = Workspace.initWorkspace(3);
28 | StableBuffer.add<DataZone>(ws, StableBuffer.initPresized<DataChunk>(1));
29 | StableBuffer.add<DataZone>(ws, StableBuffer.initPresized<DataChunk>(5));
30 | StableBuffer.add<DataZone>(ws, StableBuffer.initPresized<DataChunk>(2));
31 | let ws_clone = Workspace.workspaceDeepClone(ws);

public func fromAddressedChunks(x : AddressedChunkArray) : Workspace

Create a Workspace from an AddressedChunkArray.

32 |

Example:

33 |
motoko include=import
34 | let arr: AddressedChunkArray = [(1, 1, #Nat(10)), (8, 4, #Float(3.14))];
35 | let ws = Workspace.fromAddressedChunks(arr);

public func fileAddressedChunks(workspace : Workspace, x : AddressedChunkArray)

public func getDataZoneSize(dz : DataZone) : Nat

Get the size in bytes taken up by all values in the DataZone.

36 |

public func getWorkspaceChunkSize(_workspace : Workspace, _maxChunkSize : Nat) : Nat

Get the number of chunks for Workspace when dividing into chunks of size <= _maxChunkSize .

37 |

Example:

38 |
motoko include=import
39 | let ws = Workspace.initWorkspace(3);
40 | StableBuffer.add<DataZone>(ws, StableBuffer.initPresized<DataChunk>(1));
41 | StableBuffer.add<DataZone>(ws, StableBuffer.initPresized<DataChunk>(5));
42 | StableBuffer.add<DataZone>(ws, StableBuffer.initPresized<DataChunk>(2));
43 | let chunk_size = Workspace.getWorkspaceChunkSize(ws, 2);

public func getWorkspaceChunk(
  _workspace : Workspace,
  _chunkID : Nat,
  _maxChunkSize : Nat
) : ({#eof; #chunk}, AddressedChunkBuffer)

public func getAddressedChunkArraySize(item : AddressedChunkArray) : Nat

Get the size of the AddressedChunkArray.

44 |

Example:

45 |
motoko include=import
46 | let arr: AddressedChunkArray = [(1, 1, #Nat(10)), (8, 4, #Float(3.14))];
47 | let size = Workspace.getAddressedChunkArraySize(arr); // 21

Note: This only works for up to 32 bytes addresses.

48 |

public func getDataChunkFromAddressedChunkArray(
  item : AddressedChunkArray,
  dataZone : Nat,
  dataChunk : Nat
) : CandyShared

Get the data chunk from a AddressedChunkArray at the given zone and chunk.

49 |

Example:

50 |
motoko include=import
51 | let arr: AddressedChunkArray = [(1, 1, #Nat(10)), (8, 4, #Float(3.14))];
52 | let chunk = Workspace.getDataChunkFromAddressedChunkArray(arr, 8, 4); // #Float(3.14)

public func byteBufferDataZoneToBuffer(dz : DataZone) : Buffer.Buffer<Buffer.Buffer<Nat8>>

public func byteBufferChunksToCandyBufferDataZone(buffer : Buffer.Buffer<Buffer.Buffer<Nat8>>) : DataZone

public func initDataZone(val : Candy) : DataZone

Initialize a DataZone with the given value.

53 |

public func flattenAddressedChunkArray(data : AddressedChunkArray) : [Nat8]

Flatten an AddressedChunkArray to produce [Nat8].

54 |

Example:

55 |
motoko include=import
56 | let arr: AddressedChunkArray = [(1, 1, #Nat(10)), (8, 4, #Float(3.14))];
57 | let bytes = Workspace.flattenAddressedChunkArray(arr);

Note: Loses integrity after 256 Zones or 256 chunks.

58 |

-------------------------------------------------------------------------------- /mops.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | matchers = "https://github.com/kritzcreek/motoko-matchers#v1.3.0@3dac8a071b69e4e651b25a7d9683fe831eb7cffd" 3 | candid-old = "https://github.com/gekctek/motoko_candid#v1.0.1@4f0c445a4f998d4d07b616fafd463eea8f52e4bc" 4 | xtendedNumbers = "https://github.com/gekctek/motoko_numbers#v1.0.2@775995c49c55f7c53e5c9e5a52dc3536201064b2" 5 | stablebuffer = "https://github.com/skilesare/StableBuffer#v0.2.0@110660769d11ba93c618dc4712525d20503bdc37" 6 | stablebuffer_1_3_0 = "https://github.com/canscale/StableBuffer#v1.3.0@acdde6bb5b939227997cebdbb8919d2e6da8691c" 7 | map7 = "https://github.com/ZhenyaUsenko/motoko-hash-map#v7.0.0@f0e25632e5da80118274e78ceeb5bec95f3c2b81" 8 | map9 = "https://github.com/ZhenyaUsenko/motoko-hash-map#v9.0.1@10b68f6ea8df5e72dfa4c07a50c8bb60a916c233" 9 | candy_0_1_12 = "https://github.com/icdevs/candy_library#v0.1.12@20db7a8a74258bb07c7d354ea477bf95121747a3" 10 | candy_0_2_0 = "https://github.com/icdevs/candy_library#0.2.0@4fc5aebec44355da94a4d3ebef87623e4545d89a" 11 | encoding_0_4_1 = "https://github.com/aviate-labs/encoding.mo#v0.4.1@2711d18727e954b11afc0d37945608512b5fbce2" 12 | vector = "0.2.0" 13 | principal-ext = "0.1.0" 14 | base = "0.14.4" 15 | 16 | [package] 17 | name = "candy" 18 | version = "0.3.1" 19 | description = "Library for Converting Types and Creating Workable Motoko Collections" 20 | repository = "https://github.com/icdevs/candy_library" 21 | 22 | [dev-dependencies] 23 | test = "1.1.0" 24 | fuzz = "0.1.1" -------------------------------------------------------------------------------- /old_package-set.dhall: -------------------------------------------------------------------------------- 1 | let vessel_package_set = 2 | https://github.com/dfinity/vessel-package-set/releases/download/mo-0.8.3-20230224/package-set.dhall 3 | let Package = 4 | { name : Text, version : Text, repo : Text, dependencies : List Text } 5 | 6 | let 7 | -- This is where you can add your own packages to the package-set 8 | additions = 9 | [{ name = "candid" 10 | , version = "v1.0.1" 11 | , repo = "https://github.com/gekctek/motoko_candid" 12 | , dependencies = ["xtendedNumbers", "base"] : List Text 13 | }, 14 | { name = "candy_0_1_12" 15 | , version = "v0.1.12" 16 | , repo = "https://github.com/icdevs/candy_library" 17 | , dependencies = ["base"] : List Text 18 | }, 19 | { name = "xtendedNumbers" 20 | , version = "v1.0.2" 21 | , repo = "https://github.com/gekctek/motoko_numbers" 22 | , dependencies = [] : List Text 23 | }, 24 | { name = "stablebuffer" 25 | , repo = "https://github.com/skilesare/StableBuffer" 26 | , version = "v0.2.0" 27 | , dependencies = [ "base"] 28 | }, 29 | { name = "base", repo = "https://github.com/dfinity/motoko-base.git", version = "moc-0.8.1", dependencies = []: List Text }, 30 | { name = "map7" 31 | , repo = "https://github.com/ZhenyaUsenko/motoko-hash-map" 32 | , version = "v7.0.0" 33 | , dependencies = [ "base"] 34 | },] : List Package 35 | 36 | 37 | 38 | in vessel_package_set # additions -------------------------------------------------------------------------------- /old_vessel.dhall: -------------------------------------------------------------------------------- 1 | { 2 | dependencies = [ "base", "matchers", "candid", "xtendedNumbers", "stablebuffer", "map7","candy_0_1_12"], 3 | compiler = Some "0.8.1", 4 | } 5 | -------------------------------------------------------------------------------- /src/candid.mo: -------------------------------------------------------------------------------- 1 | /// Candid support for the candy values. 2 | 3 | import Buffer "mo:base/Buffer"; 4 | import Nat "mo:base/Nat"; 5 | import Iter "mo:base/Iter"; 6 | import Nat16 "mo:base/Nat16"; 7 | import Nat32 "mo:base/Nat32"; 8 | import Nat64 "mo:base/Nat64"; 9 | import Nat8 "mo:base/Nat8"; 10 | import Float "mo:base/Float"; 11 | import Int "mo:base/Int"; 12 | import Int8 "mo:base/Int8"; 13 | import Int16 "mo:base/Int16"; 14 | import Int32 "mo:base/Int32"; 15 | import Int64 "mo:base/Int64"; 16 | import Bool "mo:base/Bool"; 17 | import Blob "mo:base/Blob"; 18 | import Principal "mo:base/Principal"; 19 | import Text "mo:base/Text"; 20 | 21 | import Map "mo:map9/Map"; 22 | 23 | import Types "types"; 24 | 25 | import CandidTypes "mo:candid-old/Type"; 26 | import Arg "mo:candid-old/Arg"; 27 | import Value "mo:candid-old/Value"; 28 | 29 | 30 | module { 31 | /// Convert a `CandyShared` to an Array of Candid `Arg`. 32 | /// 33 | /// Example: 34 | /// ```motoko include=import 35 | /// let val: CandyShared = #Option(?#Principal(Principal.fromText("xyz"))); 36 | /// let candid = Candid.value_to_candid(val); 37 | /// ``` 38 | public func value_to_candid(val: Types.CandyShared): [Arg.Arg] { 39 | 40 | let buffer = Buffer.Buffer(0); 41 | 42 | switch(val){ 43 | //nat 44 | case(#Nat(val)) buffer.add({_type = #nat; value = #nat(val)}); 45 | case(#Nat64(val)) buffer.add({_type = #nat64; value = #nat64(val)}); 46 | case(#Nat32(val)) buffer.add({_type = #nat32; value = #nat32(val)}); 47 | case(#Nat16(val)) buffer.add({_type = #nat16; value = #nat16(val)}); 48 | case(#Nat8(val)) buffer.add({_type = #nat8; value = #nat8(val)}); 49 | //text 50 | case(#Text(val)) buffer.add({_type = #text; value = #text(val)}); 51 | //class 52 | case(#Class(val)){ 53 | let types: Buffer.Buffer = Buffer.Buffer(val.size()); 54 | let body: Buffer.Buffer = Buffer.Buffer(val.size()); 55 | for(this_item in val.vals()){ 56 | types.add({tag = #name(this_item.name); _type = (value_to_candid(this_item.value))[0]._type}); 57 | body.add({tag = #name(this_item.name); value = (value_to_candid(this_item.value))[0].value}); 58 | }; 59 | buffer.add({_type=#record(Buffer.toArray(types)); value = #record(Buffer.toArray(body))}) 60 | }; 61 | //array 62 | case(#Array(val)){ 63 | let list = val; 64 | 65 | var bFoundMultipleTypes = false; 66 | var lastType : ?CandidTypes.Type = null; 67 | let values: Buffer.Buffer = Buffer.Buffer(list.size()); 68 | let types: Buffer.Buffer = Buffer.Buffer(list.size()); 69 | let body: Buffer.Buffer = Buffer.Buffer(list.size()); 70 | var tracker : Nat32 = 0; 71 | for(this_item in list.vals()){ 72 | let item = (value_to_candid(this_item))[0]; 73 | switch(lastType){ 74 | case(null) lastType := ?item._type; 75 | case(?lastType){ 76 | if(CandidTypes.equal(lastType, item._type)){ 77 | 78 | } else { 79 | bFoundMultipleTypes := true; 80 | }; 81 | }; 82 | }; 83 | types.add({_type = item._type; tag = #hash(tracker)}); 84 | body.add({tag = #hash(tracker); value = item.value}); 85 | values.add(item.value); 86 | tracker += 1; 87 | }; 88 | 89 | if(bFoundMultipleTypes){ 90 | //need to make a record 91 | buffer.add({_type=#record(Buffer.toArray(types)); value = #record(Buffer.toArray(body))}) 92 | } else { 93 | let thisType = switch(lastType){ 94 | case(null) #_null ; 95 | case(?val) val ; 96 | }; 97 | buffer.add({_type=#vector(thisType); value = #vector(Buffer.toArray(values))}); 98 | }; 99 | 100 | }; 101 | case(#Option(val)){ 102 | switch(val){ 103 | case(null){ 104 | buffer.add({_type = #opt(#_null); value = #_null}); 105 | }; 106 | case(?val){ 107 | let item = (value_to_candid(val))[0]; 108 | buffer.add({_type = #opt(item._type); value = #opt(?item.value)}); 109 | }; 110 | }; 111 | }; 112 | case(#Nats(val)){ 113 | let list = val; 114 | 115 | let values: Buffer.Buffer = Buffer.Buffer(list.size()); 116 | for(this_item in list.vals()){ 117 | values.add(#nat(this_item)); 118 | }; 119 | buffer.add({_type=#vector(#nat); value = #vector(Buffer.toArray(values))}); 120 | }; 121 | case(#Ints(val)){ 122 | let list = val; 123 | 124 | let values: Buffer.Buffer = Buffer.Buffer(list.size()); 125 | for(this_item in list.vals()){ 126 | values.add(#int(this_item)); 127 | }; 128 | buffer.add({_type=#vector(#int); value = #vector(Buffer.toArray(values))}); 129 | }; 130 | case(#Floats(val)){ 131 | let list = val; 132 | 133 | let values: Buffer.Buffer = Buffer.Buffer(list.size()); 134 | for(this_item in list.vals()){ 135 | values.add(#float64(this_item)); 136 | }; 137 | 138 | buffer.add({_type=#vector(#float64); value = #vector(Buffer.toArray(values))}); 139 | }; 140 | //bytes 141 | case(#Bytes(val)){ 142 | let list = val; 143 | 144 | let values: Buffer.Buffer = Buffer.Buffer(list.size()); 145 | for(this_item in list.vals()){ 146 | values.add(#nat8(this_item)); 147 | }; 148 | 149 | buffer.add({_type=#vector(#nat8); value = #vector(Buffer.toArray(values))}); 150 | }; 151 | //bytes 152 | case(#Blob(val)){ 153 | 154 | let list = Blob.toArray(val); 155 | 156 | let values: Buffer.Buffer = Buffer.Buffer(list.size()); 157 | for(this_item in list.vals()){ 158 | values.add(#nat8(this_item)); 159 | }; 160 | 161 | buffer.add({_type=#vector(#nat8); value = #vector(Buffer.toArray(values))}); 162 | 163 | }; 164 | //principal 165 | case(#Principal(val)) buffer.add({_type = #principal; value = #principal(#transparent(val))}); 166 | //bool 167 | case(#Bool(val))buffer.add({_type = #bool; value = #bool(val)}); 168 | 169 | //float 170 | case(#Float(val))buffer.add({_type = #float64; value = #float64(val)}); 171 | case(#Int(val))buffer.add({_type = #int; value = #int(val)}); 172 | case(#Int64(val))buffer.add({_type = #int64; value = #int64(val)}); 173 | case(#Int32(val))buffer.add({_type = #int32; value = #int32(val)}); 174 | case(#Int16(val))buffer.add({_type = #int16; value = #int16(val)}); 175 | case(#Int8(val))buffer.add({_type = #int8; value = #int8(val)}); 176 | 177 | case(#Map(val)){ 178 | let list = val; 179 | 180 | let values: Buffer.Buffer = Buffer.Buffer(list.size()); 181 | 182 | let types: Buffer.Buffer = Buffer.Buffer(list.size()); 183 | 184 | let localValues: Buffer.Buffer = Buffer.Buffer(2); 185 | 186 | 187 | 188 | let body: Buffer.Buffer = Buffer.Buffer(list.size()); 189 | 190 | 191 | 192 | var tracker : Nat32 = 0; 193 | let localTypes: Buffer.Buffer = Buffer.Buffer(2); 194 | let localBody: Buffer.Buffer = Buffer.Buffer(2); 195 | for(this_item in list.vals()){ 196 | let key = (this_item.0); 197 | let value = (value_to_candid(this_item.1))[0]; 198 | 199 | 200 | 201 | localTypes.add({_type = value._type; tag = #name(key)}); 202 | 203 | localBody.add({tag = #name(key); value = value.value}); 204 | 205 | 206 | 207 | //buffer.add(thisItem); 208 | 209 | //types.add({_type = thisItem._type; tag = #hash(tracker)}); 210 | //body.add({tag = #hash(tracker); value = thisItem.value}); 211 | //values.add(thisItem.value); 212 | tracker += 1; 213 | }; 214 | 215 | 216 | buffer.add({_type=#record(Buffer.toArray(localTypes)); value = #record(Buffer.toArray(localBody))}) 217 | 218 | }; 219 | 220 | case(#ValueMap(val)){ 221 | let list = val; 222 | 223 | let values: Buffer.Buffer = Buffer.Buffer(list.size()); 224 | 225 | let types: Buffer.Buffer = Buffer.Buffer(list.size()); 226 | 227 | let localValues: Buffer.Buffer = Buffer.Buffer(2); 228 | 229 | 230 | 231 | let body: Buffer.Buffer = Buffer.Buffer(list.size()); 232 | 233 | 234 | 235 | var tracker : Nat32 = 0; 236 | for(this_item in list.vals()){ 237 | let key = (value_to_candid(this_item.0))[0]; 238 | let value = (value_to_candid(this_item.1))[0]; 239 | 240 | let localTypes: Buffer.Buffer = Buffer.Buffer(2); 241 | let localBody: Buffer.Buffer = Buffer.Buffer(2); 242 | 243 | 244 | localTypes.add({_type = key._type; tag = #hash(0)}); 245 | localTypes.add({_type = value._type; tag = #hash(1)}); 246 | 247 | 248 | localBody.add({tag = #hash(0); value = key.value}); 249 | localBody.add({tag = #hash(1); value = value.value}); 250 | 251 | let thisItem = {_type=#record(Buffer.toArray(localTypes)); value = #record(Buffer.toArray(localBody))}; 252 | 253 | types.add({_type = #record(Buffer.toArray(localTypes)); tag = #hash(tracker)}); 254 | body.add({tag = #hash(tracker); value = #record(Buffer.toArray(localBody))}); 255 | 256 | tracker += 1; 257 | }; 258 | 259 | buffer.add({_type=#record(Buffer.toArray(types)); value = #record(Buffer.toArray(body))}) 260 | //buffer.add({_type=#record(Buffer.toArray(types)); value = #record(Buffer.toArray(body))}) 261 | 262 | }; 263 | //array 264 | case(#Set(val)){ 265 | let list = val; 266 | 267 | var bFoundMultipleTypes = false; 268 | var lastType : ?CandidTypes.Type = null; 269 | let values: Buffer.Buffer = Buffer.Buffer(list.size()); 270 | let types: Buffer.Buffer = Buffer.Buffer(list.size()); 271 | let body: Buffer.Buffer = Buffer.Buffer(list.size()); 272 | var tracker : Nat32 = 0; 273 | for(this_item in list.vals()){ 274 | let item = (value_to_candid(this_item))[0]; 275 | switch(lastType){ 276 | case(null) lastType := ?item._type; 277 | case(?lastType){ 278 | if(CandidTypes.equal(lastType, item._type)){ 279 | 280 | } else { 281 | bFoundMultipleTypes := true; 282 | }; 283 | }; 284 | }; 285 | types.add({_type = item._type; tag = #hash(tracker)}); 286 | body.add({tag = #hash(tracker); value = item.value}); 287 | values.add(item.value); 288 | tracker += 1; 289 | }; 290 | 291 | if(bFoundMultipleTypes){ 292 | //need to make a record 293 | buffer.add({_type=#record(Buffer.toArray(types)); value = #record(Buffer.toArray(body))}) 294 | } else { 295 | let thisType = switch(lastType){ 296 | case(null) #_null ; 297 | case(?val) val ; 298 | }; 299 | buffer.add({_type=#vector(thisType); value = #vector(Buffer.toArray(values))}); 300 | }; 301 | }; 302 | 303 | }; 304 | 305 | Buffer.toArray(buffer); 306 | }; 307 | }; -------------------------------------------------------------------------------- /src/clone.mo: -------------------------------------------------------------------------------- 1 | /////////////////////////////// 2 | // 3 | // ©2021 @aramakme 4 | // 5 | //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: 6 | // 7 | //The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | // 9 | //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. 10 | /////////////////////////////// 11 | 12 | /// Cloning support for the candy values. 13 | /// 14 | /// This module contains a few utilities for deep cloning candy values. 15 | 16 | import Types "types"; 17 | import Array "mo:base/Array"; 18 | import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; 19 | import Map "mo:map9/Map"; 20 | import Set "mo:map9/Set"; 21 | 22 | module { 23 | 24 | type CandyShared = Types.CandyShared; 25 | type Candy = Types.Candy; 26 | type Property = Types.Property; 27 | 28 | /// Deep clone a `Candy`. 29 | /// 30 | /// Example: 31 | /// ```motoko include=import 32 | /// let val: Candy = #Option(?#Principal(Principal.fromText("xyz"))); 33 | /// let cloned_val = Clone.cloneCandy(val); 34 | /// ``` 35 | public func cloneCandy(val : Candy) : Candy { 36 | switch(val){ 37 | case(#Class(val)){ 38 | return #Class( 39 | Map.fromIter( 40 | Map.entries(val) 41 | , Map.thash) 42 | ); 43 | }; 44 | case(#Bytes(val)){#Bytes(StableBuffer.clone(val))}; 45 | case(#Nats(val)){#Nats(StableBuffer.clone(val))}; 46 | case(#Ints(val)){#Ints(StableBuffer.clone(val))}; 47 | case(#Floats(val)){#Floats(StableBuffer.clone(val))}; 48 | case(#Array(val)){#Array(StableBuffer.clone(val))}; 49 | case(#ValueMap(val)){#ValueMap(Map.fromIter(Map.entries(val), Types.candyMapHashTool))}; 50 | case(#Set(val)){#Set(Set.fromIter(Set.keys(val), Types.candyMapHashTool))}; 51 | case(_) val; 52 | }; 53 | }; 54 | } -------------------------------------------------------------------------------- /src/json.mo: -------------------------------------------------------------------------------- 1 | /// JSON support for the candy library. 2 | /// 3 | /// This module contains the utilities useful for handling JSON values. 4 | 5 | import Buffer "mo:base/Buffer"; 6 | import Nat "mo:base/Nat"; 7 | import Nat16 "mo:base/Nat16"; 8 | import Nat32 "mo:base/Nat32"; 9 | import Nat64 "mo:base/Nat64"; 10 | import Nat8 "mo:base/Nat8"; 11 | import Float "mo:base/Float"; 12 | import Int "mo:base/Int"; 13 | import Int8 "mo:base/Int8"; 14 | import Int16 "mo:base/Int16"; 15 | import Int32 "mo:base/Int32"; 16 | import Int64 "mo:base/Int64"; 17 | import Bool "mo:base/Bool"; 18 | import Blob "mo:base/Blob"; 19 | import Principal "mo:base/Principal"; 20 | import Text "mo:base/Text"; 21 | 22 | import Types "types"; 23 | import Hex "mo:encoding_0_4_1/Hex"; 24 | 25 | module { 26 | /// Convert `CandyShared` to JSON format as `Text`. 27 | /// 28 | /// Example: 29 | /// ```motoko include=import 30 | /// let val: CandyShared = #Option(?#Principal(Principal.fromText("xyz"))); 31 | /// let val_as_json = Json.value_to_json(val); 32 | /// ``` 33 | public func value_to_json(val: Types.CandyShared): Text { 34 | switch(val){ 35 | //nat 36 | case(#Nat(val)){ Nat.toText(val)}; 37 | case(#Nat64(val)){ Nat64.toText(val)}; 38 | case(#Nat32(val)){ Nat32.toText(val)}; 39 | case(#Nat16(val)){ Nat16.toText(val)}; 40 | case(#Nat8(val)){ Nat8.toText(val)}; 41 | //text 42 | case(#Text(val)){ "\"" # val # "\""; }; 43 | //class 44 | case(#Class(val)){ 45 | var body: Buffer.Buffer = Buffer.Buffer(1); 46 | for(this_item in val.vals()){ 47 | body.add("\"" # this_item.name # "\"" # ":" # value_to_json(this_item.value)); 48 | }; 49 | return "{" # Text.join(",", body.vals()) # "}"; 50 | }; 51 | //array 52 | case(#Array(val)){ 53 | var body: Buffer.Buffer = Buffer.Buffer(1); 54 | for(this_item in val.vals()){ 55 | body.add(value_to_json(this_item)); 56 | }; 57 | return "[" # Text.join(",", body.vals()) # "]"; 58 | }; 59 | case(#Option(val)){ 60 | switch(val){ 61 | case(null){"null";}; 62 | case(?val){value_to_json(val);} 63 | } 64 | }; 65 | case(#Nats(val)){ 66 | var body: Buffer.Buffer = Buffer.Buffer(1); 67 | for(this_item in val.vals()){ 68 | body.add(Nat.toText(this_item)); 69 | }; 70 | return "[" # Text.join(",", body.vals()) # "]"; 71 | }; 72 | case(#Ints(val)){ 73 | var body: Buffer.Buffer = Buffer.Buffer(1); 74 | for(this_item in val.vals()){ 75 | body.add(Int.toText(this_item)); 76 | }; 77 | return "[" # Text.join(",", body.vals()) # "]"; 78 | }; 79 | case(#Floats(val)){ 80 | var body: Buffer.Buffer = Buffer.Buffer(1); 81 | for(this_item in val.vals()){ 82 | body.add(Float.format(#exact, this_item)); 83 | }; 84 | return "[" # Text.join(",", body.vals()) # "]"; 85 | }; 86 | //bytes 87 | case(#Bytes(val)){ 88 | return "\"" # Hex.encode(val) # "\"";//CandyHex.encode(val); 89 | }; 90 | //bytes 91 | case(#Blob(val)){ 92 | return "\"" # Hex.encode(Blob.toArray(val)) # "\"";//CandyHex.encode(val); 93 | }; 94 | //principal 95 | case(#Principal(val)){ "\"" # Principal.toText(val) # "\"";}; 96 | //bool 97 | case(#Bool(val)){ "\"" # Bool.toText(val) # "\"";}; 98 | //float 99 | case(#Float(val)){ Float.format(#exact, val)}; 100 | case(#Int(val)){Int.toText(val);}; 101 | case(#Int64(val)){Int64.toText(val);}; 102 | case(#Int32(val)){Int32.toText(val);}; 103 | case(#Int16(val)){Int16.toText(val);}; 104 | case(#Int8(val)){Int8.toText(val);}; 105 | case(_){"";}; 106 | }; 107 | }; 108 | }; 109 | -------------------------------------------------------------------------------- /src/properties.mo: -------------------------------------------------------------------------------- 1 | /////////////////////////////// 2 | // 3 | // ©2021 @aramakme 4 | // 5 | //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: 6 | // 7 | //The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | // 9 | //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. 10 | /////////////////////////////// 11 | 12 | /// Properties for the candy library. 13 | /// 14 | /// This module contains the properties and class functions for updating and 15 | /// manipulating classes. 16 | 17 | import Buffer "mo:base/Buffer"; 18 | import Map "mo:map9/Map"; 19 | import Text "mo:base/Text"; 20 | import Array "mo:base/Array"; 21 | import Result "mo:base/Result"; 22 | import Iter "mo:base/Iter"; 23 | import Types "types"; 24 | 25 | module { 26 | 27 | type PropertiesShared = Types.PropertiesShared; 28 | type Query = Types.Query; 29 | type PropertySharedError = Types.PropertySharedError; 30 | type UpdateShared = Types.UpdateShared; 31 | type PropertyShared = Types.PropertyShared; 32 | type Property = Types.Property; 33 | type CandyShared = Types.CandyShared; 34 | type Properties = Types.Properties; 35 | type Update = Types.Update; 36 | 37 | private func toPropertyMap(ps : PropertiesShared) : Map.Map { 38 | let m = Map.fromIter(Iter.map(ps.vals(), func(x){ 39 | (x.name, x) 40 | }), Map.thash); 41 | 42 | m; 43 | }; 44 | 45 | private func fromPropertyMap(m : Map.Map) : PropertiesShared { 46 | Iter.toArray(Map.vals(m)); 47 | }; 48 | 49 | /// Get a subset of fields from the `PropertiesShared` based on the given query. 50 | /// 51 | /// Example: 52 | /// ```motoko include=import 53 | /// let properties: PropertiesShared = [ 54 | /// { 55 | /// name = "prop1"; 56 | /// value = #Principal(Principal.fromText("abc")); 57 | /// immutable = false; 58 | /// }, 59 | /// { 60 | /// name = "prop2"; 61 | /// value = #Nat8(44); 62 | /// immutable = true; 63 | /// }, 64 | /// { 65 | /// name = "prop3"; 66 | /// value = #Class( 67 | /// { 68 | /// name = "class_field1"; 69 | /// value = #Nat(222); 70 | /// immutable = false; 71 | /// }, 72 | /// { 73 | /// name = "class_field2"; 74 | /// value = #Text("sample"); 75 | /// immutable = true; 76 | /// } 77 | /// ); 78 | /// immutable = false; 79 | /// } 80 | /// ]; 81 | /// let qs = [ 82 | /// { 83 | /// name = "prop2" 84 | /// }, 85 | /// { 86 | /// name = "prop3"; 87 | /// next = [ 88 | /// { 89 | /// name = "class_field2"; 90 | /// } 91 | /// ]; 92 | /// } 93 | /// ]; 94 | /// // Will return prop2 and the class_field2 from prop3. 95 | /// let subset_result = Properties.getPropertiesShared(properties, qs); 96 | /// ``` 97 | /// Note: Ignores unknown properties. 98 | public func getPropertiesShared(properties : PropertiesShared, qs : [Query]) : Result.Result { 99 | let m = toPropertyMap(properties); 100 | var ps : Buffer.Buffer = Buffer.Buffer(Map.size(m)); 101 | for (q in qs.vals()) { 102 | switch (Map.get(m, Map.thash, q.name)) { 103 | case (null) return #err(#NotFound); // Query contained an unknown property. 104 | case (? p) { 105 | switch (p.value) { 106 | case (#Class(c)) { 107 | if (q.next.size() == 0) { 108 | // Return every sub-attribute attribute. 109 | ps.add(p); 110 | } else { 111 | let sps = switch (getPropertiesShared(c, q.next)) { 112 | case (#err(e)) return #err(e); 113 | case (#ok(v)) v; 114 | }; 115 | 116 | ps.add({ 117 | name = p.name; 118 | value = #Class(sps); 119 | immutable = p.immutable; 120 | }); 121 | }; 122 | }; 123 | case (other) { 124 | // Not possible to get sub-attribute of a non-class property. 125 | if (q.next.size() != 0) return #err(#NotFound); 126 | ps.add(p); 127 | }; 128 | } 129 | }; 130 | }; 131 | }; 132 | #ok(Buffer.toArray(ps)); 133 | }; 134 | 135 | /// Updates the given properties based on the given update query. 136 | /// 137 | /// Example: 138 | /// ```motoko include=import 139 | /// let properties: PropertiesShared = [ 140 | /// { 141 | /// name = "prop1"; 142 | /// value = #Principal(Principal.fromText("abc")); 143 | /// immutable = true; 144 | /// }, 145 | /// { 146 | /// name = "prop2"; 147 | /// value = #Nat8(44); 148 | /// immutable = false; 149 | /// }, 150 | /// { 151 | /// name = "prop3"; 152 | /// value = #Class( 153 | /// { 154 | /// name = "class_field1"; 155 | /// value = #Nat(222); 156 | /// immutable = false; 157 | /// }, 158 | /// { 159 | /// name = "class_field2"; 160 | /// value = #Text("sample"); 161 | /// immutable = true; 162 | /// } 163 | /// ); 164 | /// immutable = false; 165 | /// } 166 | /// ]; 167 | /// let us = [ 168 | /// { 169 | /// name = "prop1", 170 | /// mode = #Set(#Nat8(66)) 171 | /// }, 172 | /// { 173 | /// name = "prop3"; 174 | /// mode = #Next([ 175 | /// { 176 | /// name = "class_field1"; 177 | /// mode = #Lock(#Nat(333)); 178 | /// } 179 | /// ]) 180 | /// } 181 | /// ]; 182 | /// // Will update prop1 and the class_field1 from prop3 to new values. 183 | /// let updated_properties = Properties.updatePropertiesShared(properties, us); 184 | /// ``` 185 | /// Note: 186 | /// - Creates unknown properties. 187 | /// - Returns error if the query tries to update an immutable property. 188 | public func updatePropertiesShared(properties : PropertiesShared, us : [UpdateShared]) : Result.Result { 189 | let m = toPropertyMap(properties); 190 | for (u in us.vals()) { 191 | switch (Map.get(m, Map.thash, u.name)) { 192 | case (null) { 193 | // Update contained an unknown property, so it gets created. 194 | switch (u.mode) { 195 | case (#Next(sus)) { 196 | let sps = switch(updatePropertiesShared([], sus)) { 197 | case (#err(e)) return #err(e); 198 | case (#ok(v)) v; 199 | }; 200 | 201 | ignore Map.put(m, Map.thash, u.name, { 202 | name = u.name; 203 | value = #Class(sps); 204 | immutable = false; 205 | }); 206 | }; 207 | case (#Set(v)) { 208 | ignore Map.put(m, Map.thash, u.name, { 209 | name = u.name; 210 | value = v; 211 | immutable = false; 212 | }); 213 | }; 214 | case (#Lock(v)) { 215 | ignore Map.put(m, Map.thash, u.name, { 216 | name = u.name; 217 | value = v; 218 | immutable = true; 219 | }); 220 | }; 221 | }; 222 | }; 223 | case (? p) { 224 | // Can not update immutable property. 225 | if (p.immutable) { 226 | return #err(#Immutable); 227 | }; 228 | switch (u.mode) { 229 | case (#Next(sus)) { 230 | switch (p.value) { 231 | case (#Class(c)) { 232 | let sps = switch(updatePropertiesShared(c, sus)) { 233 | case (#err(e)) return #err(e); 234 | case (#ok(v)) v; 235 | }; 236 | 237 | ignore Map.put(m, Map.thash, u.name, { 238 | name = p.name; 239 | value = #Class(sps); 240 | immutable = false; 241 | }); 242 | }; 243 | case (other) return #err(#NotFound); // Not possible to update sub-attribute of a non-class property. 244 | }; 245 | return #err(#NotFound); 246 | }; 247 | case (#Set(v)) { 248 | ignore Map.put(m, Map.thash, u.name, { 249 | name = p.name; 250 | value = v; 251 | immutable = false; 252 | }); 253 | }; 254 | case (#Lock(v)) { 255 | ignore Map.put(m, Map.thash, u.name, { 256 | name = p.name; 257 | value = v; 258 | immutable = true; 259 | }); 260 | }; 261 | }; 262 | }; 263 | }; 264 | }; 265 | 266 | #ok(fromPropertyMap(m)); 267 | }; 268 | 269 | /// Updates the given properties based on the given update query. 270 | /// 271 | /// Example: 272 | /// ```motoko include=import 273 | /// let c = #Class( 274 | /// { 275 | /// name = "class_field1"; 276 | /// value = #Nat(222); 277 | /// immutable = false; 278 | /// }, 279 | /// { 280 | /// name = "class_field2"; 281 | /// value = #Text("sample"); 282 | /// immutable = true; 283 | /// } 284 | /// ); 285 | /// let prop = Properties.getClassPropertyShared(c, "class_field1"); 286 | /// ``` 287 | /// Note: Returns null if: 288 | /// - The underlying value isn't a #Class. 289 | /// - The property with the given name wasn't found inside the class. 290 | public func getClassPropertyShared(val: CandyShared, name : Text) : ?PropertyShared{ 291 | switch(val){ 292 | case(#Class(val)){ 293 | for(thisItem in val.vals()){ 294 | if(thisItem.name == name){ 295 | return ?thisItem; 296 | }; 297 | }; 298 | 299 | return null; 300 | 301 | }; 302 | case(_){ 303 | //assert(false); 304 | //unreachable 305 | return null; 306 | } 307 | }; 308 | }; 309 | 310 | //////////////////////////////////// 311 | // 312 | // The following functions were copied from departurelabs' property.mo. They work as a plug and play 313 | // here with CandyShared and Candy. 314 | // 315 | // https://github.com/DepartureLabsIC/non-fungible-token/blob/main/src/property.mo 316 | // 317 | // The following lines are issued under the MIT License Copyright (c) 2021 Departure Labs: 318 | // 319 | /////////////////////////////////// 320 | 321 | private func toPropertySharedMap(ps : Properties) : Map.Map { 322 | let m = Map.new(); 323 | for (property in Map.vals(ps)) { 324 | ignore Map.put(m, Map.thash, property.name, Types.shareProperty(property)); 325 | }; 326 | m; 327 | }; 328 | 329 | private func fromPropertySharedMap(m : Map.Map) : Properties { 330 | Map.fromIter(Iter.map(Map.vals(m), func(x){(x.name, Types.unshareProperty(x))}), Map.thash); 331 | }; 332 | 333 | /// Get a subset of fields from the `Properties` based on the given query. 334 | /// 335 | /// Example: 336 | /// ```motoko include=import 337 | /// let properties: Properties = [ 338 | /// { 339 | /// name = "prop1"; 340 | /// value = #Principal(Principal.fromText("abc")); 341 | /// immutable = false; 342 | /// }, 343 | /// { 344 | /// name = "prop2"; 345 | /// value = #Nat8(44); 346 | /// immutable = true; 347 | /// }, 348 | /// { 349 | /// name = "prop3"; 350 | /// value = #Class( 351 | /// { 352 | /// name = "class_field1"; 353 | /// value = #Nat(222); 354 | /// immutable = false; 355 | /// }, 356 | /// { 357 | /// name = "class_field2"; 358 | /// value = #Text("sample"); 359 | /// immutable = true; 360 | /// } 361 | /// ); 362 | /// immutable = false; 363 | /// } 364 | /// ]; 365 | /// let qs = [ 366 | /// { 367 | /// name = "prop2" 368 | /// }, 369 | /// { 370 | /// name = "prop3"; 371 | /// next = [ 372 | /// { 373 | /// name = "class_field2"; 374 | /// } 375 | /// ]; 376 | /// } 377 | /// ]; 378 | /// // Will return prop2 and the class_field2 from prop3. 379 | /// let subset_result = Properties.getProperties(properties, qs); 380 | /// ``` 381 | /// Note: Ignores unknown properties. 382 | public func getProperties(properties : Properties, qs : [Query]) : Result.Result { 383 | let m = properties; 384 | var ps : Buffer.Buffer = Buffer.Buffer(1); 385 | for (q in qs.vals()) { 386 | switch (Map.get(m, Map.thash, q.name)) { 387 | case (null) { 388 | // Query contained an unknown property. 389 | //return #err(#NotFound); 390 | //for now, ignore unfound properteis 391 | }; 392 | case (? p) { 393 | switch (p.value) { 394 | case (#Class(c)) { 395 | if (q.next.size() == 0) { 396 | // Return every sub-attribute attribute. 397 | ps.add(p); 398 | } else { 399 | let sps = switch (getProperties(c, q.next)) { 400 | case (#err(e)) return #err(e); 401 | case (#ok(v)) v; 402 | }; 403 | 404 | ps.add({ 405 | name = p.name; 406 | value = #Class(sps); 407 | immutable = p.immutable; 408 | }); 409 | }; 410 | }; 411 | case (other) { 412 | // Not possible to get sub-attribute of a non-class property. 413 | if (q.next.size() != 0) return #err(#NotFound); 414 | ps.add(p); 415 | }; 416 | } 417 | }; 418 | }; 419 | }; 420 | #ok(Map.fromIter( 421 | Iter.map(ps.vals(), 422 | func(x){(x.name, x)} 423 | ), Map.thash 424 | )); 425 | }; 426 | 427 | /// Updates the given properties based on the given update query. 428 | /// 429 | /// Example: 430 | /// ```motoko include=import 431 | /// let properties: Properties = [ 432 | /// { 433 | /// name = "prop1"; 434 | /// value = #Principal(Principal.fromText("abc")); 435 | /// immutable = true; 436 | /// }, 437 | /// { 438 | /// name = "prop2"; 439 | /// value = #Nat8(44); 440 | /// immutable = false; 441 | /// }, 442 | /// { 443 | /// name = "prop3"; 444 | /// value = #Class( 445 | /// { 446 | /// name = "class_field1"; 447 | /// value = #Nat(222); 448 | /// immutable = false; 449 | /// }, 450 | /// { 451 | /// name = "class_field2"; 452 | /// value = #Text("sample"); 453 | /// immutable = true; 454 | /// } 455 | /// ); 456 | /// immutable = false; 457 | /// } 458 | /// ]; 459 | /// let us = [ 460 | /// { 461 | /// name = "prop1", 462 | /// mode = #Set(#Nat8(66)) 463 | /// }, 464 | /// { 465 | /// name = "prop3"; 466 | /// mode = #Next([ 467 | /// { 468 | /// name = "class_field1"; 469 | /// mode = #Lock(#Nat(333)); 470 | /// } 471 | /// ]) 472 | /// } 473 | /// ]; 474 | /// // Will update prop1 and the class_field1 from prop3 to new values. 475 | /// let updated_properties = Properties.updateProperties(properties, us); 476 | /// ``` 477 | /// Note: 478 | /// - Creates unknown properties. 479 | /// - Returns error if the query tries to update an immutable property. 480 | public func updateProperties(properties : Properties, us : [Update]) : Result.Result { 481 | let m = properties; 482 | for (u in us.vals()) { 483 | switch (Map.get(m, Map.thash, u.name)) { 484 | case (null) { 485 | // Update contained an unknown property, so it gets created. 486 | switch (u.mode) { 487 | case (#Next(sus)) { 488 | let sps = switch(updateProperties(Map.new(), sus)) { 489 | case (#err(e)) return #err(e); 490 | case (#ok(v)) v; 491 | }; 492 | 493 | ignore Map.put(m, Map.thash, u.name, { 494 | name = u.name; 495 | value = #Class(sps); 496 | immutable = false; 497 | }); 498 | }; 499 | case (#Set(v)) { 500 | ignore Map.put(m, Map.thash, u.name, { 501 | name = u.name; 502 | value = v; 503 | immutable = false; 504 | }); 505 | }; 506 | case (#Lock(v)) { 507 | ignore Map.put(m, Map.thash, u.name, { 508 | name = u.name; 509 | value = v; 510 | immutable = true; 511 | }); 512 | }; 513 | }; 514 | }; 515 | case (? p) { 516 | // Can not update immutable property. 517 | if (p.immutable) return #err(#Immutable); 518 | 519 | switch (u.mode) { 520 | case (#Next(sus)) { 521 | switch (p.value) { 522 | case (#Class(c)) { 523 | let sps = switch(updateProperties(c, sus)) { 524 | case (#err(e)) return #err(e); 525 | case (#ok(v)) v; 526 | }; 527 | 528 | ignore Map.put(m, Map.thash, u.name, { 529 | name = p.name; 530 | value = #Class(sps); 531 | immutable = false; 532 | }); 533 | }; 534 | case (other) return #err(#NotFound); // Not possible to update sub-attribute of a non-class property. 535 | }; 536 | return #err(#NotFound); 537 | }; 538 | case (#Set(v)) { 539 | ignore Map.put(m, Map.thash, u.name, { 540 | name = p.name; 541 | value = v; 542 | immutable = false; 543 | }); 544 | }; 545 | case (#Lock(v)) { 546 | ignore Map.put(m, Map.thash, u.name, { 547 | name = p.name; 548 | value = v; 549 | immutable = true; 550 | }); 551 | }; 552 | }; 553 | }; 554 | }; 555 | }; 556 | 557 | #ok(m); 558 | }; 559 | 560 | //////////////////////////////////// 561 | // 562 | // End code from Departure labs property.mo 563 | // 564 | /////////////////////////////////// 565 | 566 | } 567 | -------------------------------------------------------------------------------- /src/upgrade.mo: -------------------------------------------------------------------------------- 1 | /////////////////////////////// 2 | // 3 | // ©2021 @aramakme 4 | // 5 | //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: 6 | // 7 | //The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | // 9 | //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. 10 | /////////////////////////////// 11 | 12 | /// Upgrade utilities for the candy library. 13 | /// 14 | /// This module contains the utility functions to upgrade candy values from version 1 to version 2. 15 | 16 | import CandyOld "mo:candy_0_1_12/types"; 17 | import Candy0_2_0 "mo:candy_0_2_0/types"; 18 | import CandyTypes "types"; 19 | import Array "mo:base/Array"; 20 | import Iter "mo:base/Iter"; 21 | import Buffer "mo:base/Buffer"; 22 | import Map7 "mo:map7/Map"; 23 | import Set7 "mo:map7/Set"; 24 | import Map "mo:map9/Map"; 25 | import Set "mo:map9/Set"; 26 | import StableBuffer_Old "mo:stablebuffer/StableBuffer"; 27 | import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; 28 | 29 | module { 30 | 31 | public func toBufferOld(x :[T]) : StableBuffer_Old.StableBuffer{ 32 | let thisBuffer = StableBuffer_Old.initPresized(x.size()); 33 | for(thisItem in x.vals()){ 34 | StableBuffer_Old.add(thisBuffer,thisItem); 35 | }; 36 | return thisBuffer; 37 | }; 38 | 39 | /// Upgrade from V1 representation of `CandyShared` to the V2 representation. 40 | public func upgradeCandy0_1_2_to_0_2_0(item : CandyOld.CandyValueUnstable) : Candy0_2_0.Candy { 41 | switch (item) { 42 | case (#Int(val)) { #Int(val) }; 43 | case (#Int8(val)) { #Int8(val) }; 44 | case (#Int16(val)) { #Int16(val) }; 45 | case (#Int32(val)) { #Int32(val) }; 46 | case (#Int64(val)) { #Int64(val) }; 47 | case (#Nat(val)) { #Nat(val) }; 48 | case (#Nat8(val)) { #Nat8(val) }; 49 | case (#Nat16(val)) { #Nat16(val) }; 50 | case (#Nat32(val)) { #Nat32(val) }; 51 | case (#Nat64(val)) { #Nat64(val) }; 52 | case (#Float(val)) { #Float(val) }; 53 | case (#Text(val)) { #Text(val) }; 54 | case (#Bool(val)) { #Bool(val) }; 55 | case (#Blob(val)) { #Blob(val) }; 56 | case (#Class(val)) { 57 | let iter = Iter.map( 58 | val.vals(), 59 | func(x) {(x.name, 60 | { 61 | x with value = upgradeCandy0_1_2_to_0_2_0(x.value) 62 | }); 63 | }, 64 | ); 65 | 66 | #Class( 67 | Map7.fromIter(iter, 68 | Map7.thash)); 69 | }; 70 | case (#Principal(val)) { #Principal(val) }; 71 | case (#Array(val)) { 72 | switch (val) { 73 | case (#frozen(val)) { 74 | #Array(Candy0_2_0.toBuffer(Array.map(val, upgradeCandy0_1_2_to_0_2_0))); 75 | }; 76 | case (#thawed(val)) { 77 | #Array(Candy0_2_0.toBuffer(Array.map(Buffer.toArray(val), upgradeCandy0_1_2_to_0_2_0))); 78 | }; 79 | }; 80 | }; 81 | case (#Bytes(val)) { 82 | switch (val) { 83 | case (#frozen(val)) { #Bytes(toBufferOld(val)) }; 84 | case (#thawed(val)) { #Bytes(toBufferOld(Buffer.toArray(val))) }; 85 | }; 86 | }; 87 | case (#Floats(val)) { 88 | switch (val) { 89 | case (#frozen(val)) { #Floats(toBufferOld(val)) }; 90 | case (#thawed(val)) { #Floats(toBufferOld(Buffer.toArray(val))) }; 91 | }; 92 | }; 93 | case (#Nats(val)) { 94 | switch (val) { 95 | case (#frozen(val)) { #Nats(toBufferOld(val)) }; 96 | case (#thawed(val)) { #Nats(toBufferOld(Buffer.toArray(val))) }; 97 | }; 98 | }; 99 | 100 | case (#Option(val)) { 101 | switch (val) { 102 | case (null) { #Option(null) }; 103 | case (?val) { #Option(?upgradeCandy0_1_2_to_0_2_0(val)) }; 104 | }; 105 | }; 106 | 107 | case (#Empty) { #Option(null) }; 108 | }; 109 | }; 110 | 111 | /// Upgrade from V1 representation of `CandySharedUnstable` to the V2 representation 'CandyValudShared'. 112 | public func upgradeCandyShared0_1_2_to_0_2_0(item : CandyOld.CandyValue) : Candy0_2_0.CandyShared { 113 | switch (item) { 114 | case (#Int(val)) { #Int(val) }; 115 | case (#Int8(val)) { #Int8(val) }; 116 | case (#Int16(val)) { #Int16(val) }; 117 | case (#Int32(val)) { #Int32(val) }; 118 | case (#Int64(val)) { #Int64(val) }; 119 | case (#Nat(val)) { #Nat(val) }; 120 | case (#Nat8(val)) { #Nat8(val) }; 121 | case (#Nat16(val)) { #Nat16(val) }; 122 | case (#Nat32(val)) { #Nat32(val) }; 123 | case (#Nat64(val)) { #Nat64(val) }; 124 | case (#Float(val)) { #Float(val) }; 125 | case (#Text(val)) { #Text(val) }; 126 | case (#Bool(val)) { #Bool(val) }; 127 | case (#Blob(val)) { #Blob(val) }; 128 | case (#Class(val)) { 129 | #Class( 130 | Array.map( 131 | val, 132 | func(x) { 133 | { 134 | x with value = upgradeCandyShared0_1_2_to_0_2_0(x.value) 135 | }; 136 | }, 137 | ), 138 | ); 139 | }; 140 | case (#Principal(val)) { #Principal(val) }; 141 | case (#Array(val)) { 142 | switch (val) { 143 | case (#frozen(val)) { 144 | #Array(Array.map(val, upgradeCandyShared0_1_2_to_0_2_0)); 145 | }; 146 | case (#thawed(val)) { 147 | #Array(Array.map(Iter.toArray(val.vals()), upgradeCandyShared0_1_2_to_0_2_0)); 148 | }; 149 | }; 150 | }; 151 | case (#Option(val)) { 152 | switch (val) { 153 | case (null) { #Option(null) }; 154 | case (?val) { #Option(?upgradeCandyShared0_1_2_to_0_2_0(val)) }; 155 | }; 156 | }; 157 | case (#Bytes(val)) { 158 | switch (val) { 159 | case (#frozen(val)) { #Bytes(val) }; 160 | case (#thawed(val)) { #Bytes(val) }; 161 | }; 162 | }; 163 | case (#Floats(val)) { 164 | switch (val) { 165 | case (#frozen(val)) { #Floats(val) }; 166 | case (#thawed(val)) { #Floats(val) }; 167 | }; 168 | }; 169 | case (#Nats(val)) { 170 | switch (val) { 171 | case (#frozen(val)) { #Nats(val) }; 172 | case (#thawed(val)) { #Nats(val) }; 173 | }; 174 | }; 175 | 176 | case (#Empty) { #Option(null) }; 177 | }; 178 | }; 179 | 180 | 181 | /// Upgrade from V2 representation of `CandyShared` to the V3 representation. 182 | public func upgradeCandy0_2_0_to_0_3_0(item : Candy0_2_0.Candy) : CandyTypes.Candy { 183 | switch (item) { 184 | case (#Int(val)) { #Int(val) }; 185 | case (#Int8(val)) { #Int8(val) }; 186 | case (#Int16(val)) { #Int16(val) }; 187 | case (#Int32(val)) { #Int32(val) }; 188 | case (#Int64(val)) { #Int64(val) }; 189 | case (#Ints(val)) { #Ints(StableBuffer.fromArray(StableBuffer_Old.toArray(val))) }; 190 | case (#Nat(val)) { #Nat(val) }; 191 | case (#Nat8(val)) { #Nat8(val) }; 192 | case (#Nat16(val)) { #Nat16(val) }; 193 | case (#Nat32(val)) { #Nat32(val) }; 194 | case (#Nat64(val)) { #Nat64(val) }; 195 | case (#Float(val)) { #Float(val) }; 196 | case (#Text(val)) { #Text(val) }; 197 | case (#Bool(val)) { #Bool(val) }; 198 | case (#Blob(val)) { #Blob(val) }; 199 | case (#Class(val)) { 200 | let iter = Iter.map( 201 | Map7.vals(val), 202 | func(x) {(x.name, 203 | { 204 | x with value = upgradeCandy0_2_0_to_0_3_0(x.value) 205 | }); 206 | }, 207 | ); 208 | 209 | #Class( 210 | Map.fromIter(iter, 211 | Map.thash)); 212 | }; 213 | case (#Principal(val)) { #Principal(val) }; 214 | case (#Array(val)) { 215 | #Array(CandyTypes.toBuffer(Array.map(StableBuffer_Old.toArray(val), upgradeCandy0_2_0_to_0_3_0))); 216 | }; 217 | case (#Bytes(val)) {#Bytes(StableBuffer.fromArray(StableBuffer_Old.toArray(val)))}; 218 | case (#Floats(val)) {#Floats(StableBuffer.fromArray(StableBuffer_Old.toArray(val)))}; 219 | case (#Nats(val)) {#Nats(StableBuffer.fromArray(StableBuffer_Old.toArray(val)))}; 220 | case (#Option(val)) { 221 | switch(val){ 222 | case(null) #Option(null); 223 | case(?val){ 224 | #Option(?upgradeCandy0_2_0_to_0_3_0(val)); 225 | }; 226 | }; 227 | }; 228 | case (#Map(val)) { 229 | let iter = Iter.map<(Candy0_2_0.Candy, Candy0_2_0.Candy), (CandyTypes.Candy, CandyTypes.Candy)>( 230 | Map7.entries(val), 231 | func(x) {(upgradeCandy0_2_0_to_0_3_0(x.0), upgradeCandy0_2_0_to_0_3_0(x.1)); 232 | }, 233 | ); 234 | #ValueMap(Map.fromIter(iter, 235 | CandyTypes.candyMapHashTool))}; 236 | case (#Set(val)) { 237 | #Set(Set.fromIter(Iter.map(Set7.keys(val), upgradeCandy0_2_0_to_0_3_0), CandyTypes.candyMapHashTool)); 238 | }; 239 | }; 240 | }; 241 | 242 | /// Upgrade from V1 representation of `CandySharedUnstable` to the V2 representation 'CandyValudShared'. 243 | public func upgradeCandyShared0_2_0_to_0_3_0(item : Candy0_2_0.CandyShared) : CandyTypes.CandyShared { 244 | 245 | switch (item) { 246 | case (#Int(val)) { #Int(val) }; 247 | case (#Int8(val)) { #Int8(val) }; 248 | case (#Int16(val)) { #Int16(val) }; 249 | case (#Int32(val)) { #Int32(val) }; 250 | case (#Int64(val)) { #Int64(val) }; 251 | case (#Ints(val)) { #Ints(val) }; 252 | case (#Nat(val)) { #Nat(val) }; 253 | case (#Nat8(val)) { #Nat8(val) }; 254 | case (#Nat16(val)) { #Nat16(val) }; 255 | case (#Nat32(val)) { #Nat32(val) }; 256 | case (#Nat64(val)) { #Nat64(val) }; 257 | case (#Float(val)) { #Float(val) }; 258 | case (#Text(val)) { #Text(val) }; 259 | case (#Bool(val)) { #Bool(val) }; 260 | case (#Blob(val)) { #Blob(val) }; 261 | case (#Class(val)) { 262 | #Class( 263 | Array.map( 264 | val, 265 | func(x) { 266 | { 267 | x with value = upgradeCandyShared0_2_0_to_0_3_0(x.value) 268 | }; 269 | }, 270 | )); 271 | }; 272 | case (#Principal(val)) { #Principal(val) }; 273 | case (#Array(val)) { 274 | #Array(Array.map(val, upgradeCandyShared0_2_0_to_0_3_0)); 275 | }; 276 | case (#Bytes(val)) {#Bytes(val)}; 277 | case (#Floats(val)) {#Floats(val)}; 278 | case (#Nats(val)) {#Nats(val)}; 279 | case (#Option(val)) { 280 | switch(val){ 281 | case(null) #Option(null); 282 | case(?val){ 283 | #Option(?upgradeCandyShared0_2_0_to_0_3_0(val)); 284 | }; 285 | }; 286 | }; 287 | case (#Map(val)) { 288 | let iter = Iter.map<(Candy0_2_0.CandyShared, Candy0_2_0.CandyShared), (CandyTypes.CandyShared, CandyTypes.CandyShared)>( 289 | val.vals(), 290 | func(x) {(upgradeCandyShared0_2_0_to_0_3_0(x.0), upgradeCandyShared0_2_0_to_0_3_0(x.1)); 291 | }, 292 | ); 293 | #ValueMap(Iter.toArray< (CandyTypes.CandyShared, CandyTypes.CandyShared)>(iter))}; 294 | case (#Set(val)) { 295 | let iter = Iter.map<(Candy0_2_0.CandyShared), (CandyTypes.CandyShared)>( 296 | val.vals(), 297 | upgradeCandyShared0_2_0_to_0_3_0, 298 | ); 299 | #Set(Iter.toArray< CandyTypes.CandyShared>(iter))}; 300 | }; 301 | }; 302 | }; 303 | -------------------------------------------------------------------------------- /src/workspace.mo: -------------------------------------------------------------------------------- 1 | /////////////////////////////// 2 | // 3 | // ©2021 @aramakme 4 | // 5 | //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: 6 | // 7 | //The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | // 9 | //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. 10 | /////////////////////////////// 11 | 12 | /// Workspace utilities for the candy library. 13 | /// 14 | /// This module contains the utilities useful for keeping workable data in 15 | /// chunks that can be moved around canisters. They enable the inspection and 16 | /// manipulation of workspaces. 17 | 18 | import Array "mo:base/Array"; 19 | import Buffer "mo:base/Buffer"; 20 | import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; 21 | import Map "mo:map9/Map"; 22 | import Set "mo:map9/Set"; 23 | 24 | import Int "mo:base/Int"; 25 | import Iter "mo:base/Iter"; 26 | import Nat "mo:base/Nat"; 27 | import Types "types"; 28 | import Conversion "conversion"; 29 | import Clone "clone"; 30 | 31 | module { 32 | type CandyShared = Types.CandyShared; 33 | type Candy = Types.Candy; 34 | type Workspace = Types.Workspace; 35 | type DataZone = Types.DataZone; 36 | type AddressedChunkArray = Types.AddressedChunkArray; 37 | type AddressedChunk = Types.AddressedChunk; 38 | type AddressedChunkBuffer = Types.AddressedChunkBuffer; 39 | type DataChunk = Types.DataChunk; 40 | 41 | /// Get the count of addressed chunks in the given `Workspace`. 42 | /// 43 | /// Example: 44 | /// ```motoko include=import 45 | /// let ws = Workspace.initWorkspace(3); 46 | /// StableBuffer.add(ws, StableBuffer.initPresized(1)); 47 | /// StableBuffer.add(ws, StableBuffer.initPresized(5)); 48 | /// StableBuffer.add(ws, StableBuffer.initPresized(2)); 49 | /// let count = Workspace.countAddressedChunksInWorkspace(ws); // 8. 50 | /// ``` 51 | public func countAddressedChunksInWorkspace(x : Workspace) : Nat { 52 | var chunks = 0; 53 | for (thisZone in Iter.range(0, StableBuffer.size(x) - 1)) { 54 | chunks += StableBuffer.size(StableBuffer.get(x, thisZone)); 55 | }; 56 | chunks; 57 | }; 58 | 59 | /// Create an empty `Workspace`. 60 | public func emptyWorkspace() : Workspace { 61 | return StableBuffer.init(); 62 | }; 63 | 64 | /// Initialize a `Workspace` with the given capacity. 65 | public func initWorkspace(size : Nat) : Workspace { 66 | return StableBuffer.initPresized(size); 67 | }; 68 | 69 | /// Get the size in bytes taken up by the `Candy`. 70 | public func getCandySize(item : Candy) : Nat { 71 | let varSize = switch (item) { 72 | case (#Int(val)) { 73 | var a : Nat = 0; 74 | var b : Nat = Int.abs(val); 75 | var test = true; 76 | while test { 77 | a += 1; 78 | b := b / 256; 79 | test := b > 0; 80 | }; 81 | a + 1; //add the sign 82 | }; 83 | case (#Int8(val)) { 1 }; 84 | case (#Int16(val)) { 2 }; 85 | case (#Int32(val)) { 3 }; 86 | case (#Int64(val)) { 4 }; 87 | case (#Nat(val)) { 88 | var a : Nat = 0; 89 | var b = val; 90 | var test = true; 91 | while test { 92 | a += 1; 93 | b := b / 256; 94 | test := b > 0; 95 | }; 96 | a; 97 | }; 98 | case (#Nat8(val)) { 1 }; 99 | case (#Nat16(val)) { 2 }; 100 | case (#Nat32(val)) { 3 }; 101 | case (#Nat64(val)) { 4 }; 102 | case (#Float(val)) { 4 }; 103 | case (#Text(val)) { (val.size() * 4) }; 104 | case (#Bool(val)) { 1 }; 105 | case (#Blob(val)) { val.size() }; 106 | case (#Class(val)) { 107 | var size = 0; 108 | for (thisItem in Map.entries(val)) { 109 | size += 1 + (thisItem.1.name.size() * 4) + getCandySize(thisItem.1.value) + (thisItem.1.name.size() * 4); //name + name + value + bit 110 | }; 111 | return size; 112 | }; 113 | case (#Principal(val)) { Conversion.principalToBytes(val).size() }; //don't like this but need to confirm it is constant 114 | case (#Option(val)) { 115 | switch val { 116 | case (null) { 0 }; 117 | case (?val) { getCandySize(val) }; 118 | }; 119 | }; 120 | case (#Array(val)) { 121 | var size = 0; 122 | for (thisItem in StableBuffer.vals(val)) { 123 | size += 1 + getCandySize(thisItem); 124 | }; 125 | return size; 126 | }; 127 | case (#Bytes(val)) { 128 | StableBuffer.size(val) + 2; 129 | }; 130 | case (#Floats(val)) { 131 | (StableBuffer.size(val) * 4) + 2; 132 | }; 133 | case (#Nats(val)) { 134 | var size = 0; 135 | for (thisItem in StableBuffer.vals(val)) { 136 | size += 1 + getCandySize(#Nat(thisItem)); 137 | }; 138 | return size; 139 | }; 140 | case (#Ints(val)) { 141 | var size = 0; 142 | for (thisItem in StableBuffer.vals(val)) { 143 | size += 1 + getCandySize(#Int(thisItem)); 144 | }; 145 | return size; 146 | }; 147 | case (#ValueMap(val)) { 148 | var size = 0; 149 | for (thisItem in Map.entries(val)) { 150 | size += getCandySize(thisItem.0) + getCandySize(thisItem.1) + 2; 151 | }; 152 | return size; 153 | }; 154 | case (#Map(val)) { 155 | var size = 0; 156 | for (thisItem in Map.entries(val)) { 157 | size += (thisItem.0.size() * 4) + getCandySize(thisItem.1) + 2; 158 | }; 159 | return size; 160 | }; 161 | case (#Set(val)) { 162 | var size = 0; 163 | for (thisItem in Set.keys(val)) { 164 | size += getCandySize(thisItem) + 2; 165 | }; 166 | return size; 167 | }; 168 | }; 169 | 170 | return varSize; 171 | }; 172 | 173 | /// Get the size in bytes taken up by the `CandyShared`. 174 | public func getCandySharedSize(item : CandyShared) : Nat { 175 | let varSize = switch (item) { 176 | case (#Int(val)) { 177 | var a : Nat = 0; 178 | var b : Nat = Int.abs(val); 179 | var test = true; 180 | while test { 181 | a += 1; 182 | b := b / 256; 183 | test := b > 0; 184 | }; 185 | a + 1; //add the sign 186 | }; 187 | case (#Int8(val)) { 1 }; 188 | case (#Int16(val)) { 2 }; 189 | case (#Int32(val)) { 3 }; 190 | case (#Int64(val)) { 4 }; 191 | case (#Nat(val)) { 192 | var a : Nat = 0; 193 | var b = val; 194 | var test = true; 195 | while test { 196 | a += 1; 197 | b := b / 256; 198 | test := b > 0; 199 | }; 200 | a; 201 | }; 202 | case (#Nat8(val)) { 1 }; 203 | case (#Nat16(val)) { 2 }; 204 | case (#Nat32(val)) { 3 }; 205 | case (#Nat64(val)) { 4 }; 206 | case (#Float(val)) { 4 }; 207 | case (#Text(val)) { val.size() * 4 }; 208 | case (#Bool(val)) { 1 }; 209 | case (#Blob(val)) { val.size() }; 210 | case (#Class(val)) { 211 | var size = 0; 212 | for (thisItem in val.vals()) { 213 | size += 1 + (thisItem.name.size() * 4) + getCandySharedSize(thisItem.value); 214 | }; 215 | 216 | return size; 217 | }; 218 | case (#Principal(val)) { Conversion.principalToBytes(val).size() }; //don't like this but need to confirm it is constant 219 | case (#Option(val)) { 220 | switch val { 221 | case (null) { 0 }; 222 | case (?val) { getCandySharedSize(val) }; 223 | }; 224 | }; 225 | case (#Array(val)) { 226 | 227 | var size = 0; 228 | for (thisItem in val.vals()) { 229 | size += 1 + getCandySharedSize(thisItem); 230 | }; 231 | 232 | return size; 233 | 234 | }; 235 | case (#Bytes(val)) { 236 | val.size() + 2; 237 | }; 238 | case (#Floats(val)) { 239 | (val.size() * 4) + 2; 240 | }; 241 | case (#Nats(val)) { 242 | var size = 0; 243 | for (thisItem in val.vals()) { 244 | size += 1 + getCandySharedSize(#Nat(thisItem)); 245 | }; 246 | return size; 247 | }; 248 | case (#Ints(val)) { 249 | var size = 0; 250 | for (thisItem in val.vals()) { 251 | size += 1 + getCandySharedSize(#Int(thisItem)); 252 | }; 253 | return size; 254 | }; 255 | case (#ValueMap(val)) { 256 | var size = 0; 257 | for (thisItem in val.vals()) { 258 | size += getCandySharedSize(thisItem.0) + getCandySharedSize(thisItem.1) + 2; 259 | }; 260 | 261 | return size; 262 | 263 | }; 264 | case (#Map(val)) { 265 | var size = 0; 266 | for (thisItem in val.vals()) { 267 | size += (thisItem.0.size() *4) + getCandySharedSize(thisItem.1) + 2; 268 | }; 269 | 270 | return size; 271 | 272 | }; 273 | case (#Set(val)) { 274 | var size = 0; 275 | for (thisItem in val.vals()) { 276 | size += getCandySharedSize(thisItem) + 2; 277 | }; 278 | return size; 279 | }; 280 | }; 281 | return varSize + 2; 282 | }; 283 | 284 | /// Convert `Workspace` to `AddressedChunkArray`. 285 | /// 286 | /// Example: 287 | /// ```motoko include=import 288 | /// let ws = Workspace.initWorkspace(3); 289 | /// StableBuffer.add(ws, StableBuffer.initPresized(1)); 290 | /// StableBuffer.add(ws, StableBuffer.initPresized(5)); 291 | /// StableBuffer.add(ws, StableBuffer.initPresized(2)); 292 | /// let addressed_chunk_array = Workspace.workspaceToAddressedChunkArray(ws); 293 | /// ``` 294 | public func workspaceToAddressedChunkArray(x : Workspace) : AddressedChunkArray { 295 | var currentZone = 0; 296 | var currentChunk = 0; 297 | let result = Array.tabulate( 298 | countAddressedChunksInWorkspace(x), 299 | func(thisChunk) { 300 | let thisChunk = (currentZone, currentChunk, Types.shareCandy(StableBuffer.get(StableBuffer.get(x, currentZone), currentChunk))); 301 | if (currentChunk == Nat.sub(StableBuffer.size(StableBuffer.get(x, currentZone)), 1)) { 302 | currentZone += 1; 303 | currentChunk := 0; 304 | } else { 305 | currentChunk += 1; 306 | }; 307 | thisChunk; 308 | }, 309 | ); 310 | return result; 311 | }; 312 | 313 | /// Get a deep clone of the given `Workspace`. 314 | /// 315 | /// Example: 316 | /// ```motoko include=import 317 | /// let ws = Workspace.initWorkspace(3); 318 | /// StableBuffer.add(ws, StableBuffer.initPresized(1)); 319 | /// StableBuffer.add(ws, StableBuffer.initPresized(5)); 320 | /// StableBuffer.add(ws, StableBuffer.initPresized(2)); 321 | /// let ws_clone = Workspace.workspaceDeepClone(ws); 322 | /// ``` 323 | public func workspaceDeepClone(x : Workspace) : Workspace { 324 | var currentZone = 0; 325 | var currentChunk = 0; 326 | let ws = StableBuffer.initPresized(StableBuffer.size(x)); 327 | for (thisZone in StableBuffer.vals(x)) { 328 | let tz = StableBuffer.initPresized(StableBuffer.size(thisZone)); 329 | StableBuffer.add(ws, tz); 330 | for (thisDataChunk in StableBuffer.vals(thisZone)) { 331 | StableBuffer.add(tz, Clone.cloneCandy(thisDataChunk)); 332 | }; 333 | }; 334 | return ws; 335 | }; 336 | 337 | /// Create a `Workspace` from an `AddressedChunkArray`. 338 | /// 339 | /// Example: 340 | /// ```motoko include=import 341 | /// let arr: AddressedChunkArray = [(1, 1, #Nat(10)), (8, 4, #Float(3.14))]; 342 | /// let ws = Workspace.fromAddressedChunks(arr); 343 | /// ``` 344 | public func fromAddressedChunks(x : AddressedChunkArray) : Workspace { 345 | let result = StableBuffer.initPresized(x.size()); 346 | fileAddressedChunks(result, x); 347 | return result; 348 | }; 349 | 350 | public func fileAddressedChunks(workspace : Workspace, x : AddressedChunkArray) { 351 | for (thisChunk : AddressedChunk in Array.vals(x)) { 352 | let resultSize : Nat = StableBuffer.size(workspace); 353 | let targetZone = thisChunk.0 + 1; 354 | 355 | if (targetZone <= resultSize) { 356 | //zone exist 357 | } else { 358 | //append zone 359 | for (thisIndex in Iter.range(resultSize, targetZone -1)) { 360 | StableBuffer.add(workspace, StableBuffer.init()); 361 | }; 362 | }; 363 | 364 | let thisZone = StableBuffer.get(workspace, thisChunk.0); 365 | if (thisChunk.1 + 1 <= StableBuffer.size(thisZone)) { 366 | //zone exists 367 | StableBuffer.put(thisZone, thisChunk.1, Types.unshare(thisChunk.2)); 368 | } else { 369 | //append zone 370 | for (newChunk in Iter.range(StableBuffer.size(thisZone), thisChunk.1)) { 371 | let newBuffer = if (thisChunk.1 == newChunk) { 372 | //we know the size 373 | Types.unshare(thisChunk.2); 374 | } else { 375 | #Option(null); 376 | }; 377 | StableBuffer.add(thisZone, newBuffer); 378 | }; 379 | //return thisZone.get(thisChunk.1); 380 | }; 381 | }; 382 | return; 383 | }; 384 | 385 | /// Get the size in bytes taken up by all values in the `DataZone`. 386 | public func getDataZoneSize(dz : DataZone) : Nat { 387 | var size : Nat = 0; 388 | for (thisChunk in StableBuffer.vals(dz)) { 389 | size += getCandySize(thisChunk); 390 | }; 391 | return size; 392 | }; 393 | 394 | /// Get the number of chunks for `Workspace` when dividing into chunks of size <= _maxChunkSize . 395 | /// 396 | /// Example: 397 | /// ```motoko include=import 398 | /// let ws = Workspace.initWorkspace(3); 399 | /// StableBuffer.add(ws, StableBuffer.initPresized(1)); 400 | /// StableBuffer.add(ws, StableBuffer.initPresized(5)); 401 | /// StableBuffer.add(ws, StableBuffer.initPresized(2)); 402 | /// let chunk_size = Workspace.getWorkspaceChunkSize(ws, 2); 403 | /// ``` 404 | public func getWorkspaceChunkSize(_workspace : Workspace, _maxChunkSize : Nat) : Nat { 405 | var currentChunk : Nat = 0; 406 | var handBrake = 0; 407 | var zoneTracker = 0; 408 | var chunkTracker = 0; 409 | 410 | label chunking while (1 == 1) { 411 | handBrake += 1; 412 | if (handBrake > 10000) { break chunking }; 413 | var foundBytes = 0; 414 | //calc bytes 415 | for (thisZone in Iter.range(zoneTracker, StableBuffer.size(_workspace) -1)) { 416 | for (thisChunk in Iter.range(chunkTracker, StableBuffer.size(StableBuffer.get(_workspace, thisZone)) -1)) { 417 | let thisItem = StableBuffer.get(StableBuffer.get(_workspace, thisZone), thisChunk); 418 | let newSize = foundBytes + getCandySize(thisItem); 419 | if (newSize > _maxChunkSize) { 420 | //went over bytes 421 | currentChunk += 1; 422 | zoneTracker := thisZone; 423 | chunkTracker := thisChunk; 424 | continue chunking; 425 | }; 426 | //adding some bytes 427 | foundBytes := newSize; 428 | }; 429 | }; 430 | }; 431 | 432 | currentChunk += 1; 433 | return currentChunk; 434 | }; 435 | 436 | public func getWorkspaceChunk(_workspace : Workspace, _chunkID : Nat, _maxChunkSize : Nat) : ({ #eof; #chunk }, AddressedChunkBuffer) { 437 | var currentChunk : Nat = 0; 438 | var handBrake = 0; 439 | var zoneTracker = 0; 440 | var chunkTracker = 0; 441 | 442 | let resultBuffer = StableBuffer.init(); 443 | label chunking while (1 == 1) { 444 | handBrake += 1; 445 | if (handBrake > 10000) { break chunking }; 446 | var foundBytes = 0; 447 | //calc bytes 448 | for (thisZone in Iter.range(zoneTracker, StableBuffer.size(_workspace) -1)) { 449 | for (thisChunk in Iter.range(chunkTracker, StableBuffer.size(StableBuffer.get(_workspace, thisZone)) -1)) { 450 | let thisItem = StableBuffer.get(StableBuffer.get(_workspace, thisZone), thisChunk); 451 | 452 | let newSize = foundBytes + getCandySize(thisItem); 453 | if (newSize > _maxChunkSize) { 454 | //went over bytes 455 | if (currentChunk == _chunkID) { 456 | return (#chunk, resultBuffer); 457 | }; 458 | currentChunk += 1; 459 | zoneTracker := thisZone; 460 | chunkTracker := thisChunk; 461 | continue chunking; 462 | }; 463 | if (currentChunk == _chunkID) { 464 | //add it to our return 465 | StableBuffer.add(resultBuffer, (thisZone, thisChunk, Types.shareCandy(thisItem))); 466 | }; 467 | 468 | foundBytes := newSize; 469 | }; 470 | }; 471 | return (#eof, resultBuffer); 472 | }; 473 | return (#eof, resultBuffer); 474 | }; 475 | 476 | /// Get the size of the `AddressedChunkArray`. 477 | /// 478 | /// Example: 479 | /// ```motoko include=import 480 | /// let arr: AddressedChunkArray = [(1, 1, #Nat(10)), (8, 4, #Float(3.14))]; 481 | /// let size = Workspace.getAddressedChunkArraySize(arr); // 21 482 | /// ``` 483 | /// Note: This only works for up to 32 bytes addresses. 484 | public func getAddressedChunkArraySize(item : AddressedChunkArray) : Nat { 485 | var size : Nat = 0; 486 | for (thisItem in item.vals()) { 487 | size += getCandySharedSize(thisItem.2) + 4 + 4; //only works for up to 32 byte adresess...should be fine but verify and document. 488 | }; 489 | return size; 490 | }; 491 | 492 | /// Get the data chunk from a `AddressedChunkArray` at the given zone and chunk. 493 | /// 494 | /// Example: 495 | /// ```motoko include=import 496 | /// let arr: AddressedChunkArray = [(1, 1, #Nat(10)), (8, 4, #Float(3.14))]; 497 | /// let chunk = Workspace.getDataChunkFromAddressedChunkArray(arr, 8, 4); // #Float(3.14) 498 | /// ``` 499 | public func getDataChunkFromAddressedChunkArray(item : AddressedChunkArray, dataZone : Nat, dataChunk : Nat) : CandyShared { 500 | for (thisItem in item.vals()) { 501 | if (thisItem.0 == dataZone and thisItem.1 == dataChunk) { 502 | return thisItem.2; 503 | }; 504 | }; 505 | return #Option(null); 506 | }; 507 | 508 | /// Convert the `DataZone` into a `Buffer>`. 509 | /// 510 | /// Example: 511 | /// ```motoko include=import 512 | /// let dz = Workspace.initDataZone(#Nat(24)); 513 | /// let buff = Workspace.byteBufferDataZoneToBuffer(dz); 514 | /// ``` 515 | public func byteBufferDataZoneToBuffer(dz : DataZone) : Buffer.Buffer> { 516 | let result = Buffer.Buffer>(StableBuffer.size(dz)); 517 | for (thisItem in StableBuffer.vals(dz)) { 518 | result.add(Conversion.candyToBytesBuffer(thisItem)); 519 | }; 520 | return result; 521 | }; 522 | 523 | public func byteBufferChunksToCandyBufferDataZone(buffer : Buffer.Buffer>) : DataZone { 524 | let result = StableBuffer.initPresized(buffer.size()); 525 | for (thisItem in buffer.vals()) { 526 | StableBuffer.add(result, #Bytes(Types.toBuffer(Buffer.toArray(thisItem)))); 527 | }; 528 | return result; 529 | }; 530 | 531 | /// Initialize a `DataZone` with the given value. 532 | public func initDataZone(val : Candy) : DataZone { 533 | let result = StableBuffer.init(); 534 | StableBuffer.add(result, val); 535 | return result; 536 | }; 537 | 538 | /// Flatten an `AddressedChunkArray` to produce `[Nat8]`. 539 | /// 540 | /// Example: 541 | /// ```motoko include=import 542 | /// let arr: AddressedChunkArray = [(1, 1, #Nat(10)), (8, 4, #Float(3.14))]; 543 | /// let bytes = Workspace.flattenAddressedChunkArray(arr); 544 | /// ``` 545 | /// Note: Loses integrity after 256 Zones or 256 chunks. 546 | public func flattenAddressedChunkArray(data : AddressedChunkArray) : [Nat8] { 547 | let accumulator : Buffer.Buffer = Buffer.Buffer(getAddressedChunkArraySize(data)); 548 | for (thisItem in data.vals()) { 549 | for (thisbyte in Conversion.natToBytes(thisItem.0).vals()) { 550 | accumulator.add(thisbyte); 551 | }; 552 | for (thisbyte in Conversion.natToBytes(thisItem.1).vals()) { 553 | accumulator.add(thisbyte); 554 | }; 555 | for (thisbyte in Conversion.candySharedToBytes(thisItem.2).vals()) { 556 | accumulator.add(thisbyte); 557 | }; 558 | }; 559 | return Buffer.toArray(accumulator); 560 | }; 561 | }; 562 | -------------------------------------------------------------------------------- /test/bool_to_bytes.test.mo: -------------------------------------------------------------------------------- 1 | import Debug "mo:base/Debug"; 2 | import Conversion "../src/icrc16/conversion"; 3 | import {test} "mo:test"; 4 | 5 | 6 | func testBoolToBytes() { 7 | // Test case 1: Convert true to bytes 8 | let result1 = Conversion.boolToBytes(true); 9 | Debug.print(debug_show(result1)); 10 | assert(result1 == [1]); 11 | 12 | // Test case 2: Convert false to bytes 13 | let result2 = Conversion.boolToBytes(false); 14 | Debug.print(debug_show(result2)); 15 | assert(result2 == [0]); 16 | 17 | Debug.print("All boolToBytes tests passed."); 18 | }; 19 | 20 | test("boolToBytes", testBoolToBytes); 21 | 22 | -------------------------------------------------------------------------------- /test/bytes_to_bool.test.mo: -------------------------------------------------------------------------------- 1 | import Debug "mo:base/Debug"; 2 | import Conversion "../src/icrc16/conversion"; 3 | import {test} "mo:test"; 4 | 5 | 6 | func testBytesToBool() { 7 | // Test case 1: Convert bytes [1] to true 8 | let result1 = Conversion.bytesToBool([1]); 9 | Debug.print(debug_show(result1)); 10 | assert(result1 == true); 11 | 12 | // Test case 2: Convert bytes [0] to false 13 | let result2 = Conversion.bytesToBool([0]); 14 | Debug.print(debug_show(result2)); 15 | assert(result2 == false); 16 | 17 | // Test case 3: Invalid bytes input (empty array) 18 | let result3 = Conversion.bytesToBool([]); 19 | 20 | //Debug.print(debug_show(result3)); 21 | assert(result3 == false); // Assuming empty input returns false 22 | 23 | // Test case 4: Invalid bytes input (array with multiple elements) 24 | let result4 = Conversion.bytesToBool([1, 0]); 25 | 26 | //Debug.print(debug_show(result4)); 27 | assert(result4); 28 | 29 | Debug.print("All bytesToBool tests passed."); 30 | }; 31 | 32 | test("testBytesToBool", testBytesToBool); 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/candid.test.mo: -------------------------------------------------------------------------------- 1 | import D "mo:base/Debug"; 2 | import {test} "mo:test"; 3 | import Candid "../src/candid"; 4 | import Types "../src/types"; 5 | import Conv "../src/conversion"; 6 | import Principal "mo:base/Principal"; 7 | import Nat "mo:base/Nat"; 8 | import Int "mo:base/Int"; 9 | import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; 10 | import Array "mo:base/Array"; 11 | import Nat8 "mo:base/Nat8"; 12 | import Text "mo:base/Text"; 13 | import CandidTypes "mo:candid-old/Type"; 14 | import Arg "mo:candid-old/Arg"; 15 | import Value "mo:candid-old/Value"; 16 | 17 | type CandyShared = Types.CandyShared; 18 | 19 | // Helper function to assert equality on Arg values. 20 | func assertArgEq(expected: Arg.Arg, actual: Arg.Arg) { 21 | D.print("in assert equal" # debug_show(expected, actual)); 22 | assert(CandidTypes.equal(expected._type, actual._type)); 23 | switch (expected.value, actual.value) { 24 | case (#text(e), #text(a)) { assert(e == a); }; 25 | case (#principal(e), #principal(a)) { assert(e == a); }; 26 | case (#int(e), #int(a)) { assert(e == a); }; 27 | case (#nat(e), #nat(a)) { assert(e == a); }; 28 | case (#int8(e), #int8(a)) { assert(e == a); }; 29 | case (#int16(e), #int16(a)) { assert(e == a); }; 30 | case (#int32(e), #int32(a)) { assert(e == a); }; 31 | case (#int64(e), #int64(a)) { assert(e == a); }; 32 | case (#nat8(e), #nat8(a)) { assert(e == a); }; 33 | case (#nat16(e), #nat16(a)) { assert(e == a); }; 34 | case (#nat32(e), #nat32(a)) { assert(e == a); }; 35 | case (#nat64(e), #nat64(a)) { assert(e == a); }; 36 | case (#bool(e), #bool(a)) { assert(e == a); }; 37 | case (#float32(e), #float32(a)) { assert(e == a); }; 38 | case (#float64(e), #float64(a)) { assert(e == a); }; 39 | case (#opt(?e), #opt(?a)) { assert(Value.equal(e,a)) }; 40 | case (#vector(e), #vector(a)) { assert(Array.equal((e,a, Value.equal))) }; 41 | case (#record(e), #record(a)) { assert(Value.equal((expected.value, actual.value))) }; 42 | case (#_null(e), #_null(a)) { assert(true); }; 43 | // ... Add other cases as needed for complete coverage of Value variants. 44 | case (_, _) { assert(false);//, "Types of Arg values do not match."); 45 | }; 46 | }; 47 | }; 48 | 49 | // Helper function to construct Arg values for testing. 50 | func makeArg(t: CandidTypes.Type, v: Value.Value): Arg.Arg { 51 | {_type = t; value = v} 52 | }; 53 | 54 | // Test cases for value_to_candid 55 | test("should convert CandyShared #Nat to Arg with appropriate Candid type", func() { 56 | let candy: CandyShared = #Nat(42); 57 | let expectedArg = makeArg(#nat, #nat(42)); 58 | let result = Candid.value_to_candid(candy); 59 | assertArgEq(expectedArg, result[0]); 60 | }); 61 | 62 | test("should convert CandyShared #Nat64 to Arg with appropriate Candid type", func() { 63 | let candy: CandyShared = #Nat64(42); 64 | let expectedArg = makeArg(#nat64, #nat64(42: Nat64)); 65 | let result = Candid.value_to_candid(candy); 66 | assertArgEq(expectedArg, result[0]); 67 | }); 68 | 69 | // ... Similar tests for #Nat32, #Nat16, #Nat8, #Text, #Principal, #Bool, #Float, and so on. 70 | 71 | test("should convert CandyShared #Text to Arg with appropriate Candid type", func() { 72 | let candy: CandyShared = #Text("Hello"); 73 | let expectedArg = makeArg(#text, #text("Hello")); 74 | let result = Candid.value_to_candid(candy); 75 | assertArgEq(expectedArg, result[0]); 76 | }); 77 | 78 | test("should convert CandyShared #Bool to Arg with appropriate Candid type", func() { 79 | let candy: CandyShared = #Bool(true); 80 | let expectedArg = makeArg(#bool, #bool(true)); 81 | let result = Candid.value_to_candid(candy); 82 | assertArgEq(expectedArg, result[0]); 83 | }); 84 | 85 | test("should convert CandyShared #Principal to Arg with appropriate Candid type", func() { 86 | let principalValue = Principal.fromText("rrkah-fqaaa-aaaaa-aaaaq-cai"); 87 | let candy: CandyShared = #Principal(principalValue); 88 | let expectedArg = makeArg(#principal, #principal(#transparent(principalValue))); 89 | let result = Candid.value_to_candid(candy); 90 | assertArgEq(expectedArg, result[0]); 91 | }); 92 | 93 | test("should convert CandyShared #Option(null) to proper Arg with appropriate Candid type", func() { 94 | let candy: CandyShared = #Option(null); 95 | let expectedArg = makeArg(#opt(#_null), #_null); 96 | let result = Candid.value_to_candid(candy); 97 | D.print(debug_show(expectedArg, result)); 98 | assertArgEq(expectedArg, result[0]); 99 | }); 100 | 101 | test("should convert CandyShared #Option(value) to proper Arg with appropriate Candid type", func() { 102 | let candy: CandyShared = #Option(?#Nat(42)); 103 | let expectedArg = makeArg(#opt(#nat), #opt(?#nat(42))); 104 | let result = Candid.value_to_candid(candy); 105 | D.print(debug_show(expectedArg, result)); 106 | assertArgEq(expectedArg, result[0]); 107 | }); 108 | 109 | test("should convert CandyShared #Set to Arg with appropriate Candid type", func() { 110 | let candy: CandyShared = #Set([#Nat(1), #Nat(2), #Nat(3)]); 111 | let expectedArg = makeArg(#vector(#nat), 112 | #vector([ 113 | #nat(1), 114 | #nat(2), 115 | #nat(3) 116 | ]) 117 | ); 118 | let result = Candid.value_to_candid(candy); 119 | assertArgEq(expectedArg, result[0]); 120 | }); 121 | 122 | // Test conversion of CandyShared to Candid for the #Map type. 123 | test("should convert CandyShared #Map to Arg with appropriate Candid type", func() { 124 | let candy: CandyShared = #Map([ 125 | ("key1", #Nat(1)), 126 | ("key2", #Text("two")), 127 | ("key3", #Bool(true)) 128 | ]); 129 | let expectedArg = makeArg(#record([ 130 | // Need to sort fields in lexicographical order for #record type 131 | {tag = #name("key1"); _type = #nat}, 132 | {tag = #name("key2"); _type = #text}, 133 | {tag = #name("key3"); _type = #bool} 134 | ]), 135 | #record([ 136 | {tag = #name("key1"); value = #nat(1)}, 137 | {tag = #name("key2"); value = #text("two")}, 138 | {tag = #name("key3"); value = #bool(true)} 139 | ])); 140 | let result = Candid.value_to_candid(candy); 141 | D.print(debug_show(result)); 142 | D.print(debug_show(expectedArg)); 143 | assertArgEq(expectedArg, result[0]); 144 | }); 145 | 146 | // Test conversion of CandyShared to Candid for the #ValueMap type. 147 | test("should convert CandyShared #ValueMap to Arg with appropriate Candid type", func() { 148 | let keyVal1: (CandyShared, CandyShared) = (#Nat(10), #Nat(30)); 149 | let keyVal2: (CandyShared, CandyShared) = (#Nat(20), #Nat(40)); 150 | let candy: CandyShared = #ValueMap([keyVal1, keyVal2]); 151 | let expectedArg = makeArg(#record([ 152 | {tag = #hash(0 : Nat32); _type = #record([{tag = #hash(0 : Nat32); _type = #nat;},{tag = #hash(1 : Nat32); _type = #nat;}])}, 153 | {tag = #hash(1 : Nat32); _type = #record([{tag = #hash(0 : Nat32); _type = #nat;},{tag = #hash(1 : Nat32); _type = #nat;}])}, 154 | ]), 155 | #record([ 156 | {tag = #hash(0 : Nat32); value = #record([ {tag = #hash(0 : Nat32);value=#nat(10)}, {tag = #hash(1 : Nat32); value=#nat(30)}])}, 157 | {tag = #hash(1 : Nat32); value = #record([ {tag = #hash(0 : Nat32);value=#nat(20)}, {tag = #hash(1 : Nat32); value=#nat(40)}])}, 158 | ])); 159 | let result = Candid.value_to_candid(candy); 160 | 161 | D.print(debug_show(result)); 162 | D.print(debug_show(expectedArg)); 163 | assertArgEq(expectedArg, result[0]); 164 | // Repeat this pattern for each pair in the ValueMap. 165 | }); 166 | 167 | // Test conversion of CandyShared to Candid for the #Class type. 168 | test("should convert CandyShared #Class to Arg with appropriate Candid type", func() { 169 | let candy: CandyShared = #Class([ 170 | {name = "age"; value = #Nat(30); immutable = true}, 171 | {name = "name"; value = #Text("Alice"); immutable = true}, 172 | {name = "verified"; value = #Bool(true); immutable = true} 173 | ]); 174 | let expectedArg = makeArg(#record([ 175 | {tag = #name("age"); _type = #nat}, 176 | {tag = #name("name"); _type = #text}, 177 | {tag = #name("verified"); _type = #bool} 178 | ]), 179 | #record([ 180 | {tag = #name("age"); value = #nat(30)}, 181 | {tag = #name("name"); value = #text("Alice")}, 182 | {tag = #name("verified"); value = #bool(true)} 183 | ])); 184 | let result = Candid.value_to_candid(candy); 185 | assertArgEq(expectedArg, result[0]); 186 | }); 187 | 188 | // Test conversion of CandyShared to Candid for the #Array type. 189 | test("should convert CandyShared #Array to Arg with appropriate Candid type", func() { 190 | let candy: CandyShared = #Array([#Nat(1), #Nat(2), #Nat(3)]); 191 | let expectedArg = makeArg(#vector(#nat), 192 | #vector([ 193 | #nat(1), 194 | #nat(2), 195 | #nat(3) 196 | ]) 197 | ); 198 | let result = Candid.value_to_candid(candy); 199 | assertArgEq(expectedArg, result[0]); 200 | }); 201 | 202 | // Test conversion of CandyShared to Candid for the #Bytes type. 203 | test("should convert CandyShared #Bytes to Arg with appropriate Candid type", func() { 204 | let candy: CandyShared = #Bytes([0x01, 0x02, 0x03]); 205 | let expectedArg = makeArg(#vector(#nat8), 206 | #vector([ 207 | #nat8(1:Nat8), 208 | #nat8(2:Nat8), 209 | #nat8(3:Nat8) 210 | ]) 211 | ); 212 | let result = Candid.value_to_candid(candy); 213 | assertArgEq(expectedArg, result[0]); 214 | }); 215 | 216 | 217 | // Test conversion of CandyShared #Float to Arg with appropriate Candid type 218 | test("should convert CandyShared #Float to Arg with appropriate Candid type", func() { 219 | let candy: CandyShared = #Float(42.42); 220 | let expectedArg = makeArg(#float64, #float64(42.42)); 221 | let result = Candid.value_to_candid(candy); 222 | assertArgEq(expectedArg, result[0]); 223 | }); 224 | 225 | // Test conversion of CandyShared #Int to Arg with appropriate Candid type 226 | test("should convert CandyShared #Int to Arg with appropriate Candid type", func() { 227 | let candy: CandyShared = #Int(42); 228 | let expectedArg = makeArg(#int, #int(42)); 229 | let result = Candid.value_to_candid(candy); 230 | assertArgEq(expectedArg, result[0]); 231 | }); 232 | 233 | // Test conversion of CandyShared #Int8 to Arg with appropriate Candid type 234 | test("should convert CandyShared #Int8 to Arg with appropriate Candid type", func() { 235 | let candy: CandyShared = #Int8(42); 236 | let expectedArg = makeArg(#int8, #int8(42 : Int8)); 237 | let result = Candid.value_to_candid(candy); 238 | assertArgEq(expectedArg, result[0]); 239 | }); 240 | 241 | // Test conversion of CandyShared #Int16 to Arg with appropriate Candid type 242 | test("should convert CandyShared #Int16 to Arg with appropriate Candid type", func() { 243 | let candy: CandyShared = #Int16(42); 244 | let expectedArg = makeArg(#int16, #int16(42 : Int16)); 245 | let result = Candid.value_to_candid(candy); 246 | assertArgEq(expectedArg, result[0]); 247 | }); 248 | 249 | // Test conversion of CandyShared #Int32 to Arg with appropriate Candid type 250 | test("should convert CandyShared #Int32 to Arg with appropriate Candid type", func() { 251 | let candy: CandyShared = #Int32(42); 252 | let expectedArg = makeArg(#int32, #int32(42:Int32)); 253 | let result = Candid.value_to_candid(candy); 254 | assertArgEq(expectedArg, result[0]); 255 | }); 256 | 257 | // Test conversion of CandyShared #Int64 to Arg with appropriate Candid type 258 | test("should convert CandyShared #Int64 to Arg with appropriate Candid type", func() { 259 | let candy: CandyShared = #Int64(42); 260 | let expectedArg = makeArg(#int64, #int64(42:Int64)); 261 | let result = Candid.value_to_candid(candy); 262 | assertArgEq(expectedArg, result[0]); 263 | }); 264 | 265 | // Test conversion of CandyShared #Class with mixed types to Arg with appropriate Candid type 266 | test("should convert CandyShared #Class with mixed types to Arg with appropriate Candid type", func() { 267 | let candy: CandyShared = #Class([ 268 | {name = "age"; value = #Nat(30); immutable = true}, 269 | {name = "name"; value = #Text("Alice"); immutable = true}, 270 | {name = "verified"; value = #Bool(true); immutable = true}, 271 | {name = "balance"; value = #Float(100.50); immutable = false} 272 | ]); 273 | let expectedArg = makeArg(#record([ 274 | {tag = #name("age"); _type = #nat}, 275 | {tag = #name("name"); _type = #text}, 276 | {tag = #name("verified"); _type = #bool}, 277 | {tag = #name("balance"); _type = #float64} 278 | ]), 279 | #record([ 280 | {tag = #name("age"); value = #nat(30)}, 281 | {tag = #name("name"); value = #text("Alice")}, 282 | {tag = #name("verified"); value = #bool(true)}, 283 | {tag = #name("balance"); value = #float64(100.50)} 284 | ])); 285 | let result = Candid.value_to_candid(candy); 286 | assertArgEq(expectedArg, result[0]); 287 | }); 288 | 289 | /* // Test conversion of CandyShared #Array with mixed types to Arg with appropriate Candid type 290 | test("should convert CandyShared #Array with mixed types to Arg with appropriate Candid type", func() { 291 | let candy: CandyShared = #Array([ 292 | #Nat(1), 293 | #Text("Alice"), 294 | #Bool(true), 295 | #Float(100.50) 296 | ]); 297 | let expectedArg = makeArg(#vector, 298 | #vector([ 299 | #nat(1 : Nat), 300 | #text("Alice"), 301 | #bool(true), 302 | #float64(100.50 : Float) 303 | ]) 304 | ); 305 | let result = Candid.value_to_candid(candy); 306 | assertArgEq(expectedArg, result[0]); 307 | }); */ 308 | 309 | /* // Test conversion of CandyShared #ValueMap with different value types to Arg with appropriate Candid type 310 | test("should convert CandyShared #ValueMap with different value types to Arg with appropriate Candid type", func() { 311 | let candy: CandyShared = #ValueMap([ 312 | (#Nat(10), #Text("ten")), 313 | (#Bool(false), #Float(200.25)) 314 | ]); 315 | let expectedArg = makeArg(#record([{_type = #any; tag = #hash(0)}]), 316 | #record([ 317 | {tag = #hash(0); value = #record([{tag = #hash(0); value = #nat(10)}, {tag = #hash(1); value = #text("ten")}])}, 318 | {tag = #hash(1); value = #record([{tag = #hash(0); value = #bool(false)}, {tag = #hash(1); value = #float64(200.25)}])} 319 | ]) 320 | ); 321 | let result = Candid.value_to_candid(candy); 322 | assertArgEq(expectedArg, result[0]); 323 | }); */ -------------------------------------------------------------------------------- /test/clone.test.mo: -------------------------------------------------------------------------------- 1 | import D "mo:base/Debug"; 2 | import {test} "mo:test"; 3 | import Types "../src/types"; 4 | import Clone "../src/clone"; 5 | import Principal "mo:base/Principal"; 6 | import Nat "mo:base/Nat"; 7 | import Nat8 "mo:base/Nat8"; 8 | import Nat16 "mo:base/Nat16"; 9 | import Nat32 "mo:base/Nat32"; 10 | import Nat64 "mo:base/Nat64"; 11 | import Int "mo:base/Int"; 12 | import Int8 "mo:base/Int8"; 13 | import Int16 "mo:base/Int16"; 14 | import Int32 "mo:base/Int32"; 15 | import Int64 "mo:base/Int64"; 16 | import Iter "mo:base/Iter"; 17 | import Float "mo:base/Float"; 18 | import Bool "mo:base/Bool"; 19 | import Text "mo:base/Text"; 20 | import Blob "mo:base/Blob"; 21 | import Array "mo:base/Array"; 22 | import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; 23 | import Map "mo:map9/Map"; 24 | import Set "mo:map9/Set"; 25 | 26 | type Candy = Types.Candy; 27 | type CandyShared = Types.CandyShared; 28 | type Property = Types.Property; 29 | 30 | // Helper function for deep equality check of Candy type 31 | func deepEqualCandy(x : Candy, y : Candy) : Bool { 32 | switch (x, y) { 33 | case (#Int(a), #Int(b)) Int.equal(a, b); 34 | case (#Int8(a), #Int8(b)) Int8.equal(a, b); 35 | case (#Int16(a), #Int16(b)) Int16.equal(a, b); 36 | case (#Int32(a), #Int32(b)) Int32.equal(a, b); 37 | case (#Int64(a), #Int64(b)) Int64.equal(a, b); 38 | case (#Nat(a), #Nat(b)) Nat.equal(a, b); 39 | case (#Nat8(a), #Nat8(b)) Nat8.equal(a, b); 40 | case (#Nat16(a), #Nat16(b)) Nat16.equal(a, b); 41 | case (#Nat32(a), #Nat32(b)) Nat32.equal(a, b); 42 | case (#Nat64(a), #Nat64(b)) Nat64.equal(a, b); 43 | case (#Float(a), #Float(b)) Float.equal(a, b); 44 | case (#Text(a), #Text(b)) Text.equal(a, b); 45 | case (#Bool(a), #Bool(b)) a == b; 46 | case (#Blob(a), #Blob(b)) Blob.equal(a, b); 47 | case (#Principal(a), #Principal(b)) Principal.equal(a, b); 48 | case (#Array(a), #Array(b)) { 49 | if (StableBuffer.size(a) != StableBuffer.size(b)) { 50 | return false; 51 | }; 52 | for (index in Iter.range(0, StableBuffer.size(a) - 1)) { 53 | if (not (deepEqualCandy(StableBuffer.get(a, index), StableBuffer.get(b, index)))) { 54 | return false; 55 | }; 56 | }; 57 | true; 58 | }; 59 | case (#Class(a), #Class(b)) { 60 | if (Map.size(a) != Map.size(b)) { 61 | return false; 62 | }; 63 | for ((name, propA) in Map.entries(a)) { 64 | switch (Map.get(b, Map.thash, name)) { 65 | case (null) { return false; }; 66 | case (?propB) { 67 | if (not (propA.immutable == propB.immutable and deepEqualCandy(propA.value, propB.value))) { 68 | return false; 69 | }; 70 | }; 71 | }; 72 | }; 73 | true; 74 | }; 75 | case (#Set(a), #Set(b)) { 76 | if (Set.size(a) != Set.size(b)) { 77 | return false; 78 | }; 79 | for (elem in Set.keys(a)) { 80 | if (not (Set.has(b, Types.candyMapHashTool, elem))) { 81 | return false; 82 | }; 83 | }; 84 | true; 85 | }; 86 | case (#Map(a), #Map(b)) { 87 | if (Map.size(a) != Map.size(b)) { 88 | return false; 89 | }; 90 | for ((key, valueA) in Map.entries(a)) { 91 | switch (Map.get(b, Map.thash, key)) { 92 | case (null) { return false; }; 93 | case (?valueB) { 94 | if (not(deepEqualCandy(valueA, valueB))) { 95 | return false; 96 | }; 97 | }; 98 | }; 99 | }; 100 | true; 101 | }; 102 | case (#ValueMap(a), #ValueMap(b)) { 103 | if (Map.size(a) != Map.size(b)) { 104 | return false; 105 | }; 106 | for ((keyA, valueA) in Map.entries(a)) { 107 | var found = false; 108 | for ((keyB, valueB) in Map.entries(b)) { 109 | // Since keys can be any candy, we cannot use Map.get here and have to iterate 110 | if (deepEqualCandy(keyA, keyB) and deepEqualCandy(valueA, valueB)) { 111 | found := true; 112 | }; 113 | }; 114 | if (not found) { 115 | return false; 116 | }; 117 | }; 118 | true; 119 | }; 120 | case (#Option(aOpt), #Option(bOpt)) { 121 | switch (aOpt, bOpt) { 122 | case (null, null) true; 123 | case (?a, ?b) deepEqualCandy(a, b); 124 | case (_, _) false; 125 | } 126 | }; 127 | case (_, _) false; 128 | }; 129 | }; 130 | 131 | test("cloneCandy should deep clone #Int", func() { 132 | let value: Candy = #Int(42); 133 | let cloned = Clone.cloneCandy(value); 134 | assert(deepEqualCandy(value, cloned)); 135 | }); 136 | 137 | test("cloneCandy should deep clone #Int64", func() { 138 | let value: Candy = #Int64(42); 139 | let cloned = Clone.cloneCandy(value); 140 | assert(deepEqualCandy(value, cloned)); 141 | }); 142 | 143 | test("cloneCandy should deep clone #Nat literals", func() { 144 | let value: Candy = #Nat(123); 145 | let cloned = Clone.cloneCandy(value); 146 | assert(deepEqualCandy(value, cloned)); 147 | }); 148 | 149 | test("cloneCandy should deep clone #Float", func() { 150 | let value: Candy = #Float(42.0); 151 | let cloned = Clone.cloneCandy(value); 152 | assert(deepEqualCandy(value, cloned)); 153 | }); 154 | 155 | test("cloneCandy should deep clone #Text", func() { 156 | let value: Candy = #Text("Hello, world!"); 157 | let cloned = Clone.cloneCandy(value); 158 | assert(deepEqualCandy(value, cloned)); 159 | }); 160 | 161 | test("cloneCandy should deep clone #Blob", func() { 162 | let value: Candy = #Blob(Blob.fromArray([1, 2])); 163 | let cloned = Clone.cloneCandy(value); 164 | assert(deepEqualCandy(value, cloned)); 165 | }); 166 | 167 | test("cloneCandy should deep clone #Principal", func() { 168 | let value: Candy = #Principal(Principal.fromText("2vxsx-fae")); 169 | let cloned = Clone.cloneCandy(value); 170 | assert(deepEqualCandy(value, cloned)); 171 | }); 172 | 173 | test("cloneCandy should deep clone #Class", func() { 174 | let prop1: Candy = #Int(42); 175 | let prop2: Candy = #Text("Hello, world!"); 176 | let value: Candy = #Class(Map.fromIter([ 177 | ("int_prop", {name = "int_prop"; value = prop1; immutable = false}), 178 | ("text_prop", {name = "text_prop"; value = prop2; immutable = false}) 179 | ].vals(), Map.thash)); 180 | let cloned = Clone.cloneCandy(value); 181 | assert(deepEqualCandy(value, cloned)); 182 | }); 183 | 184 | test("cloneCandy should deep clone #Array", func() { 185 | let value: Candy = #Array(StableBuffer.fromArray([#Int(42), #Text("Hello, world!")])); 186 | let cloned = Clone.cloneCandy(value); 187 | assert(deepEqualCandy(value, cloned)); 188 | }); 189 | 190 | test("cloneCandy should deep clone #Map", func() { 191 | let value: Candy = #Map(Map.fromIter( 192 | [("key1", #Int(42)), ("key2", #Text("Hello, world!"))].vals(), 193 | Map.thash 194 | )); 195 | let cloned = Clone.cloneCandy(value); 196 | assert(deepEqualCandy(value, cloned)); 197 | }); 198 | 199 | test("cloneCandy should deep clone #ValueMap", func() { 200 | let value: Candy = #ValueMap(Map.fromIter( 201 | [(#Int(1), #Int(42)), (#Int(2), #Text("Hello, world!"))].vals(), 202 | Types.candyMapHashTool 203 | )); 204 | let cloned = Clone.cloneCandy(value); 205 | assert(deepEqualCandy(value, cloned)); 206 | }); 207 | 208 | test("cloneCandy should deep clone #Set", func() { 209 | let value: Candy = #Set(Set.fromIter( 210 | [#Int(42), #Text("Hello, world!")].vals(), 211 | Types.candyMapHashTool 212 | )); 213 | let cloned = Clone.cloneCandy(value); 214 | assert(deepEqualCandy(value, cloned)); 215 | }); 216 | 217 | test("cloneCandy should deep clone #Option", func() { 218 | let value: Candy = #Option(?#Int(42)); 219 | let cloned = Clone.cloneCandy(value); 220 | assert(deepEqualCandy(value, cloned)); 221 | }); -------------------------------------------------------------------------------- /test/json.test.mo: -------------------------------------------------------------------------------- 1 | import D "mo:base/Debug"; 2 | import {test} "mo:test"; 3 | import Json "../src/json"; 4 | import Types "../src/types"; 5 | import Nat "mo:base/Nat"; 6 | import Nat8 "mo:base/Nat8"; 7 | import Text "mo:base/Text"; 8 | import Principal "mo:base/Principal"; 9 | 10 | type CandyShared = Types.CandyShared; 11 | 12 | test("value_to_json should convert Nat to JSON", func() { 13 | let value: CandyShared = #Nat(123); 14 | let json = Json.value_to_json(value); 15 | assert(json == "123"); 16 | }); 17 | 18 | test("value_to_json should convert Nat8 to JSON", func() { 19 | let value: CandyShared = #Nat8(45); 20 | let json = Json.value_to_json(value); 21 | assert(json == "45"); 22 | }); 23 | 24 | test("value_to_json should convert Nat16 to JSON", func() { 25 | let value: CandyShared = #Nat16(400); 26 | let json = Json.value_to_json(value); 27 | assert(json == "400"); 28 | }); 29 | 30 | test("value_to_json should convert Nat32 to JSON", func() { 31 | let value: CandyShared = #Nat32(50000); 32 | let json = Json.value_to_json(value); 33 | assert(json == "50000"); 34 | }); 35 | 36 | test("value_to_json should convert Nat64 to JSON", func() { 37 | let value: CandyShared = #Nat64(70000000); 38 | let json = Json.value_to_json(value); 39 | assert(json == "70000000"); 40 | }); 41 | 42 | test("value_to_json should convert Int to JSON", func() { 43 | let value: CandyShared = #Int(-123); 44 | let json = Json.value_to_json(value); 45 | assert(json == "-123"); 46 | }); 47 | 48 | test("value_to_json should convert Int8 to JSON", func() { 49 | let value: CandyShared = #Int8(-45); 50 | let json = Json.value_to_json(value); 51 | assert(json == "-45"); 52 | }); 53 | 54 | test("value_to_json should convert Int16 to JSON", func() { 55 | let value: CandyShared = #Int16(-400); 56 | let json = Json.value_to_json(value); 57 | assert(json == "-400"); 58 | }); 59 | 60 | test("value_to_json should convert Int32 to JSON", func() { 61 | let value: CandyShared = #Int32(-50000); 62 | let json = Json.value_to_json(value); 63 | assert(json == "-50000"); 64 | }); 65 | 66 | test("value_to_json should convert Int64 to JSON", func() { 67 | let value: CandyShared = #Int64(-70000000); 68 | let json = Json.value_to_json(value); 69 | assert(json == "-70000000"); 70 | }); 71 | 72 | test("value_to_json should convert Float to JSON", func() { 73 | let value: CandyShared = #Float(3.1415); 74 | let json = Json.value_to_json(value); 75 | D.print(debug_show(json)); 76 | assert(Text.startsWith(json, #text("3.1415"))); // Handle float precision 77 | }); 78 | 79 | test("value_to_json should convert Text to JSON", func() { 80 | let value: CandyShared = #Text("Hello, World!"); 81 | let json = Json.value_to_json(value); 82 | assert(json == "\"Hello, World!\""); 83 | }); 84 | 85 | test("value_to_json should convert Bool to JSON", func() { 86 | let value: CandyShared = #Bool(true); 87 | let json = Json.value_to_json(value); 88 | assert(json == "\"true\""); 89 | }); 90 | 91 | test("value_to_json should convert Blob to JSON", func() { 92 | let value: CandyShared = #Blob(Text.encodeUtf8("Blob")); 93 | let json = Json.value_to_json(value); 94 | D.print(debug_show(json)); 95 | assert(json == "\"426c6f62\""); 96 | }); 97 | 98 | test("value_to_json should convert Principal to JSON", func() { 99 | let principalValue = Principal.fromText("rrkah-fqaaa-aaaaa-aaaaq-cai"); 100 | let value: CandyShared = #Principal(principalValue); 101 | let json = Json.value_to_json(value); 102 | assert(json == "\"" # Principal.toText(principalValue) # "\""); 103 | }); 104 | 105 | test("value_to_json should convert Array to JSON", func() { 106 | let value: CandyShared = #Array([#Nat(1), #Nat(2), #Nat(3)]); 107 | let json = Json.value_to_json(value); 108 | assert(json == "[1,2,3]"); 109 | }); 110 | 111 | test("value_to_json should convert empty Array to JSON", func() { 112 | let value: CandyShared = #Array([]); 113 | let json = Json.value_to_json(value); 114 | assert(json == "[]"); 115 | }); 116 | 117 | test("value_to_json should convert Class to JSON", func() { 118 | let value: CandyShared = #Class([ 119 | { 120 | name = "age"; 121 | value = #Nat(30); 122 | immutable = true; 123 | }, 124 | { 125 | name = "name"; 126 | value = #Text("John Doe"); 127 | immutable = true; 128 | } 129 | ]); 130 | let json = Json.value_to_json(value); 131 | assert(json == "{\"age\":30,\"name\":\"John Doe\"}"); 132 | }); 133 | 134 | test("value_to_json should convert Nats to JSON", func() { 135 | let value: CandyShared = #Nats([Nat8.toNat(1), Nat8.toNat(2), Nat8.toNat(3)]); 136 | let json = Json.value_to_json(value); 137 | assert(json == "[1,2,3]"); 138 | }); 139 | 140 | test("value_to_json should convert Floats to JSON", func() { 141 | let value: CandyShared = #Floats([1.1, 2.2, 3.3]); 142 | let json = Json.value_to_json(value); 143 | // Note: Adjust the test for potential loss of precision in the float conversion 144 | D.print(debug_show(json)); 145 | assert(Text.startsWith(json, #text("[1.10000"))); 146 | assert(Text.contains(json, #text(",2"))); 147 | assert(Text.contains(json, #text(",3"))); 148 | }); 149 | 150 | test("value_to_json should convert Option to JSON", func() { 151 | let value: CandyShared = #Option(?#Bool(true)); 152 | let json = Json.value_to_json(value); 153 | assert(json == "\"true\""); 154 | }); 155 | 156 | test("value_to_json should convert none Option to JSON", func() { 157 | let value: CandyShared = #Option(null); 158 | let json = Json.value_to_json(value); 159 | assert(json == "null"); 160 | }); 161 | 162 | // Additional tests for Map, Set, ValueMap, and any other types can follow the pattern above. -------------------------------------------------------------------------------- /test/properties.test.mo: -------------------------------------------------------------------------------- 1 | import D "mo:base/Debug"; 2 | import {test} "mo:test"; 3 | import Properties "../src/properties"; 4 | import Types "../src/types"; 5 | import Principal "mo:base/Principal"; 6 | import Nat "mo:base/Nat"; 7 | import Text "mo:base/Text"; 8 | 9 | type Property = Types.Property; 10 | type UpdateShared = Types.UpdateShared; 11 | type Query = Types.Query; 12 | 13 | type CandyShared = Types.CandyShared; 14 | 15 | // Shared Test Instances 16 | let principal = Principal.fromText("rrkah-fqaaa-aaaaa-aaaaq-cai"); 17 | let natValue: Nat = 42; 18 | 19 | // Utility function to create properties 20 | func createPropertiesShared() : Types.PropertiesShared { 21 | return [ 22 | {name = "prop1"; value = #Nat(natValue); immutable = true}, 23 | {name = "prop2"; value = #Principal(principal); immutable = false}, 24 | {name = "prop3"; value = #Class([ 25 | {name = "subclass_prop1"; value = #Nat(natValue); immutable = false} 26 | ]); immutable = false} 27 | ]; 28 | }; 29 | 30 | 31 | // Test getPropertiesShared 32 | test("getPropertiesShared should return specified shared properties", func() { 33 | let properties = createPropertiesShared(); 34 | let queries: [Query] = [{name="prop1"; next=[];}]; 35 | let result = Properties.getPropertiesShared(properties, queries); 36 | switch(result) { 37 | case(#ok(res)) { 38 | assert(res.size() == 1); 39 | assert(res[0].name == "prop1"); 40 | }; 41 | case(_) { assert(false); }; 42 | }; 43 | }); 44 | 45 | // Test getPropertiesShared with nested queries 46 | test("getPropertiesShared should return nested shared properties", func() { 47 | let properties = createPropertiesShared(); 48 | let queries: [Query] = [{name = "prop3"; next = [{name = "subclass_prop1"; next=[]}]}]; 49 | let result = Properties.getPropertiesShared(properties, queries); 50 | switch(result) { 51 | case(#ok(res)) { 52 | assert(res.size() == 1); 53 | switch(res[0].value) { 54 | case (#Class(props)) { assert(props.size() == 1); assert(props[0].name == "subclass_prop1"); }; 55 | case (_) { assert(false); }; 56 | }; 57 | }; 58 | case(_) { assert(false); }; 59 | }; 60 | }); 61 | 62 | // Test updatePropertiesShared 63 | test("updatePropertiesShared should update mutable properties", func() { 64 | let properties = createPropertiesShared(); 65 | let updates: [UpdateShared] = [{name = "prop2"; mode = #Set(#Nat(999))}]; 66 | let result = Properties.updatePropertiesShared(properties, updates); 67 | switch(result) { 68 | case(#ok(res)) { 69 | assert(res.size() == 3); 70 | assert((res[1]).value == #Nat(999)); 71 | }; 72 | case(_) { assert(false); }; 73 | }; 74 | }); 75 | 76 | // Test updatePropertiesShared on immutable property 77 | test("updatePropertiesShared should not update immutable properties", func() { 78 | let properties = createPropertiesShared(); 79 | let updates: [UpdateShared] = [{name = "prop1"; mode = #Set(#Nat(999))}]; 80 | let result = Properties.updatePropertiesShared(properties, updates); 81 | switch(result) { 82 | case(#err(err)) { 83 | switch(err) { 84 | case(#Immutable) { assert(true); }; 85 | case(_) { assert(false); }; 86 | }; 87 | }; 88 | case(_) { assert(false); }; 89 | }; 90 | }); 91 | 92 | // Test updatePropertiesShared with non-existent property 93 | test("updatePropertiesShared should add non-existent properties", func() { 94 | let properties = createPropertiesShared(); 95 | let updates: [UpdateShared] = [{name = "non_existent"; mode = #Set(#Nat(999))}]; 96 | let result = Properties.updatePropertiesShared(properties, updates); 97 | switch(result) { 98 | case(#err(err)) { 99 | switch(err) { 100 | case(#NotFound) { assert(false); }; 101 | case(_) { assert(false); }; 102 | }; 103 | }; 104 | case(#ok(val)) { 105 | switch(val[3].value){ 106 | case(#Nat(val)){ 107 | assert(val == 999); 108 | }; 109 | case(_) return assert(false); 110 | };}; 111 | }; 112 | }); 113 | 114 | let nestedProperty: Types.PropertyShared = { 115 | name = "nestedProp"; 116 | value = #Class([ // Nested class inside the property 117 | { 118 | name = "subProp1"; 119 | value = #Nat(123); 120 | immutable = true 121 | } 122 | ]); 123 | immutable = false 124 | }; 125 | 126 | // Test getProperties uses the same `properties` setup as getPropertiesShared, so omitted for brevity 127 | // ... 128 | 129 | // Test getClassPropertyShared 130 | test("getClassPropertyShared should return a specific property from a class if available", func() { 131 | let classValue: CandyShared = #Class([nestedProperty]); 132 | let propertyName = "nestedProp"; 133 | let property = Properties.getClassPropertyShared(classValue, propertyName); 134 | switch (property) { 135 | case (null) { assert(false); }; 136 | case (?p) { 137 | assert(p.name == propertyName); 138 | assert(p.immutable == false); 139 | }; 140 | }; 141 | }); 142 | 143 | // Test updateProperties 144 | test("updateProperties should update a mutable property within a Candy class", func() { 145 | var properties = createPropertiesShared(); 146 | let mutablePropName = "prop2"; 147 | let newMutableValue: CandyShared = #Nat(100); 148 | let updates: [Types.UpdateShared] = [{name = mutablePropName; mode = #Set(newMutableValue)}]; 149 | let result = Properties.updatePropertiesShared(properties, updates); 150 | switch (result) { 151 | case (#ok(updatedProperties)) { 152 | switch(Properties.getClassPropertyShared(#Class(updatedProperties), mutablePropName)){ 153 | case(?value){ 154 | assert(value.value == newMutableValue); 155 | }; 156 | case(null){ 157 | return assert(false); 158 | }; 159 | } 160 | 161 | }; 162 | case (#err(_)) { 163 | assert(false); 164 | }; 165 | }; 166 | }); 167 | 168 | // Test updating an immutable property 169 | test("updateProperties should not update an immutable property", func() { 170 | let properties = createPropertiesShared(); 171 | let immutablePropertyName = "prop1"; 172 | let updates: [UpdateShared] = [{name = immutablePropertyName; mode = #Set(#Nat(999))}]; 173 | let result = Properties.updatePropertiesShared(properties, updates); 174 | switch (result) { 175 | case (#ok(_)) { 176 | assert(false); // Should not occur 177 | }; 178 | case (#err(e)) { 179 | switch (e) { 180 | case (#Immutable) { 181 | assert(true); // Correct error 182 | }; 183 | case (_) { 184 | assert(false); // Incorrect error type 185 | }; 186 | }; 187 | }; 188 | }; 189 | }); 190 | 191 | -------------------------------------------------------------------------------- /test/types.test.mo: -------------------------------------------------------------------------------- 1 | import D "mo:base/Debug"; 2 | import {test} "mo:test"; 3 | import Types "../src/types"; 4 | import Conv "../src/conversion"; 5 | import Principal "mo:base/Principal"; 6 | import Nat "mo:base/Nat"; 7 | import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; 8 | import Array "mo:base/Array"; 9 | import Nat8 "mo:base/Nat8"; 10 | 11 | type Candy = Types.Candy; 12 | type CandyShared = Types.CandyShared; 13 | 14 | // Test Instances 15 | let principal = Principal.fromText("rrkah-fqaaa-aaaaa-aaaaq-cai"); 16 | let natValue: Nat = 42; 17 | let arrayValue: [Candy] = [#Nat(natValue)]; 18 | let bufferValue: StableBuffer.StableBuffer = StableBuffer.fromArray([natValue]); 19 | 20 | // Test `Types` Module 21 | test("shareCandy should convert Candy to CandyShared preserving the structure", func() { 22 | let original: Candy = #Principal(principal); 23 | let shared_: CandyShared = Types.shareCandy(original); 24 | switch(shared_) { 25 | case (#Principal(p)) { 26 | assert(p == principal); 27 | }; 28 | case (_) { 29 | return assert(false); 30 | }; 31 | }; 32 | }); 33 | 34 | test("unshare should convert CandyShared to Candy preserving the structure", func() { 35 | let shared_: CandyShared = #Principal(principal); 36 | let original: Candy = Types.unshare(shared_); 37 | switch(original) { 38 | case (#Principal(p)) { 39 | assert(p == principal); 40 | }; 41 | case (_) { 42 | return assert(false); 43 | }; 44 | }; 45 | }); 46 | 47 | test("shareProperty should convert Property to PropertyShared preserving the structure", func() { 48 | let property: Types.Property = {name = "testProp"; value = #Nat(natValue); immutable = true}; 49 | let sharedProperty: Types.PropertyShared = Types.shareProperty(property); 50 | assert(sharedProperty.name ==property.name); 51 | assert(sharedProperty.immutable == property.immutable); 52 | switch(sharedProperty.value) { 53 | case (#Nat(n)) { 54 | assert(n == natValue); 55 | }; 56 | case (_) { 57 | return assert(false); 58 | }; 59 | }; 60 | }); 61 | 62 | test("unshareProperty should convert PropertyShared to Property preserving the structure", func() { 63 | let sharedProperty: Types.PropertyShared = {name = "testProp"; value = #Nat(natValue); immutable = true}; 64 | let property: Types.Property = Types.unshareProperty(sharedProperty); 65 | assert(property.name ==sharedProperty.name); 66 | assert(property.immutable ==sharedProperty.immutable); 67 | switch(property.value) { 68 | case (#Nat(n)) { 69 | assert(n == natValue); 70 | }; 71 | case (_) { 72 | return assert(false); 73 | }; 74 | }; 75 | }); 76 | 77 | test("shareCandyArray should convert list of Candy to list of CandyShared", func() { 78 | let sharedArray: [CandyShared] = Types.shareCandyArray(arrayValue); 79 | for (value in Array.vals(sharedArray)) { 80 | switch(value) { 81 | case (#Nat(n)) { 82 | assert(n == natValue); 83 | }; 84 | case (_) { 85 | return assert(false); 86 | }; 87 | }; 88 | }; 89 | }); 90 | 91 | test("unshareArray should convert list of CandyShared to list of Candy", func() { 92 | let sharedArray: [CandyShared] = Types.shareCandyArray(arrayValue); 93 | let originalArray: [Candy] = Types.unshareArray(sharedArray); 94 | for (value in Array.vals(originalArray)) { 95 | switch(value) { 96 | case (#Nat(n)) { 97 | assert(n == natValue); 98 | }; 99 | case (_) { 100 | return assert(false); 101 | }; 102 | }; 103 | }; 104 | }); 105 | 106 | test("shareCandyBuffer should convert a DataZone to an array of CandyShared", func() { 107 | let dataZone: Types.DataZone = StableBuffer.fromArray(arrayValue); 108 | let sharedBuffer: [CandyShared] = Types.shareCandyBuffer(dataZone); 109 | for (value in sharedBuffer.vals()) { 110 | switch(value) { 111 | case (#Nat(n)) { 112 | assert(n == natValue); 113 | }; 114 | case (_) { 115 | return assert(false); 116 | }; 117 | }; 118 | }; 119 | }); 120 | 121 | test("toBuffer should convert an array to a stable buffer of the same elements", func() { 122 | let buffer: StableBuffer.StableBuffer = Types.toBuffer([natValue]); 123 | assert(StableBuffer.get(buffer, 0) == natValue); 124 | }); 125 | 126 | test("hash function should produce consistent hash codes for Candy", func() { 127 | let original: Candy = #Nat(natValue); 128 | let hashCode1: Nat32 = Types.hash(original); 129 | let hashCode2: Nat32 = Types.hash(original); 130 | assert(hashCode1 == hashCode2); 131 | }); 132 | 133 | test("nat32ToBytes should convert Nat32 to a byte array representation", func() { 134 | let value = 42 : Nat32; 135 | let bytes = Conv.nat32ToBytes(value); 136 | let expectedBytes: [Nat8] = [0, 0, 0, 42]; 137 | assert(Array.equal(bytes, expectedBytes, Nat8.equal)); 138 | }); 139 | 140 | test("eq function should compare two Candy for equality", func() { 141 | let candy1: Candy = #Nat(natValue); 142 | let candy2: Candy = #Nat(natValue); 143 | let unequalCandy: Candy = #Nat(natValue + 1); 144 | assert(Types.eq(candy1, candy2) == true); 145 | assert(Types.eq(candy1, unequalCandy) == false); 146 | }); 147 | 148 | test("candyMapHashTool should provide hash and equality functions for Candy", func() { 149 | let candy: Candy = #Nat(natValue); 150 | let hashCode: Nat32 = Types.candyMapHashTool.0(candy); 151 | let equality: Bool = Types.candyMapHashTool.1(candy, candy); 152 | assert(equality == true); 153 | }); 154 | 155 | test("hashShared function should produce consistent hash codes for CandyShared", func() { 156 | let shared_: CandyShared = #Nat(natValue); 157 | let hashCode1: Nat32 = Types.hashShared(shared_); 158 | let hashCode2: Nat32 = Types.hashShared(shared_); 159 | assert(hashCode1 == hashCode2); 160 | }); 161 | 162 | test("eqShared function should compare two CandyShared for equality", func() { 163 | let shared1: CandyShared = #Nat(natValue); 164 | let shared2: CandyShared = #Nat(natValue); 165 | let unequalShared: CandyShared = #Nat(natValue + 1); 166 | assert(Types.eqShared(shared1, shared2) == true); 167 | assert(Types.eqShared(shared1, unequalShared) == false); 168 | }); 169 | 170 | test("candySharedMapHashTool should provide hash and equality functions for CandyShared", func() { 171 | let shared_: CandyShared = #Nat(natValue); 172 | let hashCode: Nat32 = Types.candySharedMapHashTool.0(shared_); 173 | let equality: Bool = Types.candySharedMapHashTool.1(shared_, shared_); 174 | assert(equality == true); 175 | }); -------------------------------------------------------------------------------- /test/upgrade.test.mo: -------------------------------------------------------------------------------- 1 | import D "mo:base/Debug"; 2 | import {test} "mo:test"; 3 | import Types "../src/types"; 4 | import Upgrade "../src/upgrade"; 5 | import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; 6 | import Nat8 "mo:base/Nat8"; 7 | import Principal "mo:base/Principal"; 8 | import Array "mo:base/Array"; 9 | import Blob "mo:base/Blob"; 10 | import CandyOld "mo:candy_0_1_12/types"; 11 | import Candy0_2_0 "mo:candy_0_2_0/types"; 12 | 13 | type CandyV1 = CandyOld.CandyValueUnstable; 14 | type CandySharedV1 = CandyOld.CandyValue; 15 | type CandyV2 = Candy0_2_0.Candy; 16 | type CandySharedV2 = Candy0_2_0.CandyShared; 17 | type CandyV3 = Types.Candy; 18 | type CandySharedV3 = Types.CandyShared; 19 | 20 | let testPrincipalV1 = Principal.fromText("rrkah-fqaaa-aaaaa-aaaaq-cai"); 21 | let testPrincipalV2 = testPrincipalV1; 22 | let testPrincipalV3 = testPrincipalV1; 23 | 24 | // Sample data for various types 25 | let testInt: Int = 1; 26 | let testNat: Nat = 42; 27 | let testBool: Bool = true; 28 | let testText: Text = "test"; 29 | let testFloat: Float = 3.14; 30 | let testBlob: Blob = Blob.fromArray([0, 1, 2, 3]); 31 | let testArrayV1: [CandyV1] = [#Int(testInt)]; // V1 only supports an array of CandyV1 32 | let testArrayV2: [CandySharedV2] = Array.tabulate(3, func(_){#Int(testInt)}); // V2 supports long array of CandyV2 33 | // Helpers for constructing V3 test values 34 | let testSharedArrayV3: [CandySharedV3] = [#Int(testInt), #Text(testText), #Bool(testBool)]; 35 | 36 | 37 | // Begin Test Suite 38 | 39 | // Test Cases for Upgrade from V1 representation of Candy to V2 40 | test("upgradeCandy0_1_2_to_0_2_0 - Int", func() { 41 | let originalCandyV1: CandyV1 = #Int(testInt); 42 | let upgradedCandyV2: CandyV2 = Upgrade.upgradeCandy0_1_2_to_0_2_0(originalCandyV1); 43 | let #Int(val) = upgradedCandyV2; 44 | assert(val == testInt); 45 | }); 46 | 47 | // Add more test cases to cover all Candy types for upgrade function `upgradeCandy0_1_2_to_0_2_0` 48 | // ... 49 | 50 | // Test Cases for Upgrade from V1 representation of CandyShared to V2 representations CandyShared 51 | test("upgradeCandyShared0_1_2_to_0_2_0 - Bool", func() { 52 | let originalCandySharedV1: CandySharedV1 = #Bool(testBool); 53 | let upgradedCandySharedV2: CandySharedV2 = Upgrade.upgradeCandyShared0_1_2_to_0_2_0(originalCandySharedV1); 54 | assert(upgradedCandySharedV2 == #Bool(testBool)); 55 | }); 56 | 57 | // Add more test cases to cover all CandyShared types for upgrade function `upgradeCandyShared0_1_2_to_0_2_0` 58 | // ... 59 | 60 | // Test Cases for Upgrade from V2 representation of Candy to V3 61 | test("upgradeCandy0_2_0_to_0_3_0 - Text", func() { 62 | let originalCandyV2: CandyV2 = #Text(testText); 63 | let upgradedCandyV3: CandyV3 = Upgrade.upgradeCandy0_2_0_to_0_3_0(originalCandyV2); 64 | let #Text(val) = upgradedCandyV3; 65 | assert(upgradedCandyV3 == testText); 66 | }); 67 | 68 | test("upgradeCandy0_1_2_to_0_2_0 - Nat", func() { 69 | let originalCandyV1: CandyV1 = #Nat(testNat); 70 | let upgradedCandyV2: CandyV2 = Upgrade.upgradeCandy0_1_2_to_0_2_0(originalCandyV1); 71 | let #Nat(val) = upgradedCandyV2; 72 | assert(val == testNat); 73 | }); 74 | 75 | test("upgradeCandy0_1_2_to_0_2_0 - Bool", func() { 76 | let originalCandyV1: CandyV1 = #Bool(testBool); 77 | let upgradedCandyV2: CandyV2 = Upgrade.upgradeCandy0_1_2_to_0_2_0(originalCandyV1); 78 | let #Bool(val) = upgradedCandyV2; 79 | assert(val == testBool); 80 | }); 81 | 82 | test("upgradeCandy0_1_2_to_0_2_0 - Float", func() { 83 | let originalCandyV1: CandyV1 = #Float(testFloat); 84 | let upgradedCandyV2: CandyV2 = Upgrade.upgradeCandy0_1_2_to_0_2_0(originalCandyV1); 85 | let #Float(val) = upgradedCandyV2; 86 | assert(val == testFloat); 87 | }); 88 | 89 | test("upgradeCandy0_1_2_to_0_2_0 - Principal", func() { 90 | let originalCandyV1: CandyV1 = #Principal(testPrincipalV1); 91 | let upgradedCandyV2: CandyV2 = Upgrade.upgradeCandy0_1_2_to_0_2_0(originalCandyV1); 92 | let #Principal(val) = upgradedCandyV2; 93 | assert(val == testPrincipalV2); 94 | }); 95 | 96 | test("upgradeCandy0_1_2_to_0_2_0 - Null", func() { 97 | let originalCandyV1: CandyV1 = #Empty; 98 | let upgradedCandyV2: CandyV2 = Upgrade.upgradeCandy0_1_2_to_0_2_0(originalCandyV1); 99 | let #Option(val) = upgradedCandyV2; 100 | switch(val){ 101 | case(null){assert(true)}; 102 | case(_) assert(false); 103 | }; 104 | }); 105 | 106 | 107 | // Test Cases for Upgrade from V2 representation of CandyShared to V3 representations CandyShared 108 | test("upgradeCandyShared0_2_0_to_0_3_0 - Array", func() { 109 | let originalCandySharedV2: CandySharedV2 = #Array(testArrayV2); 110 | let upgradedCandySharedV3: CandySharedV3 = Upgrade.upgradeCandyShared0_2_0_to_0_3_0(originalCandySharedV2); 111 | D.print(debug_show(upgradedCandySharedV3, testSharedArrayV3)); 112 | assert(Types.eqShared(upgradedCandySharedV3, #Array([#Int(1), #Int(1), #Int(1)]))); // Convert upgraded array to V3 format 113 | }); 114 | 115 | // Add more test cases to cover all CandyShared types for upgrade function `upgradeCandyShared0_2_0_to_0_3_0` 116 | // ... 117 | 118 | // End Test Suite 119 | 120 | -------------------------------------------------------------------------------- /test/workspaces.test.mo: -------------------------------------------------------------------------------- 1 | // Assuming workspace.mo contains the Workspace module functions. 2 | import Workspace "../src/workspace"; 3 | import Types "../src/types"; 4 | import {test} "mo:test"; 5 | import StableBuffer "mo:stablebuffer_1_3_0/StableBuffer"; 6 | import Array "mo:base/Array"; 7 | import Iter "mo:base/Iter"; 8 | import Buffer "mo:base/Buffer"; 9 | import Nat8 "mo:base/Nat8"; 10 | import Blob "mo:base/Blob"; 11 | import D "mo:base/Debug"; 12 | 13 | type Workspace = Types.Workspace; 14 | type DataZone = Types.DataZone; 15 | type Candy = Types.Candy; 16 | type CandyShared = Types.CandyShared; 17 | type AddressedChunkArray = Types.AddressedChunkArray; 18 | type DataChunk = Types.DataChunk; 19 | 20 | // Helper function to create a data chunk with an array of Nats up to the size specified 21 | func buildDataChunk(size: Nat) : DataChunk { 22 | #Array(StableBuffer.fromArray(Array.tabulate(size, func (i :Nat): Candy{#Nat(i)}))) 23 | }; 24 | 25 | func buildDataZone(size: Nat) : DataZone { 26 | StableBuffer.fromArray(Array.tabulate(size, func (i :Nat): Candy{#Nat(i)})) 27 | }; 28 | 29 | // Testing countAddressedChunksInWorkspace 30 | test("countAddressedChunksInWorkspace should correctly count chunks in workspace", func() { 31 | let testDataChunkSizes = [1, 3, 5, 2]; // Array size represents chunk setup 32 | let ws: Workspace = Workspace.initWorkspace(testDataChunkSizes.size()); 33 | for (size in testDataChunkSizes.vals()) { 34 | StableBuffer.add(ws, buildDataZone(size)); // Using helper to build chunks 35 | }; 36 | assert(Workspace.countAddressedChunksInWorkspace(ws) == 11); 37 | }); 38 | 39 | // Testing emptyWorkspace 40 | test("emptyWorkspace should create an empty workspace", func() { 41 | let ws = Workspace.emptyWorkspace(); 42 | assert(StableBuffer.size(ws) == 0); 43 | }); 44 | 45 | // Testing initWorkspace 46 | test("initWorkspace should initialize a workspace with a given capacity", func() { 47 | let ws = Workspace.initWorkspace(5); 48 | D.print(debug_show(StableBuffer.size(ws))); 49 | assert(StableBuffer.size(ws) == 0); //init is only presized 50 | }); 51 | 52 | // Testing getCandySize 53 | test("getCandySize should return correct byte size of a Candy", func() { 54 | let candyInt: Candy = #Int(255); 55 | assert(Workspace.getCandySize(candyInt) == 2); // Test for a simple Int Candy, use others as needed 56 | }); 57 | 58 | // Testing getCandySharedSize 59 | test("getCandySharedSize should return correct byte size of a CandyShared", func() { 60 | let candySharedInt: CandyShared = #Int(255); 61 | D.print(debug_show(Workspace.getCandySharedSize(candySharedInt))); 62 | assert(Workspace.getCandySharedSize(candySharedInt) == 4); // Similar to getCandySize but with CandyShared 63 | }); 64 | 65 | // Testing workspaceToAddressedChunkArray 66 | test("workspaceToAddressedChunkArray should convert workspace to addressed chunk array", func() { 67 | let ws: Workspace = Workspace.initWorkspace(2); 68 | StableBuffer.add(ws, buildDataZone(1)); 69 | StableBuffer.add(ws, buildDataZone(1)); 70 | let addressedChunks = Workspace.workspaceToAddressedChunkArray(ws); 71 | assert(addressedChunks.size() == 2); 72 | // Test individual chunks and positions accurately 73 | }); 74 | 75 | // Testing workspaceDeepClone 76 | test("workspaceDeepClone should produce an identical but independent copy of workspace", func() { 77 | let ws: Workspace = Workspace.initWorkspace(3); 78 | StableBuffer.add(ws, buildDataZone(1)); 79 | StableBuffer.add(ws, buildDataZone(2)); 80 | let wsClone = Workspace.workspaceDeepClone(ws); 81 | // Assert deep equality of workspaces 82 | assert(StableBuffer.size(ws) == StableBuffer.size(wsClone)); 83 | // Assert that modifying one does not change the other 84 | let updatedChunk = buildDataZone(5); 85 | StableBuffer.put(wsClone, 1, updatedChunk); 86 | assert(StableBuffer.size(StableBuffer.get(ws, 1)) != StableBuffer.size(StableBuffer.get(wsClone, 1))); 87 | }); 88 | 89 | // Testing fromAddressedChunks 90 | 91 | 92 | test("fromAddressedChunks should create a new workspace from an addressed chunk array", func() { 93 | let addressedChunks: AddressedChunkArray = [(0, 0, #Int(42)), (1, 0, #Int(43))]; 94 | let ws = Workspace.fromAddressedChunks(addressedChunks); 95 | assert(StableBuffer.size(ws) == 2); 96 | // Assert that chunks are correctly configured in the new workspace 97 | assert(Types.eq(StableBuffer.get(StableBuffer.get(ws, 0),0),#Int(42))); 98 | assert(Types.eq(StableBuffer.get(StableBuffer.get(ws, 1),0), #Int(43))); 99 | }); 100 | 101 | // Testing getDataZoneSize 102 | test("getDataZoneSize should return size of DataZone in bytes", func() { 103 | let dz = buildDataZone(356); 104 | let size = Workspace.getDataZoneSize(dz); 105 | D.print(debug_show(size)); 106 | assert(size > 0); 107 | let expectedSize: Nat = 456; // 1 byte for the variant tag, plus size of Nat 108 | assert(size == expectedSize); 109 | }); 110 | 111 | // Testing getWorkspaceChunkSize 112 | test("getWorkspaceChunkSize should return the number of chunks after partitioning", func() { 113 | let ws: Workspace = Workspace.initWorkspace(3); 114 | for (x in Iter.range(0, 2)) { // Initialize with 3 chunks 115 | StableBuffer.add(ws, buildDataZone(64)); // Assuming max chunk size is smaller than 64 116 | }; 117 | let chunkSize = Workspace.getWorkspaceChunkSize(ws, 32); // Assuming max chunk size is 32 118 | assert(chunkSize > 3); 119 | }); 120 | 121 | // Testing getWorkspaceChunk 122 | test("getWorkspaceChunk should return a specific chunk data", func() { 123 | let ws: Workspace = Workspace.initWorkspace(1); 124 | StableBuffer.add(ws, buildDataZone(4)); 125 | let chunk = Workspace.getWorkspaceChunk(ws, 0, 32); // Assuming max chunk size is 32 and we want the first chunk 126 | // Verify chunk content 127 | }); 128 | 129 | // Testing getAddressedChunkArraySize 130 | test("getAddressedChunkArraySize should return accurate size of the addressed chunks", func() { 131 | 132 | let addressedChunks: AddressedChunkArray = [(0, 0, #Int(42)), (1, 0, #Nat(15))]; 133 | let size = Workspace.getAddressedChunkArraySize(addressedChunks); 134 | D.print(debug_show(size)); 135 | let expectedSize: Nat = 23; // Assuming each zone chunk is one Nat size plus tag 136 | assert(size == expectedSize); 137 | }); 138 | 139 | // Testing getDataChunkFromAddressedChunkArray 140 | test("getDataChunkFromAddressedChunkArray should return correct chunk data", func() { 141 | let addressedChunks: AddressedChunkArray = [(0, 0, #Int(42)), (1, 1, #Nat(15))]; 142 | let dataChunk = Workspace.getDataChunkFromAddressedChunkArray(addressedChunks, 1, 1); 143 | // Verify returned `dataChunk` is #Nat(15) from the addressedChunks setup 144 | }); 145 | 146 | // Testing byteBufferDataZoneToBuffer 147 | test("byteBufferDataZoneToBuffer should convert data zone to buffer of bytes buffers", func() { 148 | let dz = buildDataZone(2); // Build your DataZone with the helper function 149 | let byteBuffer = Workspace.byteBufferDataZoneToBuffer(dz); 150 | // Verify that the byteBuffer contains expected byte arrays 151 | }); 152 | 153 | // Testing byteBufferChunksToCandyBufferDataZone 154 | test("byteBufferChunksToCandyBufferDataZone should convert buffer of bytes buffers to data zone", func() { 155 | let byteBuffer = Buffer.Buffer(2); 156 | byteBuffer.add( Nat8.fromNat(42)); 157 | byteBuffer.add( Nat8.fromNat(43)); 158 | let byteBuffers = Buffer.Buffer>(1); 159 | byteBuffers.add(byteBuffer); 160 | let dz = Workspace.byteBufferChunksToCandyBufferDataZone(byteBuffers); 161 | // Verify that the DataZone 'dz' correctly contains the converted data 162 | }); 163 | 164 | // Testing initDataZone 165 | test("initDataZone should create a DataZone with initial value", func() { 166 | let dz = Workspace.initDataZone(#Nat(42)); 167 | assert(StableBuffer.size(dz) == 1); 168 | // Test initialized DataZone has the expected content 169 | }); 170 | 171 | // Testing flattenAddressedChunkArray 172 | test("flattenAddressedChunkArray should return a flattened byte array for AddressedChunkArray", func() { 173 | let addressedChunks: AddressedChunkArray = [(0, 0, #Int(42))]; 174 | let flattenedBytes = Workspace.flattenAddressedChunkArray(addressedChunks); 175 | assert(Blob.fromArray(flattenedBytes).size() > 0); // Replace with expected array size 176 | // Test that bytes are flattened as expected 177 | }); 178 | 179 | // Testing workspaceToAddressedChunkArray 180 | test("workspaceToAddressedChunkArray should convert workspace to addressed chunk array", func() { 181 | let ws: Workspace = Workspace.initWorkspace(3); 182 | StableBuffer.add(ws, buildDataZone(1)); 183 | StableBuffer.add(ws, buildDataZone(2)); 184 | StableBuffer.add(ws, buildDataZone(3)); 185 | let addressedChunks = Workspace.workspaceToAddressedChunkArray(ws); 186 | assert(addressedChunks.size() == 6); 187 | // Test individual chunks and positions accurately 188 | assert(addressedChunks[0].0 == 0); 189 | assert(addressedChunks[0].1 == 0); 190 | assert(Types.eqShared(addressedChunks[0].2, #Nat(0))); 191 | 192 | assert(addressedChunks[1].0 == 1); 193 | assert(addressedChunks[1].1 == 0); 194 | assert(Types.eqShared(addressedChunks[1].2, #Nat(0))); 195 | 196 | assert(addressedChunks[2].0 == 1); 197 | assert(addressedChunks[2].1 == 1); 198 | assert(Types.eqShared(addressedChunks[02].2, #Nat(1))); 199 | 200 | assert(addressedChunks[3].0 == 2); 201 | assert(addressedChunks[3].1 == 0); 202 | assert(Types.eqShared(addressedChunks[3].2, #Nat(0))); 203 | 204 | assert(addressedChunks[4].0 == 2); 205 | assert(addressedChunks[4].1 == 1); 206 | assert(Types.eqShared(addressedChunks[4].2, #Nat(1))); 207 | 208 | assert(addressedChunks[5].0 == 2); 209 | assert(addressedChunks[5].1 == 2); 210 | assert(Types.eqShared(addressedChunks[5].2, #Nat(2))); 211 | 212 | 213 | 214 | }); 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /test_runner.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | dfx identity new test_candy || true 4 | dfx identity use test_candy 5 | 6 | ADMIN_PRINCIPAL=$(dfx identity get-principal) 7 | ADMIN_ACCOUNTID=$(dfx ledger account-id) 8 | 9 | echo $ADMIN_PRINCIPAL 10 | echo $ADMIN_ACCOUNTID 11 | 12 | dfx canister create --all 13 | dfx build --all 14 | dfx canister install test_runner --mode=reinstall 15 | echo "yes" 16 | 17 | TEST_RUNNER_ID=$(dfx canister id test_runner) 18 | 19 | echo $TEST_RUNNER_ID 20 | 21 | dfx canister call test_runner test 22 | 23 | -------------------------------------------------------------------------------- /tests/test_runner.mo: -------------------------------------------------------------------------------- 1 | 2 | import C "mo:matchers/Canister"; 3 | import M "mo:matchers/Matchers"; 4 | import T "mo:matchers/Testable"; 5 | import S "mo:matchers/Suite"; 6 | import Debug "mo:base/Debug"; 7 | import Principal "mo:base/Principal"; 8 | 9 | import Types "../src/types"; 10 | import Clone "../src/clone"; 11 | import Conversion "../src/conversion"; 12 | import Properties "../src/properties"; 13 | import Workspace "../src/workspace"; 14 | import Candid "../src/candid"; 15 | 16 | 17 | 18 | 19 | shared (deployer) actor class test_runner() = this { 20 | let it = C.Tester({ batchSize = 8 }); 21 | 22 | 23 | 24 | public shared func test() : async {#success; #fail : Text} { 25 | 26 | let suite = S.suite("test nft", [ 27 | //test getting witness returns empty if no witness 28 | S.test("testOwner", switch(await testConversions()){case(#success){true};case(_){false};}, M.equals(T.bool(true))), 29 | 30 | ]); 31 | S.run(suite); 32 | 33 | return #success; 34 | }; 35 | 36 | // US.2 37 | // US.3 38 | public shared func testConversions() : async {#success; #fail : Text} { 39 | Debug.print("running testOwner"); 40 | 41 | let owner = Principal.toText(deployer.caller); 42 | 43 | let suite = S.suite("test conversion", [ 44 | 45 | S.test("Nat32 is Nat", Conversion.candySharedToNat(#Nat32(10)), M.equals(T.nat(10))) 46 | ]); 47 | 48 | S.run(suite); 49 | 50 | return #success; 51 | 52 | 53 | return #success; 54 | 55 | 56 | }; 57 | 58 | public shared func testRefernece() : async Types.CandyShared { 59 | Debug.print("running testRefernece"); 60 | 61 | 62 | 63 | 64 | return #Option(null); 65 | 66 | 67 | }; 68 | 69 | 70 | } --------------------------------------------------------------------------------