diff --git a/.bench/serde.bench.json b/.bench/serde.bench.json new file mode 100644 index 0000000..f3d5df8 --- /dev/null +++ b/.bench/serde.bench.json @@ -0,0 +1,130 @@ +{ + "version": 1, + "moc": "0.14.13", + "replica": "dfx", + "replicaVersion": "0.29.2", + "gc": "generational", + "forceGc": true, + "results": [ + [ + "Serde: One Shot:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 394461139, + "rts_memory_size": 17563648, + "rts_total_allocation": 27418360, + "rts_collector_instructions": -29134, + "rts_mutator_instructions": -10415, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 1580216, + "rts_reclaimed": 25838144 + } + ], + [ + "Serde: One Shot:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 1105298555, + "rts_memory_size": 40894464, + "rts_total_allocation": 65569368, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 308, + "rts_reclaimed": 65569060 + } + ], + [ + "Serde: One Shot sans type inference:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 225586733, + "rts_memory_size": 0, + "rts_total_allocation": 18405496, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 272, + "rts_reclaimed": 18405224 + } + ], + [ + "Serde: One Shot sans type inference:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 891671974, + "rts_memory_size": 0, + "rts_total_allocation": 35602928, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 272, + "rts_reclaimed": 35602656 + } + ], + [ + "Motoko (to_candid(), from_candid()):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 31314021, + "rts_memory_size": 0, + "rts_total_allocation": 555728, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 272, + "rts_reclaimed": 555456 + } + ], + [ + "Motoko (to_candid(), from_candid()):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 9061804, + "rts_memory_size": 0, + "rts_total_allocation": 611788, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 272, + "rts_reclaimed": 611516 + } + ], + [ + "Serde: Single Type Serializer:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 111955117, + "rts_memory_size": 0, + "rts_total_allocation": 6834008, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 272, + "rts_reclaimed": 6833736 + } + ], + [ + "Serde: Single Type Serializer:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 222580666, + "rts_memory_size": 0, + "rts_total_allocation": 8413432, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 272, + "rts_reclaimed": 8413160 + } + ] + ] +} \ No newline at end of file diff --git a/.bench/types.bench.json b/.bench/types.bench.json new file mode 100644 index 0000000..73fee6c --- /dev/null +++ b/.bench/types.bench.json @@ -0,0 +1,1750 @@ +{ + "version": 1, + "moc": "0.14.13", + "replica": "dfx", + "replicaVersion": "0.28.0-beta.1", + "gc": "incremental", + "forceGc": true, + "results": [ + [ + "Nat:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 72692, + "rts_memory_size": 0, + "rts_total_allocation": 20204, + "rts_collector_instructions": 6559, + "rts_mutator_instructions": -13063, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20204, + "rts_reclaimed": 0 + } + ], + [ + "Nat:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 64497, + "rts_memory_size": 0, + "rts_total_allocation": 17284, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17284, + "rts_reclaimed": 0 + } + ], + [ + "Nat:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 14668, + "rts_memory_size": 0, + "rts_total_allocation": 10592, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10592, + "rts_reclaimed": 0 + } + ], + [ + "Nat:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 29866, + "rts_memory_size": 0, + "rts_total_allocation": 10536, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10536, + "rts_reclaimed": 0 + } + ], + [ + "Nat8:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 72122, + "rts_memory_size": 0, + "rts_total_allocation": 20192, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20192, + "rts_reclaimed": 0 + } + ], + [ + "Nat8:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 64226, + "rts_memory_size": 0, + "rts_total_allocation": 17272, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17272, + "rts_reclaimed": 0 + } + ], + [ + "Nat8:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 14455, + "rts_memory_size": 0, + "rts_total_allocation": 10552, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10552, + "rts_reclaimed": 0 + } + ], + [ + "Nat8:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 29970, + "rts_memory_size": 0, + "rts_total_allocation": 10496, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10496, + "rts_reclaimed": 0 + } + ], + [ + "Nat16:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 73276, + "rts_memory_size": 0, + "rts_total_allocation": 20200, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20200, + "rts_reclaimed": 0 + } + ], + [ + "Nat16:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 65677, + "rts_memory_size": 0, + "rts_total_allocation": 17280, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17280, + "rts_reclaimed": 0 + } + ], + [ + "Nat16:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 15356, + "rts_memory_size": 0, + "rts_total_allocation": 10572, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10572, + "rts_reclaimed": 0 + } + ], + [ + "Nat16:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 31188, + "rts_memory_size": 0, + "rts_total_allocation": 10516, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10516, + "rts_reclaimed": 0 + } + ], + [ + "Nat32:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 75053, + "rts_memory_size": 0, + "rts_total_allocation": 20208, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20208, + "rts_reclaimed": 0 + } + ], + [ + "Nat32:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 67752, + "rts_memory_size": 0, + "rts_total_allocation": 17288, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17288, + "rts_reclaimed": 0 + } + ], + [ + "Nat32:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 16643, + "rts_memory_size": 0, + "rts_total_allocation": 10612, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10612, + "rts_reclaimed": 0 + } + ], + [ + "Nat32:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 32792, + "rts_memory_size": 0, + "rts_total_allocation": 10556, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10556, + "rts_reclaimed": 0 + } + ], + [ + "Nat64:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77811, + "rts_memory_size": 0, + "rts_total_allocation": 20228, + "rts_collector_instructions": 6559, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20228, + "rts_reclaimed": 0 + } + ], + [ + "Nat64:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 70808, + "rts_memory_size": 0, + "rts_total_allocation": 17308, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17308, + "rts_reclaimed": 0 + } + ], + [ + "Nat64:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 18709, + "rts_memory_size": 0, + "rts_total_allocation": 10708, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10708, + "rts_reclaimed": 0 + } + ], + [ + "Nat64:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 35175, + "rts_memory_size": 0, + "rts_total_allocation": 10652, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10652, + "rts_reclaimed": 0 + } + ], + [ + "Int:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 75309, + "rts_memory_size": 0, + "rts_total_allocation": 20204, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20204, + "rts_reclaimed": 0 + } + ], + [ + "Int:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 68734, + "rts_memory_size": 0, + "rts_total_allocation": 17284, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17284, + "rts_reclaimed": 0 + } + ], + [ + "Int:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 17281, + "rts_memory_size": 0, + "rts_total_allocation": 10592, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10592, + "rts_reclaimed": 0 + } + ], + [ + "Int:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 34064, + "rts_memory_size": 0, + "rts_total_allocation": 10536, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10536, + "rts_reclaimed": 0 + } + ], + [ + "Int8:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 74577, + "rts_memory_size": 0, + "rts_total_allocation": 20192, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20192, + "rts_reclaimed": 0 + } + ], + [ + "Int8:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 68301, + "rts_memory_size": 0, + "rts_total_allocation": 17272, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17272, + "rts_reclaimed": 0 + } + ], + [ + "Int8:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 16930, + "rts_memory_size": 0, + "rts_total_allocation": 10552, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10552, + "rts_reclaimed": 0 + } + ], + [ + "Int8:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 34030, + "rts_memory_size": 0, + "rts_total_allocation": 10496, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10496, + "rts_reclaimed": 0 + } + ], + [ + "Int16:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 75731, + "rts_memory_size": 0, + "rts_total_allocation": 20200, + "rts_collector_instructions": 6559, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20200, + "rts_reclaimed": 0 + } + ], + [ + "Int16:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 69752, + "rts_memory_size": 0, + "rts_total_allocation": 17280, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17280, + "rts_reclaimed": 0 + } + ], + [ + "Int16:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 17831, + "rts_memory_size": 0, + "rts_total_allocation": 10572, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10572, + "rts_reclaimed": 0 + } + ], + [ + "Int16:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 35248, + "rts_memory_size": 0, + "rts_total_allocation": 10516, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10516, + "rts_reclaimed": 0 + } + ], + [ + "Int32:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77492, + "rts_memory_size": 0, + "rts_total_allocation": 20208, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20208, + "rts_reclaimed": 0 + } + ], + [ + "Int32:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 71811, + "rts_memory_size": 0, + "rts_total_allocation": 17288, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17288, + "rts_reclaimed": 0 + } + ], + [ + "Int32:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 19123, + "rts_memory_size": 0, + "rts_total_allocation": 10612, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10612, + "rts_reclaimed": 0 + } + ], + [ + "Int32:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 36857, + "rts_memory_size": 0, + "rts_total_allocation": 10556, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10556, + "rts_reclaimed": 0 + } + ], + [ + "Int64:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 80226, + "rts_memory_size": 0, + "rts_total_allocation": 20228, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20228, + "rts_reclaimed": 0 + } + ], + [ + "Int64:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 74843, + "rts_memory_size": 0, + "rts_total_allocation": 17308, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17308, + "rts_reclaimed": 0 + } + ], + [ + "Int64:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 21188, + "rts_memory_size": 0, + "rts_total_allocation": 10708, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10708, + "rts_reclaimed": 0 + } + ], + [ + "Int64:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 39239, + "rts_memory_size": 0, + "rts_total_allocation": 10652, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10652, + "rts_reclaimed": 0 + } + ], + [ + "Float:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 102683, + "rts_memory_size": 0, + "rts_total_allocation": 20932, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20932, + "rts_reclaimed": 0 + } + ], + [ + "Float:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 97520, + "rts_memory_size": 0, + "rts_total_allocation": 18012, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 18012, + "rts_reclaimed": 0 + } + ], + [ + "Float:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 48826, + "rts_memory_size": 0, + "rts_total_allocation": 11428, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 11428, + "rts_reclaimed": 0 + } + ], + [ + "Float:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 67194, + "rts_memory_size": 0, + "rts_total_allocation": 11372, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 11372, + "rts_reclaimed": 0 + } + ], + [ + "Bool:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77158, + "rts_memory_size": 0, + "rts_total_allocation": 20192, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20192, + "rts_reclaimed": 0 + } + ], + [ + "Bool:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 72320, + "rts_memory_size": 0, + "rts_total_allocation": 17272, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17272, + "rts_reclaimed": 0 + } + ], + [ + "Bool:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 19285, + "rts_memory_size": 0, + "rts_total_allocation": 10552, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10552, + "rts_reclaimed": 0 + } + ], + [ + "Bool:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 38030, + "rts_memory_size": 0, + "rts_total_allocation": 10496, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10496, + "rts_reclaimed": 0 + } + ], + [ + "Text:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 81842, + "rts_memory_size": 0, + "rts_total_allocation": 20260, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20260, + "rts_reclaimed": 0 + } + ], + [ + "Text:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77278, + "rts_memory_size": 0, + "rts_total_allocation": 17340, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17340, + "rts_reclaimed": 0 + } + ], + [ + "Text:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 23306, + "rts_memory_size": 0, + "rts_total_allocation": 10772, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10772, + "rts_reclaimed": 0 + } + ], + [ + "Text:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 42313, + "rts_memory_size": 0, + "rts_total_allocation": 10716, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10716, + "rts_reclaimed": 0 + } + ], + [ + "Null:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77016, + "rts_memory_size": 0, + "rts_total_allocation": 20156, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20156, + "rts_reclaimed": 0 + } + ], + [ + "Null:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 72968, + "rts_memory_size": 0, + "rts_total_allocation": 17236, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17236, + "rts_reclaimed": 0 + } + ], + [ + "Null:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 88300, + "rts_memory_size": 0, + "rts_total_allocation": 20604, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20604, + "rts_reclaimed": 0 + } + ], + [ + "Null:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 83698, + "rts_memory_size": 0, + "rts_total_allocation": 17596, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17596, + "rts_reclaimed": 0 + } + ], + [ + "Empty:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77430, + "rts_memory_size": 0, + "rts_total_allocation": 20156, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20156, + "rts_reclaimed": 0 + } + ], + [ + "Empty:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 73680, + "rts_memory_size": 0, + "rts_total_allocation": 17236, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17236, + "rts_reclaimed": 0 + } + ], + [ + "Empty:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 88800, + "rts_memory_size": 0, + "rts_total_allocation": 20604, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20604, + "rts_reclaimed": 0 + } + ], + [ + "Empty:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 84421, + "rts_memory_size": 0, + "rts_total_allocation": 17596, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17596, + "rts_reclaimed": 0 + } + ], + [ + "Principal:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 98730, + "rts_memory_size": 0, + "rts_total_allocation": 20516, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20516, + "rts_reclaimed": 0 + } + ], + [ + "Principal:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 95090, + "rts_memory_size": 0, + "rts_total_allocation": 17596, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17596, + "rts_reclaimed": 0 + } + ], + [ + "Principal:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 33722, + "rts_memory_size": 0, + "rts_total_allocation": 11392, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 11392, + "rts_reclaimed": 0 + } + ], + [ + "Principal:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 53690, + "rts_memory_size": 0, + "rts_total_allocation": 11336, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 11336, + "rts_reclaimed": 0 + } + ], + [ + "Blob:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 243704, + "rts_memory_size": 0, + "rts_total_allocation": 28872, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 28872, + "rts_reclaimed": 0 + } + ], + [ + "Blob:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 210308, + "rts_memory_size": 0, + "rts_total_allocation": 22568, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 22568, + "rts_reclaimed": 0 + } + ], + [ + "Blob:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 98472, + "rts_memory_size": 0, + "rts_total_allocation": 16316, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 16316, + "rts_reclaimed": 0 + } + ], + [ + "Blob:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 116728, + "rts_memory_size": 0, + "rts_total_allocation": 16044, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 16044, + "rts_reclaimed": 0 + } + ], + [ + "Option(Nat):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 116006, + "rts_memory_size": 0, + "rts_total_allocation": 23296, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 23296, + "rts_reclaimed": 0 + } + ], + [ + "Option(Nat):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 88982, + "rts_memory_size": 0, + "rts_total_allocation": 17712, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17712, + "rts_reclaimed": 0 + } + ], + [ + "Option(Nat):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 26921, + "rts_memory_size": 0, + "rts_total_allocation": 10920, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10920, + "rts_reclaimed": 0 + } + ], + [ + "Option(Nat):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 45515, + "rts_memory_size": 0, + "rts_total_allocation": 10648, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10648, + "rts_reclaimed": 0 + } + ], + [ + "Option(Text):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 120251, + "rts_memory_size": 0, + "rts_total_allocation": 23356, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 23356, + "rts_reclaimed": 0 + } + ], + [ + "Option(Text):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 93315, + "rts_memory_size": 0, + "rts_total_allocation": 17772, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17772, + "rts_reclaimed": 0 + } + ], + [ + "Option(Text):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 30441, + "rts_memory_size": 0, + "rts_total_allocation": 11100, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 11100, + "rts_reclaimed": 0 + } + ], + [ + "Option(Text):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 49302, + "rts_memory_size": 0, + "rts_total_allocation": 10828, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10828, + "rts_reclaimed": 0 + } + ], + [ + "Array(Nat8):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 136470, + "rts_memory_size": 0, + "rts_total_allocation": 25040, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 25040, + "rts_reclaimed": 0 + } + ], + [ + "Array(Nat8):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 91988, + "rts_memory_size": 0, + "rts_total_allocation": 17816, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17816, + "rts_reclaimed": 0 + } + ], + [ + "Array(Nat8):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 29575, + "rts_memory_size": 0, + "rts_total_allocation": 11072, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 11072, + "rts_reclaimed": 0 + } + ], + [ + "Array(Nat8):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 48797, + "rts_memory_size": 0, + "rts_total_allocation": 10800, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10800, + "rts_reclaimed": 0 + } + ], + [ + "Array(Text):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 187126, + "rts_memory_size": 0, + "rts_total_allocation": 26208, + "rts_collector_instructions": 6559, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 26208, + "rts_reclaimed": 0 + } + ], + [ + "Array(Text):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 127872, + "rts_memory_size": 0, + "rts_total_allocation": 18456, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 18456, + "rts_reclaimed": 0 + } + ], + [ + "Array(Text):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 57932, + "rts_memory_size": 0, + "rts_total_allocation": 12824, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 12824, + "rts_reclaimed": 0 + } + ], + [ + "Array(Text):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77426, + "rts_memory_size": 0, + "rts_total_allocation": 12552, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 12552, + "rts_reclaimed": 0 + } + ], + [ + "Array(Record):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 298069, + "rts_memory_size": 0, + "rts_total_allocation": 33364, + "rts_collector_instructions": 6559, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 33364, + "rts_reclaimed": 0 + } + ], + [ + "Array(Record):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 204295, + "rts_memory_size": 0, + "rts_total_allocation": 21484, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 21484, + "rts_reclaimed": 0 + } + ], + [ + "Array(Record):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 73874, + "rts_memory_size": 0, + "rts_total_allocation": 14052, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 14052, + "rts_reclaimed": 0 + } + ], + [ + "Array(Record):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 86210, + "rts_memory_size": 0, + "rts_total_allocation": 13376, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 13376, + "rts_reclaimed": 0 + } + ], + [ + "Record(Nested):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 761090, + "rts_memory_size": 0, + "rts_total_allocation": 54784, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 54784, + "rts_reclaimed": 0 + } + ], + [ + "Record(Nested):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 528828, + "rts_memory_size": 0, + "rts_total_allocation": 32152, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 32152, + "rts_reclaimed": 0 + } + ], + [ + "Record(Nested):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 309543, + "rts_memory_size": 0, + "rts_total_allocation": 23948, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 23948, + "rts_reclaimed": 0 + } + ], + [ + "Record(Nested):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 148515, + "rts_memory_size": 0, + "rts_total_allocation": 17364, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17364, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Simple):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 149940, + "rts_memory_size": 0, + "rts_total_allocation": 24912, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 24912, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Simple):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 141065, + "rts_memory_size": 0, + "rts_total_allocation": 18860, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 18860, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Simple):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 63607, + "rts_memory_size": 0, + "rts_total_allocation": 12456, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 12456, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Simple):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 54068, + "rts_memory_size": 0, + "rts_total_allocation": 10752, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10752, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Complex):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 959922, + "rts_memory_size": 0, + "rts_total_allocation": 65264, + "rts_collector_instructions": 6565, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 65264, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Complex):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 808793, + "rts_memory_size": 0, + "rts_total_allocation": 42560, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 42560, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Complex):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 417094, + "rts_memory_size": 0, + "rts_total_allocation": 27176, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 27176, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Complex):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 124034, + "rts_memory_size": 0, + "rts_total_allocation": 15360, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 15360, + "rts_reclaimed": 0 + } + ], + [ + "Large Text:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 5829081, + "rts_memory_size": 0, + "rts_total_allocation": 146600, + "rts_collector_instructions": 6564, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 146600, + "rts_reclaimed": 0 + } + ], + [ + "Large Text:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 5829668, + "rts_memory_size": 0, + "rts_total_allocation": 143680, + "rts_collector_instructions": 6265, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 143680, + "rts_reclaimed": 0 + } + ], + [ + "Large Text:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 1907297, + "rts_memory_size": 0, + "rts_total_allocation": 138216, + "rts_collector_instructions": 6253, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 138216, + "rts_reclaimed": 0 + } + ], + [ + "Large Text:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 1931456, + "rts_memory_size": 0, + "rts_total_allocation": 138160, + "rts_collector_instructions": 6259, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 138160, + "rts_reclaimed": 0 + } + ], + [ + "Large Array:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 6041873, + "rts_memory_size": 0, + "rts_total_allocation": 229372, + "rts_collector_instructions": 6564, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 229372, + "rts_reclaimed": 0 + } + ], + [ + "Large Array:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 2735910, + "rts_memory_size": 0, + "rts_total_allocation": 92780, + "rts_collector_instructions": 6253, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 92780, + "rts_reclaimed": 0 + } + ], + [ + "Large Array:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 1221435, + "rts_memory_size": 0, + "rts_total_allocation": 86656, + "rts_collector_instructions": 6259, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 86656, + "rts_reclaimed": 0 + } + ], + [ + "Large Array:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 1243882, + "rts_memory_size": 0, + "rts_total_allocation": 86384, + "rts_collector_instructions": 6265, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 86384, + "rts_reclaimed": 0 + } + ], + [ + "Deep Nesting:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 627380, + "rts_memory_size": 0, + "rts_total_allocation": 54440, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 54440, + "rts_reclaimed": 0 + } + ], + [ + "Deep Nesting:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 433625, + "rts_memory_size": 0, + "rts_total_allocation": 29820, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 29820, + "rts_reclaimed": 0 + } + ], + [ + "Deep Nesting:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 125086, + "rts_memory_size": 0, + "rts_total_allocation": 17016, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17016, + "rts_reclaimed": 0 + } + ], + [ + "Deep Nesting:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 121838, + "rts_memory_size": 0, + "rts_total_allocation": 15376, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 15376, + "rts_reclaimed": 0 + } + ], + [ + "Wide Record:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 850204, + "rts_memory_size": 0, + "rts_total_allocation": 45720, + "rts_collector_instructions": 6559, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 45720, + "rts_reclaimed": 0 + } + ], + [ + "Wide Record:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 507152, + "rts_memory_size": 0, + "rts_total_allocation": 26956, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 26956, + "rts_reclaimed": 0 + } + ], + [ + "Wide Record:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 336816, + "rts_memory_size": 0, + "rts_total_allocation": 30388, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 30388, + "rts_reclaimed": 0 + } + ], + [ + "Wide Record:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 329957, + "rts_memory_size": 0, + "rts_total_allocation": 28840, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 28840, + "rts_reclaimed": 0 + } + ] + ] +} \ No newline at end of file diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index fd5851b..7ba9648 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -46,7 +46,6 @@ jobs: npm --yes -g i ic-mops mops i mops toolchain init - mops toolchain use moc latest # set moc path for dfx to use echo "DFX_MOC_PATH=$(mops toolchain bin moc)" >> $GITHUB_ENV @@ -61,16 +60,15 @@ jobs: continue-on-error: true - name: Checkout out the branch with benchmark results - if: github.event_name == 'pull_request' uses: actions/checkout@v4 with: ref: benchmark-results path: .benchmark-results-branch/ + - name: Move Saved Benchmarks - if: github.event_name == 'pull_request' run: mv .benchmark-results-branch/.bench .bench 2>/dev/null || mkdir -p .bench - - name: Benchmarks + - name: Pull Request Benchmarks if: github.event_name == 'pull_request' id: benchmarks run: | diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..55ea2b2 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "useTabs": false +} diff --git a/bench/serde.bench.mo b/bench/serde.bench.mo index 8333b2c..b6c60b6 100644 --- a/bench/serde.bench.mo +++ b/bench/serde.bench.mo @@ -1,12 +1,12 @@ -import Iter "mo:base@0.14.14/Iter"; -import Debug "mo:base@0.14.14/Debug"; -import Prelude "mo:base@0.14.14/Prelude"; -import Text "mo:base@0.14.14/Text"; -import Char "mo:base@0.14.14/Char"; -import Buffer "mo:base@0.14.14/Buffer"; +import Iter "mo:base@0.16.0/Iter"; +import Debug "mo:base@0.16.0/Debug"; +import Prelude "mo:base@0.16.0/Prelude"; +import Text "mo:base@0.16.0/Text"; +import Char "mo:base@0.16.0/Char"; +import Buffer "mo:base@0.16.0/Buffer"; import Bench "mo:bench"; -import Fuzz "mo:fuzz@1.0.0"; +import Fuzz "mo:fuzz"; import Itertools "mo:itertools@0.2.2/Iter"; import Serde "../src"; @@ -24,6 +24,7 @@ module { "Serde: One Shot", "Serde: One Shot sans type inference", "Motoko (to_candid(), from_candid())", + "Serde: Single Type Serializer", ]); bench.cols([ @@ -181,23 +182,6 @@ module { }; }; - // case ("Serde 'mo:motoko_candid' lib", "decode()") { - // for (i in Itertools.range(0, limit)) { - // let item : StoreItem = buffer.get(i); - // let candid_blob = candify_store_item.to_blob(item); - // candid_blobs.add(candid_blob); - // let #ok(candid) = LegacyCandidDecoder.decode(candid_blob, StoreItemKeys, null); - // candid_buffer.add(candid); - // }; - // }; - // case ("Serde 'mo:motoko_candid' lib", "encode()") { - // for (i in Itertools.range(0, limit)) { - // let candid = candid_buffer.get(i); - // let res = LegacyCandidEncoder.encode(candid, null); - // let #ok(blob) = res; - // }; - // }; - case ("Serde: One Shot", "decode()") { for (i in Itertools.range(0, limit)) { let item = buffer.get(i); @@ -220,8 +204,6 @@ module { let item = buffer.get(i); let candid_blob = candify_store_item.to_blob(item); - let FormattedStoreItem = Serde.Candid.formatCandidType([StoreItem], null); - let options = { Serde.Candid.defaultOptions with types = ?FormattedStoreItem }; @@ -243,14 +225,31 @@ module { }; }; - // case ("#Nat", "decode()"){ - // for (i in Itertools.range(0, limit)) { - // let item = buffer.get(i); - // let candid_blob = candify_store_item.to_blob(item); - // let #ok(candid) = CandidDecoder.decode(candid_blob, StoreItemKeys, null); - // // candid_buffer.add(candid); - // }; - // }; + case ("Serde: Single Type Serializer", "decode()") { + let options = { + Serde.Candid.defaultOptions with types = ?FormattedStoreItem + }; + let serializer = Serde.Candid.TypedSerializer.fromBlob(candify_store_item.to_blob(buffer.get(0)), StoreItemKeys, ?options); + + for (i in Itertools.range(0, limit)) { + let item = buffer.get(i); + let candid_blob = candify_store_item.to_blob(item); + + let #ok(candid) = Serde.Candid.TypedSerializer.decode(serializer, candid_blob); + // candid_buffer.add(candid); + }; + }; + + case ("Serde: Single Type Serializer", "encode()") { + + let serializer = Serde.Candid.TypedSerializer.new(FormattedStoreItem, null); + + for (i in Itertools.range(0, limit)) { + let candid = candid_buffer.get(i); + let res = Serde.Candid.TypedSerializer.encode(serializer, candid); + }; + }; + case (_, _) { Debug.trap("Should be unreachable:\n row = \"" # debug_show row # "\" and col = \"" # debug_show col # "\""); }; diff --git a/bench/types.bench.mo b/bench/types.bench.mo new file mode 100644 index 0000000..8b51c6f --- /dev/null +++ b/bench/types.bench.mo @@ -0,0 +1,1479 @@ +import Iter "mo:base@0.16.0/Iter"; +import Debug "mo:base@0.16.0/Debug"; +import Prelude "mo:base@0.16.0/Prelude"; +import Text "mo:base@0.16.0/Text"; +import Char "mo:base@0.16.0/Char"; +import Buffer "mo:base@0.16.0/Buffer"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Principal "mo:base@0.16.0/Principal"; +import Int "mo:base@0.16.0/Int"; +import Int8 "mo:base@0.16.0/Int8"; +import Int16 "mo:base@0.16.0/Int16"; +import Int32 "mo:base@0.16.0/Int32"; +import Int64 "mo:base@0.16.0/Int64"; +import Nat "mo:base@0.16.0/Nat"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat16 "mo:base@0.16.0/Nat16"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Float "mo:base@0.16.0/Float"; + +import Bench "mo:bench"; +import Fuzz "mo:fuzz"; +import Itertools "mo:itertools@0.2.2/Iter"; + +import Serde "../src"; +import CandidEncoder "../src/Candid/Blob/Encoder"; +import CandidDecoder "../src/Candid/Blob/Decoder"; + +module { + public func init() : Bench.Bench { + let bench = Bench.Bench(); + + bench.name("Benchmarking Serde by Data Types"); + bench.description("Performance comparison across all supported Candid data types with 10k operations"); + + bench.rows([ + // Primitive Types + "Nat", + "Nat8", + "Nat16", + "Nat32", + "Nat64", + "Int", + "Int8", + "Int16", + "Int32", + "Int64", + "Float", + "Bool", + "Text", + "Null", + "Empty", + "Principal", + "Blob", + + // Compound Types + "Option(Nat)", + "Option(Text)", + "Array(Nat8)", + "Array(Text)", + "Array(Record)", + // "Record(Simple)", // failing with 'Blob index out of bounds' + "Record(Nested)", + // "Tuple(Mixed)", // failing with 'Blob index out of bounds' + "Variant(Simple)", + "Variant(Complex)", + // "Map(Text->Nat)", // redundant - same as record + + // Performance Edge Cases + "Large Text", + "Large Array", + "Deep Nesting", + "Wide Record", + // "Recursive Structure", // not supported + ]); + + bench.cols([ + "encode()", + "encode(sans inference)", + "decode()", + "decode(sans inference)", + ]); + + type Candid = Serde.Candid; + + func createGenerator(seed : Nat) : { next() : Nat } { + // Pure bitwise xorshift64 - no multiplication or addition! + var state : Nat64 = Nat64.fromNat(seed); + if (state == 0) state := 1; // Avoid zero state + + { + next = func() : Nat { + // Only XOR and bit shifts - fastest possible + state ^= state << 13 : Nat64; + state ^= state >> 7 : Nat64; + state ^= state << 17 : Nat64; + Nat64.toNat(state); + }; + }; + }; + + let fuzz = Fuzz.create(createGenerator(42)); + let limit = 1; + + // Generate test data for each type + let nat_values = Buffer.Buffer(limit); + let nat8_values = Buffer.Buffer(limit); + let nat16_values = Buffer.Buffer(limit); + let nat32_values = Buffer.Buffer(limit); + let nat64_values = Buffer.Buffer(limit); + let int_values = Buffer.Buffer(limit); + let int8_values = Buffer.Buffer(limit); + let int16_values = Buffer.Buffer(limit); + let int32_values = Buffer.Buffer(limit); + let int64_values = Buffer.Buffer(limit); + let float_values = Buffer.Buffer(limit); + let bool_values = Buffer.Buffer(limit); + let text_values = Buffer.Buffer(limit); + let principal_values = Buffer.Buffer(limit); + let blob_values = Buffer.Buffer(limit); + + // Compound type test data + let option_nat_values = Buffer.Buffer(limit); + let option_text_values = Buffer.Buffer(limit); + let array_nat8_values = Buffer.Buffer<[Nat8]>(limit); + let array_text_values = Buffer.Buffer<[Text]>(limit); + + // Complex structures + type SimpleRecord = { id : Nat; name : Text; active : Bool }; + type NestedRecord = { + user : SimpleRecord; + metadata : { created : Int; tags : [Text] }; + settings : ?{ theme : Text; notifications : Bool }; + }; + type MixedTuple = (Nat, Text, Bool, ?Float); + type SimpleVariant = { #success : Nat; #error : Text; #pending }; + type ComplexVariant = { + #user : SimpleRecord; + #admin : NestedRecord; + #guest; + }; + + let simple_record_values = Buffer.Buffer(limit); + let nested_record_values = Buffer.Buffer(limit); + let mixed_tuple_values = Buffer.Buffer(limit); + let simple_variant_values = Buffer.Buffer(limit); + let complex_variant_values = Buffer.Buffer(limit); + let map_values = Buffer.Buffer<[(Text, Nat)]>(limit); + + // Edge case data + let large_text_values = Buffer.Buffer(limit); + let large_array_values = Buffer.Buffer<[Nat]>(limit); + let deep_nesting_values = Buffer.Buffer(limit); + let wide_record_values = Buffer.Buffer(limit); + + let random_principal = fuzz.principal.randomPrincipal(29); + + Debug.print("Generating test data for all types..."); + + // Populate test data + for (i in Itertools.range(0, limit)) { + // Primitive types + let nat = fuzz.nat.randomRange(0, 1_000_000); + let nat8 = fuzz.nat8.random(); + let text = fuzz.text.randomAlphanumeric(fuzz.nat.randomRange(5, 10)); + let int = fuzz.int.randomRange(-1_000_000, 1_000_000); + + nat_values.add(nat); + nat8_values.add(nat8); + nat16_values.add(fuzz.nat16.random()); + nat32_values.add(fuzz.nat32.random()); + nat64_values.add(fuzz.nat64.random()); + int_values.add(int); + int8_values.add(fuzz.int8.random()); + int16_values.add(fuzz.int16.random()); + int32_values.add(fuzz.int32.random()); + int64_values.add(fuzz.int64.random()); + float_values.add(fuzz.float.randomRange(-1000.0, 1000.0)); + bool_values.add(fuzz.bool.random()); + text_values.add(text); + principal_values.add(random_principal); + blob_values.add(Blob.fromArray(fuzz.array.randomArray(fuzz.nat.randomRange(10, 100), fuzz.nat8.random))); + + // Option types + option_nat_values.add(if (fuzz.bool.random()) ?nat else null); + option_text_values.add(if (fuzz.bool.random()) ?text else null); + + // Arrays + array_nat8_values.add(fuzz.array.randomArray(fuzz.nat.randomRange(3, 10), func() : Nat8 = nat8)); + array_text_values.add(fuzz.array.randomArray(fuzz.nat.randomRange(3, 10), func() = text)); + + // Records and complex types + simple_record_values.add({ + id = nat; + name = text; + active = fuzz.bool.random(); + }); + + nested_record_values.add({ + user = { + id = fuzz.nat.randomRange(1, 1000); + name = text; + active = fuzz.bool.random(); + }; + metadata = { + created = int; + tags = fuzz.array.randomArray(fuzz.nat.randomRange(1, 5), func() = text); + }; + settings = if (fuzz.bool.random()) ?{ + theme = fuzz.array.randomEntry(["dark", "light", "auto"]).1; + notifications = fuzz.bool.random(); + } else null; + }); + + mixed_tuple_values.add(( + fuzz.nat.randomRange(0, 1000), + fuzz.text.randomAlphanumeric(10), + fuzz.bool.random(), + if (fuzz.bool.random()) ?fuzz.float.randomRange(0.0, 100.0) else null, + )); + + simple_variant_values.add( + switch (fuzz.nat.randomRange(0, 2)) { + case (0) #success(nat); + case (1) #error(text); + case (_) #pending; + } + ); + + complex_variant_values.add( + switch (fuzz.nat.randomRange(0, 2)) { + case (0) #user({ + id = nat; + name = text; + active = fuzz.bool.random(); + }); + case (1) #admin({ + user = { + id = nat; + name = text; + active = fuzz.bool.random(); + }; + metadata = { + created = int; + tags = fuzz.array.randomArray(fuzz.nat.randomRange(1, 5), func() = text); + }; + settings = if (fuzz.bool.random()) ?{ + theme = fuzz.array.randomEntry(["dark", "light", "auto"]).1; + notifications = fuzz.bool.random(); + } else null; + }); + case (_) #guest; + } + ); + + map_values.add( + Array.tabulate<(Text, Nat)>( + fuzz.nat.randomRange(3, 8), + func(j) = ( + "key" # Nat.toText(j), + nat, + ), + ) + ); + + // Edge cases + large_text_values.add(fuzz.text.randomAlphanumeric(fuzz.nat.randomRange(1000, 5000))); + large_array_values.add(fuzz.array.randomArray(fuzz.nat.randomRange(500, 1000), func() = nat)); + + // Deep nesting - 5 levels deep + deep_nesting_values.add(#Record([("level1", #Record([("level2", #Record([("level3", #Record([("level4", #Record([("level5", #Nat(nat))]))]))]))]))])); + + // Wide record - 20 fields + wide_record_values.add( + #Record( + Array.tabulate<(Text, Candid)>( + 20, + func(j) = ( + "field" # Nat.toText(j), + #Nat(nat), + ), + ) + ) + ); + }; + + Debug.print("Generated test data for all types"); + + // Define type schemas for sans-inference benchmarks + let primitive_types = { + nat = [#Nat : Serde.Candid.CandidType]; + nat8 = [#Nat8 : Serde.Candid.CandidType]; + nat16 = [#Nat16 : Serde.Candid.CandidType]; + nat32 = [#Nat32 : Serde.Candid.CandidType]; + nat64 = [#Nat64 : Serde.Candid.CandidType]; + int = [#Int : Serde.Candid.CandidType]; + int8 = [#Int8 : Serde.Candid.CandidType]; + int16 = [#Int16 : Serde.Candid.CandidType]; + int32 = [#Int32 : Serde.Candid.CandidType]; + int64 = [#Int64 : Serde.Candid.CandidType]; + float = [#Float : Serde.Candid.CandidType]; + bool = [#Bool : Serde.Candid.CandidType]; + text = [#Text : Serde.Candid.CandidType]; + null_ = [#Null : Serde.Candid.CandidType]; + empty = [#Empty : Serde.Candid.CandidType]; + principal = [#Principal : Serde.Candid.CandidType]; + blob = [#Blob : Serde.Candid.CandidType]; + }; + + let compound_types = { + option_nat = [#Option(#Nat) : Serde.Candid.CandidType]; + option_text = [#Option(#Text)]; + array_nat8 = [#Array(#Nat8)]; + array_text = [#Array(#Text)]; + array_record = [#Array(#Record([("id", #Nat), ("name", #Text), ("active", #Bool)]))]; + simple_record = [#Record([("id", #Nat), ("name", #Text), ("active", #Bool)])]; + nested_record = [#Record([("user", #Record([("id", #Nat), ("name", #Text), ("active", #Bool)])), ("metadata", #Record([("created", #Int), ("tags", #Array(#Text))])), ("settings", #Option(#Record([("theme", #Text), ("notifications", #Bool)])))])]; + tuple_mixed = [#Tuple([#Nat, #Text, #Bool, #Option(#Float)])]; + variant_simple = [#Variant([("success", #Nat), ("error", #Text), ("pending", #Null)])]; + variant_complex = [#Variant([("user", #Record([("id", #Nat), ("name", #Text), ("active", #Bool)])), ("admin", #Record([("user", #Record([("id", #Nat), ("name", #Text), ("active", #Bool)])), ("metadata", #Record([("created", #Int), ("tags", #Array(#Text))])), ("settings", #Option(#Record([("theme", #Text), ("notifications", #Bool)])))])), ("guest", #Null)])]; + map_text_nat = [#Map([("", #Nat)])]; + large_text = [#Text]; + large_array = [#Array(#Nat)]; + deep_nesting = [#Record([("level1", #Record([("level2", #Record([("level3", #Record([("level4", #Record([("level5", #Nat)]))]))]))]))])]; + wide_record = [#Record(Array.tabulate<(Text, Serde.Candid.CandidType)>(20, func(j) = ("field" # Nat.toText(j), #Nat)))]; + }; + + // Format type schemas for optimal performance with sans-inference encoding + let formatted_primitive_types = { + nat = Serde.Candid.formatCandidType(primitive_types.nat, null); + nat8 = Serde.Candid.formatCandidType(primitive_types.nat8, null); + nat16 = Serde.Candid.formatCandidType(primitive_types.nat16, null); + nat32 = Serde.Candid.formatCandidType(primitive_types.nat32, null); + nat64 = Serde.Candid.formatCandidType(primitive_types.nat64, null); + int = Serde.Candid.formatCandidType(primitive_types.int, null); + int8 = Serde.Candid.formatCandidType(primitive_types.int8, null); + int16 = Serde.Candid.formatCandidType(primitive_types.int16, null); + int32 = Serde.Candid.formatCandidType(primitive_types.int32, null); + int64 = Serde.Candid.formatCandidType(primitive_types.int64, null); + float = Serde.Candid.formatCandidType(primitive_types.float, null); + bool = Serde.Candid.formatCandidType(primitive_types.bool, null); + text = Serde.Candid.formatCandidType(primitive_types.text, null); + null_ = Serde.Candid.formatCandidType(primitive_types.null_, null); + empty = Serde.Candid.formatCandidType(primitive_types.empty, null); + principal = Serde.Candid.formatCandidType(primitive_types.principal, null); + blob = Serde.Candid.formatCandidType(primitive_types.blob, null); + }; + + let formatted_compound_types = { + option_nat = Serde.Candid.formatCandidType(compound_types.option_nat, null); + option_text = Serde.Candid.formatCandidType(compound_types.option_text, null); + array_nat8 = Serde.Candid.formatCandidType(compound_types.array_nat8, null); + array_text = Serde.Candid.formatCandidType(compound_types.array_text, null); + array_record = Serde.Candid.formatCandidType(compound_types.array_record, null); + simple_record = Serde.Candid.formatCandidType(compound_types.simple_record, null); + nested_record = Serde.Candid.formatCandidType(compound_types.nested_record, null); + tuple_mixed = Serde.Candid.formatCandidType(compound_types.tuple_mixed, null); + variant_simple = Serde.Candid.formatCandidType(compound_types.variant_simple, null); + variant_complex = Serde.Candid.formatCandidType(compound_types.variant_complex, null); + map_text_nat = Serde.Candid.formatCandidType(compound_types.map_text_nat, null); + large_text = Serde.Candid.formatCandidType(compound_types.large_text, null); + large_array = Serde.Candid.formatCandidType(compound_types.large_array, null); + deep_nesting = Serde.Candid.formatCandidType(compound_types.deep_nesting, null); + wide_record = Serde.Candid.formatCandidType(compound_types.wide_record, null); + }; + + // Storage for encoded blobs + let encoded_blobs = { + var nat = Buffer.Buffer(limit); + var nat8 = Buffer.Buffer(limit); + var nat16 = Buffer.Buffer(limit); + var nat32 = Buffer.Buffer(limit); + var nat64 = Buffer.Buffer(limit); + var int = Buffer.Buffer(limit); + var int8 = Buffer.Buffer(limit); + var int16 = Buffer.Buffer(limit); + var int32 = Buffer.Buffer(limit); + var int64 = Buffer.Buffer(limit); + var float = Buffer.Buffer(limit); + var bool = Buffer.Buffer(limit); + var text = Buffer.Buffer(limit); + var principal = Buffer.Buffer(limit); + var blob = Buffer.Buffer(limit); + var option_nat = Buffer.Buffer(limit); + var option_text = Buffer.Buffer(limit); + var array_nat8 = Buffer.Buffer(limit); + var array_text = Buffer.Buffer(limit); + var simple_record = Buffer.Buffer(limit); + var nested_record = Buffer.Buffer(limit); + var mixed_tuple = Buffer.Buffer(limit); + var simple_variant = Buffer.Buffer(limit); + var complex_variant = Buffer.Buffer(limit); + var map_values = Buffer.Buffer(limit); + var large_text = Buffer.Buffer(limit); + var large_array = Buffer.Buffer(limit); + var deep_nesting = Buffer.Buffer(limit); + var wide_record = Buffer.Buffer(limit); + }; + + bench.runner( + func(row, col) = switch (col, row) { + // Primitive Types - Encoding + case ("encode()", "Nat") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Nat(nat_values.get(i))], null); + encoded_blobs.nat.add(blob); + }; + }; + case ("encode()", "Nat8") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Nat8(nat8_values.get(i))], null); + encoded_blobs.nat8.add(blob); + }; + }; + case ("encode()", "Nat16") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Nat16(nat16_values.get(i))], null); + encoded_blobs.nat16.add(blob); + }; + }; + case ("encode()", "Nat32") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Nat32(nat32_values.get(i))], null); + encoded_blobs.nat32.add(blob); + }; + }; + case ("encode()", "Nat64") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Nat64(nat64_values.get(i))], null); + encoded_blobs.nat64.add(blob); + }; + }; + case ("encode()", "Int") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Int(int_values.get(i))], null); + encoded_blobs.int.add(blob); + }; + }; + case ("encode()", "Int8") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Int8(int8_values.get(i))], null); + encoded_blobs.int8.add(blob); + }; + }; + case ("encode()", "Int16") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Int16(int16_values.get(i))], null); + encoded_blobs.int16.add(blob); + }; + }; + case ("encode()", "Int32") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Int32(int32_values.get(i))], null); + encoded_blobs.int32.add(blob); + }; + }; + case ("encode()", "Int64") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Int64(int64_values.get(i))], null); + encoded_blobs.int64.add(blob); + }; + }; + case ("encode()", "Float") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Float(float_values.get(i))], null); + encoded_blobs.float.add(blob); + }; + }; + case ("encode()", "Bool") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Bool(bool_values.get(i))], null); + encoded_blobs.bool.add(blob); + }; + }; + case ("encode()", "Text") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Text(text_values.get(i))], null); + encoded_blobs.text.add(blob); + }; + }; + case ("encode()", "Null") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Null], null); + }; + }; + case ("encode()", "Empty") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Empty], null); + }; + }; + case ("encode()", "Principal") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Principal(principal_values.get(i))], null); + encoded_blobs.principal.add(blob); + }; + }; + case ("encode()", "Blob") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Blob(blob_values.get(i))], null); + encoded_blobs.blob.add(blob); + }; + }; + + // Compound Types - Encoding + case ("encode()", "Option(Nat)") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot( + [ + #Option( + switch (option_nat_values.get(i)) { + case (?n) #Nat(n); + case (null) #Null; + } + ) + ], + null, + ); + encoded_blobs.option_nat.add(blob); + }; + }; + case ("encode()", "Option(Text)") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot( + [ + #Option( + switch (option_text_values.get(i)) { + case (?t) #Text(t); + case (null) #Null; + } + ) + ], + null, + ); + encoded_blobs.option_text.add(blob); + }; + }; + case ("encode()", "Array(Nat8)") { + for (i in Itertools.range(0, limit)) { + let arr = Array.map(array_nat8_values.get(i), func(n) = #Nat8(n)); + let #ok(blob) = CandidEncoder.one_shot([#Array(arr)], null); + encoded_blobs.array_nat8.add(blob); + }; + }; + case ("encode()", "Array(Text)") { + for (i in Itertools.range(0, limit)) { + let arr = Array.map(array_text_values.get(i), func(t) = #Text(t)); + let #ok(blob) = CandidEncoder.one_shot([#Array(arr)], null); + encoded_blobs.array_text.add(blob); + }; + }; + case ("encode()", "Array(Record)") { + for (i in Itertools.range(0, limit)) { + let record_arr = Array.map( + [simple_record_values.get(i)], + func(r) = #Record([("id", #Nat(r.id)), ("name", #Text(r.name)), ("active", #Bool(r.active))]), + ); + let #ok(blob) = CandidEncoder.one_shot([#Array(record_arr)], null); + encoded_blobs.simple_record.add(blob); + }; + }; + case ("encode()", "Record(Simple)") { + for (i in Itertools.range(0, limit)) { + let record = simple_record_values.get(i); + let #ok(blob) = CandidEncoder.one_shot( + [ + #Record([("id", #Nat(record.id)), ("name", #Text(record.name)), ("active", #Bool(record.active))]) + ], + null, + ); + encoded_blobs.simple_record.add(blob); + }; + }; + case ("encode()", "Record(Nested)") { + for (i in Itertools.range(0, limit)) { + let record = nested_record_values.get(i); + let settings_candid = switch (record.settings) { + case (?s) #Option(#Record([("theme", #Text(s.theme)), ("notifications", #Bool(s.notifications))])); + case (null) #Option(#Null); + }; + let #ok(blob) = CandidEncoder.one_shot( + [ + #Record([ + ("user", #Record([("id", #Nat(record.user.id)), ("name", #Text(record.user.name)), ("active", #Bool(record.user.active))])), + ("metadata", #Record([("created", #Int(record.metadata.created)), ("tags", #Array(Array.map(record.metadata.tags, func(t) = #Text(t))))])), + ("settings", settings_candid), + ]) + ], + null, + ); + encoded_blobs.nested_record.add(blob); + }; + }; + case ("encode()", "Tuple(Mixed)") { + for (i in Itertools.range(0, limit)) { + let (n, t, b, f) = mixed_tuple_values.get(i); + let float_opt = switch (f) { + case (?fl) #Option(#Float(fl)); + case (null) #Option(#Null); + }; + let #ok(blob) = CandidEncoder.one_shot( + [ + #Tuple([#Nat(n), #Text(t), #Bool(b), float_opt]) + ], + null, + ); + encoded_blobs.mixed_tuple.add(blob); + }; + }; + case ("encode()", "Variant(Simple)") { + for (i in Itertools.range(0, limit)) { + let variant_candid = switch (simple_variant_values.get(i)) { + case (#success(n)) #Variant(("success", #Nat(n))); + case (#error(msg)) #Variant(("error", #Text(msg))); + case (#pending) #Variant(("pending", #Null)); + }; + let #ok(blob) = CandidEncoder.one_shot([variant_candid], null); + encoded_blobs.simple_variant.add(blob); + }; + }; + case ("encode()", "Variant(Complex)") { + for (i in Itertools.range(0, limit)) { + let variant_candid = switch (complex_variant_values.get(i)) { + case (#user(u)) #Variant(("user", #Record([("id", #Nat(u.id)), ("name", #Text(u.name)), ("active", #Bool(u.active))]))); + case (#admin(a)) { + let settings_candid = switch (a.settings) { + case (?s) #Option(#Record([("theme", #Text(s.theme)), ("notifications", #Bool(s.notifications))])); + case (null) #Option(#Null); + }; + #Variant(("admin", #Record([("user", #Record([("id", #Nat(a.user.id)), ("name", #Text(a.user.name)), ("active", #Bool(a.user.active))])), ("metadata", #Record([("created", #Int(a.metadata.created)), ("tags", #Array(Array.map(a.metadata.tags, func(t) = #Text(t))))])), ("settings", settings_candid)]))); + }; + case (#guest) #Variant(("guest", #Null)); + }; + let #ok(blob) = CandidEncoder.one_shot([variant_candid], null); + encoded_blobs.complex_variant.add(blob); + }; + }; + case ("encode()", "Map(Text->Nat)") { + for (i in Itertools.range(0, limit)) { + let map_entries = Array.map<(Text, Nat), (Text, Candid)>( + map_values.get(i), + func((k, v)) = (k, #Nat(v)), + ); + let #ok(blob) = CandidEncoder.one_shot([#Map(map_entries)], null); + encoded_blobs.map_values.add(blob); + }; + }; + case ("encode()", "Large Text") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Text(large_text_values.get(i))], null); + encoded_blobs.large_text.add(blob); + }; + }; + case ("encode()", "Large Array") { + for (i in Itertools.range(0, limit)) { + let arr = Array.map(large_array_values.get(i), func(n) = #Nat(n)); + let #ok(blob) = CandidEncoder.one_shot([#Array(arr)], null); + encoded_blobs.large_array.add(blob); + }; + }; + case ("encode()", "Deep Nesting") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([deep_nesting_values.get(i)], null); + encoded_blobs.deep_nesting.add(blob); + }; + }; + case ("encode()", "Wide Record") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([wide_record_values.get(i)], null); + encoded_blobs.wide_record.add(blob); + }; + }; + case ("encode()", "Recursive Structure") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([deep_nesting_values.get(i)], null); + }; + }; + + // Primitive Types - Decoding + case ("decode()", "Nat") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat.get(i), [], null); + }; + }; + case ("decode()", "Nat8") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat8.get(i), [], null); + }; + }; + case ("decode()", "Nat16") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat16.get(i), [], null); + }; + }; + case ("decode()", "Nat32") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat32.get(i), [], null); + }; + }; + case ("decode()", "Nat64") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat64.get(i), [], null); + }; + }; + case ("decode()", "Int") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int.get(i), [], null); + }; + }; + case ("decode()", "Int8") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int8.get(i), [], null); + }; + }; + case ("decode()", "Int16") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int16.get(i), [], null); + }; + }; + case ("decode()", "Int32") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int32.get(i), [], null); + }; + }; + case ("decode()", "Int64") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int64.get(i), [], null); + }; + }; + case ("decode()", "Float") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.float.get(i), [], null); + }; + }; + case ("decode()", "Bool") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.bool.get(i), [], null); + }; + }; + case ("decode()", "Text") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.text.get(i), [], null); + }; + }; + case ("decode()", "Null") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Null], null); + let #ok(candid) = CandidDecoder.one_shot(blob, [], null); + }; + }; + case ("decode()", "Empty") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Empty], null); + let #ok(candid) = CandidDecoder.one_shot(blob, [], null); + }; + }; + case ("decode()", "Principal") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.principal.get(i), [], null); + }; + }; + case ("decode()", "Blob") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.blob.get(i), [], null); + }; + }; + + // Compound Types - Decoding + case ("decode()", "Option(Nat)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.option_nat.get(i), [], null); + }; + }; + case ("decode()", "Option(Text)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.option_text.get(i), [], null); + }; + }; + case ("decode()", "Array(Nat8)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.array_nat8.get(i), [], null); + }; + }; + case ("decode()", "Array(Text)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.array_text.get(i), [], null); + }; + }; + case ("decode()", "Array(Record)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.simple_record.get(i), ["id", "name", "active"], null); + }; + }; + case ("decode()", "Record(Simple)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.simple_record.get(i), ["id", "name", "active"], null); + }; + }; + case ("decode()", "Record(Nested)") { + for (i in Itertools.range(0, limit)) { + let record_keys = ["user", "metadata", "settings"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nested_record.get(i), record_keys, null); + }; + }; + case ("decode()", "Tuple(Mixed)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.mixed_tuple.get(i), [], null); + }; + }; + case ("decode()", "Variant(Simple)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.simple_variant.get(i), [], null); + }; + }; + case ("decode()", "Variant(Complex)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.complex_variant.get(i), [], null); + }; + }; + case ("decode()", "Map(Text->Nat)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.map_values.get(i), [], null); + }; + }; + case ("decode()", "Large Text") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.large_text.get(i), [], null); + }; + }; + case ("decode()", "Large Array") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.large_array.get(i), [], null); + }; + }; + case ("decode()", "Deep Nesting") { + for (i in Itertools.range(0, limit)) { + let record_keys = ["level1", "level2", "level3", "level4", "level5"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.deep_nesting.get(i), record_keys, null); + }; + }; + case ("decode()", "Wide Record") { + for (i in Itertools.range(0, limit)) { + let record_keys = Array.tabulate(20, func(j) = "field" # Nat.toText(j)); + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.wide_record.get(i), record_keys, null); + }; + }; + case ("decode()", "Recursive Structure") { + for (i in Itertools.range(0, limit)) { + let record_keys = ["level1", "level2", "level3", "level4", "level5"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.deep_nesting.get(i), record_keys, null); + }; + }; + + // Sans-inference encoding (with formatted types for optimal performance) + case ("encode(sans inference)", "Nat") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat + }; + let #ok(blob) = CandidEncoder.one_shot([#Nat(nat_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Nat8") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat8 + }; + let #ok(blob) = CandidEncoder.one_shot([#Nat8(nat8_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Nat16") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat16 + }; + let #ok(blob) = CandidEncoder.one_shot([#Nat16(nat16_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Nat32") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat32 + }; + let #ok(blob) = CandidEncoder.one_shot([#Nat32(nat32_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Nat64") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat64 + }; + let #ok(blob) = CandidEncoder.one_shot([#Nat64(nat64_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Int") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int + }; + let #ok(blob) = CandidEncoder.one_shot([#Int(int_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Int8") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int8 + }; + let #ok(blob) = CandidEncoder.one_shot([#Int8(int8_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Int16") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int16 + }; + let #ok(blob) = CandidEncoder.one_shot([#Int16(int16_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Int32") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int32 + }; + let #ok(blob) = CandidEncoder.one_shot([#Int32(int32_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Int64") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int64 + }; + let #ok(blob) = CandidEncoder.one_shot([#Int64(int64_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Float") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.float + }; + let #ok(blob) = CandidEncoder.one_shot([#Float(float_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Bool") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.bool + }; + let #ok(blob) = CandidEncoder.one_shot([#Bool(bool_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Text") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.text + }; + let #ok(blob) = CandidEncoder.one_shot([#Text(text_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Null") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.null_ + }; + let #ok(blob) = CandidEncoder.one_shot([#Null], ?options); + }; + }; + case ("encode(sans inference)", "Empty") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.empty + }; + let #ok(blob) = CandidEncoder.one_shot([#Empty], ?options); + }; + }; + case ("encode(sans inference)", "Principal") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.principal + }; + let #ok(blob) = CandidEncoder.one_shot([#Principal(principal_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Blob") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.blob + }; + let #ok(blob) = CandidEncoder.one_shot([#Blob(blob_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Option(Nat)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.option_nat + }; + let #ok(blob) = CandidEncoder.one_shot( + [ + #Option( + switch (option_nat_values.get(i)) { + case (?n) #Nat(n); + case (null) #Null; + } + ) + ], + ?options, + ); + }; + }; + case ("encode(sans inference)", "Option(Text)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.option_text + }; + let #ok(blob) = CandidEncoder.one_shot( + [ + #Option( + switch (option_text_values.get(i)) { + case (?t) #Text(t); + case (null) #Null; + } + ) + ], + ?options, + ); + }; + }; + case ("encode(sans inference)", "Array(Nat8)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.array_nat8 + }; + let arr = Array.map(array_nat8_values.get(i), func(n) = #Nat8(n)); + let #ok(blob) = CandidEncoder.one_shot([#Array(arr)], ?options); + }; + }; + case ("encode(sans inference)", "Array(Text)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.array_text + }; + let arr = Array.map(array_text_values.get(i), func(t) = #Text(t)); + let #ok(blob) = CandidEncoder.one_shot([#Array(arr)], ?options); + }; + }; + case ("encode(sans inference)", "Array(Record)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.array_record + }; + let record_arr = Array.map( + [simple_record_values.get(i)], + func(r) = #Record([("id", #Nat(r.id)), ("name", #Text(r.name)), ("active", #Bool(r.active))]), + ); + let #ok(blob) = CandidEncoder.one_shot([#Array(record_arr)], ?options); + }; + }; + case ("encode(sans inference)", "Record(Simple)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.simple_record + }; + let record = simple_record_values.get(i); + let #ok(blob) = CandidEncoder.one_shot( + [ + #Record([("id", #Nat(record.id)), ("name", #Text(record.name)), ("active", #Bool(record.active))]) + ], + ?options, + ); + }; + }; + case ("encode(sans inference)", "Record(Nested)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.nested_record + }; + let record = nested_record_values.get(i); + let settings_candid = switch (record.settings) { + case (?s) #Option(#Record([("theme", #Text(s.theme)), ("notifications", #Bool(s.notifications))])); + case (null) #Option(#Null); + }; + let #ok(blob) = CandidEncoder.one_shot( + [ + #Record([ + ("user", #Record([("id", #Nat(record.user.id)), ("name", #Text(record.user.name)), ("active", #Bool(record.user.active))])), + ("metadata", #Record([("created", #Int(record.metadata.created)), ("tags", #Array(Array.map(record.metadata.tags, func(t) = #Text(t))))])), + ("settings", settings_candid), + ]) + ], + ?options, + ); + }; + }; + case ("encode(sans inference)", "Tuple(Mixed)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.tuple_mixed + }; + let (n, t, b, f) = mixed_tuple_values.get(i); + let float_opt = switch (f) { + case (?fl) #Option(#Float(fl)); + case (null) #Option(#Null); + }; + let #ok(blob) = CandidEncoder.one_shot( + [ + #Tuple([#Nat(n), #Text(t), #Bool(b), float_opt]) + ], + ?options, + ); + }; + }; + case ("encode(sans inference)", "Variant(Simple)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.variant_simple + }; + let variant_candid = switch (simple_variant_values.get(i)) { + case (#success(n)) #Variant(("success", #Nat(n))); + case (#error(msg)) #Variant(("error", #Text(msg))); + case (#pending) #Variant(("pending", #Null)); + }; + let #ok(blob) = CandidEncoder.one_shot([variant_candid], ?options); + }; + }; + case ("encode(sans inference)", "Variant(Complex)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.variant_complex + }; + let variant_candid = switch (complex_variant_values.get(i)) { + case (#user(u)) #Variant(("user", #Record([("id", #Nat(u.id)), ("name", #Text(u.name)), ("active", #Bool(u.active))]))); + case (#admin(a)) { + let settings_candid = switch (a.settings) { + case (?s) #Option(#Record([("theme", #Text(s.theme)), ("notifications", #Bool(s.notifications))])); + case (null) #Option(#Null); + }; + #Variant(("admin", #Record([("user", #Record([("id", #Nat(a.user.id)), ("name", #Text(a.user.name)), ("active", #Bool(a.user.active))])), ("metadata", #Record([("created", #Int(a.metadata.created)), ("tags", #Array(Array.map(a.metadata.tags, func(t) = #Text(t))))])), ("settings", settings_candid)]))); + }; + case (#guest) #Variant(("guest", #Null)); + }; + let #ok(blob) = CandidEncoder.one_shot([variant_candid], ?options); + }; + }; + case ("encode(sans inference)", "Map(Text->Nat)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.map_text_nat + }; + let map_entries = Array.map<(Text, Nat), (Text, Candid)>( + map_values.get(i), + func((k, v)) = (k, #Nat(v)), + ); + let #ok(blob) = CandidEncoder.one_shot([#Map(map_entries)], ?options); + }; + }; + case ("encode(sans inference)", "Large Text") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.large_text + }; + let #ok(blob) = CandidEncoder.one_shot([#Text(large_text_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Large Array") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.large_array + }; + let arr = Array.map(large_array_values.get(i), func(n) = #Nat(n)); + let #ok(blob) = CandidEncoder.one_shot([#Array(arr)], ?options); + }; + }; + case ("encode(sans inference)", "Deep Nesting") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.deep_nesting + }; + let #ok(blob) = CandidEncoder.one_shot([deep_nesting_values.get(i)], ?options); + }; + }; + case ("encode(sans inference)", "Wide Record") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.wide_record + }; + let #ok(blob) = CandidEncoder.one_shot([wide_record_values.get(i)], ?options); + }; + }; + case ("encode(sans inference)", "Recursive Structure") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.deep_nesting + }; + let #ok(blob) = CandidEncoder.one_shot([deep_nesting_values.get(i)], ?options); + }; + }; + + // Sans-inference decoding (with predefined types) + case ("decode(sans inference)", "Nat") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Nat8") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat8 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat8.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Nat16") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat16 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat16.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Nat32") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat32 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat32.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Nat64") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat64 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat64.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Int") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Int8") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int8 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int8.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Int16") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int16 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int16.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Int32") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int32 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int32.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Int64") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int64 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int64.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Float") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.float + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.float.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Bool") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.bool + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.bool.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Text") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.text + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.text.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Null") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.null_ + }; + let #ok(blob) = CandidEncoder.one_shot([#Null], ?options); + let #ok(candid) = CandidDecoder.one_shot(blob, [], ?options); + }; + }; + case ("decode(sans inference)", "Empty") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.empty + }; + let #ok(blob) = CandidEncoder.one_shot([#Empty], ?options); + let #ok(candid) = CandidDecoder.one_shot(blob, [], ?options); + }; + }; + case ("decode(sans inference)", "Principal") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.principal + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.principal.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Blob") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.blob + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.blob.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Option(Nat)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.option_nat + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.option_nat.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Option(Text)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.option_text + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.option_text.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Array(Nat8)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.array_nat8 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.array_nat8.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Array(Text)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.array_text + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.array_text.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Array(Record)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.array_record + }; + let record_keys = ["id", "name", "active"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.simple_record.get(i), record_keys, ?options); + }; + }; + case ("decode(sans inference)", "Record(Simple)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.simple_record + }; + let record_keys = ["id", "name", "active"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.simple_record.get(i), record_keys, ?options); + }; + }; + case ("decode(sans inference)", "Record(Nested)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.nested_record + }; + let record_keys = ["user", "metadata", "settings"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nested_record.get(i), record_keys, ?options); + }; + }; + case ("decode(sans inference)", "Tuple(Mixed)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.tuple_mixed + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.mixed_tuple.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Variant(Simple)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.variant_simple + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.simple_variant.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Variant(Complex)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.variant_complex + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.complex_variant.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Map(Text->Nat)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.map_text_nat + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.map_values.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Large Text") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.large_text + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.large_text.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Large Array") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.large_array + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.large_array.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Deep Nesting") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.deep_nesting + }; + let record_keys = ["level1", "level2", "level3", "level4", "level5"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.deep_nesting.get(i), record_keys, ?options); + }; + }; + case ("decode(sans inference)", "Wide Record") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.wide_record + }; + let record_keys = Array.tabulate(20, func(j) = "field" # Nat.toText(j)); + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.wide_record.get(i), record_keys, ?options); + }; + }; + case ("decode(sans inference)", "Recursive Structure") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.deep_nesting + }; + let record_keys = ["level1", "level2", "level3", "level4", "level5"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.deep_nesting.get(i), record_keys, ?options); + }; + }; + + case (_, _) { + Debug.trap("Unhandled benchmark case: row = \"" # row # "\", col = \"" # col # "\""); + }; + } + ); + + bench; + }; +}; diff --git a/mops.toml b/mops.toml index c60a814..e7b0a70 100644 --- a/mops.toml +++ b/mops.toml @@ -3,26 +3,26 @@ name = "serde" version = "3.3.3" description = "A serialisation and deserialisation library for Motoko." repository = "https://github.com/NatLabs/serde" -keywords = ["json", "candid", "cbor", "urlencoded", "serialization"] +keywords = [ "json", "candid", "cbor", "urlencoded", "serialization" ] license = "MIT" [dependencies] -'base@0.14.14' = "0.14.14" -'itertools@0.2.2' = "0.2.2" -'xtended-numbers@2.0.0' = "2.0.0" -'cbor@4.0.0' = "4.0.0" -'map@9.0.1' = "9.0.1" -'sha2@0.1.6' = "0.1.6" -'byte-utils@0.1.2' = "0.1.2" +"base@0.16.0" = "0.16.0" +"itertools@0.2.2" = "0.2.2" +"xtended-numbers@2.0.0" = "2.0.0" +"cbor@4.0.0" = "4.0.0" +"map@9.0.1" = "9.0.1" +"sha2@0.1.6" = "0.1.6" +"byte-utils@0.1.2" = "0.1.2" "base@0.7.3" = "0.7.3" [dev-dependencies] -'test@2.1.1' = "2.1.1" -'fuzz@1.0.0' = "1.0.0" -'rep-indy-hash@0.1.1' = "0.1.1" -'candid@2.0.0' = "2.0.0" +test = "2.1.1" +fuzz = "1.0.0" +rep-indy-hash = "0.1.1" +candid = "2.0.0" bench = "1.0.0" [toolchain] wasmtime = "33.0.1" -moc = "0.14.14" +moc = "0.16.2" diff --git a/src/CBOR/lib.mo b/src/CBOR/lib.mo index 2d4b687..a88b396 100644 --- a/src/CBOR/lib.mo +++ b/src/CBOR/lib.mo @@ -1,13 +1,13 @@ -import Buffer "mo:base@0.14.14/Buffer"; -import Blob "mo:base@0.14.14/Blob"; -import Int8 "mo:base@0.14.14/Int8"; -import Int16 "mo:base@0.14.14/Int16"; -import Int32 "mo:base@0.14.14/Int32"; -import Int64 "mo:base@0.14.14/Int64"; -import Option "mo:base@0.14.14/Option"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Result "mo:base@0.14.14/Result"; -import Principal "mo:base@0.14.14/Principal"; +import Buffer "mo:base@0.16.0/Buffer"; +import Blob "mo:base@0.16.0/Blob"; +import Int8 "mo:base@0.16.0/Int8"; +import Int16 "mo:base@0.16.0/Int16"; +import Int32 "mo:base@0.16.0/Int32"; +import Int64 "mo:base@0.16.0/Int64"; +import Option "mo:base@0.16.0/Option"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Result "mo:base@0.16.0/Result"; +import Principal "mo:base@0.16.0/Principal"; import CBOR_Types "mo:cbor@4.0.0/Types"; import CBOR_Encoder "mo:cbor@4.0.0/Encoder"; diff --git a/src/Candid/Blob/CandidUtils.mo b/src/Candid/Blob/CandidUtils.mo index 6b66d73..5f77531 100644 --- a/src/Candid/Blob/CandidUtils.mo +++ b/src/Candid/Blob/CandidUtils.mo @@ -1,17 +1,17 @@ -import Array "mo:base@0.14.14/Array"; -import Buffer "mo:base@0.14.14/Buffer"; -import Result "mo:base@0.14.14/Result"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat "mo:base@0.14.14/Nat"; -import Iter "mo:base@0.14.14/Iter"; -import Text "mo:base@0.14.14/Text"; -import Order "mo:base@0.14.14/Order"; +import Array "mo:base@0.16.0/Array"; +import Buffer "mo:base@0.16.0/Buffer"; +import Result "mo:base@0.16.0/Result"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat "mo:base@0.16.0/Nat"; +import Iter "mo:base@0.16.0/Iter"; +import Text "mo:base@0.16.0/Text"; +import Order "mo:base@0.16.0/Order"; import Itertools "mo:itertools@0.2.2/Iter"; import Map "mo:map@9.0.1/Map"; import T "../Types"; -import TrieMap "mo:base@0.14.14/TrieMap"; +import TrieMap "mo:base@0.16.0/TrieMap"; import Utils "../../Utils"; module { diff --git a/src/Candid/Blob/Decoder.mo b/src/Candid/Blob/Decoder.mo index eed17e7..dc5dbd9 100644 --- a/src/Candid/Blob/Decoder.mo +++ b/src/Candid/Blob/Decoder.mo @@ -1,27 +1,27 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Buffer "mo:base@0.14.14/Buffer"; -import Debug "mo:base@0.14.14/Debug"; -import Result "mo:base@0.14.14/Result"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Int8 "mo:base@0.14.14/Int8"; -import Int32 "mo:base@0.14.14/Int32"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Int64 "mo:base@0.14.14/Int64"; -import Nat "mo:base@0.14.14/Nat"; -import Int "mo:base@0.14.14/Int"; -import Iter "mo:base@0.14.14/Iter"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; -import Order "mo:base@0.14.14/Order"; -import Int16 "mo:base@0.14.14/Int16"; -import TrieMap "mo:base@0.14.14/TrieMap"; -import Option "mo:base@0.14.14/Option"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Buffer "mo:base@0.16.0/Buffer"; +import Debug "mo:base@0.16.0/Debug"; +import Result "mo:base@0.16.0/Result"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Int8 "mo:base@0.16.0/Int8"; +import Int32 "mo:base@0.16.0/Int32"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Int64 "mo:base@0.16.0/Int64"; +import Nat "mo:base@0.16.0/Nat"; +import Int "mo:base@0.16.0/Int"; +import Iter "mo:base@0.16.0/Iter"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; +import Order "mo:base@0.16.0/Order"; +import Int16 "mo:base@0.16.0/Int16"; +import TrieMap "mo:base@0.16.0/TrieMap"; +import Option "mo:base@0.16.0/Option"; import Map "mo:map@9.0.1/Map"; import Set "mo:map@9.0.1/Set"; -import FloatX "mo:xtended-numbers/FloatX"; +import ByteUtils "mo:byte-utils@0.1.2"; import T "../Types"; import Utils "../../Utils"; @@ -53,7 +53,6 @@ module { /// - **blob** - A blob encoded in the candid format /// - **record_keys** - The record keys to use when decoding a record. /// - **options** - An optional arguement to specify options for decoding. - public func decode(blob : Blob, record_keys : [Text], options : ?T.Options) : Result<[Candid], Text> { one_shot(blob, record_keys, options); }; @@ -82,7 +81,7 @@ module { }; public func split(blob : Blob, options : ?T.Options) : Result { - let bytes = Blob.toArray(blob); + let bytes = blob; let state : [var Nat] = [var 0]; @@ -104,16 +103,16 @@ module { let sequences : CandidBlobSequences = { magic = Blob.fromArray(magic); - compound_types = Blob.fromArray(Utils.array_slice(bytes, compound_types_start_index, types_start_index)); - types = Blob.fromArray(Utils.array_slice(bytes, types_start_index, values_start_index)); - values = Blob.fromArray(Utils.array_slice(bytes, values_start_index, bytes.size())); + compound_types = Blob.fromArray(Utils.blob_slice(bytes, compound_types_start_index, types_start_index)); + types = Blob.fromArray(Utils.blob_slice(bytes, types_start_index, values_start_index)); + values = Blob.fromArray(Utils.blob_slice(bytes, values_start_index, bytes.size())); }; #ok(sequences); }; public func extract_values_sequence(blob : Blob) : Result { - let bytes = Blob.toArray(blob); + let bytes = blob; let state : [var Nat] = [var 0]; @@ -129,7 +128,7 @@ module { skip_types(bytes, state); let values_start_index = state[C.BYTES_INDEX]; - let values_sequence = Blob.fromArray(Utils.array_slice(bytes, values_start_index, bytes.size())); + let values_sequence = Blob.fromArray(Utils.blob_slice(bytes, values_start_index, bytes.size())); #ok(values_sequence) @@ -156,7 +155,7 @@ module { let new_key = formatVariantKey(key_pairs_to_rename[i].1); let hash = Utils.hash_record_key(original_key); - ignore Map.put(record_key_map, n32hash, hash, new_key); + // ignore Map.put(record_key_map, n32hash, hash, new_key); i += 1; }; @@ -166,14 +165,83 @@ module { one_shot_decode(blob, record_key_map, Option.get(options, T.defaultOptions)); }; - func read(bytes : [Nat8], state : [var Nat]) : Nat8 { - let byte = bytes[state[C.BYTES_INDEX]]; + func read(bytes : Blob, state : [var Nat]) : Nat8 { + let byte = bytes.get(state[C.BYTES_INDEX]); state[C.BYTES_INDEX] += 1; byte; }; - func peek(bytes : [Nat8], state : [var Nat]) : Nat8 { - bytes[state[C.BYTES_INDEX]]; + func peek(bytes : Blob, state : [var Nat]) : Nat8 { + bytes.get(state[C.BYTES_INDEX]); + }; + + // Helper function to create an iterator from read function + func createByteIterator(bytes : Blob, state : [var Nat]) : Iter.Iter { + object { + public func next() : ?Nat8 { + let i = state[C.BYTES_INDEX]; + if (i < bytes.size()) { + let byte = bytes[i]; + state[C.BYTES_INDEX] += 1; + ?byte; + } else { + null; + }; + }; + }; + }; + + // Iterator-based helper functions + func read_from_iter(iter : Iter.Iter) : Nat8 { + switch (iter.next()) { + case (?byte) byte; + case (null) Debug.trap("Unexpected end of data stream"); + }; + }; + + func decode_leb128_from_iter(iter : Iter.Iter) : Nat { + var n64 : Nat64 = 0; + var shift : Nat64 = 0; + + label decoding_leb loop { + let byte = read_from_iter(iter); + + n64 |= (Nat64.fromNat(Nat8.toNat(byte & 0x7f)) << shift); + + if (byte & 0x80 == 0) break decoding_leb; + shift += 7; + }; + + Nat64.toNat(n64); + }; + + func decode_signed_leb_64_from_iter(iter : Iter.Iter) : Int { + var result : Nat64 = 0; + var shift : Nat64 = 0; + var byte : Nat8 = 0; + var i = 0; + + label analyzing loop { + byte := read_from_iter(iter); + i += 1; + + // Add this byte's 7 bits to the result + result |= Nat64.fromNat(Nat8.toNat(byte & 0x7F)) << shift; + shift += 7; + + // If continuation bit is not set, we're done reading bytes + if ((byte & 0x80) == 0) { + break analyzing; + }; + }; + + // Sign extend if this is a negative number + if (byte & 0x40 != 0 and shift < 64) { + // Fill the rest with 1s (sign extension) + result |= ^((Nat64.fromNat(1) << shift) - 1); + }; + + Int64.toInt(Int64.fromNat64(result)); }; func code_to_primitive_type(code : Nat8) : CandidType { @@ -218,7 +286,7 @@ module { (code >= 0x70 and code <= 0x7f) or (code == 0x6f) or (code == 0x68); }; - func extract_compound_types(bytes : [Nat8], state : [var Nat], total_compound_types : Nat, record_key_map : Map) : [ShallowCandidTypes] { + public func extract_compound_types(bytes : Blob, state : [var Nat], total_compound_types : Nat, record_key_map : Map) : [ShallowCandidTypes] { func extract_compound_type(i : Nat) : ShallowCandidTypes { let compound_type_code = read(bytes, state); @@ -281,8 +349,8 @@ module { Array.tabulate(total_compound_types, extract_compound_type); }; - func build_compound_type(compound_types : [ShallowCandidTypes], start_pos : Nat, recursive_types : Map) : CandidType { - func _build_compound_type(compound_types : [ShallowCandidTypes], start_pos : Nat, visited : Set, is_recursive_set : Set, recursive_types : Map) : CandidType { + func build_compound_type(compound_types : [ShallowCandidTypes], start_pos : Nat, recursive_types_map : Map) : CandidType { + func _build_compound_type(compound_types : [ShallowCandidTypes], start_pos : Nat, visited : Set, is_recursive_set : Set, recursive_types_map : Map) : CandidType { var pos = start_pos; func resolve_field_types((field_key, ref_pos) : (Text, Nat)) : ((Text, CandidType)) { @@ -290,7 +358,7 @@ module { let resolved_type : CandidType = if (is_code_primitive_type(Nat8.fromNat(ref_pos))) { code_to_primitive_type(Nat8.fromNat(ref_pos)); } else { - _build_compound_type(compound_types, ref_pos, visited, is_recursive_set, recursive_types); + _build_compound_type(compound_types, ref_pos, visited, is_recursive_set, recursive_types_map); }; while (Set.size(visited) > visited_size) { @@ -300,7 +368,7 @@ module { (field_key, resolved_type); }; - switch (Map.get(recursive_types, nhash, pos)) { + switch (Map.get(recursive_types_map, nhash, pos)) { case (?candid_type) return candid_type; case (null) {}; }; @@ -317,7 +385,7 @@ module { let ref_type = if (is_code_primitive_type(Nat8.fromNat(ref_pos))) { code_to_primitive_type(Nat8.fromNat(ref_pos)); } else { - _build_compound_type(compound_types, ref_pos, visited, is_recursive_set, recursive_types); + _build_compound_type(compound_types, ref_pos, visited, is_recursive_set, recursive_types_map); }; #Option(ref_type); @@ -326,7 +394,7 @@ module { let ref_type = if (is_code_primitive_type(Nat8.fromNat(ref_pos))) { code_to_primitive_type(Nat8.fromNat(ref_pos)); } else { - _build_compound_type(compound_types, ref_pos, visited, is_recursive_set, recursive_types); + _build_compound_type(compound_types, ref_pos, visited, is_recursive_set, recursive_types_map); }; #Array(ref_type); }; @@ -340,8 +408,8 @@ module { }; }; - if (Set.has(is_recursive_set, nhash, pos) and not Map.has(recursive_types, nhash, pos)) { - ignore Map.put(recursive_types, nhash, pos, resolved_compound_type); + if (Set.has(is_recursive_set, nhash, pos) and not Map.has(recursive_types_map, nhash, pos)) { + ignore Map.put(recursive_types_map, nhash, pos, resolved_compound_type); }; resolved_compound_type; @@ -350,32 +418,35 @@ module { let visited = Set.new(); let is_recursive_set = Set.new(); - _build_compound_type(compound_types, start_pos, visited, is_recursive_set, recursive_types); + _build_compound_type(compound_types, start_pos, visited, is_recursive_set, recursive_types_map); }; - func build_types(bytes : [Nat8], state : [var Nat], compound_types : [ShallowCandidTypes], recursive_types : Map) : [CandidType] { + public func build_types(bytes : Blob, state : [var Nat], compound_types : [ShallowCandidTypes], recursive_types_map : Map) : [CandidType] { let total_candid_types = decode_leb128(bytes, state); let candid_types = Array.tabulate( total_candid_types, func(i : Nat) : CandidType { let code = peek(bytes, state); - if (is_code_primitive_type(code)) { + let candid_type = if (is_code_primitive_type(code)) { ignore read(bytes, state); let primitive_type = code_to_primitive_type(code); primitive_type; } else { let start_pos = decode_leb128(bytes, state); - let compound_type = build_compound_type(compound_types, start_pos, recursive_types); + let compound_type = build_compound_type(compound_types, start_pos, recursive_types_map); compound_type; }; + + CandidUtils.sort_candid_type(candid_type); + }, ); - candid_types; + (candid_types); }; - func skip_compound_types(bytes : [Nat8], state : [var Nat], total_compound_types : Nat) { + public func skip_compound_types(bytes : Blob, state : [var Nat], total_compound_types : Nat) { var i = 0; while (i < total_compound_types) { let compound_type_code = read(bytes, state); @@ -418,7 +489,7 @@ module { }; }; - func skip_types(bytes : [Nat8], state : [var Nat]) { + public func skip_types(bytes : Blob, state : [var Nat]) { let total_candid_types = decode_leb128(bytes, state); @@ -437,15 +508,11 @@ module { }; public func one_shot_decode(candid_blob : Blob, record_key_map : Map, options : T.Options) : Result<[Candid], Text> { - let bytes = Blob.toArray(candid_blob); + let bytes = candid_blob; // let stream = BitBuffer.fromArray(bytes); let is_types_set = Option.isSome(options.types); - if (options.blob_contains_only_values and not is_types_set) { - return #err("if 'options.blob_contains_only_values' is set, you need to also pass in the types"); - }; - var candid_types = Option.get(options.types, []); let state : [var Nat] = [var 0]; @@ -456,15 +523,15 @@ module { return #err("Invalid Magic Number"); }; - let recursive_types = Map.new(); + let recursive_types_map = Map.new(); if (not is_types_set) { // extract types from blob let total_compound_types = decode_leb128(bytes, state); let compound_types = extract_compound_types(bytes, state, total_compound_types, record_key_map); - candid_types := build_types(bytes, state, compound_types, recursive_types); + candid_types := build_types(bytes, state, compound_types, recursive_types_map); - } else if (not options.blob_contains_only_values) { + } else { // types are set but 'blob_contains_only_values' is not set, // then skip type section and locate start of values section let total_compound_types = decode_leb128(bytes, state); @@ -473,8 +540,10 @@ module { }; + let renaming_map = Map.fromIter(options.renameKeys.vals(), Map.thash); + // extract values with Candid variant Types - decode_candid_values(bytes, candid_types, state, options, recursive_types); + decode_candid_values(bytes, candid_types, state, options, renaming_map, recursive_types_map); }; let C = { @@ -482,7 +551,7 @@ module { }; // https://en.wikipedia.org/wiki/LEB128 - // func decode_leb128(bytes : [Nat8], state : [var Nat]) : Nat { + // func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { // var n64 : Nat64 = 0; // var shift : Nat64 = 0; @@ -512,7 +581,7 @@ module { // }; let nat64_bound = 18_446_744_073_709_551_616; - // func decode_leb128(bytes : [Nat8], state : [var Nat]) : Nat { + // func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { // var n64 : Nat64 = 0; // var shift : Nat64 = 0; // var shifted : Nat = 0; @@ -556,7 +625,7 @@ module { // }; // https://en.wikipedia.org/wiki/LEB128 - func decode_leb128(bytes : [Nat8], state : [var Nat]) : Nat { + func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { var n64 : Nat64 = 0; var shift : Nat64 = 0; @@ -572,42 +641,20 @@ module { Nat64.toNat(n64); }; - func decode_signed_leb_64(bytes : [Nat8], state : [var Nat]) : Int { - var result : Nat64 = 0; - var shift : Nat64 = 0; - var byte : Nat8 = 0; - var i = 0; - - label analyzing loop { - byte := read(bytes, state); - i += 1; - - // Add this byte's 7 bits to the result - result |= Nat64.fromNat(Nat8.toNat(byte & 0x7F)) << shift; - shift += 7; - - // If continuation bit is not set, we're done reading bytes - if ((byte & 0x80) == 0) { - break analyzing; - }; - }; - - // Sign extend if this is a negative number - if (byte & 0x40 != 0 and shift < 64) { - // Fill the rest with 1s (sign extension) - result |= ^((Nat64.fromNat(1) << shift) - 1); - }; - - Int64.toInt(Int64.fromNat64(result)); - }; - - func decode_candid_values(bytes : [Nat8], candid_types : [CandidType], state : [var Nat], options : T.Options, recursive_map : Map) : Result<[Candid], Text> { + public func decode_candid_values( + bytes : Blob, + candid_types : [CandidType], + state : [var Nat], + options : T.Options, + renaming_map : Map, + recursive_map : Map, + ) : Result<[Candid], Text> { var error : ?Text = null; let candid_values = Array.tabulate( candid_types.size(), func(i : Nat) : Candid { - switch (decode_value(bytes, state, options, recursive_map, candid_types[i])) { + switch (decode_value(bytes, state, options, renaming_map, recursive_map, candid_types[i])) { case (#ok(candid_value)) candid_value; case (#err(msg)) { error := ?msg; @@ -625,82 +672,54 @@ module { #ok(candid_values); }; - func decode_value(bytes : [Nat8], state : [var Nat], options : T.Options, recursive_map : Map, candid_type : CandidType) : Result { - // Debug.print("Decoding candid type: " # debug_show (candid_type) # " at index: " # debug_show (state[C.BYTES_INDEX])); - - let value : Candid = switch (candid_type) { - case (#Nat) #Nat(decode_leb128(bytes, state)); - case (#Nat8) #Nat8(read(bytes, state)); - case (#Nat16) { - let n = Nat8.toNat16(read(bytes, state)) | Nat8.toNat16(read(bytes, state)) << 8; - - #Nat16(n); - }; - case (#Nat32) { - let n = Nat32.fromNat(Nat8.toNat(read(bytes, state))) | Nat32.fromNat(Nat8.toNat(read(bytes, state))) << 8 | Nat32.fromNat(Nat8.toNat(read(bytes, state))) << 16 | Nat32.fromNat(Nat8.toNat(read(bytes, state))) << 24; - - #Nat32(n); - }; - case (#Nat64) { - let n = Nat64.fromNat(Nat8.toNat(read(bytes, state))) | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 8 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 16 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 24 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 32 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 40 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 48 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 56; - - #Nat64(n); - }; - - case (#Int) #Int(decode_signed_leb_64(bytes, state)); - case (#Int8) #Int8(Int8.fromNat8(read(bytes, state))); - case (#Int16) { - let n = Nat8.toNat16(read(bytes, state)) | Nat8.toNat16(read(bytes, state)) << 8; - - #Int16(Int16.fromNat16(n)); - }; - case (#Int32) { - let n = Nat32.fromNat(Nat8.toNat(read(bytes, state))) | Nat32.fromNat(Nat8.toNat(read(bytes, state))) << 8 | Nat32.fromNat(Nat8.toNat(read(bytes, state))) << 16 | Nat32.fromNat(Nat8.toNat(read(bytes, state))) << 24; - - #Int32(Int32.fromNat32(n)); - }; - case (#Int64) { - let n = Nat64.fromNat(Nat8.toNat(read(bytes, state))) | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 8 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 16 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 24 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 32 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 40 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 48 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 56; - - #Int64(Int64.fromNat64(n)); - }; - case (#Float) { - let bytes_iter = object { - public func next() : ?Nat8 { - let i = state[C.BYTES_INDEX]; - - if (i < bytes.size()) { - let byte = bytes[i]; - state[C.BYTES_INDEX] += 1; - ?(byte); - } else { - null; - }; - }; - }; + func decode_value( + bytes : Blob, + state : [var Nat], + options : T.Options, + renaming_map : Map, + recursive_map : Map, + candid_type : CandidType, + ) : Result { + let iter = createByteIterator(bytes, state); + decode_value_from_iter(iter, options, renaming_map, recursive_map, candid_type); + }; - let float_details = switch (FloatX.fromBytes(bytes_iter, #f64, #lsb)) { - case (?f) f; - case (null) return #err("Could not decode float sequence"); - }; + func decode_value_from_iter( + iter : Iter.Iter, + options : T.Options, + renaming_map : Map, + recursive_map : Map, + candid_type : CandidType, + ) : Result { + // Debug.print("Decoding candid type: " # debug_show (candid_type)); - let n = FloatX.toFloat(float_details); - #Float(n); - }; + let value : Candid = switch (candid_type) { + case (#Nat) #Nat(decode_leb128_from_iter(iter)); + case (#Nat8) #Nat8(read_from_iter(iter)); + case (#Nat16) #Nat16(ByteUtils.LE.toNat16(iter)); + case (#Nat32) #Nat32(ByteUtils.LE.toNat32(iter)); + case (#Nat64) #Nat64(ByteUtils.LE.toNat64(iter)); + + case (#Int) #Int(decode_signed_leb_64_from_iter(iter)); + case (#Int8) #Int8(Int8.fromNat8(read_from_iter(iter))); + case (#Int16) #Int16(ByteUtils.LE.toInt16(iter)); + case (#Int32) #Int32(ByteUtils.LE.toInt32(iter)); + case (#Int64) #Int64(ByteUtils.LE.toInt64(iter)); + case (#Float) #Float(ByteUtils.LE.toFloat(iter)); case (#Bool) { - let byte = read(bytes, state); + let byte = read_from_iter(iter); let b = if (byte == 0) false else true; #Bool(b); }; case (#Null) #Null; case (#Empty) #Empty; case (#Text) { - let size = decode_leb128(bytes, state); + let size = decode_leb128_from_iter(iter); let text_bytes = Array.tabulate( size, - func(i : Nat) : Nat8 { - read(bytes, state); + func(_ : Nat) : Nat8 { + read_from_iter(iter); }, ); @@ -713,13 +732,13 @@ module { #Text(text); }; case (#Principal) { - assert read(bytes, state) == 0x01; // transparency state. opaque not supported yet. - let size = decode_leb128(bytes, state); + assert read_from_iter(iter) == 0x01; // transparency state. opaque not supported yet. + let size = decode_leb128_from_iter(iter); let principal_bytes = Array.tabulate( size, - func(i : Nat) : Nat8 { - read(bytes, state); + func(_ : Nat) : Nat8 { + read_from_iter(iter); }, ); @@ -732,11 +751,11 @@ module { // ====================== Compound Types ======================= case (#Option(opt_type)) { - let is_null = read(bytes, state) == 0; + let is_null = read_from_iter(iter) == 0; if (is_null) return #ok(#Null); - let nested_value = switch (decode_value(bytes, state, options, recursive_map, opt_type)) { + let nested_value = switch (decode_value_from_iter(iter, options, renaming_map, recursive_map, opt_type)) { case (#ok(value)) value; case (#err(err_msg)) return #err(err_msg); }; @@ -746,13 +765,13 @@ module { }; case (#Array(#Nat8)) { - let size = decode_leb128(bytes, state); + let size = decode_leb128_from_iter(iter); var error : ?Text = null; let values = Array.tabulate( size, - func(i : Nat) : Nat8 { - switch (decode_value(bytes, state, options, recursive_map, #Nat8)) { + func(_ : Nat) : Nat8 { + switch (decode_value_from_iter(iter, options, renaming_map, recursive_map, #Nat8)) { case (#ok(#Nat8(value))) value; case (#ok(unexpected_type)) { error := ?("Expected #Nat8 value in blob type, instead got " # debug_show (unexpected_type)); @@ -775,14 +794,17 @@ module { #Blob(blob); }; + case (#Blob(_)) { + return decode_value_from_iter(iter, options, renaming_map, recursive_map, #Array(#Nat8)); + }; case (#Array(arr_type)) { - let size = decode_leb128(bytes, state); + let size = decode_leb128_from_iter(iter); var error : ?Text = null; let values = Array.tabulate( size, - func(i : Nat) : Candid { - switch (decode_value(bytes, state, options, recursive_map, arr_type)) { + func(_ : Nat) : Candid { + switch (decode_value_from_iter(iter, options, renaming_map, recursive_map, arr_type)) { case (#ok(value)) value; case (#err(err_msg)) { error := ?err_msg; @@ -820,7 +842,7 @@ module { let record_type = record_types[i].1; - let value = switch (decode_value(bytes, state, options, recursive_map, record_type)) { + let value = switch (decode_value_from_iter(iter, options, renaming_map, recursive_map, record_type)) { case (#ok(value)) value; case (#err(msg)) { error := ?(msg); @@ -828,7 +850,7 @@ module { }; }; - (record_key, value); + (get_renamed_key(renaming_map, record_key), value); }, ); @@ -839,30 +861,30 @@ module { case (null) {}; }; - if (options.use_icrc_3_value_type) { - #Map(record_entries); - } else if (is_tuple and record_entries.size() > 0) { + if (is_tuple and record_entries.size() > 0) { #Tuple(Array.map<(Text, Candid), Candid>(record_entries, func((_, v) : (Any, Candid)) : Candid = v)); + } else if (options.use_icrc_3_value_type) { + #Map(record_entries); } else { #Record(record_entries); }; }; - case (#Tuple(tuple_types)) return decode_value( - bytes, - state, + case (#Tuple(tuple_types)) return decode_value_from_iter( + iter, options, + renaming_map, recursive_map, #Record(Array.tabulate<(Text, CandidType)>(tuple_types.size(), func(i : Nat) : (Text, CandidType) = (debug_show (i), tuple_types[i]))), ); case (#Variant(variant_types)) { - let variant_index = decode_leb128(bytes, state); + let variant_index = decode_leb128_from_iter(iter); let variant_key = variant_types[variant_index].0; let variant_type = variant_types[variant_index].1; var error : ?Text = null; - let value = switch (decode_value(bytes, state, options, recursive_map, variant_type)) { + let value = switch (decode_value_from_iter(iter, options, renaming_map, recursive_map, variant_type)) { case (#ok(value)) value; case (#err(msg)) { error := ?(msg); @@ -870,7 +892,7 @@ module { }; }; - #Variant(variant_key, value); + #Variant(get_renamed_key(renaming_map, variant_key), value); }; case (#Recursive(pos)) { let recursive_type = switch (Map.get(recursive_map, nhash, pos)) { @@ -878,15 +900,22 @@ module { case (_) Debug.trap("Recursive type not found"); }; - return decode_value(bytes, state, options, recursive_map, recursive_type); + return decode_value_from_iter(iter, options, renaming_map, recursive_map, recursive_type); }; - case (val) Debug.trap(debug_show (val) # "decoding is not supported"); + case (val) Debug.trap(debug_show (val) # " decoding is not supported"); }; #ok(value); }; + func get_renamed_key(renaming_map : Map, key : Text) : Text { + Option.get( + Map.get(renaming_map, Map.thash, key), + key, + ); + }; + func formatVariantKey(key : Text) : Text { let opt = Text.stripStart(key, #text("#")); switch (opt) { diff --git a/src/Candid/Blob/Encoder.mo b/src/Candid/Blob/Encoder.mo index 2f14fce..354bd90 100644 --- a/src/Candid/Blob/Encoder.mo +++ b/src/Candid/Blob/Encoder.mo @@ -1,35 +1,35 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Buffer "mo:base@0.14.14/Buffer"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Buffer "mo:base@0.16.0/Buffer"; import B "mo:buffer"; -import Debug "mo:base@0.14.14/Debug"; -import Result "mo:base@0.14.14/Result"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Int8 "mo:base@0.14.14/Int8"; -import Int32 "mo:base@0.14.14/Int32"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat16 "mo:base@0.14.14/Nat16"; -import Int64 "mo:base@0.14.14/Int64"; -import Nat "mo:base@0.14.14/Nat"; -import Int "mo:base@0.14.14/Int"; -import Iter "mo:base@0.14.14/Iter"; -import Prelude "mo:base@0.14.14/Prelude"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; -import Order "mo:base@0.14.14/Order"; -import Option "mo:base@0.14.14/Option"; -import Func "mo:base@0.14.14/Func"; -import Char "mo:base@0.14.14/Char"; -import Int16 "mo:base@0.14.14/Int16"; +import Debug "mo:base@0.16.0/Debug"; +import Result "mo:base@0.16.0/Result"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Int8 "mo:base@0.16.0/Int8"; +import Int32 "mo:base@0.16.0/Int32"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat16 "mo:base@0.16.0/Nat16"; +import Int64 "mo:base@0.16.0/Int64"; +import Nat "mo:base@0.16.0/Nat"; +import Int "mo:base@0.16.0/Int"; +import Iter "mo:base@0.16.0/Iter"; +import Prelude "mo:base@0.16.0/Prelude"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; +import Order "mo:base@0.16.0/Order"; +import Option "mo:base@0.16.0/Option"; +import Func "mo:base@0.16.0/Func"; +import Char "mo:base@0.16.0/Char"; +import Int16 "mo:base@0.16.0/Int16"; import Itertools "mo:itertools@0.2.2/Iter"; import PeekableIter "mo:itertools@0.2.2/PeekableIter"; import Map "mo:map@9.0.1/Map"; -import FloatX "mo:xtended-numbers/FloatX"; +import ByteUtils "mo:byte-utils@0.1.2"; import T "../Types"; -import TrieMap "mo:base@0.14.14/TrieMap"; +import TrieMap "mo:base@0.16.0/TrieMap"; import Utils "../../Utils"; import CandidUtils "CandidUtils"; @@ -48,6 +48,21 @@ module { let { thash } = Map; let { unsigned_leb128; signed_leb128_64 } = Utils; + // public func encode(candid_values : [Candid], options : ?T.Options) : Result { + // let renaming_map = Map.new(); + // for ((k, v) in Option.get(options, T.defaultOptions).renameKeys.vals()) { + // ignore Map.put(renaming_map, thash, k, v); + // }; + // let (candid_types) = switch (infer_candid_types(candid_values, renaming_map)) { + // case (#ok(inferred_types)) Array.map( + // inferred_types, + // func(candid_type : CandidType) : CandidType = CandidUtils.format_candid_type(candid_type, renaming_map), + // ); + // case (#err(e)) return #err(e); + // }; + // let encoder = Encoder.new(candid_types, options); + // Encoder.encode(encoder, candid_values, options); + // }; public func encode(candid_values : [Candid], options : ?T.Options) : Result { one_shot(candid_values, options); }; @@ -97,8 +112,8 @@ module { let renaming_map = Map.new(); let compound_type_buffer = Buffer.Buffer(200); - let primitive_type_buffer = Buffer.Buffer(200); - let value_buffer = Buffer.Buffer(200); + let candid_type_buffer = Buffer.Buffer(200); + let value_buffer = Buffer.Buffer(400); let counter = [var 0]; @@ -123,33 +138,40 @@ module { candid_types, candid_values, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, counter, renaming_map, ); - let candid_buffer = Buffer.fromArray([0x44, 0x49, 0x44, 0x4C]); // 'DIDL' magic bytes + let candid_magic_bytes_buffer = Buffer.fromArray([0x44, 0x49, 0x44, 0x4C]); // 'DIDL' magic bytes // add compound type to the buffer let compound_type_size_bytes = Buffer.Buffer(8); + // ByteUtils.Buffer.addLEB128_64(compound_type_size_bytes, Nat64.fromNat(counter[C.COUNTER.COMPOUND_TYPE])); unsigned_leb128(compound_type_size_bytes, counter[C.COUNTER.COMPOUND_TYPE]); // add primitive type to the buffer - let primitive_type_size_bytes = Buffer.Buffer(8); - unsigned_leb128(primitive_type_size_bytes, candid_values.size()); + let candid_type_size_bytes = Buffer.Buffer(8); + // ByteUtils.Buffer.addLEB128_64(candid_type_size_bytes, Nat64.fromNat(candid_values.size())); + unsigned_leb128(candid_type_size_bytes, candid_values.size()); - let total_size = candid_buffer.size() + compound_type_size_bytes.size() + compound_type_buffer.size() + primitive_type_size_bytes.size() + primitive_type_buffer.size() + value_buffer.size(); + let total_size = candid_magic_bytes_buffer.size() + compound_type_size_bytes.size() + compound_type_buffer.size() + candid_type_size_bytes.size() + candid_type_buffer.size() + value_buffer.size(); let sequence = [ - candid_buffer, + candid_magic_bytes_buffer, compound_type_size_bytes, compound_type_buffer, - primitive_type_size_bytes, - primitive_type_buffer, + candid_type_size_bytes, + candid_type_buffer, value_buffer, ]; + // Print each buffer in the sequence as a Blob + // for (buf in sequence.vals()) { + // Debug.print(debug_show Blob.fromArray(Buffer.toArray(buf))); + // }; + var i = 0; var j = 0; @@ -176,20 +198,20 @@ module { func check_is_tuple(candid_types : [(Text, Any)]) : Bool { let n = candid_types.size(); // 0-based index - var sum_of_n : Int = (n * (n + 1)) / 2; + var sum_of_n : Int = 0; var i = 0; label tuple_check while (i < candid_types.size()) { let record_key = candid_types[i].0; if (Utils.text_is_number(record_key)) { - sum_of_n -= (Utils.text_to_nat(record_key) + 1); + sum_of_n += (Utils.text_to_nat(record_key) + 1); } else break tuple_check; i += 1; }; - sum_of_n == 0; + sum_of_n == (n * (n + 1)) / 2; }; func tuple_type_to_record(tuple_types : [CandidType]) : [(Text, CandidType)] { @@ -214,7 +236,7 @@ module { candid_types : [CandidType], candid_values : [Candid], compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, + candid_type_buffer : Buffer, value_buffer : Buffer, counter : [var Nat], renaming_map : Map, @@ -235,7 +257,7 @@ module { candid_types[i], candid_values[i], compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -250,6 +272,299 @@ module { }; + /// Encodes only the value part, without encoding any type information. + public func encode_value_only( + candid_type : CandidType, + candid_value : Candid, + value_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + recursive_map : Map, + counter : [var Nat], + is_nested_child_of_compound_type : Bool, + ) : ?Hash { + let candid_is_compound_type = switch candid_type { + case (#Option(_) or #Array(_) or #Record(_) or #Map(_) or #Tuple(_) or #Variant(_) or #Recursive(_) or #Blob(_)) true; + case (_) false; + }; + + if (candid_is_compound_type) { + switch (candid_type, candid_value) { + case (#Option(opt_type), #Option(opt_value)) { + if (opt_value == #Null and opt_type != #Null) { + value_buffer.add(0); // no value + } else { + value_buffer.add(1); // has value + ignore encode_value_only( + opt_type, + opt_value, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + ); + }; + }; + case (#Option(opt_type), #Null) { + value_buffer.add(0); // no value + }; + case (#Array(arr_type), #Array(arr_values)) { + unsigned_leb128(value_buffer, arr_values.size()); + var i = 0; + while (i < arr_values.size()) { + ignore encode_value_only( + arr_type, + arr_values[i], + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + ); + i += 1; + }; + }; + case (#Blob, #Blob(blob)) { + let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); + ignore encode_value_only( + #Array(#Nat8), + #Array(bytes), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Array(#Nat8), #Blob(blob)) { + let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); + ignore encode_value_only( + #Array(#Nat8), + #Array(bytes), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Blob, #Array(bytes)) { + if (bytes.size() > 0) { + switch (bytes[0]) { + case (#Nat8(_)) {}; + case (_) return Debug.trap("invalid blob value: expected array of Nat8, got array of " # debug_show bytes[0]); + }; + }; + ignore encode_value_only( + #Array(#Nat8), + #Array(bytes), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Record(record_types) or #Map(record_types), #Record(record_entries) or #Map(record_entries)) { + + let record_entry_cache : Map.Map = Utils.create_map(record_entries.size()); + + for ((k, v) in record_entries.vals()) { + let field_value_key = get_renamed_key(renaming_map, k); + ignore Map.put(record_entry_cache, thash, field_value_key, v); + }; + + var i = 0; + while (i < record_types.size()) { + let field_type = record_types[i].1; + let field_type_key = get_renamed_key(renaming_map, record_types[i].0); + let field_value = switch (Map.get(record_entry_cache, Map.thash, field_type_key)) { + case (?field_value) { + // If field type is optional but value isn't, wrap it + switch (field_type, field_value) { + case (#Option(inner_type), val) { + // Check if value is already wrapped in Option + switch (val) { + case (#Option(_) or #Null) val; // Already wrapped + case (_) #Option(val); // Wrap it + }; + }; + case (_) field_value; + }; + }; + case (null) { + // Field is missing - if the type is optional, use #Null + switch (field_type) { + case (#Option(_)) #Null; + case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); + }; + }; + }; + ignore encode_value_only( + field_type, + field_value, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + ); + i += 1; + }; + }; + + case (#Tuple(tuple_types), #Tuple(tuple_values)) { + ignore encode_value_only( + #Record(tuple_type_to_record(tuple_types)), + #Record(tuple_value_to_record(tuple_values)), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Tuple(tuple_types), #Record(tuple_values)) { + var i = 0; + assert Itertools.all( + tuple_values.vals(), + func((k, v) : (Text, Any)) : Bool { + i += 1; + Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); + }, + ); + ignore encode_value_only( + #Record(tuple_type_to_record(tuple_types)), + #Record(tuple_values), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Record(record_types), #Tuple(tuple_values)) { + var i = 0; + assert Itertools.all( + record_types.vals(), + func((k, v) : (Text, Any)) : Bool { + i += 1; + Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); + }, + ); + ignore encode_value_only( + #Record(record_types), + #Record(tuple_value_to_record(tuple_values)), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Variant(variant_types), #Variant(variant)) { + let variant_key = get_renamed_key(renaming_map, variant.0); + let variant_value = variant.1; + let variant_index_res = Array.indexOf<(Text, CandidType)>( + (variant_key, #Empty), + variant_types, + func((a, _) : (Text, CandidType), (b, _) : (Text, CandidType)) : Bool = a == b, + ); + let variant_index = switch (variant_index_res) { + case (?index) index; + case (_) Debug.trap("unable to find variant key in variant types"); + }; + unsigned_leb128(value_buffer, variant_index); + ignore encode_value_only( + variant_types[variant_index].1, + variant_value, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + ); + }; + case (_) Debug.trap("invalid (type, value) pair for encode_value_only: " # debug_show { candid_type; candid_value }); + }; + + } else { + // primitive types + switch (candid_type, candid_value) { + case (#Nat, #Nat(n)) { + unsigned_leb128(value_buffer, n); + }; + case (#Nat8, #Nat8(n)) { + value_buffer.add(n); + }; + case (#Nat16, #Nat16(n)) { + ByteUtils.Buffer.LE.addNat16(value_buffer, n); + }; + case (#Nat32, #Nat32(n)) { + ByteUtils.Buffer.LE.addNat32(value_buffer, n); + }; + case (#Nat64, #Nat64(n)) { + ByteUtils.Buffer.LE.addNat64(value_buffer, n); + }; + case (#Int, #Int(n)) { + signed_leb128_64(value_buffer, n); + }; + case (#Int8, #Int8(i8)) { + value_buffer.add(Int8.toNat8(i8)); + }; + case (#Int16, #Int16(i16)) { + ByteUtils.Buffer.LE.addInt16(value_buffer, i16); + }; + case (#Int32, #Int32(i32)) { + ByteUtils.Buffer.LE.addInt32(value_buffer, i32); + }; + case (#Int64, #Int64(i64)) { + ByteUtils.Buffer.LE.addInt64(value_buffer, i64); + }; + case (#Float, #Float(f64)) { + ByteUtils.Buffer.LE.addFloat(value_buffer, f64); + }; + case (#Bool, #Bool(b)) { + value_buffer.add(if (b) (1) else (0)); + }; + case (#Null, #Null) {}; + case (#Empty, #Empty) {}; + case (#Text, #Text(t)) { + let utf8_blob = Text.encodeUtf8(t); + unsigned_leb128(value_buffer, utf8_blob.size()); + var i = 0; + while (i < utf8_blob.size()) { + value_buffer.add(utf8_blob[i]); + i += 1; + }; + }; + case (#Principal, #Principal(p)) { + value_buffer.add(0x01); // indicate transparency state + let blob = Principal.toBlob(p); + unsigned_leb128(value_buffer, blob.size()); + var i = 0; + while (i < blob.size()) { + value_buffer.add(blob[i]); + i += 1; + }; + }; + case (_) Debug.trap("unknown (type, value) pair for encode_value_only: " # debug_show (candid_type, candid_value)); + }; + }; + null; + }; + func is_compound_type(candid_type : CandidType) : Bool { switch (candid_type) { case (#Option(_) or #Array(_) or #Record(_) or #Map(_) or #Tuple(_) or #Variant(_) or #Recursive(_) or #Blob(_)) true; @@ -260,34 +575,34 @@ module { func encode_primitive_type_only( candid_type : CandidType, compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, + candid_type_buffer : Buffer, is_nested_child_of_compound_type : Bool, ) { - let ref_primitive_type_buffer = if (is_nested_child_of_compound_type) { + let ref_candid_type_buffer = if (is_nested_child_of_compound_type) { compound_type_buffer; } else { - primitive_type_buffer; + candid_type_buffer; }; switch (candid_type) { - case (#Nat) ref_primitive_type_buffer.add(T.TypeCode.Nat); - case (#Nat8) ref_primitive_type_buffer.add(T.TypeCode.Nat8); - case (#Nat16) ref_primitive_type_buffer.add(T.TypeCode.Nat16); - case (#Nat32) ref_primitive_type_buffer.add(T.TypeCode.Nat32); - case (#Nat64) ref_primitive_type_buffer.add(T.TypeCode.Nat64); - - case (#Int) ref_primitive_type_buffer.add(T.TypeCode.Int); - case (#Int8) ref_primitive_type_buffer.add(T.TypeCode.Int8); - case (#Int16) ref_primitive_type_buffer.add(T.TypeCode.Int16); - case (#Int32) ref_primitive_type_buffer.add(T.TypeCode.Int32); - case (#Int64) ref_primitive_type_buffer.add(T.TypeCode.Int64); - - case (#Float) ref_primitive_type_buffer.add(T.TypeCode.Float); - case (#Bool) ref_primitive_type_buffer.add(T.TypeCode.Bool); - case (#Text) ref_primitive_type_buffer.add(T.TypeCode.Text); - case (#Principal) ref_primitive_type_buffer.add(T.TypeCode.Principal); - case (#Null) ref_primitive_type_buffer.add(T.TypeCode.Null); - case (#Empty) ref_primitive_type_buffer.add(T.TypeCode.Empty); + case (#Nat) ref_candid_type_buffer.add(T.TypeCode.Nat); + case (#Nat8) ref_candid_type_buffer.add(T.TypeCode.Nat8); + case (#Nat16) ref_candid_type_buffer.add(T.TypeCode.Nat16); + case (#Nat32) ref_candid_type_buffer.add(T.TypeCode.Nat32); + case (#Nat64) ref_candid_type_buffer.add(T.TypeCode.Nat64); + + case (#Int) ref_candid_type_buffer.add(T.TypeCode.Int); + case (#Int8) ref_candid_type_buffer.add(T.TypeCode.Int8); + case (#Int16) ref_candid_type_buffer.add(T.TypeCode.Int16); + case (#Int32) ref_candid_type_buffer.add(T.TypeCode.Int32); + case (#Int64) ref_candid_type_buffer.add(T.TypeCode.Int64); + + case (#Float) ref_candid_type_buffer.add(T.TypeCode.Float); + case (#Bool) ref_candid_type_buffer.add(T.TypeCode.Bool); + case (#Text) ref_candid_type_buffer.add(T.TypeCode.Text); + case (#Principal) ref_candid_type_buffer.add(T.TypeCode.Principal); + case (#Null) ref_candid_type_buffer.add(T.TypeCode.Null); + case (#Empty) ref_candid_type_buffer.add(T.TypeCode.Empty); case (_) Debug.trap("encode_primitive_type_only(): unknown primitive type " # debug_show candid_type); }; @@ -296,13 +611,13 @@ module { func encode_compound_type_only( candid_type : CandidType, compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, + candid_type_buffer : Buffer, renaming_map : Map, unique_compound_type_map : Map, counter : [var Nat], is_nested_child_of_compound_type : Bool, ) { - let type_info = debug_show candid_type; + let type_info = get_type_info(candid_type); let compound_type_exists = Map.has(unique_compound_type_map, thash, type_info); if (compound_type_exists) return; // Debug.print("encode_compound_type_only(): " # debug_show type_info); @@ -317,7 +632,7 @@ module { encode_type_only( opt_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, renaming_map, unique_compound_type_map, counter, @@ -326,7 +641,7 @@ module { if (opt_type_is_compound) { compound_type_buffer.add(T.TypeCode.Option); - let opt_type_info = debug_show opt_type; + let opt_type_info = get_type_info(opt_type); let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { case (?pos) pos; case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); @@ -345,7 +660,7 @@ module { encode_type_only( arr_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, renaming_map, unique_compound_type_map, counter, @@ -354,7 +669,7 @@ module { if (arr_type_is_compound) { compound_type_buffer.add(T.TypeCode.Array); - let arr_type_info = debug_show arr_type; + let arr_type_info = get_type_info(arr_type); let pos = switch (Map.get(unique_compound_type_map, thash, arr_type_info)) { case (?pos) pos; case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); @@ -366,11 +681,11 @@ module { case (#Blob) return encode_compound_type_only( #Array(#Nat8), compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, renaming_map, unique_compound_type_map, counter, - true, + is_nested_child_of_compound_type, ); case (#Record(record_types) or #Map(record_types)) { @@ -378,14 +693,14 @@ module { var i = 0; while (i < record_types.size()) { - let value_type = record_types[i].1; + let field_type = record_types[i].1; - let value_type_is_compound = is_compound_type(value_type); + let value_type_is_compound = is_compound_type(field_type); if (value_type_is_compound) encode_type_only( - value_type, + field_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, renaming_map, unique_compound_type_map, counter, @@ -400,9 +715,9 @@ module { i := 0; while (i < record_types.size()) { - let value_type = record_types[i].1; + let field_type = record_types[i].1; - let value_type_is_compound = is_compound_type(value_type); + let value_type_is_compound = is_compound_type(field_type); if (is_tuple) { unsigned_leb128(compound_type_buffer, i); @@ -414,7 +729,7 @@ module { }; if (value_type_is_compound) { - let value_type_info = debug_show value_type; + let value_type_info = get_type_info(field_type); let pos = switch (Map.get(unique_compound_type_map, thash, value_type_info)) { case (?pos) pos; case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); @@ -422,9 +737,9 @@ module { unsigned_leb128(compound_type_buffer, pos); } else { encode_primitive_type_only( - value_type, + field_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, true, ); }; @@ -437,7 +752,7 @@ module { return encode_compound_type_only( #Record(tuple_type_to_record(tuple_types)), compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, renaming_map, unique_compound_type_map, counter, @@ -457,7 +772,7 @@ module { encode_compound_type_only( variant_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, renaming_map, unique_compound_type_map, counter, @@ -481,7 +796,7 @@ module { unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); if (variant_type_is_compound) { - let variant_type_info = debug_show variant_type; + let variant_type_info = get_type_info(variant_type); let pos = switch (Map.get(unique_compound_type_map, thash, variant_type_info)) { case (?pos) pos; case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); @@ -491,7 +806,7 @@ module { encode_primitive_type_only( variant_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, true, ); }; @@ -508,10 +823,10 @@ module { }; - func encode_type_only( + public func encode_type_only( candid_type : CandidType, compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, + candid_type_buffer : Buffer, renaming_map : Map, unique_compound_type_map : Map, counter : [var Nat], @@ -521,17 +836,27 @@ module { encode_compound_type_only( candid_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, renaming_map, unique_compound_type_map, counter, is_nested_child_of_compound_type, ); + + // Add compound type reference to primitive type buffer for top-level types + if (not is_nested_child_of_compound_type) { + let type_info = get_type_info(candid_type); + let pos = switch (Map.get(unique_compound_type_map, thash, type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(candid_type_buffer, pos); + }; } else { encode_primitive_type_only( candid_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, is_nested_child_of_compound_type, ); }; @@ -551,7 +876,7 @@ module { candid_type : CandidType, candid_value : Candid, compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, + candid_type_buffer : Buffer, value_buffer : Buffer, renaming_map : Map, unique_compound_type_map : Map, @@ -559,125 +884,96 @@ module { is_nested_child_of_compound_type : Bool, ignore_type : Bool, ) { - let ref_primitive_type_buffer = if (ignore_type) { + let ref_candid_type_buffer = if (ignore_type) { object { public func add(_ : Nat8) {}; // do nothing }; } else if (is_nested_child_of_compound_type) { compound_type_buffer; } else { - primitive_type_buffer; + candid_type_buffer; }; switch (candid_type, candid_value) { case (#Nat, #Nat(n)) { // Debug.print("start encoding Nat: " # debug_show n); - ref_primitive_type_buffer.add(T.TypeCode.Nat); + ref_candid_type_buffer.add(T.TypeCode.Nat); // Debug.print("encoded type codde"); unsigned_leb128(value_buffer, n); }; case (#Nat8, #Nat8(n)) { - ref_primitive_type_buffer.add(T.TypeCode.Nat8); + ref_candid_type_buffer.add(T.TypeCode.Nat8); value_buffer.add(n); }; case (#Nat16, #Nat16(n)) { - ref_primitive_type_buffer.add(T.TypeCode.Nat16); - value_buffer.add((n & 0xFF) |> Nat16.toNat8(_)); - value_buffer.add((n >> 8) |> Nat16.toNat8(_)); + ref_candid_type_buffer.add(T.TypeCode.Nat16); + ByteUtils.Buffer.LE.addNat16(value_buffer, n); }; case (#Nat32, #Nat32(n)) { - ref_primitive_type_buffer.add(T.TypeCode.Nat32); - value_buffer.add((n & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - value_buffer.add(((n >> 8) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - value_buffer.add(((n >> 16) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - value_buffer.add((n >> 24) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); + ref_candid_type_buffer.add(T.TypeCode.Nat32); + ByteUtils.Buffer.LE.addNat32(value_buffer, n); }; case (#Nat64, #Nat64(n)) { - ref_primitive_type_buffer.add(T.TypeCode.Nat64); - value_buffer.add((n & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 8) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 16) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 24) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 32) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 40) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 48) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add((n >> 56) |> Nat64.toNat(_) |> Nat8.fromNat(_)); + ref_candid_type_buffer.add(T.TypeCode.Nat64); + ByteUtils.Buffer.LE.addNat64(value_buffer, n); }; case (#Int, #Int(n)) { - ref_primitive_type_buffer.add(T.TypeCode.Int); + ref_candid_type_buffer.add(T.TypeCode.Int); signed_leb128_64(value_buffer, n); }; case (#Int8, #Int8(i8)) { - ref_primitive_type_buffer.add(T.TypeCode.Int8); + ref_candid_type_buffer.add(T.TypeCode.Int8); value_buffer.add(Int8.toNat8(i8)); }; case (#Int16, #Int16(i16)) { - ref_primitive_type_buffer.add(T.TypeCode.Int16); - let n16 = Int16.toNat16(i16); - value_buffer.add((n16 & 0xFF) |> Nat16.toNat8(_)); - value_buffer.add((n16 >> 8) |> Nat16.toNat8(_)); + ref_candid_type_buffer.add(T.TypeCode.Int16); + ByteUtils.Buffer.LE.addInt16(value_buffer, i16); }; case (#Int32, #Int32(i32)) { - ref_primitive_type_buffer.add(T.TypeCode.Int32); - let n = Int32.toNat32(i32); - - value_buffer.add((n & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - value_buffer.add(((n >> 8) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - value_buffer.add(((n >> 16) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - value_buffer.add((n >> 24) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); + ref_candid_type_buffer.add(T.TypeCode.Int32); + ByteUtils.Buffer.LE.addInt32(value_buffer, i32); }; case (#Int64, #Int64(i64)) { - ref_primitive_type_buffer.add(T.TypeCode.Int64); - let n = Int64.toNat64(i64); - - value_buffer.add((n & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 8) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 16) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 24) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 32) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 40) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 48) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add((n >> 56) |> Nat64.toNat(_) |> Nat8.fromNat(_)); + ref_candid_type_buffer.add(T.TypeCode.Int64); + ByteUtils.Buffer.LE.addInt64(value_buffer, i64); }; case (#Float, #Float(f64)) { - ref_primitive_type_buffer.add(T.TypeCode.Float); - let floatX : FloatX.FloatX = FloatX.fromFloat(f64, #f64); - FloatX.toBytesBuffer(B.fromDeprecatedBuffer(value_buffer), floatX, #lsb); + ref_candid_type_buffer.add(T.TypeCode.Float); + ByteUtils.Buffer.LE.addFloat(value_buffer, f64); }; case (#Bool, #Bool(b)) { - ref_primitive_type_buffer.add(T.TypeCode.Bool); + ref_candid_type_buffer.add(T.TypeCode.Bool); value_buffer.add(if (b) (1) else (0)); }; case (#Null, #Null) { - ref_primitive_type_buffer.add(T.TypeCode.Null); + ref_candid_type_buffer.add(T.TypeCode.Null); }; case (#Empty, #Empty) { - ref_primitive_type_buffer.add(T.TypeCode.Empty); + ref_candid_type_buffer.add(T.TypeCode.Empty); }; case (#Text, #Text(t)) { - ref_primitive_type_buffer.add(T.TypeCode.Text); - - let utf8_bytes = Blob.toArray(Text.encodeUtf8(t)); - unsigned_leb128(value_buffer, utf8_bytes.size()); + ref_candid_type_buffer.add(T.TypeCode.Text); + let utf8_blob = Text.encodeUtf8(t); + unsigned_leb128(value_buffer, utf8_blob.size()); var i = 0; - while (i < utf8_bytes.size()) { - value_buffer.add(utf8_bytes[i]); + while (i < utf8_blob.size()) { + value_buffer.add(utf8_blob[i]); i += 1; }; }; case (#Principal, #Principal(p)) { - ref_primitive_type_buffer.add(T.TypeCode.Principal); + ref_candid_type_buffer.add(T.TypeCode.Principal); value_buffer.add(0x01); // indicate transparency state - let bytes = Blob.toArray(Principal.toBlob(p)); - unsigned_leb128(value_buffer, bytes.size()); + let blob = Principal.toBlob(p); + unsigned_leb128(value_buffer, blob.size()); var i = 0; - while (i < bytes.size()) { - value_buffer.add(bytes[i]); + while (i < blob.size()) { + value_buffer.add(blob[i]); i += 1; }; }; @@ -690,7 +986,7 @@ module { candid_type : CandidType, candid_value : Candid, compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, + candid_type_buffer : Buffer, value_buffer : Buffer, renaming_map : Map, unique_compound_type_map : Map, @@ -733,7 +1029,7 @@ module { if (not type_exists) encode_type_only( opt_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, renaming_map, unique_compound_type_map, counter, @@ -747,7 +1043,7 @@ module { opt_type, opt_value, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -786,7 +1082,7 @@ module { if (not type_exists) encode_type_only( opt_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, renaming_map, unique_compound_type_map, counter, @@ -824,7 +1120,7 @@ module { encode_type_only( arr_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, renaming_map, unique_compound_type_map, counter, @@ -837,7 +1133,7 @@ module { arr_type, val, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -870,7 +1166,7 @@ module { #Array(#Nat8), #Array(bytes), compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -888,7 +1184,7 @@ module { #Array(#Nat8), #Array(bytes), compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -910,7 +1206,7 @@ module { #Array(#Nat8), #Array(bytes), compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -922,40 +1218,58 @@ module { }; case (#Record(record_types) or #Map(record_types), #Record(record_entries) or #Map(record_entries)) { - assert record_entries.size() == record_types.size(); let is_tuple = check_is_tuple(record_types); - let sorted_record_entries = Array.tabulate<(Text, Candid)>( - record_types.size(), - func(i : Nat) : (Text, Candid) { - let field_type_key = get_renamed_key(renaming_map, record_types[i].0); - let res = Array.find<(Text, Candid)>( - record_entries, - func((field_value_key, _) : (Text, Candid)) : Bool { - get_renamed_key(renaming_map, field_value_key) == field_type_key; - }, - ); + let cache_size = if (record_entries.size() % 2 == 0) { + record_entries.size() + 2; + } else { + record_entries.size() + 3; + }; - switch (res) { - case (?(_, field_value)) (field_type_key, field_value); - case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); - }; - }, - ); + let record_entry_cache : Map.Map = Utils.create_map(cache_size); + + for ((k, v) in record_entries.vals()) { + let field_value_key = get_renamed_key(renaming_map, k); + ignore Map.put(record_entry_cache, thash, field_value_key, v); + }; var i = 0; while (i < record_types.size()) { - let value_type = record_types[i].1; - let field_value = sorted_record_entries[i].1; + let field_type = record_types[i].1; + + let field_type_key = get_renamed_key(renaming_map, record_types[i].0); + + let field_value = switch (Map.get(record_entry_cache, Map.thash, field_type_key)) { + case (?field_value) { + // If field type is optional but value isn't, wrap it + switch (field_type, field_value) { + case (#Option(inner_type), val) { + // Check if value is already wrapped in Option + switch (val) { + case (#Option(_) or #Null) val; // Already wrapped + case (_) #Option(val); // Wrap it + }; + }; + case (_) field_value; + }; + }; + case (null) { + // Field is missing - if the type is optional, use #Null + switch (field_type) { + case (#Option(_)) #Null; + case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); + }; + }; + }; - let value_type_is_compound = is_compound_type(value_type); + let value_type_is_compound = is_compound_type(field_type); ignore encode_candid( - value_type, + field_type, field_value, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -970,14 +1284,14 @@ module { if (not type_exists) { compound_type_buffer.add(T.TypeCode.Record); - unsigned_leb128(compound_type_buffer, record_entries.size()); + unsigned_leb128(compound_type_buffer, record_types.size()); i := 0; while (i < record_types.size()) { - let value_type = record_types[i].1; + let field_type = record_types[i].1; - let value_type_is_compound = is_compound_type(value_type); + let value_type_is_compound = is_compound_type(field_type); if (is_tuple) { unsigned_leb128(compound_type_buffer, i); @@ -988,18 +1302,18 @@ module { }; if (value_type_is_compound) { - let value_type_info = get_type_info(value_type); + let value_type_info = get_type_info(field_type); let pos = switch (Map.get(unique_compound_type_map, thash, value_type_info)) { case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, value_type)); + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, field_type)); }; unsigned_leb128(compound_type_buffer, pos); } else { encode_primitive_type_only( - value_type, + field_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, true, ); }; @@ -1014,7 +1328,7 @@ module { #Record(tuple_type_to_record(tuple_types)), #Record(tuple_value_to_record(tuple_values)), compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -1038,7 +1352,7 @@ module { #Record(tuple_type_to_record(tuple_types)), #Record(tuple_values), compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -1063,7 +1377,7 @@ module { #Record(record_types), #Record(tuple_value_to_record(tuple_values)), compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -1103,7 +1417,7 @@ module { variant_type, variant_value, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -1116,7 +1430,7 @@ module { encode_compound_type_only( variant_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, renaming_map, unique_compound_type_map, counter, @@ -1151,7 +1465,7 @@ module { encode_primitive_type_only( variant_type, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, true, ); }; @@ -1178,7 +1492,7 @@ module { case (?pos) pos; case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); }; - unsigned_leb128(primitive_type_buffer, pos); + unsigned_leb128(candid_type_buffer, pos); }; }; @@ -1186,7 +1500,7 @@ module { candid_type : CandidType, candid_value : Candid, compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, + candid_type_buffer : Buffer, value_buffer : Buffer, renaming_map : Map, unique_compound_type_map : Map, @@ -1203,7 +1517,7 @@ module { candid_type, candid_value, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -1217,7 +1531,7 @@ module { candid_type, candid_value, compound_type_buffer, - primitive_type_buffer, + candid_type_buffer, value_buffer, renaming_map, unique_compound_type_map, @@ -1462,21 +1776,100 @@ module { case (#Array(_)) { let vec_nodes = Iter.toArray(tmp_bottom_iter); - let max = { - var height = 0; - var type_ : CandidType = #Empty; - }; + // Special handling for arrays of records - merge fields across all records + let all_records = Buffer.Buffer<[(Text, CandidType)]>(vec_nodes.size()); + var has_records = false; for (node in vec_nodes.vals()) { - if (max.height < node.height) { - max.height := node.height; - max.type_ := node.type_; + switch (node.type_) { + case (#Record(fields)) { + has_records := true; + all_records.add(fields); + }; + case (_) {}; + }; + }; + + // If we have multiple records, merge their fields + let merged_type = if (has_records and all_records.size() > 1) { + let field_map = Map.new(); // key -> (type, height, count) + var max_height = 0; + + for (record_fields in all_records.vals()) { + for ((field_key, field_type) in record_fields.vals()) { + let field_depth = get_type_depth(field_type); + max_height := Nat.max(max_height, field_depth); + + switch (Map.get(field_map, thash, field_key)) { + case (?existing) { + let (existing_type, existing_height, count) = existing; + + // Choose better type + if (is_better_type(field_type, field_depth, existing_type, existing_height)) { + ignore Map.put(field_map, thash, field_key, (field_type, field_depth, count + 1)); + } else { + ignore Map.put(field_map, thash, field_key, (existing_type, existing_height, count + 1)); + }; + }; + case (null) { + ignore Map.put(field_map, thash, field_key, (field_type, field_depth, 1)); + }; + }; + }; }; + + // Build merged record type, wrapping optional fields + let merged_fields = Buffer.Buffer<(Text, CandidType)>(Map.size(field_map)); + let total_records = all_records.size(); + + for ((field_key, (field_type, field_height, count)) in Map.entries(field_map)) { + // If field doesn't appear in all records, make it optional + let final_type = if (count < total_records) { + switch (field_type) { + case (#Option(_)) field_type; // Already optional + case (_) #Option(field_type); // Make it optional + }; + } else { + field_type; + }; + merged_fields.add((field_key, final_type)); + }; + + #Record(Buffer.toArray(merged_fields)); + } else { + // Not records or only one record, use normal max selection + let max = { + var height = 0; + var type_ : CandidType = #Empty; + }; + + for (node in vec_nodes.vals()) { + if (max.type_ == #Empty or is_better_type(node.type_, node.height, max.type_, max.height)) { + max.height := node.height; + max.type_ := node.type_; + }; + }; + + max.type_; + }; + + let final_height = if (has_records and all_records.size() > 1) { + calc_height(parent_node.height, get_type_depth(merged_type)); + } else { + let max = { + var height = 0; + }; + for (node in vec_nodes.vals()) { + if (node.height > max.height) { + max.height := node.height; + }; + }; + calc_height(parent_node.height, max.height); }; let best_node : CandidTypeNode = { - type_ = #Array(max.type_); - height = calc_height(parent_node.height, max.height); + type_ = #Array(merged_type); + height = final_height; parent_index; key = parent_key; }; @@ -1555,7 +1948,34 @@ module { }; if (variants.size() > 0) { - let full_variant_type : CandidType = #Variant(Buffer.toArray(variants)); + // Merge variant types with the same key, choosing the better (more specific) type + let merged_variants = Buffer.Buffer<(Text, CandidType)>(variants.size()); + let variant_map = Map.new(); // key -> (type, height) + + for ((key, variant_type) in variants.vals()) { + switch (Map.get(variant_map, thash, key)) { + case (?existing) { + let (existing_type, existing_height) = existing; + let variant_depth = get_type_depth(variant_type); + + // Choose the better type + if (is_better_type(variant_type, variant_depth, existing_type, existing_height)) { + ignore Map.put(variant_map, thash, key, (variant_type, variant_depth)); + }; + }; + case (null) { + let variant_depth = get_type_depth(variant_type); + ignore Map.put(variant_map, thash, key, (variant_type, variant_depth)); + }; + }; + }; + + // Convert map back to array + for ((key, (variant_type, _)) in Map.entries(variant_map)) { + merged_variants.add((key, variant_type)); + }; + + let full_variant_type : CandidType = #Variant(Buffer.toArray(merged_variants)); for (index in variant_indexes.vals()) { let prev_node = buffer.get(index); @@ -1585,10 +2005,113 @@ module { }; }; - // for most of the typs we can easily retrieve it from the value, but for an array it becomes a bit tricky - // because of optional values we can have seemingly different types in the array - // for example type [?Nat] with values [null, ?1], for each values will have a inferred type of [#Option(#Null), #Option(#Nat)] + // Calculate the width (number of fields) of a type + func get_type_width(type_ : CandidType) : Nat { + switch (type_) { + case (#Record(fields) or #Map(fields)) fields.size(); + case (#Variant(variants)) variants.size(); + case (#Tuple(tuple_types)) tuple_types.size(); + case (#Array(inner)) 1 + get_type_width(inner); + case (#Option(inner)) 1 + get_type_width(inner); + case (_) 1; + }; + }; + + // Calculate the total depth of a type + func get_type_depth(type_ : CandidType) : Nat { + switch (type_) { + case (#Record(fields) or #Map(fields)) { + var max_depth = 0; + for ((_, field_type) in fields.vals()) { + let depth = get_type_depth(field_type); + if (depth > max_depth) { + max_depth := depth; + }; + }; + 1 + max_depth; + }; + case (#Variant(variants)) { + var max_depth = 0; + for ((_, variant_type) in variants.vals()) { + let depth = get_type_depth(variant_type); + if (depth > max_depth) { + max_depth := depth; + }; + }; + 1 + max_depth; + }; + case (#Tuple(tuple_types)) { + var max_depth = 0; + for (tuple_type in tuple_types.vals()) { + let depth = get_type_depth(tuple_type); + if (depth > max_depth) { + max_depth := depth; + }; + }; + 1 + max_depth; + }; + case (#Array(inner)) 1 + get_type_depth(inner); + case (#Option(inner)) 1 + get_type_depth(inner); + case (#Empty or #Null) 0; + case (_) 1; + }; + }; + + // Compare two types to determine which is "better" (more specific) + // Returns true if type1 is better than type2 + func is_better_type(type1 : CandidType, height1 : Nat, type2 : CandidType, height2 : Nat) : Bool { + // Special case: prefer non-null/non-empty types over null/empty + switch (type1, type2) { + case (#Null or #Empty, #Null or #Empty) return false; // Both are null/empty + case (#Null or #Empty, _) return false; // type2 is better (non-null) + case (_, #Null or #Empty) return true; // type1 is better (non-null) + case (_) {}; // Continue with other comparisons + }; + + // Compare Option types specially + switch (type1, type2) { + case (#Option(inner1), #Option(inner2)) { + // Both are options, compare their inner types + let inner1_depth = get_type_depth(inner1); + let inner2_depth = get_type_depth(inner2); + return is_better_type(inner1, inner1_depth, inner2, inner2_depth); + }; + case (#Option(_), _) { + // type1 is optional, type2 is not + // Generally prefer the non-optional unless type2 is null/empty + return false; + }; + case (_, #Option(_)) { + // type2 is optional, type1 is not + return true; + }; + case (_) {}; // Neither is Option, continue + }; + + // Compare by depth (height) + if (height1 != height2) { + return height1 > height2; + }; + + // If depths are equal, compare by width + let width1 = get_type_width(type1); + let width2 = get_type_width(type2); + + if (width1 != width2) { + return width1 > width2; + }; + + // All equal, no preference + false; + }; + + // for most of the types we can easily retrieve it from the value, but for an array it becomes a bit tricky + // because of optional values we can have different types in the array + // for example type [?Nat] with values [null, ?1], will have a inferred type of [#Option(#Null), #Option(#Nat)] // We need a way to choose #Option(#Nat) over #Option(#Null) in this case + // + // todo: Currently works well when there is a difference in the depth of the types (e.g. #Variant("key1", #Nat) vs #Variant("key2", #Record(...))) + // todo: However, it fails when there is a difference only in the width of the types (e.g. #Variant("key1", #Record(a: #Nat)) vs #Variant("key2", #Record(a: #Nat, b: #Option(#Nat)))) func order_candid_types_by_height_bfs(rows : Buffer<[InternalCandidTypeNode]>) { diff --git a/src/Candid/Blob/RepIndyHash.mo b/src/Candid/Blob/RepIndyHash.mo index 3aec9c4..552e189 100644 --- a/src/Candid/Blob/RepIndyHash.mo +++ b/src/Candid/Blob/RepIndyHash.mo @@ -1,69 +1,56 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Buffer "mo:base@0.14.14/Buffer"; -import Debug "mo:base@0.14.14/Debug"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Int8 "mo:base@0.14.14/Int8"; -import Int32 "mo:base@0.14.14/Int32"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat16 "mo:base@0.14.14/Nat16"; -import Int64 "mo:base@0.14.14/Int64"; -import Nat "mo:base@0.14.14/Nat"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; -import Int16 "mo:base@0.14.14/Int16"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Buffer "mo:base@0.16.0/Buffer"; +import Debug "mo:base@0.16.0/Debug"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Int8 "mo:base@0.16.0/Int8"; +import Int32 "mo:base@0.16.0/Int32"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat16 "mo:base@0.16.0/Nat16"; +import Int64 "mo:base@0.16.0/Int64"; +import Nat "mo:base@0.16.0/Nat"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; +import Int16 "mo:base@0.16.0/Int16"; import T "../Types"; import Utils "../../Utils"; import Sha256 "mo:sha2@0.1.6/Sha256"; +import ByteUtils "mo:byte-utils@0.1.2"; + module { type Buffer = Buffer.Buffer; let { ReusableBuffer; unsigned_leb128; signed_leb128_64 } = Utils; public func hash(candid_value : T.Candid) : Blob { - let buffer = ReusableBuffer(100); + // let buffer = ReusableBuffer(100); + let buffer = Buffer.Buffer(100); let sha256 = Sha256.Digest(#sha256); candid_hash(buffer, sha256, candid_value); }; func candid_hash( - buffer : Utils.ReusableBuffer, + buffer : Buffer.Buffer, sha256 : Sha256.Digest, candid_value : T.Candid, ) : Blob { switch (candid_value) { case (#Int(n)) signed_leb128_64(buffer, n); case (#Int8(i8)) { - buffer.add(Int8.toNat8(i8)); + ByteUtils.Buffer.LE.addInt8(buffer, i8); }; case (#Int16(i16)) { - let n16 = Int16.toNat16(i16); - buffer.add((n16 & 0xFF) |> Nat16.toNat8(_)); - buffer.add((n16 >> 8) |> Nat16.toNat8(_)); + ByteUtils.Buffer.LE.addInt16(buffer, i16); }; case (#Int32(i32)) { - let n = Int32.toNat32(i32); - - buffer.add((n & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - buffer.add(((n >> 8) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - buffer.add(((n >> 16) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - buffer.add((n >> 24) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); + ByteUtils.Buffer.LE.addInt32(buffer, i32); }; case (#Int64(i64)) { - let n = Int64.toNat64(i64); - - buffer.add((n & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 8) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 16) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 24) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 32) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 40) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 48) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add((n >> 56) |> Nat64.toNat(_) |> Nat8.fromNat(_)); + ByteUtils.Buffer.LE.addInt64(buffer, i64); }; case (#Nat(n)) unsigned_leb128(buffer, n); @@ -72,30 +59,17 @@ module { buffer.add(n); }; case (#Nat16(n)) { - buffer.add((n & 0xFF) |> Nat16.toNat8(_)); - buffer.add((n >> 8) |> Nat16.toNat8(_)); + ByteUtils.Buffer.LE.addNat16(buffer, n); }; case (#Nat32(n)) { - buffer.add((n & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - buffer.add(((n >> 8) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - buffer.add(((n >> 16) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - buffer.add((n >> 24) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); + ByteUtils.Buffer.LE.addNat32(buffer, n); }; case (#Nat64(n)) { - buffer.add((n & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 8) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 16) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 24) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 32) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 40) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 48) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add((n >> 56) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - + ByteUtils.Buffer.LE.addNat64(buffer, n); }; case (#Float(f64)) { - // let floatX : FloatX.FloatX = FloatX.fromFloat(f64, #f64); - // FloatX.encode(buffer, floatX, #lsb); + ByteUtils.Buffer.LE.addFloat(buffer, f64); }; case (#Bool(b)) { buffer.add(if (b) (1) else (0)); @@ -183,7 +157,7 @@ module { buffer.clear(); let resulting_hash = sha256.sum(); - sha256.reset(); + sha256.reset(); // !important to reset the sha256 instance for future use resulting_hash; diff --git a/src/Candid/Blob/TypedSerializer.mo b/src/Candid/Blob/TypedSerializer.mo new file mode 100644 index 0000000..5a01a2c --- /dev/null +++ b/src/Candid/Blob/TypedSerializer.mo @@ -0,0 +1,466 @@ +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Buffer "mo:base@0.16.0/Buffer"; +import Result "mo:base@0.16.0/Result"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat "mo:base@0.16.0/Nat"; +import Iter "mo:base@0.16.0/Iter"; +import Text "mo:base@0.16.0/Text"; +import Order "mo:base@0.16.0/Order"; +import TrieMap "mo:base@0.16.0/TrieMap"; +import Option "mo:base@0.16.0/Option"; +import Debug "mo:base@0.16.0/Debug"; + +import Map "mo:map@9.0.1/Map"; +import Set "mo:map@9.0.1/Set"; +import ByteUtils "mo:byte-utils@0.1.2"; + +import T "../Types"; +import CandidUtils "CandidUtils"; +import Encoder "Encoder"; +import Decoder "Decoder"; + +import Utils "../../Utils"; + +module TypedSerializer { + + type Iter = Iter.Iter; + type Result = Result.Result; + + type TrieMap = TrieMap.TrieMap; + type Candid = T.Candid; + type KeyValuePair = T.KeyValuePair; + + type Buffer = Buffer.Buffer; + type Hash = Nat32; + type Map = Map.Map; + type Set = Set.Set; + type Order = Order.Order; + + type CandidType = T.CandidType; + type ShallowCandidTypes = T.ShallowCandidTypes; + + let { n32hash; thash } = Map; + let { nhash } = Set; + + // Constants + let C = { + COUNTER = { + COMPOUND_TYPE = 0; + PRIMITIVE_TYPE = 1; + VALUE = 2; + }; + BYTES_INDEX = 0; + }; + + public type TypedSerializer = { + // Encoder types: keep original field names and use renaming_map during encoding + // to convert from old names to new names in the output + encoder_candid_types : [CandidType]; + + // Decoder types: store new field names so decoding can convert + // from new names back to old names using record_key_map + decoder_candid_types : [CandidType]; + + encoded_type_header : [Nat8]; + renaming_map : Map; + record_key_map : Map; + options : T.Options; + compound_types : [ShallowCandidTypes]; + recursive_types_map : Map; + }; + + func read(bytes : Blob, state : [var Nat]) : Nat8 { + let byte = bytes.get(state[C.BYTES_INDEX]); + state[C.BYTES_INDEX] += 1; + byte; + }; + + // Helper function + func formatVariantKey(key : Text) : Text { + let opt = Text.stripStart(key, #text("#")); + switch (opt) { + case (?stripped_text) stripped_text; + case (null) key; + }; + }; + + // https://en.wikipedia.org/wiki/LEB128 + func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { + var n64 : Nat64 = 0; + var shift : Nat64 = 0; + + label decoding_leb loop { + let byte = read(bytes, state); + + n64 |= (Nat64.fromNat(Nat8.toNat(byte & 0x7f)) << shift); + + if (byte & 0x80 == 0) break decoding_leb; + shift += 7; + }; + + Nat64.toNat(n64); + }; + + // Local encoder function to replace Encoder.Encoder.new + func create_encoded_type_header( + _candid_types : [CandidType], + _options : T.Options, + renaming_map : Map, + ) : [Nat8] { + let options = _options; + + let candid_types = switch (options.types) { + case (?types) types; + case (_) Array.map( + _candid_types, + func(candid_type : CandidType) : CandidType { + CandidUtils.format_candid_type(candid_type, renaming_map); + }, + ); + }; + + let compound_type_buffer = Buffer.Buffer(200); + let candid_type_buffer = Buffer.Buffer(200); + + let counter = [var 0]; + + // Only encode the types, not the values + var i = 0; + let unique_compound_type_map = Map.new(); + + while (i < candid_types.size()) { + Encoder.encode_type_only( + candid_types[i], + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + false, + ); + i += 1; + }; + + let candid_magic_bytes_buffer = Buffer.fromArray([0x44, 0x49, 0x44, 0x4C]); // 'DIDL' magic bytes + + // add compound type to the buffer + let compound_type_size_bytes = Buffer.Buffer(8); + ByteUtils.Buffer.addLEB128_64(compound_type_size_bytes, Nat64.fromNat(counter[C.COUNTER.COMPOUND_TYPE])); + + // add primitive type to the buffer + let candid_type_size_bytes = Buffer.Buffer(8); + ByteUtils.Buffer.addLEB128_64(candid_type_size_bytes, Nat64.fromNat(candid_types.size())); + + let total_size = candid_magic_bytes_buffer.size() + compound_type_size_bytes.size() + compound_type_buffer.size() + candid_type_size_bytes.size() + candid_type_buffer.size(); + + let sequence = [ + candid_magic_bytes_buffer, + compound_type_size_bytes, + compound_type_buffer, + candid_type_size_bytes, + candid_type_buffer, + ]; + + i := 0; + var j = 0; + + Array.tabulate( + total_size, + func(_ : Nat) : Nat8 { + var buffer = sequence[i]; + while (j >= buffer.size()) { + j := 0; + i += 1; + buffer := sequence[i]; + }; + + let byte = buffer.get(j); + j += 1; + byte; + }, + ); + }; + + func extract_record_or_variant_keys(schema : T.CandidType) : [Text] { + let buffer = Buffer.Buffer(8); + + func extract(schema : T.CandidType) { + switch (schema) { + case (#Record(fields) or #Map(fields)) { + for ((name, value) in fields.vals()) { + buffer.add(name); + extract(value); + }; + }; + case (#Variant(variants)) { + for ((name, value) in variants.vals()) { + buffer.add(name); + extract(value); + }; + }; + case (#Tuple(types)) { + for (tuple_type in types.vals()) { + extract(tuple_type); + }; + }; + case (#Option(inner)) { extract(inner) }; + case (#Array(inner)) { extract(inner) }; + case (_) {}; + }; + }; + + extract(schema); + + Buffer.toArray(buffer); + }; + + func is_map_equal(map1 : Map, map2 : Map, hasher : Map.HashUtils, is_value_equal : (V, V) -> Bool) : Bool { + if (Map.size(map1) != Map.size(map2)) return false; + + for ((k, v) in Map.entries(map1)) { + switch (Map.get(map2, hasher, k)) { + case (?v2) if (is_value_equal(v, v2)) {}; + case (_) return false; + }; + }; + + true; + }; + + public func equal(self : TypedSerializer, other : TypedSerializer) : Bool { + if (self.encoder_candid_types != other.encoder_candid_types) return false; + if (self.decoder_candid_types != other.decoder_candid_types) return false; + if (self.encoded_type_header != other.encoded_type_header) return false; + if (not is_map_equal(self.renaming_map, other.renaming_map, thash, Text.equal)) return false; + if (not is_map_equal(self.record_key_map, other.record_key_map, n32hash, Text.equal)) return false; + if (self.options != other.options) return false; + if (self.compound_types != other.compound_types) return false; + + // Define a function to compare CandidType values + func candid_type_equal(t1 : CandidType, t2 : CandidType) : Bool { + t1 == t2; + }; + if (not is_map_equal(self.recursive_types_map, other.recursive_types_map, Map.nhash, candid_type_equal)) return false; + + true; + }; + + // add a print fn + public func toText(self : TypedSerializer) : Text { + "TypedSerializer {\n" + # " encoder_candid_types: " # debug_show (self.encoder_candid_types) # "\n" + # " decoder_candid_types: " # debug_show (self.decoder_candid_types) # "\n" + # " encoded_type_header: " # debug_show (self.encoded_type_header) # "\n" + # " renaming_map: " # debug_show (Map.toArray(self.renaming_map)) # "\n" + # " record_key_map: " # debug_show (Map.toArray(self.record_key_map)) # "\n" + # " options: " # debug_show (self.options) # "\n" + # " compound_types: " # debug_show (self.compound_types) # "\n" + # " recursive_types_map: " # debug_show (Map.toArray(self.recursive_types_map)) # "\n" + # "}"; + }; + + /// Creates a new TypedSerializer from Candid types + public func new(_candid_types : [CandidType], _options : ?T.Options) : TypedSerializer { + let options = Option.get(_options, T.defaultOptions); + + let renaming_map = Map.new(); + for ((k, v) in options.renameKeys.vals()) { + ignore Map.put(renaming_map, thash, k, v); + }; + + // Encoder types: original types that will use renaming_map during encoding + let encoder_candid_types = switch (options.types) { + case (?types) types; + case (_) Array.map( + _candid_types, + func(candid_type : CandidType) : CandidType { + CandidUtils.format_candid_type(candid_type, renaming_map); + }, + ); + }; + + // Decoder types are the same as encoder types - renaming happens during value decoding + let decoder_candid_types = switch (options.types) { + case (?types) types; + case (_) Array.map( + _candid_types, + func(candid_type : CandidType) : CandidType { + CandidUtils.sort_candid_type(candid_type); + }, + ); + }; + + // Decoder types: types with renamed fields for decoding + + // Collect record keys from all encoder_candid_types entries + let record_keys_buffer = Buffer.Buffer(8); + for (candid_type in encoder_candid_types.vals()) { + let keys = extract_record_or_variant_keys(candid_type); + for (key in keys.vals()) { + record_keys_buffer.add(key); + }; + }; + let record_keys = Buffer.toArray(record_keys_buffer); + + let record_key_map = Map.new(); + + var i = 0; + + while (i < record_keys.size()) { + let key = formatVariantKey(record_keys[i]); + let hash = Utils.hash_record_key(key); + ignore Map.put(record_key_map, n32hash, hash, key); + i += 1; + }; + + ignore do ? { + let key_pairs_to_rename = options.renameKeys; + + var j = 0; + while (j < key_pairs_to_rename.size()) { + let original_key = formatVariantKey(key_pairs_to_rename[j].0); + let new_key = formatVariantKey(key_pairs_to_rename[j].1); + + let hash = Utils.hash_record_key(original_key); + ignore Map.put(record_key_map, n32hash, hash, new_key); + + j += 1; + }; + }; + + // Use encoder types for the encoded type header + let encoded_type_header = create_encoded_type_header(encoder_candid_types, options, renaming_map); + + { + encoded_type_header; + encoder_candid_types; + decoder_candid_types; + renaming_map; + record_key_map; + options; + compound_types = []; // Will be populated if needed + recursive_types_map = Map.new(); // Initialize empty map + } : TypedSerializer; + }; + + /// Creates a new TypedSerializer from a blob (extracts types from the blob) + public func fromBlob(blob : Blob, record_keys : [Text], _options : ?T.Options) : TypedSerializer { + let options = Option.get(_options, T.defaultOptions); + + let record_key_map = Map.new(); + + var i = 0; + while (i < record_keys.size()) { + let key = formatVariantKey(record_keys[i]); + let hash = Utils.hash_record_key(key); + ignore Map.put(record_key_map, n32hash, hash, key); + i += 1; + }; + + let renaming_map = Map.new(); + + ignore do ? { + let key_pairs_to_rename = options.renameKeys; + + var j = 0; + while (j < key_pairs_to_rename.size()) { + let original_key = formatVariantKey(key_pairs_to_rename[j].0); + let new_key = formatVariantKey(key_pairs_to_rename[j].1); + + let hash = Utils.hash_record_key(original_key); + + ignore Map.put(renaming_map, thash, original_key, new_key); + + j += 1; + }; + }; + + let bytes = blob; + let state : [var Nat] = [var 0]; + + let magic = Array.tabulate(4, func(_ : Nat) : Nat8 = read(bytes, state)); + assert (magic == [0x44, 0x49, 0x44, 0x4c]); + + let total_compound_types = decode_leb128(bytes, state); + let compound_types = Decoder.extract_compound_types(bytes, state, total_compound_types, record_key_map); + let recursive_types_map = Map.new(); + let extracted_candid_types = Decoder.build_types(bytes, state, compound_types, recursive_types_map); + + let type_header_size = state[C.BYTES_INDEX]; + let encoded_type_header = Array.tabulate(type_header_size, func(i : Nat) : Nat8 = blob.get(i)); + + // Generate encoded_type_header using the same logic as new() to ensure consistency + let _encoded_type_header2 = create_encoded_type_header(extracted_candid_types, options, renaming_map); + // assert (encoded_type_header == _encoded_type_header2); + + { + encoder_candid_types = extracted_candid_types; + decoder_candid_types = extracted_candid_types; + record_key_map; + options; + compound_types = []; // Keep consistent with new() function + encoded_type_header; + renaming_map; + recursive_types_map; + }; + }; + + /// Encodes values using the precomputed types in this TypedSerializer + public func encode(self : TypedSerializer, candid_values : [Candid]) : Result { + if (candid_values.size() != self.encoder_candid_types.size()) { + return #err("encode: candid_values size does not match encoder_candid_types size"); + }; + + let value_buffer = Buffer.Buffer(400); + let recursive_map = Map.new(); + let counter = [var 0]; + let unique_compound_type_map = Map.new(); + + var i = 0; + while (i < candid_values.size()) { + ignore Encoder.encode_value_only( + self.encoder_candid_types[i], + candid_values[i], + value_buffer, + self.renaming_map, + unique_compound_type_map, + recursive_map, + counter, + false, + ); + i += 1; + }; + + #ok( + Blob.fromArray( + Array.append( + self.encoded_type_header, + Buffer.toArray(value_buffer), + ) + ) + ); + }; + + /// Decodes values from a full candid blob using precomputed types + public func decode(self : TypedSerializer, candid_blob : Blob) : Result<[Candid], Text> { + let bytes = candid_blob; + let state : [var Nat] = [var 0]; + + // Verify magic number + let magic = Array.tabulate(4, func(_ : Nat) : Nat8 = read(bytes, state)); + if (magic != [0x44, 0x49, 0x44, 0x4c]) { + return #err("Invalid Magic Number"); + }; + + // Since we have the precomputed encoded_type_header, we can directly jump to the values section + // instead of parsing and skipping the type section + state[C.BYTES_INDEX] := self.encoded_type_header.size(); + + // Use precomputed types and recursive types map for decoding values + Decoder.decode_candid_values(bytes, self.decoder_candid_types, state, self.options, self.renaming_map, self.recursive_types_map); + }; + +}; diff --git a/src/Candid/ICRC3Value.mo b/src/Candid/ICRC3Value.mo index a34046a..9364a71 100644 --- a/src/Candid/ICRC3Value.mo +++ b/src/Candid/ICRC3Value.mo @@ -1,14 +1,14 @@ -import Array "mo:base@0.14.14/Array"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat16 "mo:base@0.14.14/Nat16"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Int8 "mo:base@0.14.14/Int8"; -import Int16 "mo:base@0.14.14/Int16"; -import Int64 "mo:base@0.14.14/Int64"; -import Int32 "mo:base@0.14.14/Int32"; -import Debug "mo:base@0.14.14/Debug"; -import Principal "mo:base@0.14.14/Principal"; +import Array "mo:base@0.16.0/Array"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat16 "mo:base@0.16.0/Nat16"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Int8 "mo:base@0.16.0/Int8"; +import Int16 "mo:base@0.16.0/Int16"; +import Int64 "mo:base@0.16.0/Int64"; +import Int32 "mo:base@0.16.0/Int32"; +import Debug "mo:base@0.16.0/Debug"; +import Principal "mo:base@0.16.0/Principal"; import T "Types"; diff --git a/src/Candid/Text/Parser/Array.mo b/src/Candid/Text/Parser/Array.mo index d90b4bf..6a8b08a 100644 --- a/src/Candid/Text/Parser/Array.mo +++ b/src/Candid/Text/Parser/Array.mo @@ -1,4 +1,4 @@ -import List "mo:base@0.14.14/List"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Blob.mo b/src/Candid/Text/Parser/Blob.mo index fe6328d..31222e3 100644 --- a/src/Candid/Text/Parser/Blob.mo +++ b/src/Candid/Text/Parser/Blob.mo @@ -1,6 +1,6 @@ -import Blob "mo:base@0.14.14/Blob"; -import Iter "mo:base@0.14.14/Iter"; -import List "mo:base@0.14.14/List"; +import Blob "mo:base@0.16.0/Blob"; +import Iter "mo:base@0.16.0/Iter"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Bool.mo b/src/Candid/Text/Parser/Bool.mo index ef9d298..938f463 100644 --- a/src/Candid/Text/Parser/Bool.mo +++ b/src/Candid/Text/Parser/Bool.mo @@ -1,4 +1,4 @@ -import List "mo:base@0.14.14/List"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Common.mo b/src/Candid/Text/Parser/Common.mo index 7cdda43..d1649de 100644 --- a/src/Candid/Text/Parser/Common.mo +++ b/src/Candid/Text/Parser/Common.mo @@ -1,8 +1,8 @@ -import Char "mo:base@0.14.14/Char"; -import Iter "mo:base@0.14.14/Iter"; -import List "mo:base@0.14.14/List"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Text "mo:base@0.14.14/Text"; +import Char "mo:base@0.16.0/Char"; +import Iter "mo:base@0.16.0/Iter"; +import List "mo:base@0.16.0/List"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Text "mo:base@0.16.0/Text"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Float.mo b/src/Candid/Text/Parser/Float.mo index 1f01818..948d5b7 100644 --- a/src/Candid/Text/Parser/Float.mo +++ b/src/Candid/Text/Parser/Float.mo @@ -1,5 +1,5 @@ -import Float "mo:base@0.14.14/Float"; -import List "mo:base@0.14.14/List"; +import Float "mo:base@0.16.0/Float"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Int.mo b/src/Candid/Text/Parser/Int.mo index 8e11752..54b3fba 100644 --- a/src/Candid/Text/Parser/Int.mo +++ b/src/Candid/Text/Parser/Int.mo @@ -1,5 +1,5 @@ -import Int "mo:base@0.14.14/Int"; -import List "mo:base@0.14.14/List"; +import Int "mo:base@0.16.0/Int"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/IntX.mo b/src/Candid/Text/Parser/IntX.mo index 0ac3a78..0d14469 100644 --- a/src/Candid/Text/Parser/IntX.mo +++ b/src/Candid/Text/Parser/IntX.mo @@ -1,9 +1,9 @@ -import Debug "mo:base@0.14.14/Debug"; -import List "mo:base@0.14.14/List"; -import Int8 "mo:base@0.14.14/Int8"; -import Int16 "mo:base@0.14.14/Int16"; -import Int32 "mo:base@0.14.14/Int32"; -import Int64 "mo:base@0.14.14/Int64"; +import Debug "mo:base@0.16.0/Debug"; +import List "mo:base@0.16.0/List"; +import Int8 "mo:base@0.16.0/Int8"; +import Int16 "mo:base@0.16.0/Int16"; +import Int32 "mo:base@0.16.0/Int32"; +import Int64 "mo:base@0.16.0/Int64"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Nat.mo b/src/Candid/Text/Parser/Nat.mo index 32653a2..6df94a2 100644 --- a/src/Candid/Text/Parser/Nat.mo +++ b/src/Candid/Text/Parser/Nat.mo @@ -1,6 +1,6 @@ -import Iter "mo:base@0.14.14/Iter"; -import List "mo:base@0.14.14/List"; -import Nat64 "mo:base@0.14.14/Nat64"; +import Iter "mo:base@0.16.0/Iter"; +import List "mo:base@0.16.0/List"; +import Nat64 "mo:base@0.16.0/Nat64"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/NatX.mo b/src/Candid/Text/Parser/NatX.mo index 26b1e4d..a1a0f41 100644 --- a/src/Candid/Text/Parser/NatX.mo +++ b/src/Candid/Text/Parser/NatX.mo @@ -1,9 +1,9 @@ -import Debug "mo:base@0.14.14/Debug"; -import List "mo:base@0.14.14/List"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat16 "mo:base@0.14.14/Nat16"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat64 "mo:base@0.14.14/Nat64"; +import Debug "mo:base@0.16.0/Debug"; +import List "mo:base@0.16.0/List"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat16 "mo:base@0.16.0/Nat16"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat64 "mo:base@0.16.0/Nat64"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Option.mo b/src/Candid/Text/Parser/Option.mo index 5d21de2..cf3cc94 100644 --- a/src/Candid/Text/Parser/Option.mo +++ b/src/Candid/Text/Parser/Option.mo @@ -1,4 +1,4 @@ -import List "mo:base@0.14.14/List"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Principal.mo b/src/Candid/Text/Parser/Principal.mo index 743531c..2dbf0ee 100644 --- a/src/Candid/Text/Parser/Principal.mo +++ b/src/Candid/Text/Parser/Principal.mo @@ -1,5 +1,5 @@ -import List "mo:base@0.14.14/List"; -import Principal "mo:base@0.14.14/Principal"; +import List "mo:base@0.16.0/List"; +import Principal "mo:base@0.16.0/Principal"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Record.mo b/src/Candid/Text/Parser/Record.mo index 6a0443f..bae290d 100644 --- a/src/Candid/Text/Parser/Record.mo +++ b/src/Candid/Text/Parser/Record.mo @@ -1,5 +1,5 @@ -import Iter "mo:base@0.14.14/Iter"; -import List "mo:base@0.14.14/List"; +import Iter "mo:base@0.16.0/Iter"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Text.mo b/src/Candid/Text/Parser/Text.mo index bc7314d..58e96bb 100644 --- a/src/Candid/Text/Parser/Text.mo +++ b/src/Candid/Text/Parser/Text.mo @@ -1,7 +1,7 @@ -import Char "mo:base@0.14.14/Char"; -import Iter "mo:base@0.14.14/Iter"; -import List "mo:base@0.14.14/List"; -import Text "mo:base@0.14.14/Text"; +import Char "mo:base@0.16.0/Char"; +import Iter "mo:base@0.16.0/Iter"; +import List "mo:base@0.16.0/List"; +import Text "mo:base@0.16.0/Text"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Variant.mo b/src/Candid/Text/Parser/Variant.mo index 73ba814..bc9195a 100644 --- a/src/Candid/Text/Parser/Variant.mo +++ b/src/Candid/Text/Parser/Variant.mo @@ -1,4 +1,4 @@ -import List "mo:base@0.14.14/List"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/lib.mo b/src/Candid/Text/Parser/lib.mo index 9f03c07..ae904e9 100644 --- a/src/Candid/Text/Parser/lib.mo +++ b/src/Candid/Text/Parser/lib.mo @@ -1,8 +1,8 @@ -import Char "mo:base@0.14.14/Char"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import List "mo:base@0.14.14/List"; -import TrieMap "mo:base@0.14.14/TrieMap"; +import Char "mo:base@0.16.0/Char"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import List "mo:base@0.16.0/List"; +import TrieMap "mo:base@0.16.0/TrieMap"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/ToText.mo b/src/Candid/Text/ToText.mo index 3563df2..f945c1a 100644 --- a/src/Candid/Text/ToText.mo +++ b/src/Candid/Text/ToText.mo @@ -1,8 +1,8 @@ -import Float "mo:base@0.14.14/Float"; -import Array "mo:base@0.14.14/Array"; -import Text "mo:base@0.14.14/Text"; -import Principal "mo:base@0.14.14/Principal"; -import TrieMap "mo:base@0.14.14/TrieMap"; +import Float "mo:base@0.16.0/Float"; +import Array "mo:base@0.16.0/Array"; +import Text "mo:base@0.16.0/Text"; +import Principal "mo:base@0.16.0/Principal"; +import TrieMap "mo:base@0.16.0/TrieMap"; import Itertools "mo:itertools@0.2.2/Iter"; diff --git a/src/Candid/Types.mo b/src/Candid/Types.mo index dfded28..4d5271a 100644 --- a/src/Candid/Types.mo +++ b/src/Candid/Types.mo @@ -74,33 +74,34 @@ module { public let TypeCode = { // primitive types - Null : Nat8 = 0x7f; - Bool : Nat8 = 0x7e; - Nat : Nat8 = 0x7d; - Int : Nat8 = 0x7c; - Nat8 : Nat8 = 0x7b; - Nat16 : Nat8 = 0x7a; - Nat32 : Nat8 = 0x79; - Nat64 : Nat8 = 0x78; - Int8 : Nat8 = 0x77; - Int16 : Nat8 = 0x76; - Int32 : Nat8 = 0x75; - Int64 : Nat8 = 0x74; - // Float32 : Nat8 = 0x73; - Float : Nat8 = 0x72; - Text : Nat8 = 0x71; - // Reserved : Nat8 = 0x70; - Empty : Nat8 = 0x6f; - Principal : Nat8 = 0x68; + Null : Nat8 = 0x7f; // 127 + Bool : Nat8 = 0x7e; // 126 + Nat : Nat8 = 0x7d; // 125 + Int : Nat8 = 0x7c; // 124 + Nat8 : Nat8 = 0x7b; // 123 + Nat16 : Nat8 = 0x7a; // 122 + Nat32 : Nat8 = 0x79; // 121 + Nat64 : Nat8 = 0x78; // 120 + Int8 : Nat8 = 0x77; // 119 + Int16 : Nat8 = 0x76; // 118 + Int32 : Nat8 = 0x75; // 117 + Int64 : Nat8 = 0x74; // 116 + // Float32 : Nat8 = 0x73; // 115 + Float : Nat8 = 0x72; // 114 + Text : Nat8 = 0x71; // 113 + // Reserved : Nat8 = 0x70; // 112 + Empty : Nat8 = 0x6f; // 111 // compound types - Option : Nat8 = 0x6e; - Array : Nat8 = 0x6d; - Record : Nat8 = 0x6c; - Variant : Nat8 = 0x6b; - // Func : Nat8 = 0x6a; - // Service : Nat8 = 0x69; + Option : Nat8 = 0x6e; // 110 + Array : Nat8 = 0x6d; // 109 + Record : Nat8 = 0x6c; // 108 + Variant : Nat8 = 0x6b; // 107 + // Func : Nat8 = 0x6a; // 106 + // Service : Nat8 = 0x69; // 105 + + Principal : Nat8 = 0x68; // 104 }; @@ -122,12 +123,6 @@ module { /// Must call `Candid.formatCandidTypes` before passing in the types types : ?[CandidType]; - /// #### Decoding Options - /// When decoding, you have the option to pass in the Candid variant type - /// and omit the type portion of the candid blob and only pass in the - /// serialized values - blob_contains_only_values : Bool; - }; public type ICRC3Value = { @@ -146,7 +141,6 @@ module { types = null; - blob_contains_only_values = false; }; }; diff --git a/src/Candid/lib.mo b/src/Candid/lib.mo index 2fc2ae0..8e95e55 100644 --- a/src/Candid/lib.mo +++ b/src/Candid/lib.mo @@ -1,12 +1,14 @@ /// A representation of the Candid format with variants for all possible types. -import Array "mo:base@0.14.14/Array"; -import Text "mo:base@0.14.14/Text"; +import Array "mo:base@0.16.0/Array"; +import Text "mo:base@0.16.0/Text"; +import Map "mo:map@9.0.1/Map"; -import Encoder "Blob/Encoder"; +import CandidEncoder "Blob/Encoder"; import Decoder "Blob/Decoder"; import RepIndyHash "Blob/RepIndyHash"; import CandidUtils "Blob/CandidUtils"; +import TypedSerializerModule "Blob/TypedSerializer"; import Parser "Text/Parser"; import ToText "Text/ToText"; @@ -15,8 +17,6 @@ import T "Types"; import Utils "../Utils"; import ICRC3Value "ICRC3Value"; -import Map "mo:map@9.0.1/Map"; - module { let { thash } = Map; @@ -25,10 +25,13 @@ module { public type Options = T.Options; public let defaultOptions = T.defaultOptions; + public let TypedSerializer = TypedSerializerModule; + public type TypedSerializer = TypedSerializerModule.TypedSerializer; + public type CandidType = T.CandidType; /// Converts a motoko value to a [Candid](#Candid) value - public let { encode; encodeOne } = Encoder; + public let { encode; encodeOne } = CandidEncoder; public let repIndyHash = RepIndyHash.hash; @@ -67,6 +70,15 @@ module { }; + public func sortCandidType(c : [CandidType]) : [CandidType] { + Array.map( + c, + func(c : CandidType) : CandidType { + CandidUtils.sort_candid_type(c); + }, + ); + }; + public let concatKeys = Utils.concatKeys; /// Converts an array of ICRC3Value values to [Candid](#Candid) values diff --git a/src/JSON/FromText.mo b/src/JSON/FromText.mo index d1dba35..815821c 100644 --- a/src/JSON/FromText.mo +++ b/src/JSON/FromText.mo @@ -1,7 +1,7 @@ -import Array "mo:base@0.14.14/Array"; -import Result "mo:base@0.14.14/Result"; -import Text "mo:base@0.14.14/Text"; -import Int "mo:base@0.14.14/Int"; +import Array "mo:base@0.16.0/Array"; +import Result "mo:base@0.16.0/Result"; +import Text "mo:base@0.16.0/Text"; +import Int "mo:base@0.16.0/Int"; import JSON "../../submodules/json.mo/src/JSON"; diff --git a/src/JSON/ToText.mo b/src/JSON/ToText.mo index c756df9..d70e9ff 100644 --- a/src/JSON/ToText.mo +++ b/src/JSON/ToText.mo @@ -1,6 +1,6 @@ -import Buffer "mo:base@0.14.14/Buffer"; -import Result "mo:base@0.14.14/Result"; -import Text "mo:base@0.14.14/Text"; +import Buffer "mo:base@0.16.0/Buffer"; +import Result "mo:base@0.16.0/Result"; +import Text "mo:base@0.16.0/Text"; import JSON "../../submodules/json.mo/src/JSON"; import NatX "mo:xtended-numbers/NatX"; diff --git a/src/UrlEncoded/FromText.mo b/src/UrlEncoded/FromText.mo index 3b9893f..ae759c1 100644 --- a/src/UrlEncoded/FromText.mo +++ b/src/UrlEncoded/FromText.mo @@ -1,14 +1,14 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Buffer "mo:base@0.14.14/Buffer"; -import Char "mo:base@0.14.14/Char"; -import Debug "mo:base@0.14.14/Debug"; -import Result "mo:base@0.14.14/Result"; -import TrieMap "mo:base@0.14.14/TrieMap"; -import Nat "mo:base@0.14.14/Nat"; -import Text "mo:base@0.14.14/Text"; -import Iter "mo:base@0.14.14/Iter"; -import Option "mo:base@0.14.14/Option"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Buffer "mo:base@0.16.0/Buffer"; +import Char "mo:base@0.16.0/Char"; +import Debug "mo:base@0.16.0/Debug"; +import Result "mo:base@0.16.0/Result"; +import TrieMap "mo:base@0.16.0/TrieMap"; +import Nat "mo:base@0.16.0/Nat"; +import Text "mo:base@0.16.0/Text"; +import Iter "mo:base@0.16.0/Iter"; +import Option "mo:base@0.16.0/Option"; import Itertools "mo:itertools@0.2.2/Iter"; diff --git a/src/UrlEncoded/Parser.mo b/src/UrlEncoded/Parser.mo index 2729375..9bac115 100644 --- a/src/UrlEncoded/Parser.mo +++ b/src/UrlEncoded/Parser.mo @@ -1,8 +1,8 @@ -import Char "mo:base@0.14.14/Char"; -import Iter "mo:base@0.14.14/Iter"; -import Float "mo:base@0.14.14/Float"; -import List "mo:base@0.14.14/List"; -import Nat32 "mo:base@0.14.14/Nat32"; +import Char "mo:base@0.16.0/Char"; +import Iter "mo:base@0.16.0/Iter"; +import Float "mo:base@0.16.0/Float"; +import List "mo:base@0.16.0/List"; +import Nat32 "mo:base@0.16.0/Nat32"; import C "../../submodules/parser-combinators.mo/src/Combinators"; import P "../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/UrlEncoded/ToText.mo b/src/UrlEncoded/ToText.mo index 1818b6d..73a18b8 100644 --- a/src/UrlEncoded/ToText.mo +++ b/src/UrlEncoded/ToText.mo @@ -1,12 +1,12 @@ -import Result "mo:base@0.14.14/Result"; -import Nat "mo:base@0.14.14/Nat"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Text "mo:base@0.14.14/Text"; -import TrieMap "mo:base@0.14.14/TrieMap"; -import Iter "mo:base@0.14.14/Iter"; -import Float "mo:base@0.14.14/Float"; -import Principal "mo:base@0.14.14/Principal"; -import Debug "mo:base@0.14.14/Debug"; +import Result "mo:base@0.16.0/Result"; +import Nat "mo:base@0.16.0/Nat"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Text "mo:base@0.16.0/Text"; +import TrieMap "mo:base@0.16.0/TrieMap"; +import Iter "mo:base@0.16.0/Iter"; +import Float "mo:base@0.16.0/Float"; +import Principal "mo:base@0.16.0/Principal"; +import Debug "mo:base@0.16.0/Debug"; import itertools "mo:itertools@0.2.2/Iter"; diff --git a/src/Utils.mo b/src/Utils.mo index e225dcd..2cd57d5 100644 --- a/src/Utils.mo +++ b/src/Utils.mo @@ -1,251 +1,275 @@ -import Array "mo:base@0.14.14/Array"; -import Char "mo:base@0.14.14/Char"; -import Order "mo:base@0.14.14/Order"; -import Float "mo:base@0.14.14/Float"; -import Text "mo:base@0.14.14/Text"; -import Iter "mo:base@0.14.14/Iter"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Int "mo:base@0.14.14/Int"; -import Buffer "mo:base@0.14.14/Buffer"; -import Result "mo:base@0.14.14/Result"; -import Int64 "mo:base@0.14.14/Int64"; -import Blob "mo:base@0.14.14/Blob"; - -import Prelude "mo:base@0.14.14/Prelude"; -import Debug "mo:base@0.14.14/Debug"; +import Array "mo:base@0.16.0/Array"; +import Char "mo:base@0.16.0/Char"; +import Order "mo:base@0.16.0/Order"; +import Float "mo:base@0.16.0/Float"; +import Text "mo:base@0.16.0/Text"; +import Iter "mo:base@0.16.0/Iter"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Int "mo:base@0.16.0/Int"; +import Buffer "mo:base@0.16.0/Buffer"; +import Result "mo:base@0.16.0/Result"; +import Int64 "mo:base@0.16.0/Int64"; +import Blob "mo:base@0.16.0/Blob"; + +import Prelude "mo:base@0.16.0/Prelude"; +import Debug "mo:base@0.16.0/Debug"; import Itertools "mo:itertools@0.2.2/Iter"; +import Map "mo:map@9.0.1/Map"; +import MapConst "mo:map@9.0.1/Map/const"; import ByteUtils "mo:byte-utils@0.1.2"; + module { - type Iter = Iter.Iter; - type Buffer = Buffer.Buffer; - type Result = Result.Result; - - /// Function copied from mo:candid@2.0.0/Tag: https://github.com/edjCase/motoko_candid/blob/d038b7bd953fb8826ae66a5f34bf06dcc29b2e0f/src/Tag.mo#L14-L30 - /// - /// Computes the hash of a given record field key. - /// - public func hash_record_key(name : Text) : Nat32 { - // hash(name) = ( Sum_(i=0..k) utf8(name)[i] * 223^(k-i) ) mod 2^32 where k = |utf8(name)|-1 - let bytes : [Nat8] = Blob.toArray(Text.encodeUtf8(name)); - Array.foldLeft( - bytes, - 0, - func(accum : Nat32, byte : Nat8) : Nat32 { - (accum *% 223) +% Nat32.fromNat(Nat8.toNat(byte)); - }, - ); - }; - - public func reverse_order(fn : (A, A) -> Order.Order) : (A, A) -> Order.Order { - func(a : A, b : A) : Order.Order { - switch (fn(a, b)) { - case (#less) #greater; - case (#equal) #equal; - case (#greater) #less; - }; - }; - }; - - public func array_slice(arr : [A], start : Nat, end : Nat) : [A] { - Array.tabulate( - end - start, - func(i : Nat) = arr[start + i], - ); - }; - - public func concatKeys(keys : [[Text]]) : [Text] { - Iter.toArray( - Itertools.flattenArray(keys) - ); - }; - - public func sized_iter_to_array(iter : Iter, size : Nat) : [A] { - Array.tabulate( - size, - func(i : Nat) { - switch (iter.next()) { - case (?x) x; - case (_) Prelude.unreachable(); + type Iter = Iter.Iter; + type Buffer = Buffer.Buffer; + type Result = Result.Result; + + public func create_map(map_size : Nat) : Map.Map = [ + var ?( + Array.init(map_size, null), + Array.init(map_size, null), + Array.init(map_size * 2, MapConst.NULL), + Array.init(3, 0), + ) + ]; + + /// Function copied from mo:candid/Tag: https://github.com/edjCase/motoko_candid/blob/d038b7bd953fb8826ae66a5f34bf06dcc29b2e0f/src/Tag.mo#L14-L30 + /// + /// Computes the hash of a given record field key. + /// + public func hash_record_key(name : Text) : Nat32 { + // hash(name) = ( Sum_(i=0..k) utf8(name)[i] * 223^(k-i) ) mod 2^32 where k = |utf8(name)|-1 + let bytes : [Nat8] = Blob.toArray(Text.encodeUtf8(name)); + Array.foldLeft( + bytes, + 0, + func(accum : Nat32, byte : Nat8) : Nat32 { + (accum *% 223) +% Nat32.fromNat(Nat8.toNat(byte)); + }, + ); + }; + + public func reverse_order(fn : (A, A) -> Order.Order) : (A, A) -> Order.Order { + func(a : A, b : A) : Order.Order { + switch (fn(a, b)) { + case (#less) #greater; + case (#equal) #equal; + case (#greater) #less; + }; }; - }, - ); - }; - - public func send_error(res : Result) : Result { - switch (res) { - case (#ok(_)) Prelude.unreachable(); - case (#err(errorMsg)) #err(errorMsg); - }; - }; - - public func subText(text : Text, start : Nat, end : Nat) : Text { - Itertools.toText( - Itertools.skip( - Itertools.take(text.chars(), end), - start, - ) - ); - }; - - public func cmpRecords(a : (Text, Any), b : (Text, Any)) : Order.Order { - Text.compare(a.0, b.0); - }; - - public func stripStart(text : Text, prefix : Text.Pattern) : Text { - switch (Text.stripStart(text, prefix)) { - case (?t) t; - case (_) text; - }; - }; - - public func log2(n : Float) : Float { - Float.log(n) / Float.log(2); - }; - - public func isHash(key : Text) : Bool { - Itertools.all( - key.chars(), - func(c : Char) : Bool { - c == '_' or Char.isDigit(c); - }, - ); - }; - - public func text_to_nat32(text : Text) : Nat32 { - Itertools.fold( - text.chars(), - 0 : Nat32, - func(acc : Nat32, c : Char) : Nat32 { - if (c == '_') { - acc; - } else { - acc * 10 + Char.toNat32(c) - Char.toNat32('0'); + }; + + public type ArrayLike = { + size : () -> Nat; + get : (Nat) -> A; + }; + + public func array_slice(arr : ArrayLike, start : Nat, end : Nat) : [A] { + Array.tabulate( + end - start, + func(i : Nat) = arr.get(start + i), + ); + }; + + public func blob_slice(blob : Blob, start : Nat, end : Nat) : [Nat8] { + Array.tabulate( + end - start, + func(i : Nat) = blob.get(start + i), + ); + }; + + public func concatKeys(keys : [[Text]]) : [Text] { + Iter.toArray( + Itertools.flattenArray(keys) + ); + }; + + public func sized_iter_to_array(iter : Iter, size : Nat) : [A] { + Array.tabulate( + size, + func(i : Nat) { + switch (iter.next()) { + case (?x) x; + case (_) Prelude.unreachable(); + }; + }, + ); + }; + + public func send_error(res : Result) : Result { + switch (res) { + case (#ok(_)) Prelude.unreachable(); + case (#err(errorMsg)) #err(errorMsg); }; - }, - ); - }; - - public func text_to_nat(text : Text) : Nat { - Itertools.fold( - text.chars(), - 0 : Nat, - func(acc : Nat, c : Char) : Nat { - if (c == '_') { - acc; - } else { - acc * 10 + Nat32.toNat(Char.toNat32(c) - Char.toNat32('0')); + }; + + public func subText(text : Text, start : Nat, end : Nat) : Text { + Itertools.toText( + Itertools.skip( + Itertools.take(text.chars(), end), + start, + ) + ); + }; + + public func cmpRecords(a : (Text, Any), b : (Text, Any)) : Order.Order { + Text.compare(a.0, b.0); + }; + + public func stripStart(text : Text, prefix : Text.Pattern) : Text { + switch (Text.stripStart(text, prefix)) { + case (?t) t; + case (_) text; }; - }, - ); - }; - - public func text_is_number(text : Text) : Bool { - Itertools.all( - text.chars(), - func(c : Char) : Bool { - Char.isDigit(c) or c == '_'; - }, - ); - }; - - type AddToBuffer = { - add : (A) -> (); - }; - - // https://en.wikipedia.org/wiki/LEB128 - // limited to 64-bit unsigned integers - // more performant than the general unsigned_leb128 - public func unsigned_leb128_64(buffer : ByteUtils.BufferLike, n : Nat) { - var value = Nat64.fromNat(n); - while (value >= 0x80) { - buffer.add(Nat8.fromNat(Nat64.toNat(value & 0x7F)) | 0x80); - value >>= 7; - }; - buffer.add(Nat8.fromNat(Nat64.toNat(value))); - }; - - public func unsigned_leb128(buffer : ByteUtils.BufferLike, n : Nat) { - var value = Nat64.fromNat(n); - while (value >= 0x80) { - buffer.add(Nat8.fromNat(Nat64.toNat(value & 0x7F)) | 0x80); - value >>= 7; - }; - buffer.add(Nat8.fromNat(Nat64.toNat(value))); - }; - - public func signed_leb128_64(buffer : ByteUtils.BufferLike, num : Int) { - ByteUtils.Buffer.addSLEB128_64(buffer, Int64.fromInt(num)); - }; - - // public func signed_leb128(buffer : AddToBuffer, num : Int) { - // let nat64_bound = 18_446_744_073_709_551_616; - - // if (num < nat64_bound and num > -nat64_bound) return signed_leb128_64(buffer, num); - - // var n = num; - // let is_negative = n < 0; - - // }; - - public class ReusableBuffer(init_capacity : Nat) { - var elems : [var ?A] = Array.init(init_capacity, null); - var count : Nat = 0; - - public func size() : Nat = count; - - public func add(elem : A) { - if (count == elems.size()) { - elems := Array.tabulateVar( - elems.size() * 2, - func(i : Nat) : ?A { - if (i < count) { - elems[i]; - } else { - null; - }; - }, + }; + + public func log2(n : Float) : Float { + Float.log(n) / Float.log(2); + }; + + public func isHash(key : Text) : Bool { + Itertools.all( + key.chars(), + func(c : Char) : Bool { + c == '_' or Char.isDigit(c); + }, + ); + }; + + public func text_to_nat32(text : Text) : Nat32 { + Itertools.fold( + text.chars(), + 0 : Nat32, + func(acc : Nat32, c : Char) : Nat32 { + if (c == '_') { + acc; + } else { + acc * 10 + Char.toNat32(c) - Char.toNat32('0'); + }; + }, + ); + }; + + public func text_to_nat(text : Text) : Nat { + Itertools.fold( + text.chars(), + 0 : Nat, + func(acc : Nat, c : Char) : Nat { + if (c == '_') { + acc; + } else { + acc * 10 + Nat32.toNat(Char.toNat32(c) - Char.toNat32('0')); + }; + }, ); - }; + }; + + public func text_is_number(text : Text) : Bool { + Itertools.all( + text.chars(), + func(c : Char) : Bool { + Char.isDigit(c) or c == '_'; + }, + ); + }; - elems[count] := ?elem; - count += 1; + type AddToBuffer = { + add : (A) -> (); }; - public func clear() { - count := 0; + // https://en.wikipedia.org/wiki/LEB128 + // limited to 64-bit unsigned integers + // more performant than the general unsigned_leb128 + public func unsigned_leb128_64(buffer : ByteUtils.BufferLike, n : Nat) { + var value = Nat64.fromNat(n); + while (value >= 0x80) { + buffer.add(Nat8.fromNat(Nat64.toNat(value & 0x7F)) | 0x80); + value >>= 7; + }; + buffer.add(Nat8.fromNat(Nat64.toNat(value))); }; - public func get(i : Nat) : A { - switch (elems[i]) { - case (?elem) elem; - case (null) Debug.trap "Index out of bounds"; - }; + public func unsigned_leb128(buffer : ByteUtils.BufferLike, n : Nat) { + var value = Nat64.fromNat(n); + while (value >= 0x80) { + buffer.add(Nat8.fromNat(Nat64.toNat(value & 0x7F)) | 0x80); + value >>= 7; + }; + buffer.add(Nat8.fromNat(Nat64.toNat(value))); }; - public func put(i : Nat, elem : A) { - if (i >= count) Debug.trap "Index out of bounds"; - elems[i] := ?elem; + public func signed_leb128_64(buffer : ByteUtils.BufferLike, num : Int) { + ByteUtils.Buffer.addSLEB128_64(buffer, Int64.fromInt(num)); }; - public func vals() : Iter.Iter { - var i = 0; + // public func signed_leb128(buffer : AddToBuffer, num : Int) { + // let nat64_bound = 18_446_744_073_709_551_616; + + // if (num < nat64_bound and num > -nat64_bound) return signed_leb128_64(buffer, num); + + // var n = num; + // let is_negative = n < 0; - object { - public func next() : ?A { - if (i < count) { - let res = elems[i]; - i += 1; - res; - } else { - null; - }; + // }; + + public class ReusableBuffer(init_capacity : Nat) { + var elems : [var ?A] = Array.init(init_capacity, null); + var count : Nat = 0; + + public func size() : Nat = count; + + public func add(elem : A) { + if (count == elems.size()) { + elems := Array.tabulateVar( + elems.size() * 2, + func(i : Nat) : ?A { + if (i < count) { + elems[i]; + } else { + null; + }; + }, + ); + }; + + elems[count] := ?elem; + count += 1; + }; + + public func clear() { + count := 0; + }; + + public func get(i : Nat) : A { + switch (elems[i]) { + case (?elem) elem; + case (null) Debug.trap "Index out of bounds"; + }; + }; + + public func put(i : Nat, elem : A) { + if (i >= count) Debug.trap "Index out of bounds"; + elems[i] := ?elem; + }; + + public func vals() : Iter.Iter { + var i = 0; + + object { + public func next() : ?A { + if (i < count) { + let res = elems[i]; + i += 1; + res; + } else { + null; + }; + }; + }; }; - }; }; - }; }; diff --git a/src/libs/motoko_candid/utils.mo b/src/libs/motoko_candid/utils.mo index 82ea71a..ce707d9 100644 --- a/src/libs/motoko_candid/utils.mo +++ b/src/libs/motoko_candid/utils.mo @@ -1,31 +1,31 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Buffer "mo:base@0.14.14/Buffer"; -import Debug "mo:base@0.14.14/Debug"; -import Result "mo:base@0.14.14/Result"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Int8 "mo:base@0.14.14/Int8"; -import Int32 "mo:base@0.14.14/Int32"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat16 "mo:base@0.14.14/Nat16"; -import Int64 "mo:base@0.14.14/Int64"; -import Nat "mo:base@0.14.14/Nat"; -import Int "mo:base@0.14.14/Int"; -import Iter "mo:base@0.14.14/Iter"; -import Prelude "mo:base@0.14.14/Prelude"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; -import Order "mo:base@0.14.14/Order"; -import Func "mo:base@0.14.14/Func"; -import Char "mo:base@0.14.14/Char"; -import TrieMap "mo:base@0.14.14/TrieMap"; -import Int16 "mo:base@0.14.14/Int16"; - -import Arg "mo:candid@2.0.0/Arg"; -import Value "mo:candid@2.0.0/Value"; -import Type "mo:candid@2.0.0/Type"; -import Tag "mo:candid@2.0.0/Tag"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Buffer "mo:base@0.16.0/Buffer"; +import Debug "mo:base@0.16.0/Debug"; +import Result "mo:base@0.16.0/Result"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Int8 "mo:base@0.16.0/Int8"; +import Int32 "mo:base@0.16.0/Int32"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat16 "mo:base@0.16.0/Nat16"; +import Int64 "mo:base@0.16.0/Int64"; +import Nat "mo:base@0.16.0/Nat"; +import Int "mo:base@0.16.0/Int"; +import Iter "mo:base@0.16.0/Iter"; +import Prelude "mo:base@0.16.0/Prelude"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; +import Order "mo:base@0.16.0/Order"; +import Func "mo:base@0.16.0/Func"; +import Char "mo:base@0.16.0/Char"; +import TrieMap "mo:base@0.16.0/TrieMap"; +import Int16 "mo:base@0.16.0/Int16"; + +import Arg "mo:candid/Arg"; +import Value "mo:candid/Value"; +import Type "mo:candid/Type"; +import Tag "mo:candid/Tag"; import Itertools "mo:itertools@0.2.2/Iter"; import PeekableIter "mo:itertools@0.2.2/PeekableIter"; import Map "mo:map@9.0.1/Map"; diff --git a/submodules/json.mo/README.md b/submodules/json.mo/README.md index 1459755..8c47eaa 100644 --- a/submodules/json.mo/README.md +++ b/submodules/json.mo/README.md @@ -6,7 +6,7 @@ ```motoko import JSON "mo:json/JSON"; -import Debug "mo:base@0.14.14/Debug"; +import Debug "mo:base@0.16.0/Debug"; let obj : JSON.JSON = #Object([]); Debug.print(JSON.show(obj)); @@ -28,7 +28,7 @@ Debug.print(JSON.show(#Object([ ```motoko import JSON "mo:json/JSON"; -import Debug "mo:base@0.14.14/Debug"; +import Debug "mo:base@0.16.0/Debug"; Debug.print(JSON.show(#Object([("amount", #Float(32.4829))]))); // {"amount": "32.48"} diff --git a/tests/CBOR.Test.mo b/tests/CBOR.Test.mo index 2d13eea..a6112d1 100644 --- a/tests/CBOR.Test.mo +++ b/tests/CBOR.Test.mo @@ -1,12 +1,12 @@ // @testmode wasi -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; - -import { test; suite } "mo:test@2.1.1"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; + +import { test; suite } "mo:test"; import { CBOR } "../src"; diff --git a/tests/Candid.Large.test.mo b/tests/Candid.Large.test.mo index b703eb4..de578b3 100644 --- a/tests/Candid.Large.test.mo +++ b/tests/Candid.Large.test.mo @@ -1,14 +1,14 @@ // @testmode wasi -import Iter "mo:base@0.14.14/Iter"; -import Debug "mo:base@0.14.14/Debug"; -import Prelude "mo:base@0.14.14/Prelude"; -import Text "mo:base@0.14.14/Text"; -import Char "mo:base@0.14.14/Char"; -import Buffer "mo:base@0.14.14/Buffer"; - -import Fuzz "mo:fuzz@1.0.0"; +import Iter "mo:base@0.16.0/Iter"; +import Debug "mo:base@0.16.0/Debug"; +import Prelude "mo:base@0.16.0/Prelude"; +import Text "mo:base@0.16.0/Text"; +import Char "mo:base@0.16.0/Char"; +import Buffer "mo:base@0.16.0/Buffer"; + +import Fuzz "mo:fuzz"; import Itertools "mo:itertools@0.2.2/Iter"; -import { test; suite } "mo:test@2.1.1"; +import { test; suite } "mo:test"; import Serde "../src"; import CandidEncoder "../src/Candid/Blob/Encoder"; diff --git a/tests/Candid.Test.mo b/tests/Candid.Test.mo index ca90dd8..616f059 100644 --- a/tests/Candid.Test.mo +++ b/tests/Candid.Test.mo @@ -1,18 +1,33 @@ // @testmode wasi -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Nat "mo:base@0.14.14/Nat"; -import Principal "mo:base@0.14.14/Principal"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Nat "mo:base@0.16.0/Nat"; +import Principal "mo:base@0.16.0/Principal"; +import Result "mo:base@0.16.0/Result"; -import { test; suite } "mo:test@2.1.1"; +import { test; suite } "mo:test"; import Serde "../src"; import Candid "../src/Candid"; import Encoder "../src/Candid/Blob/Encoder"; +import CandidTestUtils "CandidTestUtils"; + type Candid = Candid.Candid; +func encode(vals : [Candid], options : ?Candid.Options) : Result.Result { + Candid.encode(vals, options); +}; + +func encodeOne(val : Candid, options : ?Candid.Options) : Result.Result { + Candid.encodeOne(val, options); +}; + +func decode(blob : Blob, keys : [Text], options : ?Candid.Options) : Result.Result<[Candid], Text> { + Candid.decode(blob, keys, options); +}; + suite( "Candid decode()", func() { @@ -99,7 +114,15 @@ suite( let records : [Record] = [null_record, empty_record, admin_record, user_record, base_record]; let blob = to_candid (records); - let #ok(candid) = Candid.decode(blob, Serde.concatKeys([PermissionKeys, UserKeys, RecordKeys]), null); + + let RecordsType : Candid.CandidType = #Array( + #Record([ + ("group", #Text), + ("users", #Option(#Array(#Record([("name", #Text), ("age", #Nat), ("permission", #Variant([("read", #Array(#Text)), ("write", #Array(#Text)), ("read_all", #Null), ("write_all", #Null), ("admin", #Null)]))])))), + ]) + ); + + let #ok(candid) = CandidTestUtils.decode_with_types([RecordsType], Serde.concatKeys([PermissionKeys, UserKeys, RecordKeys]), blob, null) else return assert false; let expected = [#Array([#Record([("group", #Text("null")), ("users", #Null)]), #Record([("group", #Text("empty")), ("users", #Option(#Array([])))]), #Record([("group", #Text("admins")), ("users", #Option(#Array([#Record([("age", #Nat(32)), ("permission", #Variant("admin", #Null)), ("name", #Text("John"))])])))]), #Record([("group", #Text("users")), ("users", #Option(#Array([#Record([("age", #Nat(28)), ("permission", #Variant("read_all", #Null)), ("name", #Text("Ali"))]), #Record([("age", #Nat(40)), ("permission", #Variant("write_all", #Null)), ("name", #Text("James"))])])))]), #Record([("group", #Text("base")), ("users", #Option(#Array([#Record([("age", #Nat(32)), ("permission", #Variant("read", #Array([#Text("posts"), #Text("comments")]))), ("name", #Text("Henry"))]), #Record([("age", #Nat(32)), ("permission", #Variant("write", #Array([#Text("posts"), #Text("comments")]))), ("name", #Text("Steven"))])])))])])]; @@ -111,17 +134,17 @@ suite( func() { type User = { name : Text; - age : Nat; + age : ?Nat; }; let user_james = { name = "James"; - age = 23; + age = ?23; }; let user_steven = { name = "Steven"; - age = 32; + age = null; }; type Record = { @@ -135,15 +158,20 @@ suite( }; let record_blob = to_candid (record); - let candid = Candid.decode(record_blob, ["first", "second", "name", "age"], null); - assert candid == #ok([ - #Record([ - ("first", #Record([("age", #Nat(23)), ("name", #Text("James"))])), - ("second", #Record([("age", #Nat(32)), ("name", #Text("Steven"))])), - ]), + let RecordType : Candid.CandidType = #Record([ + ("first", #Record([("name", #Text), ("age", #Option(#Nat))])), + ("second", #Record([("name", #Text), ("age", #Option(#Nat))])), ]); + let #ok(candid) = CandidTestUtils.decode_with_types([RecordType], ["first", "second", "name", "age"], record_blob, null) else return assert false; + + assert candid == [ + #Record([ + ("first", #Record([("age", #Option(#Nat(23))), ("name", #Text("James"))])), + ("second", #Record([("age", #Null), ("name", #Text("Steven"))])), + ]), + ]; }, ); test( @@ -191,9 +219,20 @@ suite( Candid.defaultOptions with renameKeys = [("arr", "array"), ("name", "username")]; }; - let candid = Candid.decode(blob, ["name", "arr"], ?options); - assert candid == #ok([ + Debug.print("blob: " # debug_show blob); + Debug.print("keys: " # debug_show ["name", "arr"]); + + // Test regular decode first + let #ok(regular_decode_result) = Candid.decode(blob, ["name", "arr"], ?options); + Debug.print("regular decode result: " # debug_show regular_decode_result); + + let ArrayType : Candid.CandidType = #Array(#Record([("name", #Text), ("arr", #Array(#Nat))])); + + let #ok(candid) = CandidTestUtils.decode_with_types([ArrayType], ["name", "arr"], blob, ?options) else return assert false; + Debug.print("typed decode result: " # debug_show candid); + + assert candid == [ #Array([ #Record([ ("array", #Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)])), @@ -208,7 +247,7 @@ suite( ("username", #Text("rust")), ]), ]) - ]); + ]; }, ); test( @@ -216,9 +255,12 @@ suite( func() { let motoko = { name = "candid" }; let blob = to_candid (motoko); - let candid = Candid.decode(blob, ["name"], null); - assert candid == #ok([#Record([("name", #Text("candid"))])]); + let RecordType : Candid.CandidType = #Record([("name", #Text)]); + + let #ok(candid) = CandidTestUtils.decode_with_types([RecordType], ["name"], blob, null) else return assert false; + + assert candid == [#Record([("name", #Text("candid"))])]; }, ); test( @@ -226,9 +268,12 @@ suite( func() { let arr = [1, 2, 3, 4]; let blob = to_candid (arr); - let candid = Candid.decode(blob, [], null); - assert candid == #ok([#Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)])]); + let ArrayType : Candid.CandidType = #Array(#Nat); + + let #ok(candid) = CandidTestUtils.decode_with_types([ArrayType], [], blob, null) else return assert false; + + assert candid == [#Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)])]; }, ); test( @@ -242,8 +287,14 @@ suite( [["hello", "world"], ["foo", "bar"]], ]; - assert (Candid.decode(to_candid (arr2), [], null) == #ok([#Array([#Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)]), #Array([#Nat(5), #Nat(6), #Nat(7), #Nat(8)]), #Array([#Nat(9), #Nat(10), #Nat(11)])])])); - assert (Candid.decode(to_candid (arr3), [], null) == #ok([#Array([#Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])]), #Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])]), #Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])])])])); + let Arr2Type : Candid.CandidType = #Array(#Array(#Nat)); + let Arr3Type : Candid.CandidType = #Array(#Array(#Array(#Text))); + + let #ok(arr2_candid) = CandidTestUtils.decode_with_types([Arr2Type], [], to_candid (arr2), null) else return assert false; + let #ok(arr3_candid) = CandidTestUtils.decode_with_types([Arr3Type], [], to_candid (arr3), null) else return assert false; + + assert (arr2_candid == [#Array([#Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)]), #Array([#Nat(5), #Nat(6), #Nat(7), #Nat(8)]), #Array([#Nat(9), #Nat(10), #Nat(11)])])]); + assert (arr3_candid == [#Array([#Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])]), #Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])]), #Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])])])]); }, ); test( @@ -255,15 +306,17 @@ suite( let bytes_array = to_candid (motoko_blob); let bytes_blob = to_candid (motoko_blob); - let candid_array = Candid.decode(bytes_array, [], null); - let candid_blob = Candid.decode(bytes_blob, [], null); + let BlobType : Candid.CandidType = #Blob; + + let #ok(candid_array) = CandidTestUtils.decode_with_types([BlobType], [], bytes_array, null) else return assert false; + let #ok(candid_blob) = CandidTestUtils.decode_with_types([BlobType], [], bytes_blob, null) else return assert false; assert ( // All [Nat8] types are decoded as #Blob - candid_array != #ok([#Array([#Nat8(1), #Nat8(2), #Nat8(3), #Nat8(4)])]), + candid_array != [#Array([#Nat8(1), #Nat8(2), #Nat8(3), #Nat8(4)])], ); - assert (candid_array == #ok([#Blob(motoko_blob)])); - assert (candid_blob == #ok([#Blob(motoko_blob)])); + assert (candid_array == [#Blob(motoko_blob)]); + assert (candid_blob == [#Blob(motoko_blob)]); }, ); test( @@ -289,17 +342,25 @@ suite( let record_blob = to_candid (record); let array_blob = to_candid (array); - let text_candid = Candid.decode(text_blob, ["text"], null); - let nat_candid = Candid.decode(nat_blob, ["nat"], null); - let bool_candid = Candid.decode(bool_blob, ["bool"], null); - let record_candid = Candid.decode(record_blob, ["record", "site"], null); - let array_candid = Candid.decode(array_blob, ["array"], null); + let VariantType : Candid.CandidType = #Variant([ + ("text", #Text), + ("nat", #Nat), + ("bool", #Bool), + ("record", #Record([("site", #Text)])), + ("array", #Array(#Nat)), + ]); - assert (text_candid == #ok([#Variant("text", #Text("hello"))])); - assert (nat_candid == #ok([#Variant("nat", #Nat(123))])); - assert (bool_candid == #ok([#Variant("bool", #Bool(true))])); - assert (record_candid == #ok([#Variant("record", #Record([("site", #Text("github"))]))])); - assert (array_candid == #ok([#Variant("array", #Array([#Nat(1), #Nat(2), #Nat(3)]))])); + let #ok(text_candid) = CandidTestUtils.decode_with_types([VariantType], ["text"], text_blob, null) else return assert false; + let #ok(nat_candid) = CandidTestUtils.decode_with_types([VariantType], ["nat"], nat_blob, null) else return assert false; + let #ok(bool_candid) = CandidTestUtils.decode_with_types([VariantType], ["bool"], bool_blob, null) else return assert false; + let #ok(record_candid) = CandidTestUtils.decode_with_types([VariantType], ["record", "site"], record_blob, null) else return assert false; + let #ok(array_candid) = CandidTestUtils.decode_with_types([VariantType], ["array"], array_blob, null) else return assert false; + + assert (text_candid == [#Variant("text", #Text("hello"))]); + assert (nat_candid == [#Variant("nat", #Nat(123))]); + assert (bool_candid == [#Variant("bool", #Bool(true))]); + assert (record_candid == [#Variant("record", #Record([("site", #Text("github"))]))]); + assert (array_candid == [#Variant("array", #Array([#Nat(1), #Nat(2), #Nat(3)]))]); }, ); @@ -335,9 +396,12 @@ suite( ]; let blob = to_candid (users); - let candid = Candid.decode(blob, record_keys, null); + + let UsersType : Candid.CandidType = #Array(#Record([("name", #Text), ("age", #Nat8), ("email", #Option(#Text)), ("registered", #Bool)])); + + let #ok(candid) = CandidTestUtils.decode_with_types([UsersType], record_keys, blob, null) else return assert false; Debug.print("candid" # debug_show candid); - assert candid == #ok([ + assert candid == [ #Array([ #Record([ ("age", #Nat8(32)), @@ -358,7 +422,7 @@ suite( ("registered", #Bool(true)), ]), ]), - ]); + ]; }, ); }, @@ -475,7 +539,11 @@ suite( base_record_candid, ]); - let #ok(blob) = Candid.encodeOne(records, null); + let RecordsType : Candid.CandidType = #Array( + #Record([("group", #Text), ("users", #Option(#Array(#Record([("age", #Nat), ("name", #Text), ("permission", #Variant([("read", #Array(#Text)), ("write", #Array(#Text)), ("read_all", #Null), ("write_all", #Null), ("admin", #Null)]))]))))]) + ); + + let #ok(blob) = CandidTestUtils.encode_with_types([RecordsType], [records], null) else return assert false; let motoko : ?[Record] = from_candid (blob); assert motoko == ?[ @@ -511,12 +579,17 @@ suite( daily_downloads : [Nat]; }; + let Data : Candid.CandidType = #Array( + #Record([("array", #Array(#Nat)), ("name", #Text)]) + ); + let options = { Candid.defaultOptions with renameKeys = [("array", "daily_downloads"), ("name", "language")]; }; + Debug.print("type passed in: " # debug_show ([Data])); - let #ok(blob) = Candid.encodeOne(candid, ?options); + let #ok(blob) = CandidTestUtils.encode_with_types([Data], [candid], ?options) else return assert false; let motoko : ?[Data] = from_candid (blob); assert motoko == ?[{ language = "candid"; daily_downloads = [1, 2, 3, 4] }, { language = "motoko"; daily_downloads = [5, 6, 7, 8] }, { language = "rust"; daily_downloads = [9, 10, 11, 12] }]; }, @@ -529,7 +602,9 @@ suite( name : Text; }; - let #ok(blob) = Candid.encodeOne(candid, null); + let UserType : Candid.CandidType = #Record([("name", #Text)]); + + let #ok(blob) = CandidTestUtils.encode_with_types([UserType], [candid], null) else return assert false; let user : ?User = from_candid (blob); assert user == ?{ name = "candid" }; @@ -550,8 +625,11 @@ suite( #Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])]), ]); - let #ok(arr2_blob) = Candid.encodeOne(arr2, null); - let #ok(arr3_blob) = Candid.encodeOne(arr3, null); + let Arr2Type : Candid.CandidType = #Array(#Array(#Nat)); + let Arr3Type : Candid.CandidType = #Array(#Array(#Array(#Text))); + + let #ok(arr2_blob) = CandidTestUtils.encode_with_types([Arr2Type], [arr2], null) else return assert false; + let #ok(arr3_blob) = CandidTestUtils.encode_with_types([Arr3Type], [arr3], null) else return assert false; let arr2_encoded : ?[[Nat]] = from_candid (arr2_blob); let arr3_encoded : ?[[[Text]]] = from_candid (arr3_blob); @@ -574,8 +652,11 @@ suite( let candid_1 = #Array([#Nat8(1 : Nat8), #Nat8(2 : Nat8), #Nat8(3 : Nat8), #Nat8(4 : Nat8)]); let candid_2 = #Blob(motoko_blob); - let #ok(serialized_1) = Candid.encodeOne(candid_1, null); - let #ok(serialized_2) = Candid.encodeOne(candid_2, null); + let BlobType : Candid.CandidType = #Blob; + let Nat8ArrayType : Candid.CandidType = #Array(#Nat8); + + let #ok(serialized_1) = CandidTestUtils.encode_with_types([Nat8ArrayType], [candid_1], null) else return assert false; + let #ok(serialized_2) = CandidTestUtils.encode_with_types([BlobType], [candid_2], null) else return assert false; let blob_1 : ?Blob = from_candid (serialized_1); let blob_2 : ?Blob = from_candid (serialized_2); @@ -607,11 +688,13 @@ suite( let record = #Variant("record", #Record([("site", #Text("github"))])); let array = #Variant("array", #Array([#Nat(1), #Nat(2), #Nat(3)])); - let #ok(text_blob) = Candid.encodeOne(text, null); - let #ok(nat_blob) = Candid.encodeOne(nat, null); - let #ok(bool_blob) = Candid.encodeOne(bool, null); - let #ok(record_blob) = Candid.encodeOne(record, null); - let #ok(array_blob) = Candid.encodeOne(array, null); + let VariantType : Candid.CandidType = #Variant([("text", #Text), ("nat", #Nat), ("bool", #Bool), ("record", #Record([("site", #Text)])), ("array", #Array(#Nat))]); + + let #ok(text_blob) = CandidTestUtils.encode_with_types([VariantType], [text], null) else return assert false; + let #ok(nat_blob) = CandidTestUtils.encode_with_types([VariantType], [nat], null) else return assert false; + let #ok(bool_blob) = CandidTestUtils.encode_with_types([VariantType], [bool], null) else return assert false; + let #ok(record_blob) = CandidTestUtils.encode_with_types([VariantType], [record], null) else return assert false; + let #ok(array_blob) = CandidTestUtils.encode_with_types([VariantType], [array], null) else return assert false; let text_val : ?Variant = from_candid (text_blob); let nat_val : ?Variant = from_candid (nat_blob); @@ -888,9 +971,14 @@ suite( let motoko = { name = "candid"; age = 32 }; let blob = to_candid (motoko); - let #ok(candid) = Candid.decode(blob, ["name", "age"], null); // decode without keys - let #ok(blob_2) = Candid.encode(candid, null); + let UserDecodeType : Candid.CandidType = #Record([("name", #Text), ("age", #Nat)]); + + let #ok(candid) = CandidTestUtils.decode_with_types([UserDecodeType], ["name", "age"], blob, null) else return assert false; // decode without keys + + let UserType : Candid.CandidType = #Record([("name", #Text), ("age", #Nat)]); + + let #ok(blob_2) = CandidTestUtils.encode_with_types([UserType], candid, null) else return assert false; let motoko_2 : ?User = from_candid (blob_2); assert motoko_2 == ?motoko; @@ -914,7 +1002,9 @@ suite( ("details", #Record([("age", #Nat(32)), ("email", #Option(#Text("example@gmail.com"))), ("registered", #Bool(true))])), ]); - let #ok(blob) = Candid.encodeOne(candid, null); + let UserType : Candid.CandidType = #Record([("name", #Text), ("details", #Record([("age", #Nat), ("email", #Option(#Text)), ("registered", #Bool)]))]); + + let #ok(blob) = CandidTestUtils.encode_with_types([UserType], [candid], null) else return assert false; let mo : ?User = from_candid (blob); assert mo == ?{ diff --git a/tests/CandidTestUtils.mo b/tests/CandidTestUtils.mo new file mode 100644 index 0000000..fa34b0f --- /dev/null +++ b/tests/CandidTestUtils.mo @@ -0,0 +1,102 @@ +// @testmode wasi +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Nat "mo:base@0.16.0/Nat"; +import Principal "mo:base@0.16.0/Principal"; +import Result "mo:base@0.16.0/Result"; +import Option "mo:base@0.16.0/Option"; + +import { test; suite } "mo:test"; + +import Serde "../src"; +import Candid "../src/Candid"; +import Encoder "../src/Candid/Blob/Encoder"; + +module { + type Candid = Candid.Candid; + + public func encode_with_types(types : [Candid.CandidType], vals : [Candid], _options : ?Candid.Options) : Result.Result { + let options = Option.get(_options, Candid.defaultOptions); + + let single_function_encoding = switch (Candid.encode(vals, ?options)) { + case (#ok(blob)) blob; + case (#err(err)) { + Debug.print("encode function failed: " # err); + return #err("encode function failed: " #err); + }; + }; + + let encoder = Candid.TypedSerializer.new(types, ?options); + let encoded_blob_res = Candid.TypedSerializer.encode(encoder, vals); + let encoder_instance_blob = switch (encoded_blob_res) { + case (#ok(blob)) blob; + case (#err(err)) { + Debug.print("encoder instance failed: " # err); + return #err("encoder instance failed: " # err); + }; + }; + + if (encoder_instance_blob != single_function_encoding) { + + let single_function_encoding_with_types = switch (Candid.encode(vals, ?{ options with types = ?Candid.formatCandidType(types, ?options.renameKeys) })) { + case (#ok(blob)) blob; + case (#err(err)) { + Debug.print("encode function failed: " # err); + return #err("encode function failed: " #err); + }; + }; + + if (encoder_instance_blob != single_function_encoding_with_types) { + Debug.print("Encoded blob does not match the original encoding:"); + Debug.print("Single function: " # debug_show (Blob.toArray(single_function_encoding_with_types))); + Debug.print("Encoder instance: " # debug_show (Blob.toArray(encoder_instance_blob))); + return #err("Encoded blob does not match the original encoding: " # debug_show ({ single_function_encoding_with_types; encoder_instance_blob })); + }; + }; + + // assert Candid.TypedSerializer.equal(encoder, Candid.TypedSerializer.fromBlob(encoder_instance_blob, [], ?options)); + + return #ok(encoder_instance_blob); + }; + + public func decode_with_types(types : [Candid.CandidType], record_keys : [Text], blob : Blob, _options : ?Candid.Options) : Result.Result<[Candid], Text> { + let options = Option.get(_options, Candid.defaultOptions); + + let single_function_decoding = switch (Candid.decode(blob, record_keys, ?options)) { + case (#ok(vals)) vals; + case (#err(err)) { + Debug.print("decode function failed: " # err); + return #err("decode function failed: " # err); + }; + }; + + let decoder = Candid.TypedSerializer.fromBlob(blob, record_keys, ?options); + let decoded_vals_res = Candid.TypedSerializer.decode(decoder, blob); + let decoder_instance_vals = switch (decoded_vals_res) { + case (#ok(vals)) vals; + case (#err(err)) { + Debug.print("decoder instance failed: " # err); + return #err("decoder instance failed: " # err); + }; + }; + + if (decoder_instance_vals != single_function_decoding) { + Debug.print("Decoded values do not match the original decoding:"); + Debug.print("Candid.decode() function: " # debug_show (single_function_decoding)); + Debug.print("TypedSerializer instance: " # debug_show (decoder_instance_vals)); + return #err("Decoded values do not match the original decoding: " # debug_show ({ single_function_decoding; decoder_instance_vals })); + }; + + // !important: the decoder instance should be equal to a new TypedSerializer created with the same types and options + // if (not Candid.TypedSerializer.equal(decoder, Candid.TypedSerializer.new(types, ?options))) { + // Debug.print("Decoder and new TypedSerializer are not equal."); + // Debug.print("Decoder: " # Candid.TypedSerializer.toText(decoder)); + // Debug.print("New TypedSerializer: " # Candid.TypedSerializer.toText(Candid.TypedSerializer.new(types, ?options))); + // return #err("Decoder and new TypedSerializer are not equal."); + // }; + + return #ok(decoder_instance_vals); + }; + +}; diff --git a/tests/Candid.ICRC3.Test.mo b/tests/ICRC3.Test.mo similarity index 72% rename from tests/Candid.ICRC3.Test.mo rename to tests/ICRC3.Test.mo index 206ca42..7191567 100644 --- a/tests/Candid.ICRC3.Test.mo +++ b/tests/ICRC3.Test.mo @@ -1,14 +1,15 @@ // @testmode wasi -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; -import { test; suite } "mo:test@2.1.1"; +import { test; suite } "mo:test"; import Serde "../src"; +import CandidTestUtils "CandidTestUtils"; let { Candid } = Serde; @@ -32,7 +33,9 @@ suite( ("b", #Nat(2)), ]); - let #ok(record_candid_blob2) = Candid.encode(record_candid, ?options); + let RecordType : Candid.CandidType = #Map([("a", #Nat), ("b", #Nat)]); + + let #ok(record_candid_blob2) = CandidTestUtils.encode_with_types([RecordType], record_candid, ?options) else return assert false; assert record_candid_blob == record_candid_blob2; @@ -81,7 +84,9 @@ suite( let candid_values = Candid.fromICRC3Value([icrc3]); - let #ok(blob) = Candid.encode(candid_values, null); + let UserType : Candid.CandidType = #Record([("name", #Text), ("id", #Nat)]); + + let #ok(blob) = CandidTestUtils.encode_with_types([UserType], candid_values, null) else return assert false; let user : ?User = from_candid (blob); assert user == ?{ name = "bar"; id = 112 }; diff --git a/tests/JSON.Test.mo b/tests/JSON.Test.mo index f129e93..d51d726 100644 --- a/tests/JSON.Test.mo +++ b/tests/JSON.Test.mo @@ -1,10 +1,10 @@ // @testmode wasi -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Nat "mo:base@0.14.14/Nat"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Nat "mo:base@0.16.0/Nat"; -import { test; suite } "mo:test@2.1.1"; +import { test; suite } "mo:test"; import { Candid; JSON } "../src"; @@ -151,6 +151,49 @@ suite( }; }, ); + + test( + "partial JSON to record", + func() { + + type User = { name : Text; id : Int; email : ?Text }; + + let text = "{\"name\": \"Tomi\", \"id\": 123 }"; + + let #ok(blob) = JSON.fromText(text, null); + let user : ?User = from_candid (blob); + + assert user == ?{ name = "Tomi"; id = 123; email = null }; + + let text2 = "{ \"name\": \"Tomi\", \"id\": 123, \"email\": \"test@gmail.com\" }"; + let #ok(blob2) = JSON.fromText(text2, null); + let user2 : ?User = from_candid (blob2); + assert user2 == ?{ + name = "Tomi"; + id = 123; + email = ?"test@gmail.com"; + }; + + let jsonText = "[{\"name\": \"John\", \"id\": 123}, {\"name\": \"Jane\", \"id\": 456, \"email\": \"jane@gmail.com\"}]"; + + let #ok(blob3) = JSON.fromText(jsonText, null); // you probably want to handle the error case here :) + let users : ?[User] = from_candid (blob3); + Debug.print(debug_show ({ users })); + + assert users == ?[ + { + name = "John"; + id = 123; + email = null; + }, + { + name = "Jane"; + id = 456; + email = ?"jane@gmail.com"; + }, + ]; + }, + ); }, ); diff --git a/tests/Candid.PrimitiveType.Test.mo b/tests/PrimitiveType.Test.mo similarity index 53% rename from tests/Candid.PrimitiveType.Test.mo rename to tests/PrimitiveType.Test.mo index 950b010..6b9e2ef 100644 --- a/tests/Candid.PrimitiveType.Test.mo +++ b/tests/PrimitiveType.Test.mo @@ -1,16 +1,18 @@ // @testmode wasi -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Nat "mo:base@0.14.14/Nat"; -import Principal "mo:base@0.14.14/Principal"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Nat "mo:base@0.16.0/Nat"; +import Principal "mo:base@0.16.0/Principal"; -import { test; suite } "mo:test@2.1.1"; +import { test; suite } "mo:test"; import Serde "../src"; import Candid "../src/Candid"; import Encoder "../src/Candid/Blob/Encoder"; -import Fuzz "mo:fuzz@1.0.0"; +import Fuzz "mo:fuzz"; + +import CandidTestUtils "CandidTestUtils"; let fuzz = Fuzz.fromSeed(0x12345678); let limit = 10_000; @@ -32,6 +34,13 @@ suite( // Debug.print("Candid mismatch for Int: " # debug_show (int, candid_variant)); assert candid_variant == #ok([#Int(int)]); + + let IntType : Candid.CandidType = #Int; + let #ok(blob) = CandidTestUtils.encode_with_types([IntType], [#Int(int)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_int : ?Int = from_candid (candid_encoding); + assert decoded_int == ?int; }; }, ); @@ -47,6 +56,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Nat8(nat8)]); + + let Nat8Type : Candid.CandidType = #Nat8; + let #ok(blob) = CandidTestUtils.encode_with_types([Nat8Type], [#Nat8(nat8)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_nat8 : ?Nat8 = from_candid (candid_encoding); + assert decoded_nat8 == ?nat8; }; }, ); @@ -62,6 +78,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Nat16(nat16)]); + + let Nat16Type : Candid.CandidType = #Nat16; + let #ok(blob) = CandidTestUtils.encode_with_types([Nat16Type], [#Nat16(nat16)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_nat16 : ?Nat16 = from_candid (candid_encoding); + assert decoded_nat16 == ?nat16; }; }, ); @@ -77,6 +100,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Nat32(nat32)]); + + let Nat32Type : Candid.CandidType = #Nat32; + let #ok(blob) = CandidTestUtils.encode_with_types([Nat32Type], [#Nat32(nat32)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_nat32 : ?Nat32 = from_candid (candid_encoding); + assert decoded_nat32 == ?nat32; }; }, ); @@ -92,6 +122,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Nat64(nat64)]); + + let Nat64Type : Candid.CandidType = #Nat64; + let #ok(blob) = CandidTestUtils.encode_with_types([Nat64Type], [#Nat64(nat64)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_nat64 : ?Nat64 = from_candid (candid_encoding); + assert decoded_nat64 == ?nat64; }; }, ); @@ -107,6 +144,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Int8(int8)]); + + let Int8Type : Candid.CandidType = #Int8; + let #ok(blob) = CandidTestUtils.encode_with_types([Int8Type], [#Int8(int8)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_int8 : ?Int8 = from_candid (candid_encoding); + assert decoded_int8 == ?int8; }; }, ); @@ -122,6 +166,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Int16(int16)]); + + let Int16Type : Candid.CandidType = #Int16; + let #ok(blob) = CandidTestUtils.encode_with_types([Int16Type], [#Int16(int16)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_int16 : ?Int16 = from_candid (candid_encoding); + assert decoded_int16 == ?int16; }; }, ); @@ -137,6 +188,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Int32(int32)]); + + let Int32Type : Candid.CandidType = #Int32; + let #ok(blob) = CandidTestUtils.encode_with_types([Int32Type], [#Int32(int32)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_int32 : ?Int32 = from_candid (candid_encoding); + assert decoded_int32 == ?int32; }; }, ); @@ -152,6 +210,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Int64(int64)]); + + let Int64Type : Candid.CandidType = #Int64; + let #ok(blob) = CandidTestUtils.encode_with_types([Int64Type], [#Int64(int64)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_int64 : ?Int64 = from_candid (candid_encoding); + assert decoded_int64 == ?int64; }; }, ); @@ -167,6 +232,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Text(text)]); + + let TextType : Candid.CandidType = #Text; + let #ok(blob) = CandidTestUtils.encode_with_types([TextType], [#Text(text)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_text : ?Text = from_candid (candid_encoding); + assert decoded_text == ?text; }; }, ); @@ -182,6 +254,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Principal(principal)]); + + let PrincipalType : Candid.CandidType = #Principal; + let #ok(blob) = CandidTestUtils.encode_with_types([PrincipalType], [#Principal(principal)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_principal : ?Principal = from_candid (candid_encoding); + assert decoded_principal == ?principal; }; }, ); @@ -197,6 +276,15 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Blob(blob)]); + + let BlobType : Candid.CandidType = #Blob; + let #ok(encoded_blob) = CandidTestUtils.encode_with_types([BlobType], [#Blob(blob)], null) else return assert false; + Debug.print("comparing encoded blob: " # debug_show (encoded_blob)); + Debug.print("with candid encoding: " # debug_show (candid_encoding)); + assert encoded_blob == candid_encoding; + + let decoded_blob : ?Blob = from_candid (candid_encoding); + assert decoded_blob == ?blob; }; }, ); diff --git a/tests/RepIndyHash.Test.mo b/tests/RepIndyHash.Test.mo index fce605e..d4a1267 100644 --- a/tests/RepIndyHash.Test.mo +++ b/tests/RepIndyHash.Test.mo @@ -1,11 +1,11 @@ -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; -import { test; suite } "mo:test@2.1.1"; +import { test; suite } "mo:test"; import { Candid } "../src"; -import RepIndyHash "mo:rep-indy-hash@0.1.1"; +import RepIndyHash "mo:rep-indy-hash"; func validate_hash(candid_record : Candid.Candid, icrc3_value_record : RepIndyHash.Value) : Bool { let candid_hash = Candid.repIndyHash(candid_record); @@ -22,7 +22,6 @@ suite( func() { assert validate_hash(#Nat(1), #Nat(1)); assert validate_hash(#Nat(22345), #Nat(22345)); - }, ); diff --git a/tests/Stress.test.mo b/tests/Stress.test.mo new file mode 100644 index 0000000..7ee83b0 --- /dev/null +++ b/tests/Stress.test.mo @@ -0,0 +1,410 @@ +// @testmode wasi +import Iter "mo:base@0.16.0/Iter"; +import Debug "mo:base@0.16.0/Debug"; +import Prelude "mo:base@0.16.0/Prelude"; +import Text "mo:base@0.16.0/Text"; +import Char "mo:base@0.16.0/Char"; +import Buffer "mo:base@0.16.0/Buffer"; +import Nat64 "mo:base@0.16.0/Nat64"; + +import Fuzz "mo:fuzz"; +import Itertools "mo:itertools@0.2.2/Iter"; +import { test; suite } "mo:test"; + +import Serde "../src"; +import CandidEncoder "../src/Candid/Blob/Encoder"; +import CandidDecoder "../src/Candid/Blob/Decoder"; +import LegacyCandidDecoder "../src/Candid/Blob/Decoder"; +import LegacyCandidEncoder "../src/Candid/Blob/Encoder"; + +import CandidTestUtils "CandidTestUtils"; + +func createGenerator(seed : Nat) : { next() : Nat } { + // Pure bitwise xorshift64 - no multiplication or addition! + var state : Nat64 = Nat64.fromNat(seed); + if (state == 0) state := 1; // Avoid zero state + + { + next = func() : Nat { + // Only XOR and bit shifts - fastest possible + state ^= state << 13 : Nat64; + state ^= state >> 7 : Nat64; + state ^= state << 17 : Nat64; + Nat64.toNat(state); + }; + }; +}; + +let fuzz = Fuzz.create(createGenerator(42)); + +let limit = 1_000; + +let candify_store_item = { + from_blob = func(blob : Blob) : StoreItem { + let ?c : ?StoreItem = from_candid (blob); + c; + }; + to_blob = func(c : StoreItem) : Blob { to_candid (c) }; +}; + +type X = { name : Text; x : X }; + +// let x: X = { +// name = "yo"; +// x = { +// name = "yo"; +// x = {}; +// }; +// }; + +// let x : Serde.Candid = #Record([ +// ("name", #Text("yo")), +// ("x", x) +// ]); +type CustomerReview = { + username : Text; + rating : Nat; + comment : Text; +}; + +let CustomerReview = #Record([ + ("username", #Text), + ("rating", #Nat), + ("comment", #Text), +]); + +type AvailableSizes = { #xs; #s; #m; #l; #xl }; + +let AvailableSizes = #Variant([ + ("xs", #Null), + ("s", #Null), + ("m", #Null), + ("l", #Null), + ("xl", #Null), +]); + +type ColorOption = { + name : Text; + hex : Text; +}; + +let ColorOption = #Record([ + ("name", #Text), + ("hex", #Text), +]); + +type StoreItem = { + name : Text; + store : Text; + customer_reviews : [CustomerReview]; + available_sizes : AvailableSizes; + color_options : [ColorOption]; + price : Float; + in_stock : Bool; + address : (Text, Text, Text, Text); + contact : { + email : Text; + phone : ?Text; + }; +}; + +let StoreItem : Serde.Candid.CandidType = #Record([ + ("name", #Text), + ("store", #Text), + ("customer_reviews", #Array(CustomerReview)), + ("available_sizes", AvailableSizes), + ("color_options", #Array(ColorOption)), + ("price", #Float), + ("in_stock", #Bool), + ("address", #Tuple([#Text, #Text, #Text, #Text])), + ("contact", #Record([("email", #Text), ("phone", #Option(#Text))])), +]); + +let FormattedStoreItem = Serde.Candid.formatCandidType([StoreItem], null); + +let cities = ["Toronto", "Ottawa", "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia", "San Antonio", "San Diego", "Dallas", "San Jose"]; +let states = ["ON", "QC", "NY", "CA", "IL", "TX", "AZ", "PA", "TX", "CA", "TX", "CA"]; +let streets = ["King St", "Queen St", "Yonge St", "Bay St", "Bloor St", "Dundas St", "College St", "Spadina Ave", "St Clair Ave", "Danforth Ave", "Eglinton Ave", "Lawrence Ave"]; + +let stores = ["h&m", "zara", "gap", "old navy", "forever 21", "uniqlo", "urban outfitters", "american eagle", "aeropostale", "abercrombie & fitch", "hollister", "express"]; +let email_terminator = ["gmail.com", "yahoo.com", "outlook.com"]; + +let cs_starter_kid = ["black hoodie", "M1 macbook", "white hoodie", "air forces", "Algorithms textbook", "c the hard way", "Udemy subscription", "Nvidea RTX"]; + +let available_sizes = [#xs, #s, #m, #l, #xl]; + +func new_item() : StoreItem { + let store_name = fuzz.array.randomEntry(stores).1; + let store_item : StoreItem = { + name = fuzz.array.randomEntry(cs_starter_kid).1; + store = store_name; + customer_reviews = [ + { + username = "user1"; + rating = fuzz.nat.randomRange(0, 5); + comment = "good"; + }, + { + username = "user2"; + rating = fuzz.nat.randomRange(0, 5); + comment = "ok"; + }, + ]; + available_sizes = fuzz.array.randomEntry(available_sizes).1; + color_options = [ + { name = "red"; hex = "#ff0000" }, + { name = "blue"; hex = "#0000ff" }, + ]; + price = fuzz.float.randomRange(19.99, 399.99); + in_stock = fuzz.bool.random(); + address = ( + fuzz.array.randomEntry(streets).1, + fuzz.array.randomEntry(cities).1, + fuzz.array.randomEntry(states).1, + fuzz.text.randomAlphanumeric(6), + ); + contact = { + email = store_name # "@" # fuzz.array.randomEntry(email_terminator).1; + phone = if (fuzz.nat.randomRange(0, 100) % 3 == 0) { null } else { + ?Text.fromIter( + fuzz.array.randomArray(10, func() : Char { Char.fromNat32(fuzz.nat32.randomRange(0, 9) + Char.toNat32('0')) }).vals() : Iter.Iter + ); + }; + }; + }; +}; + +let store_item_keys = ["name", "store", "customer_reviews", "username", "rating", "comment", "available_sizes", "xs", "s", "m", "l", "xl", "color_options", "name", "hex", "price", "in_stock", "address", "contact", "email", "phone"]; + +let candid_buffer = Buffer.Buffer<[Serde.Candid]>(limit); +let store_items = Buffer.Buffer(limit); + +let candid_buffer_with_types = Buffer.Buffer<[Serde.Candid]>(limit); +let store_items_with_types = Buffer.Buffer(limit); + +// Roundtrip test function that takes a schema, value generator, and comparison function +func roundtripTest( + schema : Serde.Candid.CandidType, + valueGenerator : () -> T, + toBlobFn : (T) -> Blob, + fromBlobFn : (Blob) -> T, + keys : [Text], + iterations : Nat, +) : () { + let values = Buffer.Buffer(iterations); + let candid_values = Buffer.Buffer<[Serde.Candid]>(iterations); + + // Phase 1: Generate values and decode them to Candid + for (i in Itertools.range(0, iterations)) { + let value = valueGenerator(); + values.add(value); + let blob = toBlobFn(value); + let #ok(candid) = CandidDecoder.one_shot(blob, keys, null); + candid_values.add(candid); + }; + + // Phase 2: Encode Candid values back to blobs and verify roundtrip + for (i in Itertools.range(0, iterations)) { + let candid = candid_values.get(i); + let originalValue = values.get(i); + + // Use CandidTestUtils.encode_with_types for consistency validation + let #ok(encodedBlob) = CandidTestUtils.encode_with_types([schema], candid, null); + let decodedValue = fromBlobFn(encodedBlob); + + assert decodedValue == originalValue; + }; +}; + +suite( + "Serde.Candid", + func() { + test( + "decode()", + func() { + for (i in Itertools.range(0, limit)) { + let item = new_item(); + store_items.add(item); + let candid_blob = candify_store_item.to_blob(item); + let #ok(candid) = CandidDecoder.one_shot(candid_blob, store_item_keys, null); + candid_buffer.add(candid); + }; + }, + ); + test( + "encode()", + func() { + for (i in Itertools.range(0, limit)) { + let candid = candid_buffer.get(i); + let res = LegacyCandidEncoder.encode(candid, null); + let #ok(blob) = res; + let item = candify_store_item.from_blob(blob); + Debug.print("item: " # debug_show item); + Debug.print("store_items: " # debug_show store_items.get(i)); + assert item == store_items.get(i); + }; + }, + ); + test( + "decode() with types", + func() { + for (i in Itertools.range(0, limit)) { + let item = new_item(); + store_items_with_types.add(item); + let candid_blob = candify_store_item.to_blob(item); + let #ok(split_blob) = CandidDecoder.split(candid_blob, null); + let #ok(candid) = CandidDecoder.one_shot(candid_blob, store_item_keys, ?{ Serde.Candid.defaultOptions with types = ?FormattedStoreItem }); + candid_buffer_with_types.add(candid); + }; + }, + ); + test( + "encode() with types", + func() { + for (i in Itertools.range(0, limit)) { + let candid = candid_buffer_with_types.get(i); + let res = LegacyCandidEncoder.encode(candid, ?{ Serde.Candid.defaultOptions with types = ?FormattedStoreItem }); + let #ok(blob) = res; + let item = candify_store_item.from_blob(blob); + assert item == store_items_with_types.get(i); + }; + }, + ); + + test( + "roundtrip StoreItem with schema validation", + func() { + roundtripTest( + StoreItem, + new_item, + candify_store_item.to_blob, + candify_store_item.from_blob, + store_item_keys, + 100 // Smaller iteration count for this test + ); + }, + ); + }, +); + +suite( + "Roundtrip Tests", + func() { + test( + "roundtrip simple record", + func() { + type SimpleRecord = { name : Text; age : Nat }; + let SimpleRecordSchema : Serde.Candid.CandidType = #Record([("name", #Text), ("age", #Nat)]); + + let simpleRecordUtils = { + to_blob = func(r : SimpleRecord) : Blob { to_candid (r) }; + from_blob = func(blob : Blob) : SimpleRecord { + let ?r : ?SimpleRecord = from_candid (blob); + r; + }; + }; + + roundtripTest( + SimpleRecordSchema, + func() : SimpleRecord { + { + name = fuzz.text.randomAlphanumeric(10); + age = fuzz.nat.randomRange(0, 100); + }; + }, + simpleRecordUtils.to_blob, + simpleRecordUtils.from_blob, + ["name", "age"], + 100, + ); + }, + ); + + test( + "roundtrip variant type", + func() { + type SimpleVariant = { #text : Text; #num : Nat; #flag : Bool }; + let SimpleVariantSchema : Serde.Candid.CandidType = #Variant([("text", #Text), ("num", #Nat), ("flag", #Bool)]); + + let simpleVariantUtils = { + to_blob = func(v : SimpleVariant) : Blob { to_candid (v) }; + from_blob = func(blob : Blob) : SimpleVariant { + let ?v : ?SimpleVariant = from_candid (blob); + v; + }; + }; + + roundtripTest( + SimpleVariantSchema, + func() : SimpleVariant { + switch (fuzz.nat.randomRange(0, 2)) { + case (0) { #text(fuzz.text.randomAlphanumeric(5)) }; + case (1) { #num(fuzz.nat.randomRange(0, 1000)) }; + case (_) { #flag(fuzz.bool.random()) }; + }; + }, + simpleVariantUtils.to_blob, + simpleVariantUtils.from_blob, + ["text", "num", "flag"], + 100, + ); + }, + ); + + test( + "roundtrip array type", + func() { + let ArraySchema : Serde.Candid.CandidType = #Array(#Nat); + + let arrayUtils = { + to_blob = func(arr : [Nat]) : Blob { to_candid (arr) }; + from_blob = func(blob : Blob) : [Nat] { + let ?arr : ?[Nat] = from_candid (blob); + arr; + }; + }; + + roundtripTest<[Nat]>( + ArraySchema, + func() : [Nat] { + fuzz.array.randomArray(fuzz.nat.randomRange(0, 10), func() : Nat { fuzz.nat.randomRange(0, 1000) }); + }, + arrayUtils.to_blob, + arrayUtils.from_blob, + [], + 100, + ); + }, + ); + + test( + "roundtrip optional type", + func() { + let OptionalSchema : Serde.Candid.CandidType = #Option(#Text); + + let optionalUtils = { + to_blob = func(opt : ?Text) : Blob { to_candid (opt) }; + from_blob = func(blob : Blob) : ?Text { + let ?opt : ??Text = from_candid (blob); + opt; + }; + }; + + roundtripTest( + OptionalSchema, + func() : ?Text { + if (fuzz.bool.random()) { + ?fuzz.text.randomAlphanumeric(8); + } else { + null; + }; + }, + optionalUtils.to_blob, + optionalUtils.from_blob, + [], + 100, + ); + }, + ); + }, +); diff --git a/tests/UrlEncoded.Test.mo b/tests/UrlEncoded.Test.mo index cbd82eb..3adc0c5 100644 --- a/tests/UrlEncoded.Test.mo +++ b/tests/UrlEncoded.Test.mo @@ -1,8 +1,8 @@ // @testmode wasi -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; -import { test; suite } "mo:test@2.1.1"; +import { test; suite } "mo:test"; import UrlEncoded "../src/UrlEncoded"; diff --git a/tests/one-shot.backward-reference.test.mo b/tests/one-shot.backward-reference.test.mo index 36f444c..8c5b68e 100755 --- a/tests/one-shot.backward-reference.test.mo +++ b/tests/one-shot.backward-reference.test.mo @@ -1,17 +1,17 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; -import TrieMap "mo:base@0.14.14/TrieMap"; - -import Arg "mo:candid@2.0.0/Arg"; -import Decoder "mo:candid@2.0.0/Decoder"; -import Encoder "mo:candid@2.0.0/Encoder"; -import Type "mo:candid@2.0.0/Type"; -import Value "mo:candid@2.0.0/Value"; -import { test; suite } "mo:test@2.1.1"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; +import TrieMap "mo:base@0.16.0/TrieMap"; + +import Arg "mo:candid/Arg"; +import Decoder "mo:candid/Decoder"; +import Encoder "mo:candid/Encoder"; +import Type "mo:candid/Type"; +import Value "mo:candid/Value"; +import { test; suite } "mo:test"; import Candid "../src/Candid"; import CandidEncoder "../src/Candid/Blob/Encoder"; diff --git a/tests/one-shot.forward-reference.mo b/tests/one-shot.forward-reference.mo index 8791cfb..699f3f5 100755 --- a/tests/one-shot.forward-reference.mo +++ b/tests/one-shot.forward-reference.mo @@ -1,19 +1,19 @@ // @testmode wasi -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; -import TrieMap "mo:base@0.14.14/TrieMap"; -import Option "mo:base@0.14.14/Option"; - -import Arg "mo:candid@2.0.0/Arg"; -import Decoder "mo:candid@2.0.0/Decoder"; -import Encoder "mo:candid@2.0.0/Encoder"; -import Type "mo:candid@2.0.0/Type"; -import Value "mo:candid@2.0.0/Value"; -import { test; suite } "mo:test@2.1.1"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; +import TrieMap "mo:base@0.16.0/TrieMap"; +import Option "mo:base@0.16.0/Option"; + +import Arg "mo:candid/Arg"; +import Decoder "mo:candid/Decoder"; +import Encoder "mo:candid/Encoder"; +import Type "mo:candid/Type"; +import Value "mo:candid/Value"; +import { test; suite } "mo:test"; import Candid "../src/Candid"; import CandidEncoder "../src/Candid/Blob/Encoder.ForwardReference"; diff --git a/tests/test_template.md b/tests/test_template.md index 97062ee..76bff6e 100644 --- a/tests/test_template.md +++ b/tests/test_template.md @@ -1,8 +1,8 @@ Filename: `[Section]/[Function].Test.mo` ```motoko -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; import ActorSpec "../utils/ActorSpec"; import Algo "../../src";