From 143737e6f9e54c2e12a9c1b38e20b155ac790fd7 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 26 Oct 2023 11:40:41 +0200 Subject: [PATCH 01/50] Add `serde-itf` crate with native deserialization --- Cargo.toml | 1 + .../fixtures/DecideNonProposerTest0.itf.json | 1781 +++++++++++++++++ serde-itf/Cargo.toml | 12 + serde-itf/src/bigint.rs | 56 + serde-itf/src/de.rs | 447 +++++ serde-itf/src/error.rs | 48 + serde-itf/src/lib.rs | 103 + serde-itf/src/map.rs | 131 ++ serde-itf/src/meta.rs | 27 + serde-itf/src/set.rs | 82 + serde-itf/src/state.rs | 88 + serde-itf/src/trace.rs | 135 ++ serde-itf/src/tuple.rs | 87 + serde-itf/src/unserializable.rs | 38 + serde-itf/src/value.rs | 110 + 15 files changed, 3146 insertions(+) create mode 100644 itf/tests/fixtures/DecideNonProposerTest0.itf.json create mode 100644 serde-itf/Cargo.toml create mode 100644 serde-itf/src/bigint.rs create mode 100644 serde-itf/src/de.rs create mode 100644 serde-itf/src/error.rs create mode 100644 serde-itf/src/lib.rs create mode 100644 serde-itf/src/map.rs create mode 100644 serde-itf/src/meta.rs create mode 100644 serde-itf/src/set.rs create mode 100644 serde-itf/src/state.rs create mode 100644 serde-itf/src/trace.rs create mode 100644 serde-itf/src/tuple.rs create mode 100644 serde-itf/src/unserializable.rs create mode 100644 serde-itf/src/value.rs diff --git a/Cargo.toml b/Cargo.toml index 54a3f2e..3c87206 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,5 @@ resolver = "2" members = [ "itf", + "serde-itf", ] diff --git a/itf/tests/fixtures/DecideNonProposerTest0.itf.json b/itf/tests/fixtures/DecideNonProposerTest0.itf.json new file mode 100644 index 0000000..0ecfbc5 --- /dev/null +++ b/itf/tests/fixtures/DecideNonProposerTest0.itf.json @@ -0,0 +1,1781 @@ +{ + "#meta": { + "format": "ITF", + "format-description": "https://apalache.informal.systems/docs/adr/015adr-trace.html", + "source": "consensus.qnt", + "status": "passed", + "description": "Created by Quint on Wed Oct 25 2023 15:38:28 GMT+0200 (Central European Summer Time)", + "timestamp": 1698241108633 + }, + "vars": [ + "system", + "_Event", + "_Result" + ], + "states": [ + { + "#meta": { + "index": 0 + }, + "_Event": { + "height": -1, + "name": "Initial", + "round": -1, + "value": "", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 1, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "newRound", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 1 + }, + "_Event": { + "height": 1, + "name": "NewRound", + "round": 0, + "value": "", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "timeout", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "timeoutPropose", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 1, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "propose", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 2 + }, + "_Event": { + "height": 1, + "name": "NewRound", + "round": 0, + "value": "", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "timeout", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "timeoutPropose", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 1, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "propose", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 3 + }, + "_Event": { + "height": 1, + "name": "Proposal", + "round": 0, + "value": "block", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": 1, + "id": "block", + "round": 0, + "src": "Josef", + "step": "prevote" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 1, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "prevote", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 4 + }, + "_Event": { + "height": 1, + "name": "Proposal", + "round": 0, + "value": "block", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": 1, + "id": "block", + "round": 0, + "src": "Josef", + "step": "prevote" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 1, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "prevote", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 5 + }, + "_Event": { + "height": 1, + "name": "ProposalAndPolkaAndValid", + "round": 0, + "value": "block", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": 1, + "id": "block", + "round": 0, + "src": "Josef", + "step": "precommit" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 1, + "lockedRound": 0, + "lockedValue": "block", + "p": "Josef", + "round": 0, + "step": "precommit", + "validRound": 0, + "validValue": "block" + } + ] + ] + } + }, + { + "#meta": { + "index": 6 + }, + "_Event": { + "height": 1, + "name": "ProposalAndPolkaAndValid", + "round": 0, + "value": "block", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": 1, + "id": "block", + "round": 0, + "src": "Josef", + "step": "precommit" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 1, + "lockedRound": 0, + "lockedValue": "block", + "p": "Josef", + "round": 0, + "step": "precommit", + "validRound": 0, + "validValue": "block" + } + ] + ] + } + }, + { + "#meta": { + "index": 7 + }, + "_Event": { + "height": 1, + "name": "ProposalAndCommitAndValid", + "round": 0, + "value": "block", + "vr": -1 + }, + "_Result": { + "decided": "block", + "name": "decided", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 1, + "lockedRound": 0, + "lockedValue": "block", + "p": "Josef", + "round": 0, + "step": "decided", + "validRound": 0, + "validValue": "block" + } + ] + ] + } + }, + { + "#meta": { + "index": 8 + }, + "_Event": { + "height": 1, + "name": "ProposalAndCommitAndValid", + "round": 0, + "value": "block", + "vr": -1 + }, + "_Result": { + "decided": "block", + "name": "decided", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 1, + "lockedRound": 0, + "lockedValue": "block", + "p": "Josef", + "round": 0, + "step": "decided", + "validRound": 0, + "validValue": "block" + } + ] + ] + } + }, + { + "#meta": { + "index": 9 + }, + "_Event": { + "height": 2, + "name": "NewHeight", + "round": 0, + "value": "", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "newRound", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 10 + }, + "_Event": { + "height": 2, + "name": "NewHeight", + "round": 0, + "value": "", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "newRound", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 11 + }, + "_Event": { + "height": 2, + "name": "NewRoundProposer", + "round": 0, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "proposal", + "proposal": { + "height": 2, + "proposal": "nextBlock", + "round": 0, + "src": "Josef", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "propose", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 12 + }, + "_Event": { + "height": 2, + "name": "NewRoundProposer", + "round": 0, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "proposal", + "proposal": { + "height": 2, + "proposal": "nextBlock", + "round": 0, + "src": "Josef", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "propose", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 13 + }, + "_Event": { + "height": 2, + "name": "Proposal", + "round": 0, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": 2, + "id": "nextBlock", + "round": 0, + "src": "Josef", + "step": "prevote" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "prevote", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 14 + }, + "_Event": { + "height": 2, + "name": "Proposal", + "round": 0, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": 2, + "id": "nextBlock", + "round": 0, + "src": "Josef", + "step": "prevote" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "prevote", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 15 + }, + "_Event": { + "height": 2, + "name": "PolkaAny", + "round": 0, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "timeout", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "timeoutPrevote", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "prevote", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 16 + }, + "_Event": { + "height": 2, + "name": "PolkaAny", + "round": 0, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "timeout", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "timeoutPrevote", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "prevote", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 17 + }, + "_Event": { + "height": 2, + "name": "TimeoutPrevote", + "round": 0, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": 2, + "id": "nil", + "round": 0, + "src": "Josef", + "step": "precommit" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "precommit", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 18 + }, + "_Event": { + "height": 2, + "name": "TimeoutPrevote", + "round": 0, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": 2, + "id": "nil", + "round": 0, + "src": "Josef", + "step": "precommit" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "precommit", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 19 + }, + "_Event": { + "height": 2, + "name": "PrecommitAny", + "round": 0, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "timeout", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "timeoutPrecommit", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "precommit", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 20 + }, + "_Event": { + "height": 2, + "name": "PrecommitAny", + "round": 0, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "timeout", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "timeoutPrecommit", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "precommit", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 21 + }, + "_Event": { + "height": 2, + "name": "TimeoutPrecommit", + "round": 0, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "skipRound", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": 1, + "timeout": "", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "precommit", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 22 + }, + "_Event": { + "height": 2, + "name": "TimeoutPrecommit", + "round": 0, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "skipRound", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": 1, + "timeout": "", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 0, + "step": "precommit", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 23 + }, + "_Event": { + "height": 2, + "name": "NewRound", + "round": 1, + "value": "", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "timeout", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "timeoutPropose", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 1, + "step": "propose", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 24 + }, + "_Event": { + "height": 2, + "name": "NewRound", + "round": 1, + "value": "", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "timeout", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "timeoutPropose", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 1, + "step": "propose", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 25 + }, + "_Event": { + "height": 2, + "name": "TimeoutPropose", + "round": 1, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": 2, + "id": "nil", + "round": 1, + "src": "Josef", + "step": "prevote" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 1, + "step": "prevote", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 26 + }, + "_Event": { + "height": 2, + "name": "TimeoutPropose", + "round": 1, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": 2, + "id": "nil", + "round": 1, + "src": "Josef", + "step": "prevote" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 1, + "step": "prevote", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 27 + }, + "_Event": { + "height": 2, + "name": "PolkaNil", + "round": 1, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": 2, + "id": "nil", + "round": 1, + "src": "Josef", + "step": "precommit" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 1, + "step": "precommit", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 28 + }, + "_Event": { + "height": 2, + "name": "PolkaNil", + "round": 1, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "", + "voteMessage": { + "height": 2, + "id": "nil", + "round": 1, + "src": "Josef", + "step": "precommit" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 1, + "step": "precommit", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 29 + }, + "_Event": { + "height": 2, + "name": "PrecommitAny", + "round": 1, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "timeout", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "timeoutPrecommit", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 1, + "step": "precommit", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 30 + }, + "_Event": { + "height": 2, + "name": "PrecommitAny", + "round": 1, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "timeout", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "timeoutPrecommit", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 1, + "step": "precommit", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 31 + }, + "_Event": { + "height": 2, + "name": "TimeoutPrecommit", + "round": 1, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "skipRound", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": 2, + "timeout": "", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 1, + "step": "precommit", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 32 + }, + "_Event": { + "height": 2, + "name": "TimeoutPrecommit", + "round": 1, + "value": "nextBlock", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "skipRound", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": 2, + "timeout": "", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 1, + "step": "precommit", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 33 + }, + "_Event": { + "height": 2, + "name": "NewRound", + "round": 2, + "value": "", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "timeout", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "timeoutPropose", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 2, + "step": "propose", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 34 + }, + "_Event": { + "height": 2, + "name": "NewRound", + "round": 2, + "value": "", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "timeout", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": -1, + "timeout": "timeoutPropose", + "voteMessage": { + "height": -1, + "id": "", + "round": -1, + "src": "", + "step": "" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 2, + "step": "propose", + "validRound": -1, + "validValue": "nil" + } + ] + ] + } + }, + { + "#meta": { + "index": 35 + }, + "_Event": { + "height": 2, + "name": "ProposalInvalid", + "round": 2, + "value": "", + "vr": -1 + }, + "_Result": { + "decided": "", + "name": "votemessage", + "proposal": { + "height": -1, + "proposal": "", + "round": -1, + "src": "", + "validRound": -1 + }, + "skipRound": 42, + "timeout": "", + "voteMessage": { + "height": 2, + "id": "nil", + "round": 2, + "src": "Josef", + "step": "prevote" + } + }, + "system": { + "#map": [ + [ + "Josef", + { + "height": 2, + "lockedRound": -1, + "lockedValue": "nil", + "p": "Josef", + "round": 2, + "step": "prevote", + "validRound": 200, + "validValue": "nil" + } + ] + ] + } + } + ] +} diff --git a/serde-itf/Cargo.toml b/serde-itf/Cargo.toml new file mode 100644 index 0000000..3c59e02 --- /dev/null +++ b/serde-itf/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "serde-itf" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +num-bigint = { version = "0.4.4", features = ["serde"] } +num-traits = "0.2.17" +serde = { version = "1.0.190", features = ["derive"] } +serde_json = { version = "1.0.107", features = ["raw_value"] } diff --git a/serde-itf/src/bigint.rs b/serde-itf/src/bigint.rs new file mode 100644 index 0000000..abd6369 --- /dev/null +++ b/serde-itf/src/bigint.rs @@ -0,0 +1,56 @@ +use core::fmt; +use std::ops::Deref; + +use serde::ser::SerializeStruct; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct BigInt(num_bigint::BigInt); + +impl Deref for BigInt { + type Target = num_bigint::BigInt; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Debug for BigInt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Display for BigInt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Serialize for BigInt { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut s = serializer.serialize_struct("BigInt", 1)?; + s.serialize_field("#bigint", &self.to_string())?; + s.end() + } +} + +impl<'de> Deserialize<'de> for BigInt { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct BigInt { + #[serde(rename = "#bigint")] + bigint: String, + } + + let inner = BigInt::deserialize(deserializer)?; + let bigint = inner.bigint.parse().map_err(serde::de::Error::custom)?; + Ok(Self(bigint)) + } +} diff --git a/serde-itf/src/de.rs b/serde-itf/src/de.rs new file mode 100644 index 0000000..74a4376 --- /dev/null +++ b/serde-itf/src/de.rs @@ -0,0 +1,447 @@ +use std::fmt; +use std::marker::PhantomData; + +use num_traits::ToPrimitive; +use serde::de::value::{MapDeserializer, SeqDeserializer}; +use serde::de::{ + DeserializeOwned, DeserializeSeed, Deserializer, EnumAccess, Error as SerdeError, + IntoDeserializer, VariantAccess, Visitor, +}; + +use crate::bigint::BigInt; +use crate::value::{Type, Value}; + +pub fn decode_value(value: Value) -> Result +where + T: DeserializeOwned, +{ + T::deserialize(value.into_deserializer()) +} + +#[derive(Debug)] +pub enum Error { + Custom(String), + TypeMismatch(Type, Type), + BigInt(BigInt, &'static str), + UnsupportedType(&'static str), + Number(i64, &'static str), +} + +impl std::error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Custom(msg) => msg.fmt(f), + + Error::TypeMismatch(expected, actual) => { + write!(f, "type mismatch: expected {expected:?}, found {actual:?}") + } + + Error::BigInt(value, expected) => { + write!(f, "cannot convert {value} to {expected}") + } + + Error::Number(value, expected) => { + write!(f, "cannot convert {value} to {expected}") + } + + Error::UnsupportedType(ty) => write!(f, "unsupported type: {ty}"), + } + } +} + +impl SerdeError for Error { + fn custom(msg: T) -> Self + where + T: fmt::Display, + { + Self::Custom(msg.to_string()) + } +} + +impl<'de> IntoDeserializer<'de, Error> for Value { + type Deserializer = ValueDeserializer; + + fn into_deserializer(self) -> Self::Deserializer { + ValueDeserializer { input: self } + } +} + +pub struct ValueDeserializer { + input: Value, +} + +impl<'de> Deserializer<'de> for ValueDeserializer { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Bool(v) => visitor.visit_bool(v), + Value::Number(v) => visitor.visit_i64(v), + Value::String(v) => visitor.visit_string(v), + Value::BigInt(v) => visitor.visit_i64(v.to_i64().unwrap()), + Value::List(v) => visitor.visit_seq(SeqDeserializer::new(v.into_iter())), + Value::Tuple(v) => visitor.visit_seq(SeqDeserializer::new(v.into_iter())), + Value::Set(v) => visitor.visit_seq(SeqDeserializer::new(v.into_iter())), + Value::Record(v) => visitor.visit_map(MapDeserializer::new(v.into_iter())), + Value::Map(v) => visitor.visit_map(MapDeserializer::new(v.into_iter())), + Value::Unserializable(_) => Err(Error::UnsupportedType("unserializable")), + } + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Bool(v) => visitor.visit_bool(v), + value => Err(Error::TypeMismatch(Type::Bool, value.value_type())), + } + } + + fn deserialize_i8(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Number(v) => visitor.visit_i8(v.to_i8().ok_or(Error::Number(v, "i8"))?), + Value::BigInt(v) => visitor.visit_i8(v.to_i8().ok_or(Error::BigInt(v, "i8"))?), + value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), + } + } + + fn deserialize_i16(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Number(v) => visitor.visit_i16(v.to_i16().ok_or(Error::Number(v, "i16"))?), + Value::BigInt(v) => visitor.visit_i16(v.to_i16().ok_or(Error::BigInt(v, "i16"))?), + value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), + } + } + + fn deserialize_i32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Number(v) => visitor.visit_i32(v.to_i32().ok_or(Error::Number(v, "i32"))?), + Value::BigInt(v) => visitor.visit_i32(v.to_i32().ok_or(Error::BigInt(v, "i32"))?), + value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), + } + } + + fn deserialize_i64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Number(v) => visitor.visit_i64(v.to_i64().ok_or(Error::Number(v, "i64"))?), + Value::BigInt(v) => visitor.visit_i64(v.to_i64().ok_or(Error::BigInt(v, "i64"))?), + value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), + } + } + + fn deserialize_u8(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Number(v) => visitor.visit_u8(v.to_u8().ok_or(Error::Number(v, "u8"))?), + Value::BigInt(v) => visitor.visit_u8(v.to_u8().ok_or(Error::BigInt(v, "u8"))?), + value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), + } + } + + fn deserialize_u16(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Number(v) => visitor.visit_u16(v.to_u16().ok_or(Error::Number(v, "u16"))?), + Value::BigInt(v) => visitor.visit_u16(v.to_u16().ok_or(Error::BigInt(v, "u16"))?), + value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), + } + } + + fn deserialize_u32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Number(v) => visitor.visit_u32(v.to_u32().ok_or(Error::Number(v, "u32"))?), + Value::BigInt(v) => visitor.visit_u32(v.to_u32().ok_or(Error::BigInt(v, "u32"))?), + value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), + } + } + + fn deserialize_u64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Number(v) => visitor.visit_u64(v.to_u64().ok_or(Error::Number(v, "u64"))?), + Value::BigInt(v) => visitor.visit_u64(v.to_u64().ok_or(Error::BigInt(v, "u64"))?), + value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), + } + } + + fn deserialize_f32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Number(v) => visitor.visit_f32(v.to_f32().ok_or(Error::Number(v, "f32"))?), + Value::BigInt(v) => visitor.visit_f32(v.to_f32().ok_or(Error::BigInt(v, "f32"))?), + value => Err(Error::TypeMismatch(Type::Float, value.value_type())), + } + } + + fn deserialize_f64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Number(v) => visitor.visit_f64(v.to_f64().ok_or(Error::Number(v, "f64"))?), + Value::BigInt(v) => visitor.visit_f64(v.to_f64().ok_or(Error::BigInt(v, "f64"))?), + value => Err(Error::TypeMismatch(Type::Float, value.value_type())), + } + } + + fn deserialize_char(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::String(v) if v.len() == 1 => visitor.visit_char(v.chars().next().unwrap()), + value => Err(Error::TypeMismatch(Type::Char, value.value_type())), + } + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::String(v) => visitor.visit_str(&v), + value => Err(Error::TypeMismatch(Type::String, value.value_type())), + } + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) + } + + fn deserialize_bytes(self, _visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(Error::UnsupportedType("bytes")) + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_bytes(visitor) + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_some(self) + } + + fn deserialize_unit(self, _visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(Error::UnsupportedType("unit")) + } + + fn deserialize_unit_struct( + self, + _name: &'static str, + _visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + todo!() + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::List(v) => visitor.visit_seq(SeqDeserializer::new(v.into_iter())), + Value::Tuple(v) => visitor.visit_seq(SeqDeserializer::new(v.into_iter())), + value => Err(Error::TypeMismatch(Type::List, value.value_type())), + } + } + + fn deserialize_tuple(self, len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Tuple(v) if v.len() == len => { + visitor.visit_seq(SeqDeserializer::new(v.into_iter())) + } + value => Err(Error::TypeMismatch(Type::Tuple, value.value_type())), + } + } + + fn deserialize_tuple_struct( + self, + _name: &'static str, + len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_tuple(len, visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::Record(v) => visitor.visit_map(MapDeserializer::new(v.into_iter())), + Value::Map(v) => visitor.visit_map(MapDeserializer::new(v.into_iter())), + value => Err(Error::TypeMismatch(Type::Record, value.value_type())), + } + } + + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + match self.input { + Value::String(v) => visitor.visit_enum(v.into_deserializer()), + Value::Map(v) => visitor.visit_enum(Enum::new(Value::Map(v))), + Value::Record(v) => visitor.visit_enum(Enum::new(Value::Record(v))), + value => Err(Error::TypeMismatch(Type::Enum, value.value_type())), + } + } + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_any(visitor) + } +} + +struct Enum<'de> { + value: Value, + marker: PhantomData<&'de ()>, +} + +impl<'de> Enum<'de> { + fn new(value: Value) -> Self { + Enum { + value, + marker: PhantomData, + } + } +} + +impl<'de> EnumAccess<'de> for Enum<'de> { + type Error = Error; + type Variant = Self; + + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: serde::de::DeserializeSeed<'de>, + { + dbg!(&self.value); + let value = seed.deserialize(self.value.clone().into_deserializer())?; + Ok((value, self)) + } +} + +impl<'de> VariantAccess<'de> for Enum<'de> { + type Error = Error; + + fn unit_variant(self) -> Result<(), Error> { + match self.value { + Value::String(_) => Ok(()), + value => Err(Error::TypeMismatch(Type::String, value.value_type())), + } + } + + fn newtype_variant_seed(self, seed: T) -> Result + where + T: DeserializeSeed<'de>, + { + seed.deserialize(self.value.into_deserializer()) + } + + fn tuple_variant(self, _len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + dbg!(_len); + Deserializer::deserialize_seq(self.value.into_deserializer(), visitor) + } + + fn struct_variant( + self, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + dbg!(_fields); + Deserializer::deserialize_map(self.value.into_deserializer(), visitor) + } +} diff --git a/serde-itf/src/error.rs b/serde-itf/src/error.rs new file mode 100644 index 0000000..d424aa6 --- /dev/null +++ b/serde-itf/src/error.rs @@ -0,0 +1,48 @@ +#[derive(Debug)] +pub enum Error { + Io(std::io::Error), + Json(serde_json::Error), + Decode(crate::de::Error), +} + +impl From for Error { + fn from(v: std::io::Error) -> Self { + Self::Io(v) + } +} + +impl From for Error { + fn from(v: serde_json::Error) -> Self { + Self::Json(v) + } +} + +impl From for Error { + fn from(v: crate::de::Error) -> Self { + Self::Decode(v) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Error::Io(e) => write!(f, "I/O error: {}", e), + Error::Json(e) => write!(f, "JSON error: {}", e), + Error::Decode(e) => write!(f, "decoding error: {}", e), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Io(e) => Some(e), + Error::Json(e) => Some(e), + Error::Decode(e) => Some(e), + } + } + + fn cause(&self) -> Option<&dyn std::error::Error> { + self.source() + } +} diff --git a/serde-itf/src/lib.rs b/serde-itf/src/lib.rs new file mode 100644 index 0000000..c43d67d --- /dev/null +++ b/serde-itf/src/lib.rs @@ -0,0 +1,103 @@ +use serde::de::DeserializeOwned; + +pub mod bigint; +pub mod de; +pub mod error; +pub mod map; +pub mod meta; +pub mod set; +pub mod state; +pub mod trace; +pub mod tuple; +pub mod unserializable; +pub mod value; + +use error::Error; +use trace::Trace; +use value::Value; + +pub fn from_str(s: &str) -> Result, Error> +where + S: DeserializeOwned, +{ + let value = serde_json::from_str(s)?; + from_value(value) +} + +pub fn from_value(value: serde_json::Value) -> Result, Error> +where + S: DeserializeOwned, +{ + let trace_value: Trace = serde_json::from_value(value)?; + trace_value.decode() +} + +#[cfg(test)] +mod tests { + use std::collections::{BTreeMap, BTreeSet}; + + use crate::value::Value; + + use super::*; + + use serde::Deserialize; + + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize)] + enum Bank { + #[serde(rename = "N")] + North, + #[serde(rename = "W")] + West, + #[serde(rename = "E")] + East, + #[serde(rename = "S")] + South, + } + + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize)] + enum Person { + #[serde(rename = "c1_OF_PERSON")] + Cannibal1, + #[serde(rename = "c2_OF_PERSON")] + Cannibal2, + #[serde(rename = "m1_OF_PERSON")] + Missionary1, + #[serde(rename = "m2_OF_PERSON")] + Missionary2, + } + + #[derive(Clone, Debug, Deserialize)] + #[allow(dead_code)] + struct State { + pub bank_of_boat: Bank, + pub who_is_on_bank: BTreeMap>, + } + + #[test] + fn de_cannibals() -> Result<(), Error> { + let path = format!( + "{}/../itf/tests/fixtures/MissionariesAndCannibals.itf.json", + env!("CARGO_MANIFEST_DIR") + ); + + let fixture = std::fs::read_to_string(path)?; + let trace: Trace = crate::from_str(&fixture)?; + dbg!(trace); + + Ok(()) + } + + #[test] + fn de_consensus() -> Result<(), Error> { + let path = format!( + "{}/../itf/tests/fixtures/DecideNonProposerTest0.itf.json", + env!("CARGO_MANIFEST_DIR") + ); + + let fixture = std::fs::read_to_string(path)?; + let trace: Trace = crate::from_str(&fixture)?; + dbg!(trace); + + Ok(()) + } +} diff --git a/serde-itf/src/map.rs b/serde-itf/src/map.rs new file mode 100644 index 0000000..0a8a902 --- /dev/null +++ b/serde-itf/src/map.rs @@ -0,0 +1,131 @@ +use core::fmt; +use std::collections::BTreeMap; + +/// A map of the form `{ "#map": [ [ , ], ..., [ , ] ] }`. +/// +/// That is, a map holds a JSON array of two-element arrays. +/// Each two-element array p is interpreted as follows: +/// - p[0] is the map key +/// - p[1] is the map value +/// +/// Importantly, a key may be an arbitrary expression. +/// It does not have to be a string or an integer. +/// TLA+ functions are written as maps in this format. +#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Map { + map: BTreeMap, +} + +impl Map { + pub fn new() -> Self { + Self { + map: BTreeMap::new(), + } + } + + pub fn iter(&self) -> impl Iterator { + self.map.iter() + } + + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } +} + +impl IntoIterator for Map { + type Item = (K, V); + type IntoIter = std::collections::btree_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.map.into_iter() + } +} + +impl fmt::Debug for Map +where + K: fmt::Debug, + V: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.map.fmt(f) + } +} + +use serde::ser::{Serialize, SerializeMap, Serializer}; +use serde::Deserialize; + +use crate::value::Value; + +/// Serialize into a JSON object of this form: +/// +///```ignore +/// { +/// "#map": [ +/// [ , ], +/// ..., +/// [ , ] +/// ] +/// } +/// ``` +impl Serialize for Map +where + K: Serialize, + V: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // let mut pairs = serializer.serialize_seq(Some(self.map.len()))?; + // + // for (key, value) in &self.map { + // let mut pair = serializer.serialize_tuple(2)?; + // pair.serialize_element(key)?; + // pair.serialize_element(value); + // } + // + // let pairs = pairs.end()?; + + let pairs = self.map.iter().collect::>(); + + let mut object = serializer.serialize_map(Some(1))?; + object.serialize_entry("#map", &pairs)?; + object.end() + } +} + +impl<'de, V> Deserialize<'de> for Map +where + V: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct InnerMap { + #[serde(rename = "#map")] + map: Vec<(Value, V)>, + } + + let map = InnerMap::deserialize(deserializer)? + .map + .into_iter() + .collect(); + + Ok(Map { map }) + } +} + +impl<'de, V> Deserialize<'de> for Map +where + V: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let map = BTreeMap::::deserialize(deserializer)?; + Ok(Map { map }) + } +} diff --git a/serde-itf/src/meta.rs b/serde-itf/src/meta.rs new file mode 100644 index 0000000..7b1355c --- /dev/null +++ b/serde-itf/src/meta.rs @@ -0,0 +1,27 @@ +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TraceMeta { + #[serde(default)] + pub format: Option, + + #[serde(rename = "format-description")] + pub format_description: Option, + + #[serde(default)] + pub source: Option, + + #[serde(default)] + pub description: Option, + + #[serde(default, rename = "varTypes")] + pub var_types: BTreeMap, + + #[serde(default)] + pub timestamp: Option, + + #[serde(flatten)] + pub other: BTreeMap, +} diff --git a/serde-itf/src/set.rs b/serde-itf/src/set.rs new file mode 100644 index 0000000..0242a02 --- /dev/null +++ b/serde-itf/src/set.rs @@ -0,0 +1,82 @@ +use core::fmt; +use std::collections::BTreeSet; + +/// A set of the form `{ "#set": [ , ..., ] }`. +/// +/// A set is different from a list in that it does not assume any ordering of its elements. +/// However, it is only a syntax form in our format. +/// Apalache distinguishes between sets and lists and thus it will output sets in the set form. +/// Other tools may interpret sets as lists. +#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Set { + set: BTreeSet, +} + +impl Set { + pub fn new() -> Self { + Self { + set: BTreeSet::new(), + } + } + + pub fn iter(&self) -> impl Iterator { + self.set.iter() + } +} + +impl IntoIterator for Set { + type Item = V; + type IntoIter = std::collections::btree_set::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.set.into_iter() + } +} + +impl fmt::Debug for Set +where + V: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.set.fmt(f) + } +} + +use serde::ser::{Serialize, SerializeMap, Serializer}; +use serde::Deserialize; + +impl Serialize for Set +where + V: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let elements = self.set.iter().collect::>(); + + let mut object = serializer.serialize_map(Some(1))?; + object.serialize_entry("#set", &elements)?; + object.end() + } +} + +impl<'de, V> Deserialize<'de> for Set +where + V: Deserialize<'de> + Ord, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct InnerSet { + #[serde(rename = "#set")] + set: BTreeSet, + } + + let set = InnerSet::deserialize(deserializer)?.set; + + Ok(Set { set }) + } +} diff --git a/serde-itf/src/state.rs b/serde-itf/src/state.rs new file mode 100644 index 0000000..3123209 --- /dev/null +++ b/serde-itf/src/state.rs @@ -0,0 +1,88 @@ +use std::collections::BTreeMap; + +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; + +use crate::error::Error; +use crate::value::Value; + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Meta { + #[serde(default)] + pub index: Option, + + #[serde(flatten)] + pub other: BTreeMap, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct State { + #[serde(rename = "#meta")] + pub meta: Meta, + + #[serde(flatten)] + pub value: S, +} + +impl State { + pub fn decode(self) -> Result, Error> + where + S: DeserializeOwned, + { + let value: Value = serde_json::from_value(self.value)?; + let inner: S = crate::de::decode_value(value)?; + + Ok(State { + meta: self.meta, + value: inner, + }) + } +} + +impl State { + pub fn decode(self) -> Result, Error> + where + S: DeserializeOwned, + { + let inner: S = crate::de::decode_value(self.value)?; + + Ok(State { + meta: self.meta, + value: inner, + }) + } +} + +// use serde_json::value::RawValue; +// +// impl State> { +// pub fn decode(self) -> Result, Error> +// where +// S: DeserializeOwned, +// { +// let value: Value = serde_json::from_str(self.value.get())?; +// dbg!(&value); +// let inner: S = crate::de::decode_value(value)?; +// +// Ok(State { +// meta: self.meta, +// value: inner, +// }) +// } +// } +// +// impl<'a> State<&'a RawValue> { +// pub fn decode(self) -> Result, Error> +// where +// S: DeserializeOwned, +// { +// let value: Value = serde_json::from_str(self.value.get())?; +// dbg!(&value); +// let inner: S = crate::de::decode_value(value)?; +// +// Ok(State { +// meta: self.meta, +// value: inner, +// }) +// } +// } diff --git a/serde-itf/src/trace.rs b/serde-itf/src/trace.rs new file mode 100644 index 0000000..d6054bc --- /dev/null +++ b/serde-itf/src/trace.rs @@ -0,0 +1,135 @@ +use std::collections::BTreeMap; + +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; + +use crate::error::Error; +use crate::state::State; +use crate::value::Value; + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Meta { + #[serde(default)] + pub format: Option, + + #[serde(rename = "format-description")] + pub format_description: Option, + + #[serde(default)] + pub source: Option, + + #[serde(default)] + pub description: Option, + + #[serde(default, rename = "varTypes")] + pub var_types: BTreeMap, + + #[serde(default)] + pub timestamp: Option, + + #[serde(flatten)] + pub other: BTreeMap, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Trace { + #[serde(rename = "#meta")] + pub meta: Meta, + + #[serde(default)] + pub params: Vec, + + #[serde(default)] + pub vars: Vec, + + #[serde(default, rename = "loop")] + pub loop_index: Option, + + pub states: Vec>, +} + +impl Trace { + pub fn decode(self) -> Result, Error> + where + S: DeserializeOwned, + { + let states = self + .states + .into_iter() + .map(|state| state.decode()) + .collect::, _>>()?; + + Ok(Trace { + meta: self.meta, + params: self.params, + vars: self.vars, + loop_index: self.loop_index, + states, + }) + } +} + +impl Trace { + pub fn decode(self) -> Result, Error> + where + S: DeserializeOwned, + { + let states = self + .states + .into_iter() + .map(|state| state.decode()) + .collect::, _>>()?; + + Ok(Trace { + meta: self.meta, + params: self.params, + vars: self.vars, + loop_index: self.loop_index, + states, + }) + } +} + +// use serde_json::value::RawValue; +// +// impl Trace> { +// pub fn decode(self) -> Result, Error> +// where +// S: DeserializeOwned, +// { +// let states = self +// .states +// .into_iter() +// .map(|state| state.decode()) +// .collect::, _>>()?; +// +// Ok(Trace { +// meta: self.meta, +// params: self.params, +// vars: self.vars, +// loop_index: self.loop_index, +// states, +// }) +// } +// } +// +// impl<'a> Trace<&'a RawValue> { +// pub fn decode(self) -> Result, Error> +// where +// S: DeserializeOwned, +// { +// let states = self +// .states +// .into_iter() +// .map(|state| state.decode()) +// .collect::, _>>()?; +// +// Ok(Trace { +// meta: self.meta, +// params: self.params, +// vars: self.vars, +// loop_index: self.loop_index, +// states, +// }) +// } +// } diff --git a/serde-itf/src/tuple.rs b/serde-itf/src/tuple.rs new file mode 100644 index 0000000..b5255ea --- /dev/null +++ b/serde-itf/src/tuple.rs @@ -0,0 +1,87 @@ +use core::fmt; + +/// A tuple of the form `{ "#tup": [ , ..., ] }`. +/// +/// There is no strict rule about when to use sequences or tuples. +/// Apalache differentiates between tuples and sequences, and it may produce both forms of expressions. +#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Tuple { + elements: Vec, +} + +impl Tuple { + pub fn new() -> Self { + Self { + elements: Vec::new(), + } + } + + pub fn len(&self) -> usize { + self.elements.len() + } + + pub fn is_empty(&self) -> bool { + self.elements.is_empty() + } + + pub fn iter(&self) -> impl Iterator { + self.elements.iter() + } +} + +impl IntoIterator for Tuple { + type Item = V; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.elements.into_iter() + } +} + +impl fmt::Debug for Tuple +where + V: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.elements.fmt(f) + } +} + +use serde::ser::{Serialize, SerializeMap, Serializer}; +use serde::Deserialize; + +impl Serialize for Tuple +where + V: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let elements = self.elements.iter().collect::>(); + + let mut object = serializer.serialize_map(Some(1))?; + object.serialize_entry("#tup", &elements)?; + object.end() + } +} + +impl<'de, V> Deserialize<'de> for Tuple +where + V: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct InnerTuple { + #[serde(rename = "#set")] + elements: Vec, + } + + let elements = InnerTuple::deserialize(deserializer)?.elements; + + Ok(Tuple { elements }) + } +} diff --git a/serde-itf/src/unserializable.rs b/serde-itf/src/unserializable.rs new file mode 100644 index 0000000..625bab6 --- /dev/null +++ b/serde-itf/src/unserializable.rs @@ -0,0 +1,38 @@ +/// An expression that cannot be serialized: `{ "#unserializable": "" }`. +/// +/// For instance, the set of all integers is represented with `{ "#unserializable": "Int" }`. +/// This should be a very rare expression, which should not occur in normal traces. +/// Usually, it indicates some form of an error. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Unserializable(String); + +use serde::ser::{Serialize, SerializeMap, Serializer}; +use serde::Deserialize; + +impl Serialize for Unserializable { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut object = serializer.serialize_map(Some(1))?; + object.serialize_entry("#unserializable", &self.0)?; + object.end() + } +} + +impl<'de> Deserialize<'de> for Unserializable { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Inner { + #[serde(rename = "#unserializable")] + string: String, + } + + let inner = Inner::deserialize(deserializer)?.string; + + Ok(Self(inner)) + } +} diff --git a/serde-itf/src/value.rs b/serde-itf/src/value.rs new file mode 100644 index 0000000..3efa037 --- /dev/null +++ b/serde-itf/src/value.rs @@ -0,0 +1,110 @@ +use serde::{Deserialize, Serialize}; + +use crate::bigint::BigInt; +use crate::map::Map; +use crate::set::Set; +use crate::tuple::Tuple; +use crate::unserializable::Unserializable; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Value { + /// A JSON Boolean: either `false` or `true`. + Bool(bool), + + /// A JSON string literal, e.g., `"hello"`. + /// + /// TLA+ strings are written as strings in this format. + String(String), + + /// A JSON number literal, e.g., `42`. + Number(i64), + + /// A big integer of the following form: { "#bigint": "[-][0-9]+" }. + /// + /// We are using this format, as many JSON parsers impose limits + /// on integer values, see RFC7159. + /// Big and small integers must be written in this format. + BigInt(BigInt), + + /// A list of the form `[ , ..., ]`. + /// + /// A list is just a JSON array. + /// TLA+ sequences are written as lists in this format. + List(Vec), + + /// A tuple of the form `{ "#tup": [ , ..., ] }`. + /// + /// There is no strict rule about when to use sequences or tuples. + /// Apalache differentiates between tuples and sequences, and it may produce both forms of expressions. + Tuple(Tuple), + + /// A set of the form `{ "#set": [ , ..., ] }`. + /// + /// A set is different from a list in that it does not assume any ordering of its elements. + /// However, it is only a syntax form in our format. + /// Apalache distinguishes between sets and lists and thus it will output sets in the set form. + /// Other tools may interpret sets as lists. + Set(Set), + + /// A map of the form `{ "#map": [ [ , ], ..., [ , ] ] }`. + /// + /// That is, a map holds a JSON array of two-element arrays. + /// Each two-element array p is interpreted as follows: + /// p[0] is the map key and p[1] is the map value. + /// + /// Importantly, a key may be an arbitrary expression. + /// It does not have to be a string or an integer. + /// TLA+ functions are written as maps in this format. + Map(Map), + + /// A record of the form `{ "field1": , ..., "fieldN": }`. + /// + /// A record is just a JSON object. Field names should not start with `#` and + /// hence should not pose any collision with other constructs. + /// TLA+ records are written as records in this format. + Record(Map), + + /// An expression that cannot be serialized: `{ "#unserializable": "" }`. + /// + /// For instance, the set of all integers is represented with `{ "#unserializable": "Int" }`. + /// This should be a very rare expression, which should not occur in normal traces. + /// Usually, it indicates some form of an error. + Unserializable(Unserializable), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Type { + Bool, + Number, + String, + BigInt, + List, + Record, + Tuple, + Set, + Map, + Unserializable, + Float, + Char, + Enum, +} + +// TODO: Display + +impl Value { + pub fn value_type(&self) -> Type { + match self { + Value::Bool(_) => Type::Bool, + Value::Number(_) => Type::Number, + Value::String(_) => Type::String, + Value::BigInt(_) => Type::BigInt, + Value::List(_) => Type::List, + Value::Record(_) => Type::Record, + Value::Tuple(_) => Type::Tuple, + Value::Set(_) => Type::Set, + Value::Map(_) => Type::Map, + Value::Unserializable(_) => Type::Unserializable, + } + } +} From f539d1616c4eecf03ca91155406909aac614ea1e Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 26 Oct 2023 21:24:00 +0200 Subject: [PATCH 02/50] Use the Visitor pattern --- serde-itf/src/de.rs | 430 ++++++++++++++++++++++++------------------- serde-itf/src/lib.rs | 33 +++- serde-itf/src/map.rs | 4 + 3 files changed, 271 insertions(+), 196 deletions(-) diff --git a/serde-itf/src/de.rs b/serde-itf/src/de.rs index 74a4376..bac8444 100644 --- a/serde-itf/src/de.rs +++ b/serde-itf/src/de.rs @@ -1,21 +1,24 @@ use std::fmt; -use std::marker::PhantomData; use num_traits::ToPrimitive; use serde::de::value::{MapDeserializer, SeqDeserializer}; use serde::de::{ - DeserializeOwned, DeserializeSeed, Deserializer, EnumAccess, Error as SerdeError, - IntoDeserializer, VariantAccess, Visitor, + DeserializeOwned, DeserializeSeed, Deserializer, EnumAccess, Error as SerdeError, Expected, + IntoDeserializer, Unexpected, VariantAccess, Visitor, }; +use serde::Deserialize; use crate::bigint::BigInt; +use crate::map::Map; +use crate::set::Set; +use crate::tuple::Tuple; use crate::value::{Type, Value}; pub fn decode_value(value: Value) -> Result where T: DeserializeOwned, { - T::deserialize(value.into_deserializer()) + T::deserialize(value) } #[derive(Debug)] @@ -60,223 +63,173 @@ impl SerdeError for Error { } } -impl<'de> IntoDeserializer<'de, Error> for Value { - type Deserializer = ValueDeserializer; +impl Value { + fn invalid_type(&self, exp: &dyn Expected) -> E + where + E: serde::de::Error, + { + serde::de::Error::invalid_type(self.unexpected(), exp) + } - fn into_deserializer(self) -> Self::Deserializer { - ValueDeserializer { input: self } + fn unexpected(&self) -> Unexpected { + match self { + Value::Bool(b) => Unexpected::Bool(*b), + Value::Number(n) => Unexpected::Signed(*n), + Value::String(s) => Unexpected::Str(s), + Value::List(_) => Unexpected::Seq, + Value::Map(_) => Unexpected::Map, + Value::Record(_) => Unexpected::Other("record"), + Value::BigInt(_) => Unexpected::Other("bigint"), + Value::Tuple(_) => Unexpected::Other("tuple"), + Value::Set(_) => Unexpected::Other("set"), + Value::Unserializable(_) => Unexpected::Other("unserializable"), + } } } -pub struct ValueDeserializer { - input: Value, +macro_rules! deserialize_number { + ($ty:ident, $visit:ident, $method:ident) => { + fn $method(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self { + Value::Number(n) => visitor.$visit($ty::try_from(n).unwrap()), + _ => Err(self.invalid_type(&visitor)), + } + } + }; +} + +impl<'de> IntoDeserializer<'de, Error> for Value { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { + self + } } -impl<'de> Deserializer<'de> for ValueDeserializer { +impl<'de> Deserializer<'de> for Value { type Error = Error; fn deserialize_any(self, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { + match self { Value::Bool(v) => visitor.visit_bool(v), Value::Number(v) => visitor.visit_i64(v), Value::String(v) => visitor.visit_string(v), Value::BigInt(v) => visitor.visit_i64(v.to_i64().unwrap()), - Value::List(v) => visitor.visit_seq(SeqDeserializer::new(v.into_iter())), - Value::Tuple(v) => visitor.visit_seq(SeqDeserializer::new(v.into_iter())), - Value::Set(v) => visitor.visit_seq(SeqDeserializer::new(v.into_iter())), - Value::Record(v) => visitor.visit_map(MapDeserializer::new(v.into_iter())), - Value::Map(v) => visitor.visit_map(MapDeserializer::new(v.into_iter())), + Value::List(v) => visit_list(v, visitor), + Value::Tuple(v) => visit_tuple(v, visitor), + Value::Set(v) => visit_set(v, visitor), + Value::Record(v) => visit_record(v, visitor), + Value::Map(v) => visit_map(v, visitor), Value::Unserializable(_) => Err(Error::UnsupportedType("unserializable")), } } - fn deserialize_bool(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self.input { - Value::Bool(v) => visitor.visit_bool(v), - value => Err(Error::TypeMismatch(Type::Bool, value.value_type())), - } - } + deserialize_number!(i8, visit_i8, deserialize_i8); + deserialize_number!(i16, visit_i16, deserialize_i16); + deserialize_number!(i32, visit_i32, deserialize_i32); + deserialize_number!(i64, visit_i64, deserialize_i64); + deserialize_number!(i128, visit_i128, deserialize_i128); + deserialize_number!(u8, visit_u8, deserialize_u8); + deserialize_number!(u16, visit_u16, deserialize_u16); + deserialize_number!(u32, visit_u32, deserialize_u32); + deserialize_number!(u64, visit_u64, deserialize_u64); + deserialize_number!(u128, visit_u128, deserialize_u128); - fn deserialize_i8(self, visitor: V) -> Result + fn deserialize_option(self, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { - Value::Number(v) => visitor.visit_i8(v.to_i8().ok_or(Error::Number(v, "i8"))?), - Value::BigInt(v) => visitor.visit_i8(v.to_i8().ok_or(Error::BigInt(v, "i8"))?), - value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), - } + visitor.visit_some(self) } - fn deserialize_i16(self, visitor: V) -> Result + fn deserialize_bool(self, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { - Value::Number(v) => visitor.visit_i16(v.to_i16().ok_or(Error::Number(v, "i16"))?), - Value::BigInt(v) => visitor.visit_i16(v.to_i16().ok_or(Error::BigInt(v, "i16"))?), - value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), + match self { + Value::Bool(v) => visitor.visit_bool(v), + _ => Err(self.invalid_type(&visitor)), } } - fn deserialize_i32(self, visitor: V) -> Result + fn deserialize_char(self, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { - Value::Number(v) => visitor.visit_i32(v.to_i32().ok_or(Error::Number(v, "i32"))?), - Value::BigInt(v) => visitor.visit_i32(v.to_i32().ok_or(Error::BigInt(v, "i32"))?), - value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), - } + self.deserialize_string(visitor) } - fn deserialize_i64(self, visitor: V) -> Result + fn deserialize_str(self, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { - Value::Number(v) => visitor.visit_i64(v.to_i64().ok_or(Error::Number(v, "i64"))?), - Value::BigInt(v) => visitor.visit_i64(v.to_i64().ok_or(Error::BigInt(v, "i64"))?), - value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), - } + self.deserialize_string(visitor) } - fn deserialize_u8(self, visitor: V) -> Result + fn deserialize_string(self, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { - Value::Number(v) => visitor.visit_u8(v.to_u8().ok_or(Error::Number(v, "u8"))?), - Value::BigInt(v) => visitor.visit_u8(v.to_u8().ok_or(Error::BigInt(v, "u8"))?), - value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), + match self { + Value::String(v) => visitor.visit_string(v), + _ => Err(self.invalid_type(&visitor)), } } - fn deserialize_u16(self, visitor: V) -> Result + fn deserialize_bytes(self, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { - Value::Number(v) => visitor.visit_u16(v.to_u16().ok_or(Error::Number(v, "u16"))?), - Value::BigInt(v) => visitor.visit_u16(v.to_u16().ok_or(Error::BigInt(v, "u16"))?), - value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), - } + self.deserialize_byte_buf(visitor) } - fn deserialize_u32(self, visitor: V) -> Result + fn deserialize_byte_buf(self, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { - Value::Number(v) => visitor.visit_u32(v.to_u32().ok_or(Error::Number(v, "u32"))?), - Value::BigInt(v) => visitor.visit_u32(v.to_u32().ok_or(Error::BigInt(v, "u32"))?), - value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), + match self { + Value::String(v) => visitor.visit_string(v), + Value::List(v) => visit_list(v, visitor), + _ => Err(self.invalid_type(&visitor)), } } - fn deserialize_u64(self, visitor: V) -> Result + fn deserialize_unit(self, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { - Value::Number(v) => visitor.visit_u64(v.to_u64().ok_or(Error::Number(v, "u64"))?), - Value::BigInt(v) => visitor.visit_u64(v.to_u64().ok_or(Error::BigInt(v, "u64"))?), - value => Err(Error::TypeMismatch(Type::BigInt, value.value_type())), - } + Err(self.invalid_type(&visitor)) } fn deserialize_f32(self, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { - Value::Number(v) => visitor.visit_f32(v.to_f32().ok_or(Error::Number(v, "f32"))?), - Value::BigInt(v) => visitor.visit_f32(v.to_f32().ok_or(Error::BigInt(v, "f32"))?), - value => Err(Error::TypeMismatch(Type::Float, value.value_type())), - } + Err(self.invalid_type(&visitor)) } fn deserialize_f64(self, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { - Value::Number(v) => visitor.visit_f64(v.to_f64().ok_or(Error::Number(v, "f64"))?), - Value::BigInt(v) => visitor.visit_f64(v.to_f64().ok_or(Error::BigInt(v, "f64"))?), - value => Err(Error::TypeMismatch(Type::Float, value.value_type())), - } - } - - fn deserialize_char(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self.input { - Value::String(v) if v.len() == 1 => visitor.visit_char(v.chars().next().unwrap()), - value => Err(Error::TypeMismatch(Type::Char, value.value_type())), - } - } - - fn deserialize_str(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self.input { - Value::String(v) => visitor.visit_str(&v), - value => Err(Error::TypeMismatch(Type::String, value.value_type())), - } - } - - fn deserialize_string(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_str(visitor) - } - - fn deserialize_bytes(self, _visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(Error::UnsupportedType("bytes")) - } - - fn deserialize_byte_buf(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_bytes(visitor) - } - - fn deserialize_option(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_some(self) - } - - fn deserialize_unit(self, _visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(Error::UnsupportedType("unit")) + Err(self.invalid_type(&visitor)) } fn deserialize_unit_struct( self, _name: &'static str, - _visitor: V, + visitor: V, ) -> Result where V: Visitor<'de>, { - todo!() + self.deserialize_unit(visitor) } fn deserialize_newtype_struct( @@ -294,45 +247,39 @@ impl<'de> Deserializer<'de> for ValueDeserializer { where V: Visitor<'de>, { - match self.input { - Value::List(v) => visitor.visit_seq(SeqDeserializer::new(v.into_iter())), - Value::Tuple(v) => visitor.visit_seq(SeqDeserializer::new(v.into_iter())), - value => Err(Error::TypeMismatch(Type::List, value.value_type())), + match self { + Value::List(v) => visit_list(v, visitor), + Value::Tuple(v) => visit_tuple(v, visitor), + _ => Err(self.invalid_type(&visitor)), } } - fn deserialize_tuple(self, len: usize, visitor: V) -> Result + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { - Value::Tuple(v) if v.len() == len => { - visitor.visit_seq(SeqDeserializer::new(v.into_iter())) - } - value => Err(Error::TypeMismatch(Type::Tuple, value.value_type())), - } + self.deserialize_seq(visitor) } fn deserialize_tuple_struct( self, _name: &'static str, - len: usize, + _len: usize, visitor: V, ) -> Result where V: Visitor<'de>, { - self.deserialize_tuple(len, visitor) + self.deserialize_seq(visitor) } fn deserialize_map(self, visitor: V) -> Result where V: Visitor<'de>, { - match self.input { - Value::Record(v) => visitor.visit_map(MapDeserializer::new(v.into_iter())), - Value::Map(v) => visitor.visit_map(MapDeserializer::new(v.into_iter())), - value => Err(Error::TypeMismatch(Type::Record, value.value_type())), + match self { + Value::Map(v) => visit_map(v, visitor), + _ => Err(self.invalid_type(&visitor)), } } @@ -345,7 +292,10 @@ impl<'de> Deserializer<'de> for ValueDeserializer { where V: Visitor<'de>, { - self.deserialize_map(visitor) + match self { + Value::Record(v) => visit_record(v, visitor), + _ => Err(self.invalid_type(&visitor)), + } } fn deserialize_enum( @@ -357,64 +307,129 @@ impl<'de> Deserializer<'de> for ValueDeserializer { where V: Visitor<'de>, { - match self.input { - Value::String(v) => visitor.visit_enum(v.into_deserializer()), - Value::Map(v) => visitor.visit_enum(Enum::new(Value::Map(v))), - Value::Record(v) => visitor.visit_enum(Enum::new(Value::Record(v))), - value => Err(Error::TypeMismatch(Type::Enum, value.value_type())), - } + let (variant, value) = match self { + Value::Record(value) => { + let mut iter = value.into_iter(); + let (variant, value) = match iter.next() { + Some(v) => v, + None => { + return Err(serde::de::Error::invalid_value( + Unexpected::Map, + &"map with a single key", + )); + } + }; + if iter.next().is_some() { + return Err(serde::de::Error::invalid_value( + Unexpected::Map, + &"map with a single key", + )); + } + (variant, Some(value)) + } + Value::String(variant) => (variant, None), + other => { + return Err(serde::de::Error::invalid_type( + other.unexpected(), + &"string or map", + )); + } + }; + + visitor.visit_enum(EnumDeserializer { variant, value }) } fn deserialize_identifier(self, visitor: V) -> Result where V: Visitor<'de>, { - self.deserialize_str(visitor) + self.deserialize_string(visitor) } fn deserialize_ignored_any(self, visitor: V) -> Result where V: Visitor<'de>, { - self.deserialize_any(visitor) + drop(self); + visitor.visit_unit() } } -struct Enum<'de> { - value: Value, - marker: PhantomData<&'de ()>, +fn visit_map<'de, V>(v: Map, visitor: V) -> Result +where + V: Visitor<'de>, +{ + let mut deserializer = MapDeserializer::new(v.into_iter()); + let map = visitor.visit_map(&mut deserializer)?; + Ok(map) } -impl<'de> Enum<'de> { - fn new(value: Value) -> Self { - Enum { - value, - marker: PhantomData, - } - } +fn visit_record<'de, V>(record: Map, visitor: V) -> Result +where + V: Visitor<'de>, +{ + let mut deserializer = MapDeserializer::new(record.into_iter()); + let map = visitor.visit_map(&mut deserializer)?; + Ok(map) +} + +fn visit_set<'de, V>(v: Set, visitor: V) -> Result +where + V: Visitor<'de>, +{ + let mut deserializer = SeqDeserializer::new(v.into_iter()); + let seq = visitor.visit_seq(&mut deserializer)?; + Ok(seq) +} + +fn visit_tuple<'de, V>(v: Tuple, visitor: V) -> Result +where + V: Visitor<'de>, +{ + let mut deserializer = SeqDeserializer::new(v.into_iter()); + let seq = visitor.visit_seq(&mut deserializer)?; + Ok(seq) +} + +fn visit_list<'de, V>(v: Vec, visitor: V) -> Result +where + V: Visitor<'de>, +{ + let mut deserializer = SeqDeserializer::new(v.into_iter()); + let seq = visitor.visit_seq(&mut deserializer)?; + Ok(seq) } -impl<'de> EnumAccess<'de> for Enum<'de> { +struct EnumDeserializer { + variant: String, + value: Option, +} + +impl<'de> EnumAccess<'de> for EnumDeserializer { type Error = Error; - type Variant = Self; + type Variant = VariantDeserializer; - fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Error> where - V: serde::de::DeserializeSeed<'de>, + V: DeserializeSeed<'de>, { - dbg!(&self.value); - let value = seed.deserialize(self.value.clone().into_deserializer())?; - Ok((value, self)) + let variant = self.variant.into_deserializer(); + let visitor = VariantDeserializer { value: self.value }; + seed.deserialize(variant).map(|v| (v, visitor)) } } -impl<'de> VariantAccess<'de> for Enum<'de> { +struct VariantDeserializer { + value: Option, +} + +impl<'de> VariantAccess<'de> for VariantDeserializer { type Error = Error; fn unit_variant(self) -> Result<(), Error> { match self.value { - Value::String(_) => Ok(()), - value => Err(Error::TypeMismatch(Type::String, value.value_type())), + Some(value) => Deserialize::deserialize(value), + None => Ok(()), } } @@ -422,15 +437,43 @@ impl<'de> VariantAccess<'de> for Enum<'de> { where T: DeserializeSeed<'de>, { - seed.deserialize(self.value.into_deserializer()) + match self.value { + Some(value) => seed.deserialize(value), + None => Err(serde::de::Error::invalid_type( + Unexpected::UnitVariant, + &"newtype variant", + )), + } } fn tuple_variant(self, _len: usize, visitor: V) -> Result where V: Visitor<'de>, { - dbg!(_len); - Deserializer::deserialize_seq(self.value.into_deserializer(), visitor) + match self.value { + Some(Value::Tuple(v)) => { + if v.is_empty() { + visitor.visit_unit() + } else { + visit_tuple(v, visitor) + } + } + // Some(Value::List(v)) => { + // if v.is_empty() { + // visitor.visit_unit() + // } else { + // visit_list(v, visitor) + // } + // } + Some(other) => Err(serde::de::Error::invalid_type( + other.unexpected(), + &"tuple variant", + )), + None => Err(serde::de::Error::invalid_type( + Unexpected::UnitVariant, + &"tuple variant", + )), + } } fn struct_variant( @@ -441,7 +484,16 @@ impl<'de> VariantAccess<'de> for Enum<'de> { where V: Visitor<'de>, { - dbg!(_fields); - Deserializer::deserialize_map(self.value.into_deserializer(), visitor) + match self.value { + Some(Value::Record(v)) => visit_record(v, visitor), + Some(other) => Err(serde::de::Error::invalid_type( + other.unexpected(), + &"struct variant", + )), + None => Err(serde::de::Error::invalid_type( + Unexpected::UnitVariant, + &"struct variant", + )), + } } } diff --git a/serde-itf/src/lib.rs b/serde-itf/src/lib.rs index c43d67d..9b89e10 100644 --- a/serde-itf/src/lib.rs +++ b/serde-itf/src/lib.rs @@ -1,4 +1,5 @@ use serde::de::DeserializeOwned; +use serde::Deserialize; pub mod bigint; pub mod de; @@ -16,15 +17,15 @@ use error::Error; use trace::Trace; use value::Value; -pub fn from_str(s: &str) -> Result, Error> +pub fn trace_from_str(str: &str) -> Result, Error> where - S: DeserializeOwned, + S: for<'de> Deserialize<'de>, { - let value = serde_json::from_str(s)?; - from_value(value) + let trace_value: Trace = serde_json::from_str(str)?; + trace_value.decode() } -pub fn from_value(value: serde_json::Value) -> Result, Error> +pub fn trace_from_value(value: serde_json::Value) -> Result, Error> where S: DeserializeOwned, { @@ -32,6 +33,24 @@ where trace_value.decode() } +pub fn from_str(str: &str) -> Result +where + S: for<'de> Deserialize<'de>, +{ + let value: Value = serde_json::from_str(str)?; + let data = S::deserialize(value)?; + Ok(data) +} + +pub fn from_value(value: serde_json::Value) -> Result +where + S: DeserializeOwned, +{ + let trace_value: Value = serde_json::from_value(value)?; + let s = S::deserialize(trace_value)?; + Ok(s) +} + #[cfg(test)] mod tests { use std::collections::{BTreeMap, BTreeSet}; @@ -81,7 +100,7 @@ mod tests { ); let fixture = std::fs::read_to_string(path)?; - let trace: Trace = crate::from_str(&fixture)?; + let trace: Trace = crate::trace_from_str(&fixture)?; dbg!(trace); Ok(()) @@ -95,7 +114,7 @@ mod tests { ); let fixture = std::fs::read_to_string(path)?; - let trace: Trace = crate::from_str(&fixture)?; + let trace: Trace = crate::trace_from_str(&fixture)?; dbg!(trace); Ok(()) diff --git a/serde-itf/src/map.rs b/serde-itf/src/map.rs index 0a8a902..89eaf1e 100644 --- a/serde-itf/src/map.rs +++ b/serde-itf/src/map.rs @@ -30,6 +30,10 @@ impl Map { pub fn is_empty(&self) -> bool { self.map.is_empty() } + + pub fn len(&self) -> usize { + self.map.len() + } } impl IntoIterator for Map { From 8f0dbfd88685d47706536e582c8a58288baf0505 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 26 Oct 2023 21:31:45 +0200 Subject: [PATCH 03/50] Support for BigInt --- serde-itf/src/de.rs | 40 ++++++++++++++++++++++++++++------------ serde-itf/src/lib.rs | 9 +++------ 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/serde-itf/src/de.rs b/serde-itf/src/de.rs index bac8444..50455de 100644 --- a/serde-itf/src/de.rs +++ b/serde-itf/src/de.rs @@ -88,13 +88,29 @@ impl Value { } macro_rules! deserialize_number { - ($ty:ident, $visit:ident, $method:ident) => { + ($ty:ty, $to:ident, $visit:ident, $method:ident) => { fn $method(self, visitor: V) -> Result where V: Visitor<'de>, { match self { - Value::Number(n) => visitor.$visit($ty::try_from(n).unwrap()), + Value::Number(n) => { + let num = <$ty>::try_from(n).map_err(|_| { + serde::de::Error::invalid_type(Unexpected::Signed(n), &stringify!($ty)) + })?; + + visitor.$visit(num) + } + Value::BigInt(n) => { + let num = n.$to().ok_or_else(|| { + serde::de::Error::invalid_type( + Unexpected::Other("bigint"), + &stringify!($ty), + ) + })?; + + visitor.$visit(num) + } _ => Err(self.invalid_type(&visitor)), } } @@ -130,16 +146,16 @@ impl<'de> Deserializer<'de> for Value { } } - deserialize_number!(i8, visit_i8, deserialize_i8); - deserialize_number!(i16, visit_i16, deserialize_i16); - deserialize_number!(i32, visit_i32, deserialize_i32); - deserialize_number!(i64, visit_i64, deserialize_i64); - deserialize_number!(i128, visit_i128, deserialize_i128); - deserialize_number!(u8, visit_u8, deserialize_u8); - deserialize_number!(u16, visit_u16, deserialize_u16); - deserialize_number!(u32, visit_u32, deserialize_u32); - deserialize_number!(u64, visit_u64, deserialize_u64); - deserialize_number!(u128, visit_u128, deserialize_u128); + deserialize_number!(i8, to_i8, visit_i8, deserialize_i8); + deserialize_number!(i16, to_i16, visit_i16, deserialize_i16); + deserialize_number!(i32, to_i32, visit_i32, deserialize_i32); + deserialize_number!(i64, to_i64, visit_i64, deserialize_i64); + deserialize_number!(i128, to_i128, visit_i128, deserialize_i128); + deserialize_number!(u8, to_u8, visit_u8, deserialize_u8); + deserialize_number!(u16, to_u16, visit_u16, deserialize_u16); + deserialize_number!(u32, to_u32, visit_u32, deserialize_u32); + deserialize_number!(u64, to_u64, visit_u64, deserialize_u64); + deserialize_number!(u128, to_u128, visit_u128, deserialize_u128); fn deserialize_option(self, visitor: V) -> Result where diff --git a/serde-itf/src/lib.rs b/serde-itf/src/lib.rs index 9b89e10..7145225 100644 --- a/serde-itf/src/lib.rs +++ b/serde-itf/src/lib.rs @@ -53,13 +53,10 @@ where #[cfg(test)] mod tests { - use std::collections::{BTreeMap, BTreeSet}; - - use crate::value::Value; - use super::*; - + use crate::value::Value; use serde::Deserialize; + use std::collections::{BTreeSet, HashMap}; #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize)] enum Bank { @@ -89,7 +86,7 @@ mod tests { #[allow(dead_code)] struct State { pub bank_of_boat: Bank, - pub who_is_on_bank: BTreeMap>, + pub who_is_on_bank: HashMap>, } #[test] From 3cc13116231cf3dc0558d9fd428ec38444ce83d9 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 26 Oct 2023 21:33:33 +0200 Subject: [PATCH 04/50] Move values under `value` module --- serde-itf/src/de.rs | 6 +----- serde-itf/src/lib.rs | 5 ----- serde-itf/src/value.rs | 16 +++++++++++----- serde-itf/src/{ => value}/bigint.rs | 0 serde-itf/src/{ => value}/map.rs | 0 serde-itf/src/{ => value}/set.rs | 0 serde-itf/src/{ => value}/tuple.rs | 0 serde-itf/src/{ => value}/unserializable.rs | 0 8 files changed, 12 insertions(+), 15 deletions(-) rename serde-itf/src/{ => value}/bigint.rs (100%) rename serde-itf/src/{ => value}/map.rs (100%) rename serde-itf/src/{ => value}/set.rs (100%) rename serde-itf/src/{ => value}/tuple.rs (100%) rename serde-itf/src/{ => value}/unserializable.rs (100%) diff --git a/serde-itf/src/de.rs b/serde-itf/src/de.rs index 50455de..ebbb151 100644 --- a/serde-itf/src/de.rs +++ b/serde-itf/src/de.rs @@ -8,11 +8,7 @@ use serde::de::{ }; use serde::Deserialize; -use crate::bigint::BigInt; -use crate::map::Map; -use crate::set::Set; -use crate::tuple::Tuple; -use crate::value::{Type, Value}; +use crate::value::{BigInt, Map, Set, Tuple, Type, Value}; pub fn decode_value(value: Value) -> Result where diff --git a/serde-itf/src/lib.rs b/serde-itf/src/lib.rs index 7145225..82c9c0f 100644 --- a/serde-itf/src/lib.rs +++ b/serde-itf/src/lib.rs @@ -1,16 +1,11 @@ use serde::de::DeserializeOwned; use serde::Deserialize; -pub mod bigint; pub mod de; pub mod error; -pub mod map; pub mod meta; -pub mod set; pub mod state; pub mod trace; -pub mod tuple; -pub mod unserializable; pub mod value; use error::Error; diff --git a/serde-itf/src/value.rs b/serde-itf/src/value.rs index 3efa037..b7d7094 100644 --- a/serde-itf/src/value.rs +++ b/serde-itf/src/value.rs @@ -1,10 +1,16 @@ use serde::{Deserialize, Serialize}; -use crate::bigint::BigInt; -use crate::map::Map; -use crate::set::Set; -use crate::tuple::Tuple; -use crate::unserializable::Unserializable; +mod bigint; +mod map; +mod set; +mod tuple; +mod unserializable; + +pub use bigint::BigInt; +pub use map::Map; +pub use set::Set; +pub use tuple::Tuple; +pub use unserializable::Unserializable; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(untagged)] diff --git a/serde-itf/src/bigint.rs b/serde-itf/src/value/bigint.rs similarity index 100% rename from serde-itf/src/bigint.rs rename to serde-itf/src/value/bigint.rs diff --git a/serde-itf/src/map.rs b/serde-itf/src/value/map.rs similarity index 100% rename from serde-itf/src/map.rs rename to serde-itf/src/value/map.rs diff --git a/serde-itf/src/set.rs b/serde-itf/src/value/set.rs similarity index 100% rename from serde-itf/src/set.rs rename to serde-itf/src/value/set.rs diff --git a/serde-itf/src/tuple.rs b/serde-itf/src/value/tuple.rs similarity index 100% rename from serde-itf/src/tuple.rs rename to serde-itf/src/value/tuple.rs diff --git a/serde-itf/src/unserializable.rs b/serde-itf/src/value/unserializable.rs similarity index 100% rename from serde-itf/src/unserializable.rs rename to serde-itf/src/value/unserializable.rs From f595a5648f27b29682618c9d6d5d64c661c21df8 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 26 Oct 2023 21:46:25 +0200 Subject: [PATCH 05/50] Cleanup --- serde-itf/Cargo.toml | 12 ++++----- serde-itf/src/de.rs | 24 +++-------------- serde-itf/src/lib.rs | 8 +++--- serde-itf/src/meta.rs | 27 ------------------- serde-itf/src/value.rs | 49 ++++++----------------------------- serde-itf/src/value/bigint.rs | 19 +++++++++++--- serde-itf/src/value/map.rs | 5 ++-- 7 files changed, 39 insertions(+), 105 deletions(-) delete mode 100644 serde-itf/src/meta.rs diff --git a/serde-itf/Cargo.toml b/serde-itf/Cargo.toml index 3c59e02..fa5e66c 100644 --- a/serde-itf/Cargo.toml +++ b/serde-itf/Cargo.toml @@ -1,12 +1,10 @@ [package] -name = "serde-itf" +name = "serde-itf" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -num-bigint = { version = "0.4.4", features = ["serde"] } -num-traits = "0.2.17" -serde = { version = "1.0.190", features = ["derive"] } -serde_json = { version = "1.0.107", features = ["raw_value"] } +num-bigint = { version = "0.4", features = ["serde"] } +num-traits = { version = "0.2" } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", features = ["raw_value"] } diff --git a/serde-itf/src/de.rs b/serde-itf/src/de.rs index ebbb151..8bf48f9 100644 --- a/serde-itf/src/de.rs +++ b/serde-itf/src/de.rs @@ -8,7 +8,7 @@ use serde::de::{ }; use serde::Deserialize; -use crate::value::{BigInt, Map, Set, Tuple, Type, Value}; +use crate::value::{Map, Set, Tuple, Value}; pub fn decode_value(value: Value) -> Result where @@ -20,10 +20,7 @@ where #[derive(Debug)] pub enum Error { Custom(String), - TypeMismatch(Type, Type), - BigInt(BigInt, &'static str), UnsupportedType(&'static str), - Number(i64, &'static str), } impl std::error::Error for Error {} @@ -32,19 +29,6 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Custom(msg) => msg.fmt(f), - - Error::TypeMismatch(expected, actual) => { - write!(f, "type mismatch: expected {expected:?}, found {actual:?}") - } - - Error::BigInt(value, expected) => { - write!(f, "cannot convert {value} to {expected}") - } - - Error::Number(value, expected) => { - write!(f, "cannot convert {value} to {expected}") - } - Error::UnsupportedType(ty) => write!(f, "unsupported type: {ty}"), } } @@ -97,8 +81,8 @@ macro_rules! deserialize_number { visitor.$visit(num) } - Value::BigInt(n) => { - let num = n.$to().ok_or_else(|| { + Value::BigInt(b) => { + let num = b.get().$to().ok_or_else(|| { serde::de::Error::invalid_type( Unexpected::Other("bigint"), &stringify!($ty), @@ -132,7 +116,7 @@ impl<'de> Deserializer<'de> for Value { Value::Bool(v) => visitor.visit_bool(v), Value::Number(v) => visitor.visit_i64(v), Value::String(v) => visitor.visit_string(v), - Value::BigInt(v) => visitor.visit_i64(v.to_i64().unwrap()), + Value::BigInt(v) => visitor.visit_i64(v.get().to_i64().unwrap()), Value::List(v) => visit_list(v, visitor), Value::Tuple(v) => visit_tuple(v, visitor), Value::Set(v) => visit_set(v, visitor), diff --git a/serde-itf/src/lib.rs b/serde-itf/src/lib.rs index 82c9c0f..ffa6ae6 100644 --- a/serde-itf/src/lib.rs +++ b/serde-itf/src/lib.rs @@ -3,14 +3,14 @@ use serde::Deserialize; pub mod de; pub mod error; -pub mod meta; pub mod state; pub mod trace; pub mod value; -use error::Error; -use trace::Trace; -use value::Value; +pub use error::Error; +pub use state::State; +pub use trace::Trace; +pub use value::Value; pub fn trace_from_str(str: &str) -> Result, Error> where diff --git a/serde-itf/src/meta.rs b/serde-itf/src/meta.rs deleted file mode 100644 index 7b1355c..0000000 --- a/serde-itf/src/meta.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::collections::BTreeMap; - -use serde::{Deserialize, Serialize}; - -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct TraceMeta { - #[serde(default)] - pub format: Option, - - #[serde(rename = "format-description")] - pub format_description: Option, - - #[serde(default)] - pub source: Option, - - #[serde(default)] - pub description: Option, - - #[serde(default, rename = "varTypes")] - pub var_types: BTreeMap, - - #[serde(default)] - pub timestamp: Option, - - #[serde(flatten)] - pub other: BTreeMap, -} diff --git a/serde-itf/src/value.rs b/serde-itf/src/value.rs index b7d7094..235aa41 100644 --- a/serde-itf/src/value.rs +++ b/serde-itf/src/value.rs @@ -12,24 +12,27 @@ pub use set::Set; pub use tuple::Tuple; pub use unserializable::Unserializable; +// TODO: Display + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(untagged)] pub enum Value { /// A JSON Boolean: either `false` or `true`. Bool(bool), + /// A JSON number literal, e.g., `42`. + Number(i64), + /// A JSON string literal, e.g., `"hello"`. /// /// TLA+ strings are written as strings in this format. String(String), - /// A JSON number literal, e.g., `42`. - Number(i64), - - /// A big integer of the following form: { "#bigint": "[-][0-9]+" }. + /// A big integer of the following form: `{ "#bigint": "[-][0-9]+" }`. /// /// We are using this format, as many JSON parsers impose limits /// on integer values, see RFC7159. + /// /// Big and small integers must be written in this format. BigInt(BigInt), @@ -57,7 +60,7 @@ pub enum Value { /// /// That is, a map holds a JSON array of two-element arrays. /// Each two-element array p is interpreted as follows: - /// p[0] is the map key and p[1] is the map value. + /// `p[0]` is the map key and `p[1]` is the map value. /// /// Importantly, a key may be an arbitrary expression. /// It does not have to be a string or an integer. @@ -78,39 +81,3 @@ pub enum Value { /// Usually, it indicates some form of an error. Unserializable(Unserializable), } - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Type { - Bool, - Number, - String, - BigInt, - List, - Record, - Tuple, - Set, - Map, - Unserializable, - Float, - Char, - Enum, -} - -// TODO: Display - -impl Value { - pub fn value_type(&self) -> Type { - match self { - Value::Bool(_) => Type::Bool, - Value::Number(_) => Type::Number, - Value::String(_) => Type::String, - Value::BigInt(_) => Type::BigInt, - Value::List(_) => Type::List, - Value::Record(_) => Type::Record, - Value::Tuple(_) => Type::Tuple, - Value::Set(_) => Type::Set, - Value::Map(_) => Type::Map, - Value::Unserializable(_) => Type::Unserializable, - } - } -} diff --git a/serde-itf/src/value/bigint.rs b/serde-itf/src/value/bigint.rs index abd6369..9ca483b 100644 --- a/serde-itf/src/value/bigint.rs +++ b/serde-itf/src/value/bigint.rs @@ -1,18 +1,29 @@ use core::fmt; -use std::ops::Deref; use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize}; +/// A big integer of the following form: `{ "#bigint": "[-][0-9]+" }`. +/// +/// We are using this format, as many JSON parsers impose limits +/// on integer values, see RFC7159. +/// +/// Big and small integers must be written in this format. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct BigInt(num_bigint::BigInt); -impl Deref for BigInt { - type Target = num_bigint::BigInt; +impl BigInt { + pub fn new(value: impl Into) -> Self { + Self(value.into()) + } - fn deref(&self) -> &Self::Target { + pub fn get(&self) -> &num_bigint::BigInt { &self.0 } + + pub fn into_inner(self) -> num_bigint::BigInt { + self.0 + } } impl fmt::Debug for BigInt { diff --git a/serde-itf/src/value/map.rs b/serde-itf/src/value/map.rs index 89eaf1e..d9a8c67 100644 --- a/serde-itf/src/value/map.rs +++ b/serde-itf/src/value/map.rs @@ -5,11 +5,12 @@ use std::collections::BTreeMap; /// /// That is, a map holds a JSON array of two-element arrays. /// Each two-element array p is interpreted as follows: -/// - p[0] is the map key -/// - p[1] is the map value +/// - `p[0]` is the map key +/// - `p[1]` is the map value /// /// Importantly, a key may be an arbitrary expression. /// It does not have to be a string or an integer. +/// /// TLA+ functions are written as maps in this format. #[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Map { From e481cf77610738b60d2a0a50b83461b35d2afb55 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 26 Oct 2023 22:31:27 +0200 Subject: [PATCH 06/50] Move tests into `serde-itf` crate and delete `itf` crate --- Cargo.toml | 1 - itf/Cargo.toml | 18 - itf/src/itf.rs | 414 ------------------ itf/src/lib.rs | 306 ------------- itf/src/meta.rs | 36 -- itf/src/trace.rs | 59 --- itf/src/util.rs | 17 - itf/tests/parse_trace.rs | 99 ----- serde-itf/src/de.rs | 17 +- serde-itf/src/lib.rs | 270 +++++++++--- serde-itf/src/value/map.rs | 6 +- serde-itf/tests/decide_non_proposer.rs | 235 ++++++++++ .../fixtures/DecideNonProposerTest0.itf.json | 0 .../MissionariesAndCannibals.itf.json | 0 .../TestInsufficientSuccess9.itf.json | 0 serde-itf/tests/insufficient_success.rs | 58 +++ serde-itf/tests/missionaries_and_cannibals.rs | 43 ++ 17 files changed, 556 insertions(+), 1023 deletions(-) delete mode 100644 itf/Cargo.toml delete mode 100644 itf/src/itf.rs delete mode 100644 itf/src/lib.rs delete mode 100644 itf/src/meta.rs delete mode 100644 itf/src/trace.rs delete mode 100644 itf/src/util.rs delete mode 100644 itf/tests/parse_trace.rs create mode 100644 serde-itf/tests/decide_non_proposer.rs rename {itf => serde-itf}/tests/fixtures/DecideNonProposerTest0.itf.json (100%) rename {itf => serde-itf}/tests/fixtures/MissionariesAndCannibals.itf.json (100%) rename {itf => serde-itf}/tests/fixtures/TestInsufficientSuccess9.itf.json (100%) create mode 100644 serde-itf/tests/insufficient_success.rs create mode 100644 serde-itf/tests/missionaries_and_cannibals.rs diff --git a/Cargo.toml b/Cargo.toml index 3c87206..194360e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,5 @@ resolver = "2" members = [ - "itf", "serde-itf", ] diff --git a/itf/Cargo.toml b/itf/Cargo.toml deleted file mode 100644 index 3811a11..0000000 --- a/itf/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "itf" -version = "0.1.1" -edition = "2021" -license = "Apache-2.0" -readme = "../README.md" -authors = ["Informal Systems "] -keywords = ["apalache", "serialization", "trace"] -description = "Library for consuming Apalache ITF traces" -repository = "https://github.com/informalsystems/itf-rs" -documentation = "https://docs.rs/itf" -rust-version = "1.65" - -[dependencies] -num-bigint = { version = "0.4", features = ["serde"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -thiserror = "1" diff --git a/itf/src/itf.rs b/itf/src/itf.rs deleted file mode 100644 index b0ce7c0..0000000 --- a/itf/src/itf.rs +++ /dev/null @@ -1,414 +0,0 @@ -use std::{ - collections::{HashMap, HashSet}, - fmt, - hash::Hash, - ops::{Deref, DerefMut}, -}; - -use num_bigint::BigInt; -use serde::{de::DeserializeOwned, Deserialize}; - -pub type ItfMap = Itf>; -pub type ItfSet = Itf>; -pub type ItfTuple = Itf; -pub type ItfBigInt = Itf; -pub type ItfInt = i64; -pub type ItfBool = bool; -pub type ItfString = String; - -#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Itf(T); - -impl fmt::Debug for Itf -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for Itf -where - T: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl Itf { - pub fn value(self) -> T { - self.0 - } -} - -impl Deref for Itf { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Itf { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl<'de, T> Deserialize<'de> for Itf> -where - T: Eq + Hash + Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - #[derive(Deserialize)] - pub struct Set { - #[serde(rename = "#set")] - set: Vec, - } - - let set = Set::::deserialize(deserializer)?; - Ok(Self(set.set.into_iter().collect())) - } -} - -impl<'de, K, V> Deserialize<'de> for Itf> -where - K: Eq + Hash + DeserializeOwned, - V: Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - #[derive(Deserialize)] - pub struct Map { - #[serde(rename = "#map")] - elements: Vec<(K, V)>, - } - - let map = Map::::deserialize(deserializer)?; - Ok(Self(map.elements.into_iter().collect())) - } -} - -impl<'de> Deserialize<'de> for Itf { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - #[derive(Deserialize)] - struct BI { - #[serde(rename = "#bigint", with = "crate::util::serde::display_from_str")] - value: num_bigint::BigInt, - } - - #[derive(Deserialize)] - #[serde(untagged)] - enum IntOrBigInt { - Int(i64), - BigInt(BI), - } - - IntOrBigInt::deserialize(deserializer) - .map(|ib| match ib { - IntOrBigInt::Int(n) => BigInt::from(n), - IntOrBigInt::BigInt(b) => b.value, - }) - .map(Itf) - } -} - -#[derive(Deserialize)] -struct Tup { - #[serde(rename = "#tup")] - elements: Vec, -} - -macro_rules! deserialize_itf_tuple { - ($len:literal, $($n:literal $ty:ident)+) => { - impl<'de, $($ty ,)+> Deserialize<'de> for Itf<($($ty ,)+)> - where - $($ty: DeserializeOwned,)+ - { - #[allow(non_snake_case)] - fn deserialize(deserializer: De) -> Result - where - De: serde::Deserializer<'de>, - { - let mut elements = Tup::deserialize(deserializer).map(|t| t.elements)?; - - if elements.len() != $len { - return Err(serde::de::Error::custom(format_args!( - "expected tuple with {} elements but found {}", $len, elements.len() - ))); - } - - $( - let $ty: $ty = serde_json::from_value(std::mem::take(&mut elements[$n])) - .map_err(|e| serde::de::Error::custom(e))?; - )+ - - Ok(Itf(($($ty,)+))) - } - } - }; -} - -deserialize_itf_tuple!(2, 0 A 1 B); -deserialize_itf_tuple!(3, 0 A 1 B 2 C); -deserialize_itf_tuple!(4, 0 A 1 B 2 C 3 D); -deserialize_itf_tuple!(5, 0 A 1 B 2 C 3 D 4 E); -deserialize_itf_tuple!(6, 0 A 1 B 2 C 3 D 4 E 5 F); -deserialize_itf_tuple!(7, 0 A 1 B 2 C 3 D 4 E 5 F 6 G); -// deserialize_itf_tuple!(8, 0 A 1 B 2 C 3 D 4 E 5 F 6 G 7 H); -// deserialize_itf_tuple!(9, 0 A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8 I); -// deserialize_itf_tuple!(10, 0 A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8 I 9 J); -// deserialize_itf_tuple!(11, 0 A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8 I 9 J 10 K); -// deserialize_itf_tuple!(12, 0 A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8 I 9 J 10 K 11 L); - -#[cfg(test)] -mod tests { - use super::*; - - use serde_json::json; - - #[test] - fn deserialize_set() { - let json = json!({ - "#set": [1, 2, 3, 4] - }); - - let set: ItfSet = serde_json::from_value(json).unwrap(); - let elems = [1_i64, 2, 3, 4].into_iter().collect::>(); - - assert_eq!(set.0, elems); - } - - #[test] - fn deserialize_map() { - let json = json!({ - "#map": [["hello", 1], ["world", 2]] - }); - - let set: ItfMap = serde_json::from_value(json).unwrap(); - let elems = [("hello".to_string(), 1), ("world".to_string(), 2)] - .into_iter() - .collect::>(); - - assert_eq!(set.0, elems); - } - - #[test] - fn deserialize_bigint_int() { - let json = json!(1024); - - let bigint: ItfBigInt = serde_json::from_value(json).unwrap(); - assert_eq!(bigint.0, BigInt::from(1024)); - } - - #[test] - fn deserialize_bigint() { - let json = json!({ - "#bigint": "1234567891011121314151617181920" - }); - - let bigint: ItfBigInt = serde_json::from_value(json).unwrap(); - assert_eq!(bigint.0, "1234567891011121314151617181920".parse().unwrap()); - } - - #[test] - #[should_panic(expected = "expected tuple with 3 elements but found 2")] - fn deserialize_tuple_wrong_cardinality() { - let json = json!({ - "#tup": [ - { "#bigint": "1234567891011121314151617181920" }, - 1234, - ] - }); - - let _tuple: ItfTuple<(ItfBigInt, ItfInt, ItfString)> = - serde_json::from_value(json).unwrap(); - } - - #[test] - fn deserialize_tuple_2() { - let json = json!({ - "#tup": [ - { "#bigint": "1234567891011121314151617181920" }, - 1234, - ] - }); - - let mut tuple: ItfTuple<(ItfBigInt, ItfInt)> = serde_json::from_value(json).unwrap(); - - assert_eq!( - tuple.deref().0, - Itf("1234567891011121314151617181920".parse().unwrap()), - ); - - assert_eq!(tuple.deref().1, 1234); - assert_eq!(tuple.deref_mut().1, 1234); - } - - #[test] - fn deserialize_tuple3() { - let json = json!({ - "#tup": [ - { "#bigint": "1234567891011121314151617181920" }, - 1234, - "Hello world", - ] - }); - - let tuple: ItfTuple<(ItfBigInt, ItfInt, ItfString)> = serde_json::from_value(json).unwrap(); - let tuple = tuple.value(); - - assert_eq!( - tuple, - ( - Itf("1234567891011121314151617181920".parse().unwrap()), - 1234, - "Hello world".to_string(), - ) - ); - } - - #[test] - fn deserialize_tuple4() { - let json = json!({ - "#tup": [ - { "#bigint": "1234567891011121314151617181920" }, - 1234, - "Hello world", - true - ] - }); - - let tuple: ItfTuple<(ItfBigInt, ItfInt, ItfString, ItfBool)> = - serde_json::from_value(json).unwrap(); - - assert_eq!( - tuple.0, - ( - Itf("1234567891011121314151617181920".parse().unwrap()), - 1234, - "Hello world".to_string(), - true - ) - ); - } - - #[test] - fn deserialize_tuple5() { - let json = json!({ - "#tup": [ - { "#bigint": "1234567891011121314151617181920" }, - 1234, - "Hello world", - true, - { "#set": [1, 2, 3] } - ] - }); - - let tuple: ItfTuple<(ItfBigInt, ItfInt, ItfString, ItfBool, ItfSet)> = - serde_json::from_value(json).unwrap(); - - assert_eq!( - tuple.0, - ( - Itf("1234567891011121314151617181920".parse().unwrap()), - 1234, - "Hello world".to_string(), - true, - Itf([1, 2, 3].into_iter().collect()) - ) - ); - } - - #[test] - #[allow(clippy::type_complexity)] - fn deserialize_tuple6() { - let json = json!({ - "#tup": [ - { "#bigint": "1234567891011121314151617181920" }, - 1234, - "Hello world", - true, - { "#set": [1, 2, 3] }, - { "#map": [[1, true], [2, false], [3, true]] } - ] - }); - - let tuple: ItfTuple<( - ItfBigInt, - ItfInt, - ItfString, - ItfBool, - ItfSet, - ItfMap, - )> = serde_json::from_value(json).unwrap(); - - assert_eq!( - tuple.0, - ( - Itf("1234567891011121314151617181920".parse().unwrap()), - 1234, - "Hello world".to_string(), - true, - Itf([1, 2, 3].into_iter().collect()), - Itf([(1, true), (2, false), (3, true)].into_iter().collect()) - ) - ); - } - - #[test] - #[allow(clippy::type_complexity)] - fn deserialize_tuple7() { - let json = json!({ - "#tup": [ - { "#bigint": "1234567891011121314151617181920" }, - 1234, - "Hello world", - true, - { "#set": [ 1, 2, 3 ] }, - { "#map": [ [1, true], [2, false], [3, true] ] }, - { "#tup": [ { "#bigint": "1" }, "hello"] }, - ] - }); - - let tuple: ItfTuple<( - ItfBigInt, - ItfInt, - ItfString, - ItfBool, - ItfSet, - ItfMap, - ItfTuple<(ItfBigInt, ItfString)>, - )> = serde_json::from_value(json).unwrap(); - - assert_eq!( - tuple.0, - ( - Itf("1234567891011121314151617181920".parse().unwrap()), - 1234, - "Hello world".to_string(), - true, - Itf([1, 2, 3].into_iter().collect()), - Itf([(1, true), (2, false), (3, true)].into_iter().collect()), - Itf((Itf(BigInt::from(1)), "hello".to_string())) - ) - ); - } - - #[test] - fn display() { - let s = "1234567891011121314151617181920"; - let itf: ItfBigInt = Itf(s.parse().unwrap()); - assert_eq!(format!("{}", itf), s.to_string()); - } -} diff --git a/itf/src/lib.rs b/itf/src/lib.rs deleted file mode 100644 index c179544..0000000 --- a/itf/src/lib.rs +++ /dev/null @@ -1,306 +0,0 @@ -//! Library for consuming [Apalache ITF Traces](https://apalache.informal.systems/docs/adr/015adr-trace.html). -//! -//! ## Example -//! -//! **Trace:** [`MissionariesAndCannibals.itf.json`](./apalache-itf/tests/fixtures/MissionariesAndCannibals.itf.json) -//! -//! ```rust -//! use serde::Deserialize; -//! -//! use itf::{trace_from_str, ItfMap, ItfSet}; -//! -//! #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] -//! enum Bank { -//! #[serde(rename = "N")] -//! North, -//! -//! #[serde(rename = "W")] -//! West, -//! -//! #[serde(rename = "E")] -//! East, -//! -//! #[serde(rename = "S")] -//! South, -//! } -//! -//! #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] -//! enum Person { -//! #[serde(rename = "c1_OF_PERSON")] -//! Cannibal1, -//! -//! #[serde(rename = "c2_OF_PERSON")] -//! Cannibal2, -//! -//! #[serde(rename = "m1_OF_PERSON")] -//! Missionary1, -//! -//! #[serde(rename = "m2_OF_PERSON")] -//! Missionary2, -//! } -//! -//! #[derive(Clone, Debug, Deserialize)] -//! struct State { -//! pub bank_of_boat: Bank, -//! pub who_is_on_bank: ItfMap>, -//! } -//! -//! let data = include_str!("../tests/fixtures/MissionariesAndCannibals.itf.json"); -//! let trace: Trace = trace_from_str(data).unwrap(); -//! -//! dbg!(trace); -//! ``` -//! -//! **Output:** -//! -//! ```rust -//! trace = Trace { -//! meta: TraceMeta { -//! description: None, -//! source: Some( -//! "MC_MissionariesAndCannibalsTyped.tla", -//! ), -//! var_types: { -//! "bank_of_boat": "Str", -//! "who_is_on_bank": "Str -> Set(PERSON)", -//! }, -//! format: None, -//! format_description: None, -//! other: {}, -//! }, -//! params: [], -//! vars: [ -//! "bank_of_boat", -//! "who_is_on_bank", -//! ], -//! loop_index: None, -//! states: [ -//! State { -//! meta: StateMeta { -//! index: Some( -//! 0, -//! ), -//! other: {}, -//! }, -//! value: State { -//! bank_of_boat: East, -//! who_is_on_bank: { -//! West: {}, -//! East: { -//! Missionary2, -//! Cannibal1, -//! Cannibal2, -//! Missionary1, -//! }, -//! }, -//! }, -//! }, -//! State { -//! meta: StateMeta { -//! index: Some( -//! 1, -//! ), -//! other: {}, -//! }, -//! value: State { -//! bank_of_boat: West, -//! who_is_on_bank: { -//! West: { -//! Missionary2, -//! Cannibal2, -//! }, -//! East: { -//! Missionary1, -//! Cannibal1, -//! }, -//! }, -//! }, -//! }, -//! State { -//! meta: StateMeta { -//! index: Some( -//! 2, -//! ), -//! other: {}, -//! }, -//! value: State { -//! bank_of_boat: East, -//! who_is_on_bank: { -//! West: { -//! Cannibal2, -//! }, -//! East: { -//! Missionary2, -//! Cannibal1, -//! Missionary1, -//! }, -//! }, -//! }, -//! }, -//! State { -//! meta: StateMeta { -//! index: Some( -//! 3, -//! ), -//! other: {}, -//! }, -//! value: State { -//! bank_of_boat: West, -//! who_is_on_bank: { -//! West: { -//! Missionary1, -//! Cannibal2, -//! Missionary2, -//! }, -//! East: { -//! Cannibal1, -//! }, -//! }, -//! }, -//! }, -//! State { -//! meta: StateMeta { -//! index: Some( -//! 4, -//! ), -//! other: {}, -//! }, -//! value: State { -//! bank_of_boat: East, -//! who_is_on_bank: { -//! East: { -//! Cannibal2, -//! Cannibal1, -//! }, -//! West: { -//! Missionary1, -//! Missionary2, -//! }, -//! }, -//! }, -//! }, -//! State { -//! meta: StateMeta { -//! index: Some( -//! 5, -//! ), -//! other: {}, -//! }, -//! value: State { -//! bank_of_boat: West, -//! who_is_on_bank: { -//! East: {}, -//! West: { -//! Cannibal1, -//! Cannibal2, -//! Missionary1, -//! Missionary2, -//! }, -//! }, -//! }, -//! }, -//! ], -//! } -//! ``` - -mod util; - -mod meta; -pub use meta::*; - -mod itf; -pub use itf::*; - -mod trace; -use serde::{de::DeserializeOwned, Deserialize}; -pub use trace::*; - -use serde_json::Result; - -pub fn trace_from_str<'a, State>(s: &'a str) -> Result> -where - State: Deserialize<'a>, -{ - serde_json::from_str(s) -} - -pub fn trace_from_slice<'a, State>(s: &'a [u8]) -> Result> -where - State: Deserialize<'a>, -{ - serde_json::from_slice(s) -} - -pub fn trace_from_value(v: serde_json::Value) -> Result> -where - State: DeserializeOwned, -{ - serde_json::from_value(v) -} - -pub fn trace_from_reader(r: R) -> Result> -where - State: DeserializeOwned, - R: std::io::Read, -{ - serde_json::from_reader(r) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] - enum Bank { - #[serde(rename = "N")] - North, - #[serde(rename = "W")] - West, - #[serde(rename = "E")] - East, - #[serde(rename = "S")] - South, - } - - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] - enum Person { - #[serde(rename = "c1_OF_PERSON")] - Cannibal1, - #[serde(rename = "c2_OF_PERSON")] - Cannibal2, - #[serde(rename = "m1_OF_PERSON")] - Missionary1, - #[serde(rename = "m2_OF_PERSON")] - Missionary2, - } - - #[derive(Clone, Debug, Deserialize)] - #[allow(dead_code)] - struct State { - pub bank_of_boat: Bank, - pub who_is_on_bank: ItfMap>, - } - - const DATA: &str = include_str!("../tests/fixtures/MissionariesAndCannibals.itf.json"); - - #[test] - fn from_str() { - let _trace = trace_from_str::(DATA).unwrap(); - } - - #[test] - fn from_slice() { - let _trace = trace_from_slice::(DATA.as_bytes()).unwrap(); - } - - #[test] - fn from_value() { - let value = serde_json::from_str(DATA).unwrap(); - let _trace = trace_from_value::(value).unwrap(); - } - - #[test] - fn from_reader() { - let _trace = trace_from_reader::(DATA.as_bytes()).unwrap(); - } -} diff --git a/itf/src/meta.rs b/itf/src/meta.rs deleted file mode 100644 index 144b6d0..0000000 --- a/itf/src/meta.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; - -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct TraceMeta { - #[serde(default)] - pub format: Option, - - #[serde(rename = "format-description")] - pub format_description: Option, - - #[serde(default)] - pub source: Option, - - #[serde(default)] - pub description: Option, - - #[serde(default, rename = "varTypes")] - pub var_types: HashMap, - - #[serde(default)] - pub timestamp: Option, - - #[serde(flatten)] - pub other: HashMap, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct StateMeta { - #[serde(default)] - pub index: Option, - - #[serde(flatten)] - pub other: HashMap, -} diff --git a/itf/src/trace.rs b/itf/src/trace.rs deleted file mode 100644 index 21a5a65..0000000 --- a/itf/src/trace.rs +++ /dev/null @@ -1,59 +0,0 @@ -use serde::Deserialize; - -use crate::{StateMeta, TraceMeta}; - -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct State { - #[serde(rename = "#meta")] - pub meta: StateMeta, - - #[serde(flatten)] - pub value: S, -} - -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Trace { - #[serde(rename = "#meta")] - pub meta: TraceMeta, - - #[serde(default)] - pub params: Vec, - - #[serde(default)] - pub vars: Vec, - - #[serde(default, rename = "loop")] - pub loop_index: Option, - - pub states: Vec>, -} - -impl Default for Trace { - fn default() -> Self { - Self { - meta: Default::default(), - params: Default::default(), - vars: Default::default(), - loop_index: Default::default(), - states: Default::default(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn trace_default() { - #[derive(Debug, PartialEq, Eq)] - struct S; // no need for `Default derive` - - let t: Trace = Trace::default(); - assert_eq!(t.meta, TraceMeta::default()); - assert_eq!(t.params, Vec::::new()); - assert_eq!(t.vars, Vec::::new()); - assert_eq!(t.loop_index, None); - assert_eq!(t.states, Vec::>::new()); - } -} diff --git a/itf/src/util.rs b/itf/src/util.rs deleted file mode 100644 index f1c1f03..0000000 --- a/itf/src/util.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub mod serde { - pub mod display_from_str { - use std::{fmt::Display, str::FromStr}; - - use serde::{de, Deserialize, Deserializer}; - - pub fn deserialize<'de, D, T, E>(deserializer: D) -> Result - where - D: Deserializer<'de>, - T: FromStr, - E: Display, - { - let s = String::deserialize(deserializer)?; - FromStr::from_str(&s).map_err(de::Error::custom) - } - } -} diff --git a/itf/tests/parse_trace.rs b/itf/tests/parse_trace.rs deleted file mode 100644 index 5b99b8c..0000000 --- a/itf/tests/parse_trace.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::collections::HashMap; - -use num_bigint::BigInt; -use serde::Deserialize; - -use itf::{trace_from_str, Itf, ItfMap, ItfSet}; - -#[test] -fn cannibals() { - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] - enum Bank { - #[serde(rename = "N")] - North, - #[serde(rename = "W")] - West, - #[serde(rename = "E")] - East, - #[serde(rename = "S")] - South, - } - - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] - enum Person { - #[serde(rename = "c1_OF_PERSON")] - Cannibal1, - #[serde(rename = "c2_OF_PERSON")] - Cannibal2, - #[serde(rename = "m1_OF_PERSON")] - Missionary1, - #[serde(rename = "m2_OF_PERSON")] - Missionary2, - } - - #[derive(Clone, Debug, Deserialize)] - #[allow(dead_code)] - struct State { - pub bank_of_boat: Bank, - pub who_is_on_bank: ItfMap>, - } - - let data = include_str!("../tests/fixtures/MissionariesAndCannibals.itf.json"); - let trace = trace_from_str::(data).unwrap(); - - dbg!(trace); -} - -#[test] -fn insufficent_success_9() { - type Balance = Itf>>; - type Balances = Itf>; - - #[derive(Copy, Clone, Debug, Deserialize)] - enum Outcome { - #[serde(rename = "")] - None, - #[serde(rename = "SUCCESS")] - Success, - #[serde(rename = "DUPLICATE_DENOM")] - DuplicateDenom, - #[serde(rename = "INSUFFICIENT_FUNDS")] - InsufficientFunds, - } - - #[derive(Clone, Debug, Deserialize)] - #[allow(dead_code)] - struct Coin { - amount: Itf, - denom: String, - } - - #[derive(Clone, Debug, Deserialize)] - #[allow(dead_code)] - #[serde(tag = "tag")] - enum Action { - #[serde(rename = "init")] - Init { balances: Balances }, - - #[serde(rename = "send")] - Send { - receiver: String, - sender: String, - coins: Vec, - }, - } - - #[derive(Clone, Debug, Deserialize)] - #[allow(dead_code)] - struct State { - action: Action, - outcome: Outcome, - balances: Balances, - step: i64, - } - - let data = include_str!("../tests/fixtures/TestInsufficientSuccess9.itf.json"); - let trace = trace_from_str::(data).unwrap(); - - dbg!(trace); -} diff --git a/serde-itf/src/de.rs b/serde-itf/src/de.rs index 8bf48f9..dd46fff 100644 --- a/serde-itf/src/de.rs +++ b/serde-itf/src/de.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::fmt; use num_traits::ToPrimitive; @@ -8,7 +9,7 @@ use serde::de::{ }; use serde::Deserialize; -use crate::value::{Map, Set, Tuple, Value}; +use crate::value::{BigInt, Map, Set, Tuple, Value}; pub fn decode_value(value: Value) -> Result where @@ -116,7 +117,7 @@ impl<'de> Deserializer<'de> for Value { Value::Bool(v) => visitor.visit_bool(v), Value::Number(v) => visitor.visit_i64(v), Value::String(v) => visitor.visit_string(v), - Value::BigInt(v) => visitor.visit_i64(v.get().to_i64().unwrap()), + Value::BigInt(v) => visit_bigint(v, visitor), Value::List(v) => visit_list(v, visitor), Value::Tuple(v) => visit_tuple(v, visitor), Value::Set(v) => visit_set(v, visitor), @@ -351,6 +352,18 @@ impl<'de> Deserializer<'de> for Value { } } +fn visit_bigint<'de, V>(v: BigInt, visitor: V) -> Result +where + V: Visitor<'de>, +{ + let map = vec![("#bigint".to_string(), Value::String(v.to_string()))] + .into_iter() + .collect::>(); + + let record = crate::value::Map::new(map); + visit_record(record, visitor) +} + fn visit_map<'de, V>(v: Map, visitor: V) -> Result where V: Visitor<'de>, diff --git a/serde-itf/src/lib.rs b/serde-itf/src/lib.rs index ffa6ae6..c8eddb3 100644 --- a/serde-itf/src/lib.rs +++ b/serde-itf/src/lib.rs @@ -1,3 +1,206 @@ +//! Library for consuming [Apalache ITF Traces](https://apalache.informal.systems/docs/adr/015adr-trace.html). +//! +//! ## Example +//! +//! **Trace:** [`MissionariesAndCannibals.itf.json`](../tests/fixtures/MissionariesAndCannibals.itf.json) +//! +//! ```rust +//! use serde::Deserialize; +//! +//! #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] +//! enum Bank { +//! #[serde(rename = "N")] +//! North, +//! +//! #[serde(rename = "W")] +//! West, +//! +//! #[serde(rename = "E")] +//! East, +//! +//! #[serde(rename = "S")] +//! South, +//! } +//! +//! #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] +//! enum Person { +//! #[serde(rename = "c1_OF_PERSON")] +//! Cannibal1, +//! +//! #[serde(rename = "c2_OF_PERSON")] +//! Cannibal2, +//! +//! #[serde(rename = "m1_OF_PERSON")] +//! Missionary1, +//! +//! #[serde(rename = "m2_OF_PERSON")] +//! Missionary2, +//! } +//! +//! #[derive(Clone, Debug, Deserialize)] +//! struct State { +//! pub bank_of_boat: Bank, +//! pub who_is_on_bank: ItfMap>, +//! } +//! +//! let data = include_str!("../tests/fixtures/MissionariesAndCannibals.itf.json"); +//! let trace: Trace = itf::trace_from_str(data).unwrap(); +//! +//! dbg!(trace); +//! ``` +//! +//! **Output:** +//! +//! ```rust +//! trace = Trace { +//! meta: TraceMeta { +//! description: None, +//! source: Some( +//! "MC_MissionariesAndCannibalsTyped.tla", +//! ), +//! var_types: { +//! "bank_of_boat": "Str", +//! "who_is_on_bank": "Str -> Set(PERSON)", +//! }, +//! format: None, +//! format_description: None, +//! other: {}, +//! }, +//! params: [], +//! vars: [ +//! "bank_of_boat", +//! "who_is_on_bank", +//! ], +//! loop_index: None, +//! states: [ +//! State { +//! meta: StateMeta { +//! index: Some( +//! 0, +//! ), +//! other: {}, +//! }, +//! value: State { +//! bank_of_boat: East, +//! who_is_on_bank: { +//! West: {}, +//! East: { +//! Missionary2, +//! Cannibal1, +//! Cannibal2, +//! Missionary1, +//! }, +//! }, +//! }, +//! }, +//! State { +//! meta: StateMeta { +//! index: Some( +//! 1, +//! ), +//! other: {}, +//! }, +//! value: State { +//! bank_of_boat: West, +//! who_is_on_bank: { +//! West: { +//! Missionary2, +//! Cannibal2, +//! }, +//! East: { +//! Missionary1, +//! Cannibal1, +//! }, +//! }, +//! }, +//! }, +//! State { +//! meta: StateMeta { +//! index: Some( +//! 2, +//! ), +//! other: {}, +//! }, +//! value: State { +//! bank_of_boat: East, +//! who_is_on_bank: { +//! West: { +//! Cannibal2, +//! }, +//! East: { +//! Missionary2, +//! Cannibal1, +//! Missionary1, +//! }, +//! }, +//! }, +//! }, +//! State { +//! meta: StateMeta { +//! index: Some( +//! 3, +//! ), +//! other: {}, +//! }, +//! value: State { +//! bank_of_boat: West, +//! who_is_on_bank: { +//! West: { +//! Missionary1, +//! Cannibal2, +//! Missionary2, +//! }, +//! East: { +//! Cannibal1, +//! }, +//! }, +//! }, +//! }, +//! State { +//! meta: StateMeta { +//! index: Some( +//! 4, +//! ), +//! other: {}, +//! }, +//! value: State { +//! bank_of_boat: East, +//! who_is_on_bank: { +//! East: { +//! Cannibal2, +//! Cannibal1, +//! }, +//! West: { +//! Missionary1, +//! Missionary2, +//! }, +//! }, +//! }, +//! }, +//! State { +//! meta: StateMeta { +//! index: Some( +//! 5, +//! ), +//! other: {}, +//! }, +//! value: State { +//! bank_of_boat: West, +//! who_is_on_bank: { +//! East: {}, +//! West: { +//! Cannibal1, +//! Cannibal2, +//! Missionary1, +//! Missionary2, +//! }, +//! }, +//! }, +//! }, +//! ], +//! } +//! ``` + use serde::de::DeserializeOwned; use serde::Deserialize; @@ -45,70 +248,3 @@ where let s = S::deserialize(trace_value)?; Ok(s) } - -#[cfg(test)] -mod tests { - use super::*; - use crate::value::Value; - use serde::Deserialize; - use std::collections::{BTreeSet, HashMap}; - - #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize)] - enum Bank { - #[serde(rename = "N")] - North, - #[serde(rename = "W")] - West, - #[serde(rename = "E")] - East, - #[serde(rename = "S")] - South, - } - - #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize)] - enum Person { - #[serde(rename = "c1_OF_PERSON")] - Cannibal1, - #[serde(rename = "c2_OF_PERSON")] - Cannibal2, - #[serde(rename = "m1_OF_PERSON")] - Missionary1, - #[serde(rename = "m2_OF_PERSON")] - Missionary2, - } - - #[derive(Clone, Debug, Deserialize)] - #[allow(dead_code)] - struct State { - pub bank_of_boat: Bank, - pub who_is_on_bank: HashMap>, - } - - #[test] - fn de_cannibals() -> Result<(), Error> { - let path = format!( - "{}/../itf/tests/fixtures/MissionariesAndCannibals.itf.json", - env!("CARGO_MANIFEST_DIR") - ); - - let fixture = std::fs::read_to_string(path)?; - let trace: Trace = crate::trace_from_str(&fixture)?; - dbg!(trace); - - Ok(()) - } - - #[test] - fn de_consensus() -> Result<(), Error> { - let path = format!( - "{}/../itf/tests/fixtures/DecideNonProposerTest0.itf.json", - env!("CARGO_MANIFEST_DIR") - ); - - let fixture = std::fs::read_to_string(path)?; - let trace: Trace = crate::trace_from_str(&fixture)?; - dbg!(trace); - - Ok(()) - } -} diff --git a/serde-itf/src/value/map.rs b/serde-itf/src/value/map.rs index d9a8c67..593ac96 100644 --- a/serde-itf/src/value/map.rs +++ b/serde-itf/src/value/map.rs @@ -18,10 +18,8 @@ pub struct Map { } impl Map { - pub fn new() -> Self { - Self { - map: BTreeMap::new(), - } + pub fn new(map: BTreeMap) -> Self { + Self { map } } pub fn iter(&self) -> impl Iterator { diff --git a/serde-itf/tests/decide_non_proposer.rs b/serde-itf/tests/decide_non_proposer.rs new file mode 100644 index 0000000..1472b05 --- /dev/null +++ b/serde-itf/tests/decide_non_proposer.rs @@ -0,0 +1,235 @@ +#![allow(dead_code)] + +use std::collections::BTreeMap; +use std::result::Result as StdResult; + +use serde::de::IntoDeserializer; +use serde::Deserialize; + +type Address = String; +type Value = String; +type Step = String; +type Round = i64; +type Height = i64; + +#[derive(Clone, Debug, Deserialize)] +enum Timeout { + #[serde(rename = "timeoutPrevote")] + Prevote, + + #[serde(rename = "timeoutPrecommit")] + Precommit, + + #[serde(rename = "timeoutPropose")] + Propose, +} + +#[derive(Clone, Debug, Deserialize)] +struct State { + system: System, + + #[serde(rename = "_Event")] + event: Event, + + #[serde(rename = "_Result")] + result: Result, +} + +#[derive(Clone, Debug, Deserialize)] +struct System(BTreeMap); + +#[derive(Clone, Debug, Deserialize)] +#[serde(tag = "name")] +enum Event { + Initial, + NewRound { + height: Height, + round: Round, + }, + Proposal { + height: Height, + round: Round, + value: Value, + }, + ProposalAndPolkaAndValid { + height: Height, + round: Round, + value: Value, + }, + ProposalAndCommitAndValid { + height: Height, + round: Round, + value: Value, + }, + NewHeight { + height: Height, + round: Round, + }, + NewRoundProposer { + height: Height, + round: Round, + value: Value, + }, + PolkaNil { + height: Height, + round: Round, + value: Value, + }, + PolkaAny { + height: Height, + round: Round, + value: Value, + }, + PrecommitAny { + height: Height, + round: Round, + value: Value, + }, + TimeoutPrevote { + height: Height, + round: Round, + }, + TimeoutPrecommit { + height: Height, + round: Round, + value: Value, + }, + TimeoutPropose { + height: Height, + round: Round, + value: Value, + }, + ProposalInvalid { + height: Height, + round: Round, + }, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct Result { + name: String, + #[serde(deserialize_with = "proposal_or_none")] + proposal: Option, + #[serde(deserialize_with = "vote_message_or_none")] + vote_message: Option, + #[serde(deserialize_with = "empty_string_as_none")] + timeout: Option, + #[serde(deserialize_with = "empty_string_as_none")] + decided: Option, + #[serde(deserialize_with = "minus_one_as_none")] + skip_round: Option, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Proposal { + src: Address, + height: Height, + round: Round, + proposal: Value, + valid_round: Round, +} + +impl Proposal { + fn is_empty(&self) -> bool { + self.src.is_empty() + && self.proposal.is_empty() + && self.height == -1 + && self.round == -1 + && self.valid_round == -1 + } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct VoteMessage { + src: Address, + height: Height, + round: Round, + step: Step, + id: Value, +} + +impl VoteMessage { + fn is_empty(&self) -> bool { + self.src.is_empty() + && self.id.is_empty() + && self.height == -1 + && self.round == -1 + && self.step.is_empty() + } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ConsensusState { + p: Address, + height: Height, + round: Round, + step: Step, + + #[serde(deserialize_with = "minus_one_as_none")] + locked_round: Option, + #[serde(deserialize_with = "empty_string_as_none")] + locked_value: Option, + #[serde(deserialize_with = "minus_one_as_none")] + valid_round: Option, + #[serde(deserialize_with = "empty_string_as_none")] + valid_value: Option, +} + +fn empty_string_as_none<'de, D, T>(de: D) -> StdResult, D::Error> +where + D: serde::Deserializer<'de>, + T: serde::Deserialize<'de>, +{ + let opt = Option::::deserialize(de)?; + match opt.as_deref() { + None | Some("") => Ok(None), + Some(s) => T::deserialize(s.into_deserializer()).map(Some), + } +} + +fn minus_one_as_none<'de, D, T>(de: D) -> StdResult, D::Error> +where + D: serde::Deserializer<'de>, + T: serde::Deserialize<'de>, +{ + let opt = Option::::deserialize(de)?; + match opt { + None | Some(-1) => Ok(None), + Some(i) => T::deserialize(i.into_deserializer()).map(Some), + } +} + +fn proposal_or_none<'de, D>(de: D) -> StdResult, D::Error> +where + D: serde::Deserializer<'de>, +{ + let proposal = Proposal::deserialize(de)?; + if proposal.is_empty() { + Ok(None) + } else { + Ok(Some(proposal)) + } +} + +fn vote_message_or_none<'de, D>(de: D) -> StdResult, D::Error> +where + D: serde::Deserializer<'de>, +{ + let vote_message = VoteMessage::deserialize(de)?; + if vote_message.is_empty() { + Ok(None) + } else { + Ok(Some(vote_message)) + } +} + +#[test] +fn deserialize() { + let data = include_str!("../tests/fixtures/DecideNonProposerTest0.itf.json"); + let trace = serde_itf::trace_from_str::(data).unwrap(); + dbg!(trace); +} diff --git a/itf/tests/fixtures/DecideNonProposerTest0.itf.json b/serde-itf/tests/fixtures/DecideNonProposerTest0.itf.json similarity index 100% rename from itf/tests/fixtures/DecideNonProposerTest0.itf.json rename to serde-itf/tests/fixtures/DecideNonProposerTest0.itf.json diff --git a/itf/tests/fixtures/MissionariesAndCannibals.itf.json b/serde-itf/tests/fixtures/MissionariesAndCannibals.itf.json similarity index 100% rename from itf/tests/fixtures/MissionariesAndCannibals.itf.json rename to serde-itf/tests/fixtures/MissionariesAndCannibals.itf.json diff --git a/itf/tests/fixtures/TestInsufficientSuccess9.itf.json b/serde-itf/tests/fixtures/TestInsufficientSuccess9.itf.json similarity index 100% rename from itf/tests/fixtures/TestInsufficientSuccess9.itf.json rename to serde-itf/tests/fixtures/TestInsufficientSuccess9.itf.json diff --git a/serde-itf/tests/insufficient_success.rs b/serde-itf/tests/insufficient_success.rs new file mode 100644 index 0000000..81aff5a --- /dev/null +++ b/serde-itf/tests/insufficient_success.rs @@ -0,0 +1,58 @@ +#![allow(dead_code)] + +use std::collections::HashMap; + +use serde::Deserialize; +use serde_itf::value::BigInt; + +type Balance = HashMap; +type Balances = HashMap; + +#[derive(Copy, Clone, Debug, Deserialize)] +enum Outcome { + #[serde(rename = "")] + None, + #[serde(rename = "SUCCESS")] + Success, + #[serde(rename = "DUPLICATE_DENOM")] + DuplicateDenom, + #[serde(rename = "INSUFFICIENT_FUNDS")] + InsufficientFunds, +} + +#[derive(Clone, Debug, Deserialize)] +struct Coin { + amount: BigInt, + denom: String, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(tag = "tag")] +enum Action { + #[serde(rename = "init")] + Init { balances: Balances }, + + #[serde(rename = "send")] + Send { + receiver: String, + sender: String, + coins: Vec, + }, +} + +#[derive(Clone, Debug, Deserialize)] +struct State { + action: Action, + outcome: Outcome, + balances: Balances, + step: i64, +} + +#[test] +#[ignore] +fn deserialize() { + let data = include_str!("../tests/fixtures/TestInsufficientSuccess9.itf.json"); + let trace = serde_itf::trace_from_str::(data).unwrap(); + + dbg!(trace); +} diff --git a/serde-itf/tests/missionaries_and_cannibals.rs b/serde-itf/tests/missionaries_and_cannibals.rs new file mode 100644 index 0000000..66ec864 --- /dev/null +++ b/serde-itf/tests/missionaries_and_cannibals.rs @@ -0,0 +1,43 @@ +#![allow(dead_code)] + +use std::collections::{BTreeSet, HashMap}; + +use serde::Deserialize; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] +enum Bank { + #[serde(rename = "N")] + North, + #[serde(rename = "W")] + West, + #[serde(rename = "E")] + East, + #[serde(rename = "S")] + South, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)] +enum Person { + #[serde(rename = "c1_OF_PERSON")] + Cannibal1, + #[serde(rename = "c2_OF_PERSON")] + Cannibal2, + #[serde(rename = "m1_OF_PERSON")] + Missionary1, + #[serde(rename = "m2_OF_PERSON")] + Missionary2, +} + +#[derive(Clone, Debug, Deserialize)] +struct State { + pub bank_of_boat: Bank, + pub who_is_on_bank: HashMap>, +} + +#[test] +fn cannibals() { + let data = include_str!("../tests/fixtures/MissionariesAndCannibals.itf.json"); + let trace = serde_itf::trace_from_str::(data).unwrap(); + + dbg!(trace); +} From 0823107f7b8b4adcd13bbcde690088be6079c10c Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 26 Oct 2023 22:43:14 +0200 Subject: [PATCH 07/50] Rename `serde-itf` crate to `itf` --- Cargo.toml | 2 +- {serde-itf => itf}/Cargo.toml | 2 +- {serde-itf => itf}/src/de.rs | 0 {serde-itf => itf}/src/error.rs | 0 {serde-itf => itf}/src/lib.rs | 0 {serde-itf => itf}/src/state.rs | 0 {serde-itf => itf}/src/trace.rs | 0 {serde-itf => itf}/src/value.rs | 0 {serde-itf => itf}/src/value/bigint.rs | 0 {serde-itf => itf}/src/value/map.rs | 0 {serde-itf => itf}/src/value/set.rs | 0 {serde-itf => itf}/src/value/tuple.rs | 0 {serde-itf => itf}/src/value/unserializable.rs | 0 {serde-itf => itf}/tests/decide_non_proposer.rs | 2 +- .../tests/fixtures/DecideNonProposerTest0.itf.json | 0 .../tests/fixtures/MissionariesAndCannibals.itf.json | 0 .../tests/fixtures/TestInsufficientSuccess9.itf.json | 0 {serde-itf => itf}/tests/insufficient_success.rs | 4 ++-- {serde-itf => itf}/tests/missionaries_and_cannibals.rs | 2 +- 19 files changed, 6 insertions(+), 6 deletions(-) rename {serde-itf => itf}/Cargo.toml (92%) rename {serde-itf => itf}/src/de.rs (100%) rename {serde-itf => itf}/src/error.rs (100%) rename {serde-itf => itf}/src/lib.rs (100%) rename {serde-itf => itf}/src/state.rs (100%) rename {serde-itf => itf}/src/trace.rs (100%) rename {serde-itf => itf}/src/value.rs (100%) rename {serde-itf => itf}/src/value/bigint.rs (100%) rename {serde-itf => itf}/src/value/map.rs (100%) rename {serde-itf => itf}/src/value/set.rs (100%) rename {serde-itf => itf}/src/value/tuple.rs (100%) rename {serde-itf => itf}/src/value/unserializable.rs (100%) rename {serde-itf => itf}/tests/decide_non_proposer.rs (98%) rename {serde-itf => itf}/tests/fixtures/DecideNonProposerTest0.itf.json (100%) rename {serde-itf => itf}/tests/fixtures/MissionariesAndCannibals.itf.json (100%) rename {serde-itf => itf}/tests/fixtures/TestInsufficientSuccess9.itf.json (100%) rename {serde-itf => itf}/tests/insufficient_success.rs (91%) rename {serde-itf => itf}/tests/missionaries_and_cannibals.rs (93%) diff --git a/Cargo.toml b/Cargo.toml index 194360e..54a3f2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,5 @@ resolver = "2" members = [ - "serde-itf", + "itf", ] diff --git a/serde-itf/Cargo.toml b/itf/Cargo.toml similarity index 92% rename from serde-itf/Cargo.toml rename to itf/Cargo.toml index fa5e66c..faca6a1 100644 --- a/serde-itf/Cargo.toml +++ b/itf/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "serde-itf" +name = "itf" version = "0.1.0" edition = "2021" diff --git a/serde-itf/src/de.rs b/itf/src/de.rs similarity index 100% rename from serde-itf/src/de.rs rename to itf/src/de.rs diff --git a/serde-itf/src/error.rs b/itf/src/error.rs similarity index 100% rename from serde-itf/src/error.rs rename to itf/src/error.rs diff --git a/serde-itf/src/lib.rs b/itf/src/lib.rs similarity index 100% rename from serde-itf/src/lib.rs rename to itf/src/lib.rs diff --git a/serde-itf/src/state.rs b/itf/src/state.rs similarity index 100% rename from serde-itf/src/state.rs rename to itf/src/state.rs diff --git a/serde-itf/src/trace.rs b/itf/src/trace.rs similarity index 100% rename from serde-itf/src/trace.rs rename to itf/src/trace.rs diff --git a/serde-itf/src/value.rs b/itf/src/value.rs similarity index 100% rename from serde-itf/src/value.rs rename to itf/src/value.rs diff --git a/serde-itf/src/value/bigint.rs b/itf/src/value/bigint.rs similarity index 100% rename from serde-itf/src/value/bigint.rs rename to itf/src/value/bigint.rs diff --git a/serde-itf/src/value/map.rs b/itf/src/value/map.rs similarity index 100% rename from serde-itf/src/value/map.rs rename to itf/src/value/map.rs diff --git a/serde-itf/src/value/set.rs b/itf/src/value/set.rs similarity index 100% rename from serde-itf/src/value/set.rs rename to itf/src/value/set.rs diff --git a/serde-itf/src/value/tuple.rs b/itf/src/value/tuple.rs similarity index 100% rename from serde-itf/src/value/tuple.rs rename to itf/src/value/tuple.rs diff --git a/serde-itf/src/value/unserializable.rs b/itf/src/value/unserializable.rs similarity index 100% rename from serde-itf/src/value/unserializable.rs rename to itf/src/value/unserializable.rs diff --git a/serde-itf/tests/decide_non_proposer.rs b/itf/tests/decide_non_proposer.rs similarity index 98% rename from serde-itf/tests/decide_non_proposer.rs rename to itf/tests/decide_non_proposer.rs index 1472b05..0470344 100644 --- a/serde-itf/tests/decide_non_proposer.rs +++ b/itf/tests/decide_non_proposer.rs @@ -230,6 +230,6 @@ where #[test] fn deserialize() { let data = include_str!("../tests/fixtures/DecideNonProposerTest0.itf.json"); - let trace = serde_itf::trace_from_str::(data).unwrap(); + let trace = itf::trace_from_str::(data).unwrap(); dbg!(trace); } diff --git a/serde-itf/tests/fixtures/DecideNonProposerTest0.itf.json b/itf/tests/fixtures/DecideNonProposerTest0.itf.json similarity index 100% rename from serde-itf/tests/fixtures/DecideNonProposerTest0.itf.json rename to itf/tests/fixtures/DecideNonProposerTest0.itf.json diff --git a/serde-itf/tests/fixtures/MissionariesAndCannibals.itf.json b/itf/tests/fixtures/MissionariesAndCannibals.itf.json similarity index 100% rename from serde-itf/tests/fixtures/MissionariesAndCannibals.itf.json rename to itf/tests/fixtures/MissionariesAndCannibals.itf.json diff --git a/serde-itf/tests/fixtures/TestInsufficientSuccess9.itf.json b/itf/tests/fixtures/TestInsufficientSuccess9.itf.json similarity index 100% rename from serde-itf/tests/fixtures/TestInsufficientSuccess9.itf.json rename to itf/tests/fixtures/TestInsufficientSuccess9.itf.json diff --git a/serde-itf/tests/insufficient_success.rs b/itf/tests/insufficient_success.rs similarity index 91% rename from serde-itf/tests/insufficient_success.rs rename to itf/tests/insufficient_success.rs index 81aff5a..d75d22e 100644 --- a/serde-itf/tests/insufficient_success.rs +++ b/itf/tests/insufficient_success.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; +use itf::value::BigInt; use serde::Deserialize; -use serde_itf::value::BigInt; type Balance = HashMap; type Balances = HashMap; @@ -52,7 +52,7 @@ struct State { #[ignore] fn deserialize() { let data = include_str!("../tests/fixtures/TestInsufficientSuccess9.itf.json"); - let trace = serde_itf::trace_from_str::(data).unwrap(); + let trace = itf::trace_from_str::(data).unwrap(); dbg!(trace); } diff --git a/serde-itf/tests/missionaries_and_cannibals.rs b/itf/tests/missionaries_and_cannibals.rs similarity index 93% rename from serde-itf/tests/missionaries_and_cannibals.rs rename to itf/tests/missionaries_and_cannibals.rs index 66ec864..a99b479 100644 --- a/serde-itf/tests/missionaries_and_cannibals.rs +++ b/itf/tests/missionaries_and_cannibals.rs @@ -37,7 +37,7 @@ struct State { #[test] fn cannibals() { let data = include_str!("../tests/fixtures/MissionariesAndCannibals.itf.json"); - let trace = serde_itf::trace_from_str::(data).unwrap(); + let trace = itf::trace_from_str::(data).unwrap(); dbg!(trace); } From a56363c5e86d1cf30bd3c6aa651914cc289ad13b Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sat, 18 Nov 2023 01:40:19 +0100 Subject: [PATCH 08/50] update visit_bigint for num_bigint::BigInt --- itf/src/de.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/itf/src/de.rs b/itf/src/de.rs index dd46fff..5da4828 100644 --- a/itf/src/de.rs +++ b/itf/src/de.rs @@ -356,12 +356,24 @@ fn visit_bigint<'de, V>(v: BigInt, visitor: V) -> Result where V: Visitor<'de>, { - let map = vec![("#bigint".to_string(), Value::String(v.to_string()))] + let (sign, digits) = v.into_inner().to_u32_digits(); + + let sign_value = match sign { + num_bigint::Sign::Minus => -1, + num_bigint::Sign::NoSign => 0, + num_bigint::Sign::Plus => 1, + }; + + let digit_value = digits .into_iter() - .collect::>(); + .map(i64::from) + .map(Value::Number) + .collect(); + + let serialized = [Value::Number(sign_value), Value::List(digit_value)]; - let record = crate::value::Map::new(map); - visit_record(record, visitor) + let deserializer = SeqDeserializer::new(serialized.into_iter()); + visitor.visit_seq(deserializer) } fn visit_map<'de, V>(v: Map, visitor: V) -> Result From c03bec1122fbb533ffe19e6c5dcac1c3465cfb82 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sat, 18 Nov 2023 01:42:39 +0100 Subject: [PATCH 09/50] update itf::value::BigInt deserialize for num_bigint::BigInt --- itf/src/value/bigint.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/itf/src/value/bigint.rs b/itf/src/value/bigint.rs index 9ca483b..7cfc68b 100644 --- a/itf/src/value/bigint.rs +++ b/itf/src/value/bigint.rs @@ -55,13 +55,31 @@ impl<'de> Deserialize<'de> for BigInt { D: serde::Deserializer<'de>, { #[derive(Deserialize)] - struct BigInt { - #[serde(rename = "#bigint")] - bigint: String, + #[serde(untagged)] + enum BigInt { + // deserialized serde_json::Value + Itf { + #[serde(rename = "#bigint")] + bigint: String, + }, + // deserialized itf::Value + BigInt(i64, Vec), } - let inner = BigInt::deserialize(deserializer)?; - let bigint = inner.bigint.parse().map_err(serde::de::Error::custom)?; - Ok(Self(bigint)) + match BigInt::deserialize(deserializer)? { + BigInt::Itf { bigint } => { + let bigint: num_bigint::BigInt = + bigint.parse().map_err(serde::de::Error::custom)?; + Ok(Self::new(bigint)) + } + BigInt::BigInt(sign, digits) => { + let sign = match sign.cmp(&0) { + std::cmp::Ordering::Less => num_bigint::Sign::Minus, + std::cmp::Ordering::Equal => num_bigint::Sign::NoSign, + std::cmp::Ordering::Greater => num_bigint::Sign::Plus, + }; + Ok(Self::new(num_bigint::BigInt::new(sign, digits))) + } + } } } From 92bb9d3d12adf7e4b26b0119385d8f5b2415fcbc Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sat, 18 Nov 2023 01:43:54 +0100 Subject: [PATCH 10/50] visit_bigint when deserialize_tuple --- itf/src/de.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/itf/src/de.rs b/itf/src/de.rs index 5da4828..641820b 100644 --- a/itf/src/de.rs +++ b/itf/src/de.rs @@ -255,7 +255,10 @@ impl<'de> Deserializer<'de> for Value { where V: Visitor<'de>, { - self.deserialize_seq(visitor) + match self { + Value::BigInt(v) => visit_bigint(v, visitor), + _ => self.deserialize_seq(visitor), + } } fn deserialize_tuple_struct( From d34686eb8a890b5b03f256800a75df0bdd1bc756 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sat, 18 Nov 2023 01:45:50 +0100 Subject: [PATCH 11/50] all integers are bigints --- .../TestInsufficientSuccess9.itf.json | 292 ++++-------------- 1 file changed, 62 insertions(+), 230 deletions(-) diff --git a/itf/tests/fixtures/TestInsufficientSuccess9.itf.json b/itf/tests/fixtures/TestInsufficientSuccess9.itf.json index 2e8d813..0e86dac 100644 --- a/itf/tests/fixtures/TestInsufficientSuccess9.itf.json +++ b/itf/tests/fixtures/TestInsufficientSuccess9.itf.json @@ -4,12 +4,7 @@ "format-description": "https://apalache.informal.systems/docs/adr/015adr-trace.html", "description": "Created by Apalache on Sat Sep 24 20:45:34 CEST 2022" }, - "vars": [ - "outcome", - "balances", - "action", - "step" - ], + "vars": ["outcome", "balances", "action", "step"], "states": [ { "#meta": { @@ -22,18 +17,9 @@ "Carol", { "#map": [ - [ - "atom", - 0 - ], - [ - "muon", - 0 - ], - [ - "gluon", - 0 - ] + ["atom", { "#bigint": "0" }], + ["muon", { "#bigint": "0" }], + ["gluon", { "#bigint": "0" }] ] } ], @@ -41,18 +27,9 @@ "Eve", { "#map": [ - [ - "atom", - 0 - ], - [ - "muon", - 0 - ], - [ - "gluon", - 0 - ] + ["atom", { "#bigint": "0" }], + ["muon", { "#bigint": "0" }], + ["gluon", { "#bigint": "0" }] ] } ], @@ -110,18 +87,9 @@ "Dave", { "#map": [ - [ - "atom", - 0 - ], - [ - "muon", - 0 - ], - [ - "gluon", - 0 - ] + ["atom", { "#bigint": "0" }], + ["muon", { "#bigint": "0" }], + ["gluon", { "#bigint": "0" }] ] } ] @@ -135,18 +103,9 @@ "Carol", { "#map": [ - [ - "atom", - 0 - ], - [ - "muon", - 0 - ], - [ - "gluon", - 0 - ] + ["atom", { "#bigint": "0" }], + ["muon", { "#bigint": "0" }], + ["gluon", { "#bigint": "0" }] ] } ], @@ -154,18 +113,9 @@ "Eve", { "#map": [ - [ - "atom", - 0 - ], - [ - "muon", - 0 - ], - [ - "gluon", - 0 - ] + ["atom", { "#bigint": "0" }], + ["muon", { "#bigint": "0" }], + ["gluon", { "#bigint": "0" }] ] } ], @@ -223,18 +173,9 @@ "Dave", { "#map": [ - [ - "atom", - 0 - ], - [ - "muon", - 0 - ], - [ - "gluon", - 0 - ] + ["atom", { "#bigint": "0" }], + ["muon", { "#bigint": "0" }], + ["gluon", { "#bigint": "0" }] ] } ] @@ -256,7 +197,7 @@ "denom": "muon" }, { - "amount": 2, + "amount": { "#bigint": "2" }, "denom": "atom" }, { @@ -276,18 +217,9 @@ "Carol", { "#map": [ - [ - "muon", - 0 - ], - [ - "gluon", - 0 - ], - [ - "atom", - 0 - ] + ["muon", { "#bigint": "0" }], + ["gluon", { "#bigint": "0" }], + ["atom", { "#bigint": "0" }] ] } ], @@ -295,10 +227,7 @@ "Eve", { "#map": [ - [ - "atom", - 2 - ], + ["atom", { "#bigint": "2" }], [ "muon", { @@ -318,14 +247,8 @@ "Alice", { "#map": [ - [ - "gluon", - 2 - ], - [ - "muon", - 2 - ], + ["gluon", { "#bigint": "2" }], + ["muon", { "#bigint": "2" }], [ "atom", { @@ -364,18 +287,9 @@ "Dave", { "#map": [ - [ - "gluon", - 0 - ], - [ - "muon", - 0 - ], - [ - "atom", - 0 - ] + ["gluon", { "#bigint": "0" }], + ["muon", { "#bigint": "0" }], + ["atom", { "#bigint": "0" }] ] } ] @@ -391,11 +305,11 @@ "action": { "coins": [ { - "amount": 1, + "amount": { "#bigint": "1" }, "denom": "gluon" }, { - "amount": 0, + "amount": { "#bigint": "0" }, "denom": "gluon" } ], @@ -409,18 +323,9 @@ "Carol", { "#map": [ - [ - "muon", - 0 - ], - [ - "gluon", - 0 - ], - [ - "atom", - 0 - ] + ["muon", { "#bigint": "0" }], + ["gluon", { "#bigint": "0" }], + ["atom", { "#bigint": "0" }] ] } ], @@ -440,10 +345,7 @@ "#bigint": "57896044618658097711785492504343953926634992332820282019728792003956564819965" } ], - [ - "atom", - 2 - ] + ["atom", { "#bigint": "2" }] ] } ], @@ -451,14 +353,8 @@ "Alice", { "#map": [ - [ - "muon", - 2 - ], - [ - "gluon", - 2 - ], + ["muon", { "#bigint": "2" }], + ["gluon", { "#bigint": "2" }], [ "atom", { @@ -497,18 +393,9 @@ "Dave", { "#map": [ - [ - "gluon", - 0 - ], - [ - "atom", - 0 - ], - [ - "muon", - 0 - ] + ["gluon", { "#bigint": "0" }], + ["atom", { "#bigint": "0" }], + ["muon", { "#bigint": "0" }] ] } ] @@ -530,7 +417,7 @@ "denom": "muon" }, { - "amount": 3, + "amount": { "#bigint": "3" }, "denom": "atom" } ], @@ -544,18 +431,9 @@ "Carol", { "#map": [ - [ - "gluon", - 0 - ], - [ - "atom", - 0 - ], - [ - "muon", - 0 - ] + ["gluon", { "#bigint": "0" }], + ["atom", { "#bigint": "0" }], + ["muon", { "#bigint": "0" }] ] } ], @@ -575,10 +453,7 @@ "#bigint": "57896044618658097711785492504343953926634992332820282019728792003956564819965" } ], - [ - "atom", - 2 - ] + ["atom", { "#bigint": "2" }] ] } ], @@ -586,20 +461,14 @@ "Alice", { "#map": [ - [ - "gluon", - 2 - ], + ["gluon", { "#bigint": "2" }], [ "atom", { "#bigint": "57896044618658097711785492504343953926634992332820282019728792003956564819965" } ], - [ - "muon", - 2 - ] + ["muon", { "#bigint": "2" }] ] } ], @@ -632,18 +501,9 @@ "Dave", { "#map": [ - [ - "gluon", - 0 - ], - [ - "muon", - 0 - ], - [ - "atom", - 0 - ] + ["gluon", { "#bigint": "0" }], + ["muon", { "#bigint": "0" }], + ["atom", { "#bigint": "0" }] ] } ] @@ -659,15 +519,15 @@ "action": { "coins": [ { - "amount": 1, + "amount": { "#bigint": "1" }, "denom": "gluon" }, { - "amount": 1, + "amount": { "#bigint": "1" }, "denom": "muon" }, { - "amount": 1, + "amount": { "#bigint": "1" }, "denom": "atom" } ], @@ -681,18 +541,9 @@ "Carol", { "#map": [ - [ - "gluon", - 0 - ], - [ - "atom", - 0 - ], - [ - "muon", - 0 - ] + ["gluon", { "#bigint": "0" }], + ["atom", { "#bigint": "0" }], + ["muon", { "#bigint": "0" }] ] } ], @@ -712,10 +563,7 @@ "#bigint": "57896044618658097711785492504343953926634992332820282019728792003956564819965" } ], - [ - "atom", - 2 - ] + ["atom", { "#bigint": "2" }] ] } ], @@ -723,20 +571,14 @@ "Alice", { "#map": [ - [ - "gluon", - 2 - ], + ["gluon", { "#bigint": "2" }], [ "atom", { "#bigint": "57896044618658097711785492504343953926634992332820282019728792003956564819965" } ], - [ - "muon", - 2 - ] + ["muon", { "#bigint": "2" }] ] } ], @@ -769,18 +611,9 @@ "Dave", { "#map": [ - [ - "gluon", - 0 - ], - [ - "muon", - 0 - ], - [ - "atom", - 0 - ] + ["gluon", { "#bigint": "0" }], + ["muon", { "#bigint": "0" }], + ["atom", { "#bigint": "0" }] ] } ] @@ -791,4 +624,3 @@ } ] } - From 27b197552baecf172abab2981d943a09123de677 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sat, 18 Nov 2023 01:46:36 +0100 Subject: [PATCH 12/50] use num_bigint::BigInt in deserialized stuct directly --- itf/tests/insufficient_success.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itf/tests/insufficient_success.rs b/itf/tests/insufficient_success.rs index d75d22e..ab8d4ef 100644 --- a/itf/tests/insufficient_success.rs +++ b/itf/tests/insufficient_success.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; -use itf::value::BigInt; +use num_bigint::BigInt; use serde::Deserialize; type Balance = HashMap; From 553b9ca181ee0ce4abef7e1d228b0aaca5a01c50 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sat, 18 Nov 2023 01:46:47 +0100 Subject: [PATCH 13/50] reactivate failing test --- itf/tests/insufficient_success.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/itf/tests/insufficient_success.rs b/itf/tests/insufficient_success.rs index ab8d4ef..0ea3178 100644 --- a/itf/tests/insufficient_success.rs +++ b/itf/tests/insufficient_success.rs @@ -49,7 +49,6 @@ struct State { } #[test] -#[ignore] fn deserialize() { let data = include_str!("../tests/fixtures/TestInsufficientSuccess9.itf.json"); let trace = itf::trace_from_str::(data).unwrap(); From e552e9385458aa2494f0824cca298c573810f095 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sat, 18 Nov 2023 02:24:28 +0100 Subject: [PATCH 14/50] rm unused import --- itf/src/de.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/itf/src/de.rs b/itf/src/de.rs index 641820b..7a231c9 100644 --- a/itf/src/de.rs +++ b/itf/src/de.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeMap; use std::fmt; use num_traits::ToPrimitive; From 255cb80f192abbfc140ce8a19347a0a762432a87 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sat, 18 Nov 2023 02:47:18 +0100 Subject: [PATCH 15/50] fix tuple deserialize --- itf/src/value/tuple.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itf/src/value/tuple.rs b/itf/src/value/tuple.rs index b5255ea..22fbb3b 100644 --- a/itf/src/value/tuple.rs +++ b/itf/src/value/tuple.rs @@ -76,7 +76,7 @@ where { #[derive(Deserialize)] struct InnerTuple { - #[serde(rename = "#set")] + #[serde(rename = "#tup")] elements: Vec, } From c2b56f533878d6e056ae9e888e5467a31f0c98e4 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sat, 18 Nov 2023 03:08:33 +0100 Subject: [PATCH 16/50] fix set deserialize --- itf/src/de.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/itf/src/de.rs b/itf/src/de.rs index 7a231c9..daa77a8 100644 --- a/itf/src/de.rs +++ b/itf/src/de.rs @@ -246,6 +246,7 @@ impl<'de> Deserializer<'de> for Value { match self { Value::List(v) => visit_list(v, visitor), Value::Tuple(v) => visit_tuple(v, visitor), + Value::Set(v) => visit_set(v, visitor), _ => Err(self.invalid_type(&visitor)), } } From 2bbeb1a591621738971414411e0b8febba5dd4ce Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sat, 18 Nov 2023 03:09:42 +0100 Subject: [PATCH 17/50] regression tests --- itf/tests/regression.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 itf/tests/regression.rs diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs new file mode 100644 index 0000000..8c0bbab --- /dev/null +++ b/itf/tests/regression.rs @@ -0,0 +1,25 @@ +#[test] +fn test_tuple() { + let itf = r##"{ + "#tup" : [1, 2, 3] + }"##; + + let _: [u8; 3] = itf::from_str(itf).unwrap(); + let _: (u8, u8, u8) = itf::from_str(itf).unwrap(); + let _: Vec = itf::from_str(itf).unwrap(); + let _: std::collections::HashSet = itf::from_str(itf).unwrap(); + let _: std::collections::BTreeSet = itf::from_str(itf).unwrap(); +} + +#[test] +fn test_set() { + let itf = r##"{ + "#set" : [1, 2, 3] + }"##; + + let _: [u8; 3] = itf::from_str(itf).unwrap(); + let _: (u8, u8, u8) = itf::from_str(itf).unwrap(); + let _: Vec = itf::from_str(itf).unwrap(); + let _: std::collections::HashSet = itf::from_str(itf).unwrap(); + let _: std::collections::BTreeSet = itf::from_str(itf).unwrap(); +} From 0b34d1c06221181e3cfcacff32e93e222142cfb7 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sun, 19 Nov 2023 14:55:25 +0100 Subject: [PATCH 18/50] bunch of bigint testcases from debugging --- itf/tests/regression.rs | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index 8c0bbab..5df29df 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -22,4 +22,55 @@ fn test_set() { let _: Vec = itf::from_str(itf).unwrap(); let _: std::collections::HashSet = itf::from_str(itf).unwrap(); let _: std::collections::BTreeSet = itf::from_str(itf).unwrap(); + let _: serde_json::Value = dbg!(itf::from_str(itf).unwrap()); +} + +#[test] +fn test_num_bigint() { + let itf = r##"[-1, [99]]"##; + + assert_eq!(itf::value::BigInt::new(-99), itf::from_str(itf).unwrap()); + assert_eq!( + itf::value::Value::BigInt(itf::value::BigInt::new(-99)), + itf::from_str(itf).unwrap() + ); + assert_eq!(-99, itf::from_str::(itf).unwrap()); + assert_eq!(num_bigint::BigInt::from(-99), itf::from_str(itf).unwrap()); + + assert!(itf::from_str::(itf).is_err()); +} + +#[test] +fn test_bigint_deser() { + let itf = r##"{ + "#bigint": "-99" + }"##; + + assert_eq!(itf::value::BigInt::new(-99), itf::from_str(itf).unwrap()); + assert_eq!( + itf::value::Value::BigInt(itf::value::BigInt::new(-99)), + itf::from_str(itf).unwrap() + ); + assert_eq!(-99, itf::from_str::(itf).unwrap()); + assert_eq!(num_bigint::BigInt::from(-99), itf::from_str(itf).unwrap()); + + assert!(itf::from_str::(itf).is_err()); +} + +#[test] +fn test_biguint_deser() { + let itf = r##"{ + "#bigint": "99" + }"##; + + assert_eq!(itf::value::BigInt::new(99), itf::from_str(itf).unwrap()); + assert_eq!( + itf::value::Value::BigInt(itf::value::BigInt::new(99)), + itf::from_str(itf).unwrap() + ); + assert_eq!(99, itf::from_str::(itf).unwrap()); + assert_eq!(99, itf::from_str::(itf).unwrap()); + assert_eq!(num_bigint::BigInt::from(99), itf::from_str(itf).unwrap()); + + assert!(dbg!(itf::from_str::(itf)).is_err()); } From 0547a775d9913bc128073861ee0f69158cb59adf Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sun, 19 Nov 2023 15:02:52 +0100 Subject: [PATCH 19/50] rm dbg!() --- itf/tests/regression.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index 5df29df..04f2cee 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -22,7 +22,7 @@ fn test_set() { let _: Vec = itf::from_str(itf).unwrap(); let _: std::collections::HashSet = itf::from_str(itf).unwrap(); let _: std::collections::BTreeSet = itf::from_str(itf).unwrap(); - let _: serde_json::Value = dbg!(itf::from_str(itf).unwrap()); + let _: serde_json::Value = itf::from_str(itf).unwrap(); } #[test] @@ -72,5 +72,5 @@ fn test_biguint_deser() { assert_eq!(99, itf::from_str::(itf).unwrap()); assert_eq!(num_bigint::BigInt::from(99), itf::from_str(itf).unwrap()); - assert!(dbg!(itf::from_str::(itf)).is_err()); + assert!(itf::from_str::(itf).is_err()); } From 1ff92d848d6d83861c3c0a69bcfff0760a1097d9 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sun, 19 Nov 2023 16:07:44 +0100 Subject: [PATCH 20/50] use serde_json::Value over json string --- itf/tests/regression.rs | 94 +++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index 04f2cee..0ece2ff 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -1,76 +1,90 @@ +use std::collections::HashMap; + +use serde::Deserialize; + #[test] fn test_tuple() { - let itf = r##"{ - "#tup" : [1, 2, 3] - }"##; + let itf = serde_json::json!({"#tup": [1, 2, 3]}); - let _: [u8; 3] = itf::from_str(itf).unwrap(); - let _: (u8, u8, u8) = itf::from_str(itf).unwrap(); - let _: Vec = itf::from_str(itf).unwrap(); - let _: std::collections::HashSet = itf::from_str(itf).unwrap(); - let _: std::collections::BTreeSet = itf::from_str(itf).unwrap(); + let _: [u8; 3] = itf::from_value(itf.clone()).unwrap(); + let _: (u8, u8, u8) = itf::from_value(itf.clone()).unwrap(); + let _: Vec = itf::from_value(itf.clone()).unwrap(); + let _: std::collections::HashSet = itf::from_value(itf.clone()).unwrap(); + let _: std::collections::BTreeSet = itf::from_value(itf.clone()).unwrap(); } #[test] fn test_set() { - let itf = r##"{ - "#set" : [1, 2, 3] - }"##; + let itf = serde_json::json!({"#set": [1, 2, 3]}); - let _: [u8; 3] = itf::from_str(itf).unwrap(); - let _: (u8, u8, u8) = itf::from_str(itf).unwrap(); - let _: Vec = itf::from_str(itf).unwrap(); - let _: std::collections::HashSet = itf::from_str(itf).unwrap(); - let _: std::collections::BTreeSet = itf::from_str(itf).unwrap(); - let _: serde_json::Value = itf::from_str(itf).unwrap(); + let _: [u8; 3] = itf::from_value(itf.clone()).unwrap(); + let _: (u8, u8, u8) = itf::from_value(itf.clone()).unwrap(); + let _: Vec = itf::from_value(itf.clone()).unwrap(); + let _: std::collections::HashSet = itf::from_value(itf.clone()).unwrap(); + let _: std::collections::BTreeSet = itf::from_value(itf.clone()).unwrap(); + let _: serde_json::Value = itf::from_value(itf.clone()).unwrap(); } #[test] fn test_num_bigint() { - let itf = r##"[-1, [99]]"##; + let itf = serde_json::json!([-1, [99]]); - assert_eq!(itf::value::BigInt::new(-99), itf::from_str(itf).unwrap()); + assert_eq!( + itf::value::BigInt::new(-99), + itf::from_value(itf.clone()).unwrap() + ); assert_eq!( itf::value::Value::BigInt(itf::value::BigInt::new(-99)), - itf::from_str(itf).unwrap() + itf::from_value(itf.clone()).unwrap() + ); + assert_eq!(-99, itf::from_value::(itf.clone()).unwrap()); + assert_eq!( + num_bigint::BigInt::from(-99), + itf::from_value(itf.clone()).unwrap() ); - assert_eq!(-99, itf::from_str::(itf).unwrap()); - assert_eq!(num_bigint::BigInt::from(-99), itf::from_str(itf).unwrap()); - assert!(itf::from_str::(itf).is_err()); + assert!(itf::from_value::(itf.clone()).is_err()); } #[test] fn test_bigint_deser() { - let itf = r##"{ - "#bigint": "-99" - }"##; + let itf = serde_json::json!({"#bigint": "-99"}); - assert_eq!(itf::value::BigInt::new(-99), itf::from_str(itf).unwrap()); + assert_eq!( + itf::value::BigInt::new(-99), + itf::from_value(itf.clone()).unwrap() + ); assert_eq!( itf::value::Value::BigInt(itf::value::BigInt::new(-99)), - itf::from_str(itf).unwrap() + itf::from_value(itf.clone()).unwrap() + ); + assert_eq!(-99, itf::from_value::(itf.clone()).unwrap()); + assert_eq!( + num_bigint::BigInt::from(-99), + itf::from_value(itf.clone()).unwrap() ); - assert_eq!(-99, itf::from_str::(itf).unwrap()); - assert_eq!(num_bigint::BigInt::from(-99), itf::from_str(itf).unwrap()); - assert!(itf::from_str::(itf).is_err()); + assert!(itf::from_value::(itf.clone()).is_err()); } #[test] fn test_biguint_deser() { - let itf = r##"{ - "#bigint": "99" - }"##; + let itf = serde_json::json!({"#bigint": "99"}); - assert_eq!(itf::value::BigInt::new(99), itf::from_str(itf).unwrap()); + assert_eq!( + itf::value::BigInt::new(99), + itf::from_value(itf.clone()).unwrap() + ); assert_eq!( itf::value::Value::BigInt(itf::value::BigInt::new(99)), - itf::from_str(itf).unwrap() + itf::from_value(itf.clone()).unwrap() + ); + assert_eq!(99, itf::from_value::(itf.clone()).unwrap()); + assert_eq!(99, itf::from_value::(itf.clone()).unwrap()); + assert_eq!( + num_bigint::BigInt::from(99), + itf::from_value(itf.clone()).unwrap() ); - assert_eq!(99, itf::from_str::(itf).unwrap()); - assert_eq!(99, itf::from_str::(itf).unwrap()); - assert_eq!(num_bigint::BigInt::from(99), itf::from_str(itf).unwrap()); - assert!(itf::from_str::(itf).is_err()); + assert!(itf::from_value::(itf.clone()).is_err()); } From d89d2fb5133b61e18d33a62971168e8ecd0fa797 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Sun, 19 Nov 2023 16:08:40 +0100 Subject: [PATCH 21/50] add testcases for Value::deserialize(Value) --- itf/tests/regression.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index 0ece2ff..5309b0a 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -88,3 +88,44 @@ fn test_biguint_deser() { assert!(itf::from_value::(itf.clone()).is_err()); } + +#[test] +fn test_itf_value_equivalent() { + let itf = serde_json::json!({ + "bool": true, + "number": -99, + "str": "hello", + "bigint": {"#bigint": "-999"}, + "list": [1, 2, 3], + "record": {"a": 1, "b": 2, "c": 3}, + }); + + let value = serde_json::from_value::(itf.clone()).unwrap(); + assert_eq!(value.clone(), itf::Value::deserialize(value).unwrap()); +} + +#[test] +#[should_panic] +fn test_itf_value_noneq() { + // Deserialized Value loses the type information + let itf = serde_json::json!({ + "tuple": {"#tup": [1, 2, 3]}, + "set": {"#set": [1, 2, 3]}, + "map": {"#map": [["1", 3], ["2", 4]]}, + }); + + let value = serde_json::from_value::(itf.clone()).unwrap(); + assert_eq!(value.clone(), itf::Value::deserialize(value).unwrap()); +} + +#[test] +#[should_panic] +fn test_map_with_non_str_key() { + // MapSerializer accepts only string keys + let itf = serde_json::json!({ + "map": {"#map": [[1, 3], [2, 4]]}, + }); + + let value = serde_json::from_value::(itf.clone()).unwrap(); + assert_eq!(value.clone(), itf::Value::deserialize(value).unwrap()); +} From 4efbf20bb94b8ad3464894986a7c3dae2ddef33f Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 20 Nov 2023 10:24:38 +0100 Subject: [PATCH 22/50] Update code coverage workflow --- .github/workflows/coverage.yml | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 14839c0..58f3e22 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,31 +1,41 @@ name: Coverage on: - pull_request: push: branches: main + paths: + - itf/** + pull_request: + paths: + - itf/** jobs: coverage: runs-on: ubuntu-latest + defaults: + run: + working-directory: itf env: CARGO_TERM_COLOR: always steps: - - uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: stable - override: true + toolchain: nightly + components: llvm-tools-preview + - name: Install cargo-nextest + uses: taiki-e/install-action@cargo-nextest - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - - name: Install cargo-nextest - uses: taiki-e/install-action@nextest - name: Generate code coverage run: cargo llvm-cov nextest --all-features --workspace --lcov --output-path lcov.info + - name: Generate text report + run: cargo llvm-cov report - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: - token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos - files: lcov.info + token: ${{ secrets.CODECOV_TOKEN }} + files: itf/lcov.info fail_ci_if_error: true From c6b1f4859598ea4f432b5b329b5dc7e4563a1546 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 20 Nov 2023 10:53:03 +0100 Subject: [PATCH 23/50] Update codecov config --- .codecov.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index afc5bcd..37f41de 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,4 +1,26 @@ codecov: require_ci_to_pass: yes - bot: romac +coverage: + precision: 2 + round: nearest + range: "50...100" + + status: + project: + default: + target: auto + threshold: 5% + removed_code_behavior: adjust_base + paths: + - "itf" + patch: + default: + target: auto + threshold: 5% + paths: + - "itf" + + changes: + default: + informational: true From 468bc0029fb4d5dac728f1fbe11d9c227fe7f824 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 20 Nov 2023 11:01:49 +0100 Subject: [PATCH 24/50] Hide `Value` enum from documentation We keep exporting it to allow for usecases where one needs the raw ITF `Value`, but avoid documenting it as there are a few pitfalls when doing so. See for more information --- itf/src/de.rs | 1 + itf/src/lib.rs | 6 +++++- itf/src/value.rs | 10 ++++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/itf/src/de.rs b/itf/src/de.rs index daa77a8..7300600 100644 --- a/itf/src/de.rs +++ b/itf/src/de.rs @@ -10,6 +10,7 @@ use serde::Deserialize; use crate::value::{BigInt, Map, Set, Tuple, Value}; +#[doc(hidden)] pub fn decode_value(value: Value) -> Result where T: DeserializeOwned, diff --git a/itf/src/lib.rs b/itf/src/lib.rs index c8eddb3..9b336f5 100644 --- a/itf/src/lib.rs +++ b/itf/src/lib.rs @@ -208,11 +208,15 @@ pub mod de; pub mod error; pub mod state; pub mod trace; -pub mod value; pub use error::Error; pub use state::State; pub use trace::Trace; + +#[doc(hidden)] +pub mod value; + +#[doc(hidden)] pub use value::Value; pub fn trace_from_str(str: &str) -> Result, Error> diff --git a/itf/src/value.rs b/itf/src/value.rs index 235aa41..174a163 100644 --- a/itf/src/value.rs +++ b/itf/src/value.rs @@ -12,10 +12,16 @@ pub use set::Set; pub use tuple::Tuple; pub use unserializable::Unserializable; -// TODO: Display - +/// An ITF value, as per the [Apalache ITF format][itf-spec] specification. +/// +/// This enum is hidden from the documentation, as it is not meant to be used directly +/// because of pitfalls documented in [this PR][pitfalls]. +/// +/// [itf-spec]: https://apalache.informal.systems/docs/adr/015adr-trace.html +/// [pitfalls]: https://github.com/informalsystems/itf-rs/pull/6#issuecomment-1817860601 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(untagged)] +#[doc(hidden)] pub enum Value { /// A JSON Boolean: either `false` or `true`. Bool(bool), From b10e7b04d9c8e3a44aa9e07fa4d558d626c010b8 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Mon, 20 Nov 2023 11:13:29 +0100 Subject: [PATCH 25/50] revert c03bec1 --- itf/src/value/bigint.rs | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/itf/src/value/bigint.rs b/itf/src/value/bigint.rs index 7cfc68b..9ca483b 100644 --- a/itf/src/value/bigint.rs +++ b/itf/src/value/bigint.rs @@ -55,31 +55,13 @@ impl<'de> Deserialize<'de> for BigInt { D: serde::Deserializer<'de>, { #[derive(Deserialize)] - #[serde(untagged)] - enum BigInt { - // deserialized serde_json::Value - Itf { - #[serde(rename = "#bigint")] - bigint: String, - }, - // deserialized itf::Value - BigInt(i64, Vec), + struct BigInt { + #[serde(rename = "#bigint")] + bigint: String, } - match BigInt::deserialize(deserializer)? { - BigInt::Itf { bigint } => { - let bigint: num_bigint::BigInt = - bigint.parse().map_err(serde::de::Error::custom)?; - Ok(Self::new(bigint)) - } - BigInt::BigInt(sign, digits) => { - let sign = match sign.cmp(&0) { - std::cmp::Ordering::Less => num_bigint::Sign::Minus, - std::cmp::Ordering::Equal => num_bigint::Sign::NoSign, - std::cmp::Ordering::Greater => num_bigint::Sign::Plus, - }; - Ok(Self::new(num_bigint::BigInt::new(sign, digits))) - } - } + let inner = BigInt::deserialize(deserializer)?; + let bigint = inner.bigint.parse().map_err(serde::de::Error::custom)?; + Ok(Self(bigint)) } } From 7ee804310c2fbb7302bc409674367cce8aee3f1c Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Mon, 20 Nov 2023 11:14:58 +0100 Subject: [PATCH 26/50] update tests --- itf/tests/regression.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index 1c472f5..d0957b7 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -93,7 +93,6 @@ fn test_itf_value_equivalent() { "bool": true, "number": -99, "str": "hello", - "bigint": {"#bigint": "-999"}, "list": [1, 2, 3], "record": {"a": 1, "b": 2, "c": 3}, }); @@ -107,6 +106,7 @@ fn test_itf_value_equivalent() { fn test_itf_value_noneq() { // Deserialized Value loses the type information let itf = serde_json::json!({ + "bigint": {"#bigint": "-999"}, "tuple": {"#tup": [1, 2, 3]}, "set": {"#set": [1, 2, 3]}, "map": {"#map": [["1", 3], ["2", 4]]}, @@ -124,6 +124,5 @@ fn test_map_with_non_str_key() { "map": {"#map": [[1, 3], [2, 4]]}, }); - let value = serde_json::from_value::(itf.clone()).unwrap(); - assert_eq!(value.clone(), itf::Value::deserialize(value).unwrap()); + assert!(serde_json::from_value::(itf.clone()).is_err()); } From 29a580c382e3b0ff598e196b33d267ec4b1372ec Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Mon, 20 Nov 2023 11:27:47 +0100 Subject: [PATCH 27/50] fix failing tests --- itf/tests/regression.rs | 49 +++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index d0957b7..4667dc8 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -27,56 +27,47 @@ fn test_set() { fn test_num_bigint() { let itf = serde_json::json!([-1, [99]]); - assert_eq!( - itf::value::BigInt::new(-99), - itf::from_value(itf.clone()).unwrap() - ); - assert_eq!( - itf::value::Value::BigInt(itf::value::BigInt::new(-99)), - itf::from_value(itf.clone()).unwrap() - ); - assert_eq!(-99, itf::from_value::(itf.clone()).unwrap()); + // successful cases assert_eq!( num_bigint::BigInt::from(-99), - itf::from_value(itf.clone()).unwrap() + itf::from_value::(itf.clone()).unwrap() ); + // unsuccessful cases + assert!(itf::from_value::(itf.clone()).is_err()); assert!(itf::from_value::(itf.clone()).is_err()); + assert!(itf::from_value::(itf.clone()).is_err()); + assert!(!matches!( + itf::from_value::(itf.clone()).unwrap(), + itf::value::Value::BigInt(_), + )); } #[test] fn test_bigint_deser() { let itf = serde_json::json!({"#bigint": "-99"}); - assert_eq!( - itf::value::BigInt::new(-99), - itf::from_value(itf.clone()).unwrap() - ); - assert_eq!( - itf::value::Value::BigInt(itf::value::BigInt::new(-99)), - itf::from_value(itf.clone()).unwrap() - ); + // successful cases assert_eq!(-99, itf::from_value::(itf.clone()).unwrap()); assert_eq!( num_bigint::BigInt::from(-99), itf::from_value(itf.clone()).unwrap() ); + // unsuccessful cases assert!(itf::from_value::(itf.clone()).is_err()); + assert!(itf::from_value::(itf.clone()).is_err()); + assert!(!matches!( + itf::from_value::(itf.clone()).unwrap(), + itf::value::Value::BigInt(_), + )); } #[test] fn test_biguint_deser() { let itf = serde_json::json!({"#bigint": "99"}); - assert_eq!( - itf::value::BigInt::new(99), - itf::from_value(itf.clone()).unwrap() - ); - assert_eq!( - itf::value::Value::BigInt(itf::value::BigInt::new(99)), - itf::from_value(itf.clone()).unwrap() - ); + // successful cases assert_eq!(99, itf::from_value::(itf.clone()).unwrap()); assert_eq!(99, itf::from_value::(itf.clone()).unwrap()); assert_eq!( @@ -84,7 +75,13 @@ fn test_biguint_deser() { itf::from_value(itf.clone()).unwrap() ); + // unsuccessful cases assert!(itf::from_value::(itf.clone()).is_err()); + assert!(itf::from_value::(itf.clone()).is_err()); + assert!(!matches!( + itf::from_value::(itf.clone()).unwrap(), + itf::value::Value::BigInt(_), + )); } #[test] From e53d02dfb2631ee8f2cb38cfd1492b6bf6e0055e Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Mon, 20 Nov 2023 11:36:22 +0100 Subject: [PATCH 28/50] fix incorrect test --- itf/tests/regression.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index 4667dc8..064657f 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -121,5 +121,6 @@ fn test_map_with_non_str_key() { "map": {"#map": [[1, 3], [2, 4]]}, }); - assert!(serde_json::from_value::(itf.clone()).is_err()); + let value = serde_json::from_value::(itf).unwrap(); + itf::Value::deserialize(value).unwrap(); } From 21aa057ec0104196a88f9bcd5353713d5b696c8d Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Mon, 20 Nov 2023 11:38:54 +0100 Subject: [PATCH 29/50] itf::Value over itf::value::Value --- itf/tests/regression.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index 064657f..18dced8 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -38,8 +38,8 @@ fn test_num_bigint() { assert!(itf::from_value::(itf.clone()).is_err()); assert!(itf::from_value::(itf.clone()).is_err()); assert!(!matches!( - itf::from_value::(itf.clone()).unwrap(), - itf::value::Value::BigInt(_), + itf::from_value::(itf.clone()).unwrap(), + itf::Value::BigInt(_), )); } @@ -58,8 +58,8 @@ fn test_bigint_deser() { assert!(itf::from_value::(itf.clone()).is_err()); assert!(itf::from_value::(itf.clone()).is_err()); assert!(!matches!( - itf::from_value::(itf.clone()).unwrap(), - itf::value::Value::BigInt(_), + itf::from_value::(itf.clone()).unwrap(), + itf::Value::BigInt(_), )); } @@ -79,8 +79,8 @@ fn test_biguint_deser() { assert!(itf::from_value::(itf.clone()).is_err()); assert!(itf::from_value::(itf.clone()).is_err()); assert!(!matches!( - itf::from_value::(itf.clone()).unwrap(), - itf::value::Value::BigInt(_), + itf::from_value::(itf.clone()).unwrap(), + itf::Value::BigInt(_), )); } From 51a3f5832e6b901b89b6fdac44f7d6511dab82ac Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Mon, 20 Nov 2023 11:50:17 +0100 Subject: [PATCH 30/50] add a complete test --- itf/tests/regression.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index 18dced8..ecf3610 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -124,3 +124,42 @@ fn test_map_with_non_str_key() { let value = serde_json::from_value::(itf).unwrap(); itf::Value::deserialize(value).unwrap(); } + +#[test] +fn test_complete() { + use std::collections::{BTreeSet, HashMap, HashSet}; + + #[derive(Deserialize, Debug)] + #[serde(untagged)] + enum RecordEnum { + One(i64, String), + Two { _foo: String, _bar: i64 }, + } + + #[derive(Deserialize, Debug)] + struct Complete { + _bool: bool, + _number: i64, + _str: String, + _bigint: num_bigint::BigInt, + _list: Vec, + _tuple: (String, num_bigint::BigInt), + _set: HashSet, + _map: HashMap, num_bigint::BigInt>, + _enum: Vec, + } + + let itf = serde_json::json!({ + "_bool": true, + "_number": -99, + "_str": "hello", + "_bigint": {"#bigint": "-999"}, + "_list": [{"#bigint": "1"}, {"#bigint": "2"}, {"#bigint": "3"}], + "_tuple": {"#tup": ["hello", {"#bigint": "999"}]}, + "_set": {"#set": [{"#bigint": "1"}, {"#bigint": "2"}, {"#bigint": "3"}]}, + "_map": {"#map": [[{"#set": [{"#bigint": "1"}, {"#bigint": "2"}]}, {"#bigint": "3"}], [{"#set": [{"#bigint": "2"}, {"#bigint": "3"}]}, {"#bigint": "4"}]]}, + "_enum": [{"#tup": [1, "hello"]}, {"_foo": "hello", "_bar": 1}], + }); + + let _: Complete = itf::from_value(itf).unwrap(); +} From 11981c43e248a949315dc270dbfd192ced27f5ab Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Mon, 20 Nov 2023 11:55:59 +0100 Subject: [PATCH 31/50] add portability test between i64 and BigInt --- itf/tests/regression.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index ecf3610..a18bf7b 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -142,6 +142,8 @@ fn test_complete() { _number: i64, _str: String, _bigint: num_bigint::BigInt, + _int_from_bigint: i64, + _bigint_from_int: num_bigint::BigInt, _list: Vec, _tuple: (String, num_bigint::BigInt), _set: HashSet, @@ -154,6 +156,8 @@ fn test_complete() { "_number": -99, "_str": "hello", "_bigint": {"#bigint": "-999"}, + "_int_from_bigint": {"#bigint": "-999"}, + "_bigint_from_int": -999, "_list": [{"#bigint": "1"}, {"#bigint": "2"}, {"#bigint": "3"}], "_tuple": {"#tup": ["hello", {"#bigint": "999"}]}, "_set": {"#set": [{"#bigint": "1"}, {"#bigint": "2"}, {"#bigint": "3"}]}, From d91d4084dc37113efb2efece1e870508c33e33d9 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Mon, 20 Nov 2023 11:57:08 +0100 Subject: [PATCH 32/50] support deserializing Number to BigInt --- itf/src/de.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/itf/src/de.rs b/itf/src/de.rs index 7300600..4b3bf96 100644 --- a/itf/src/de.rs +++ b/itf/src/de.rs @@ -258,6 +258,7 @@ impl<'de> Deserializer<'de> for Value { { match self { Value::BigInt(v) => visit_bigint(v, visitor), + Value::Number(v) => visit_bigint(BigInt::new(v), visitor), _ => self.deserialize_seq(visitor), } } From 05b1d4f5b6e0bc115dc99ac7b0796ea6583d7186 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Mon, 20 Nov 2023 12:01:58 +0100 Subject: [PATCH 33/50] test deserialization failure from true bigint to i64 --- itf/tests/regression.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index a18bf7b..f4e4041 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -125,6 +125,17 @@ fn test_map_with_non_str_key() { itf::Value::deserialize(value).unwrap(); } +#[test] +#[should_panic] +fn test_bigint_to_int() { + let itf = serde_json::json!({ + // i64::MIN - 1 + "#bigint": "-9223372036854775809", + }); + + itf::from_value::(itf).unwrap(); +} + #[test] fn test_complete() { use std::collections::{BTreeSet, HashMap, HashSet}; From 79bfbeb811d2e1fd7b31ce1ebcd3500c839b7326 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Mon, 20 Nov 2023 15:36:58 +0100 Subject: [PATCH 34/50] add success case for true bigint deserialization --- itf/tests/regression.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index f4e4041..f37bfe0 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -126,14 +126,14 @@ fn test_map_with_non_str_key() { } #[test] -#[should_panic] fn test_bigint_to_int() { let itf = serde_json::json!({ // i64::MIN - 1 "#bigint": "-9223372036854775809", }); - itf::from_value::(itf).unwrap(); + assert!(itf::from_value::(itf.clone()).is_err()); + assert!(itf::from_value::(itf).is_ok()); } #[test] From 9920493b3a74865f1d41aca72838a6e57d48d9b5 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Mon, 20 Nov 2023 15:38:25 +0100 Subject: [PATCH 35/50] test for failure when deserialize bigint to int in enum --- itf/tests/regression.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index f37bfe0..c991346 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -136,6 +136,34 @@ fn test_bigint_to_int() { assert!(itf::from_value::(itf).is_ok()); } +#[test] +fn test_enum_deserialization_failure() { + let itf = serde_json::json!({ + "_foo": {"#bigint": "1"}, + "typ": "Foo", + }); + + #[derive(Deserialize, Debug)] + #[serde(tag = "typ")] + enum FooBarInt { + // try to deserialize _foo as i64, instead of BigInt + Foo { _foo: i64 }, + Bar { _bar: String }, + } + + assert!(itf::from_value::(itf.clone()).is_err()); + + #[derive(Deserialize, Debug)] + #[serde(tag = "typ")] + enum FooBarBigInt { + // can deserialize _foo to BigInt + Foo { _foo: num_bigint::BigInt }, + Bar { _bar: String }, + } + + assert!(itf::from_value::(itf).is_ok()); +} + #[test] fn test_complete() { use std::collections::{BTreeSet, HashMap, HashSet}; From 33decd27463ffb5f380246993a31609c028d37d4 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 20 Nov 2023 15:40:46 +0100 Subject: [PATCH 36/50] Add example and update README --- README.md | 55 ++++++++++++++++++++------------------- itf/examples/cannibals.rs | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 27 deletions(-) create mode 100644 itf/examples/cannibals.rs diff --git a/README.md b/README.md index 6642655..1f7ca4c 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,11 @@ Rust library for consuming [Apalache ITF Traces][itf-adr]. **Trace:** [`MissionariesAndCannibals.itf.json`](./apalache-itf/tests/fixtures/MissionariesAndCannibals.itf.json) ```rust -use serde::Deserialize; +use std::collections::{BTreeSet, BTreeMap}; -use itf::{trace_from_str, ItfMap, ItfSet}; +use serde::Deserialize; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)] enum Bank { #[serde(rename = "N")] North, @@ -33,7 +33,7 @@ enum Bank { South, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)] enum Person { #[serde(rename = "c1_OF_PERSON")] Cannibal1, @@ -51,11 +51,11 @@ enum Person { #[derive(Clone, Debug, Deserialize)] struct State { pub bank_of_boat: Bank, - pub who_is_on_bank: ItfMap>, + pub who_is_on_bank: BTreeMap>, } let data = include_str!("../tests/fixtures/MissionariesAndCannibals.itf.json"); -let trace: Trace = trace_from_str(data).unwrap(); +let trace: Trace = itf::trace_from_str(data).unwrap(); dbg!(trace); ``` @@ -63,18 +63,19 @@ dbg!(trace); **Output:** ```rust -trace = Trace { - meta: TraceMeta { - description: None, +[itf/examples/cannibals.rs:45] trace = Trace { + meta: Meta { + format: None, + format_description: None, source: Some( "MC_MissionariesAndCannibalsTyped.tla", ), + description: None, var_types: { "bank_of_boat": "Str", "who_is_on_bank": "Str -> Set(PERSON)", }, - format: None, - format_description: None, + timestamp: None, other: {}, }, params: [], @@ -85,7 +86,7 @@ trace = Trace { loop_index: None, states: [ State { - meta: StateMeta { + meta: Meta { index: Some( 0, ), @@ -96,16 +97,16 @@ trace = Trace { who_is_on_bank: { West: {}, East: { - Missionary2, Cannibal1, Cannibal2, Missionary1, + Missionary2, }, }, }, }, State { - meta: StateMeta { + meta: Meta { index: Some( 1, ), @@ -115,18 +116,18 @@ trace = Trace { bank_of_boat: West, who_is_on_bank: { West: { - Missionary2, Cannibal2, + Missionary2, }, East: { - Missionary1, Cannibal1, + Missionary1, }, }, }, }, State { - meta: StateMeta { + meta: Meta { index: Some( 2, ), @@ -139,15 +140,15 @@ trace = Trace { Cannibal2, }, East: { - Missionary2, Cannibal1, Missionary1, + Missionary2, }, }, }, }, State { - meta: StateMeta { + meta: Meta { index: Some( 3, ), @@ -157,8 +158,8 @@ trace = Trace { bank_of_boat: West, who_is_on_bank: { West: { - Missionary1, Cannibal2, + Missionary1, Missionary2, }, East: { @@ -168,7 +169,7 @@ trace = Trace { }, }, State { - meta: StateMeta { + meta: Meta { index: Some( 4, ), @@ -177,19 +178,19 @@ trace = Trace { value: State { bank_of_boat: East, who_is_on_bank: { - East: { - Cannibal2, - Cannibal1, - }, West: { Missionary1, Missionary2, }, + East: { + Cannibal1, + Cannibal2, + }, }, }, }, State { - meta: StateMeta { + meta: Meta { index: Some( 5, ), @@ -198,13 +199,13 @@ trace = Trace { value: State { bank_of_boat: West, who_is_on_bank: { - East: {}, West: { Cannibal1, Cannibal2, Missionary1, Missionary2, }, + East: {}, }, }, }, diff --git a/itf/examples/cannibals.rs b/itf/examples/cannibals.rs new file mode 100644 index 0000000..49a25cf --- /dev/null +++ b/itf/examples/cannibals.rs @@ -0,0 +1,48 @@ +#![allow(dead_code)] + +use std::collections::{BTreeMap, BTreeSet}; + +use serde::Deserialize; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)] +enum Bank { + #[serde(rename = "N")] + North, + + #[serde(rename = "W")] + West, + + #[serde(rename = "E")] + East, + + #[serde(rename = "S")] + South, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)] +enum Person { + #[serde(rename = "c1_OF_PERSON")] + Cannibal1, + + #[serde(rename = "c2_OF_PERSON")] + Cannibal2, + + #[serde(rename = "m1_OF_PERSON")] + Missionary1, + + #[serde(rename = "m2_OF_PERSON")] + Missionary2, +} + +#[derive(Clone, Debug, Deserialize)] +struct State { + pub bank_of_boat: Bank, + pub who_is_on_bank: BTreeMap>, +} + +fn main() { + let data = include_str!("../tests/fixtures/MissionariesAndCannibals.itf.json"); + let trace: itf::Trace = itf::trace_from_str(data).unwrap(); + + dbg!(trace); +} From 3d2b8ca4e1346746f9494d132a080a99c6192216 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 20 Nov 2023 15:43:00 +0100 Subject: [PATCH 37/50] Update changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45c8c92..c34e078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,21 @@ # CHANGELOG +## Unreleased + +- Deserialize ITF values into native Rust types with a custom deserializer + instead of having to go through `Itf` wrapper type. + ([#6](https://github.com/informalsystems/itf-rs/pull/6)) + ## v0.1.2 +*November 10th, 2023* + - Add `From where T: From` instance for `ItfBigInt` ## v0.1.1 +*November 10th, 2023* + - Add support for new `timestamp` field in meta section of ITF traces ## v0.1 From 27a483eb1b1ce128fdca85d08048d32d1ecb7dfe Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 20 Nov 2023 15:44:36 +0100 Subject: [PATCH 38/50] Include README in top-level crate doc --- itf/src/lib.rs | 203 +------------------------------------------------ 1 file changed, 1 insertion(+), 202 deletions(-) diff --git a/itf/src/lib.rs b/itf/src/lib.rs index 9b336f5..b59038f 100644 --- a/itf/src/lib.rs +++ b/itf/src/lib.rs @@ -1,205 +1,4 @@ -//! Library for consuming [Apalache ITF Traces](https://apalache.informal.systems/docs/adr/015adr-trace.html). -//! -//! ## Example -//! -//! **Trace:** [`MissionariesAndCannibals.itf.json`](../tests/fixtures/MissionariesAndCannibals.itf.json) -//! -//! ```rust -//! use serde::Deserialize; -//! -//! #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] -//! enum Bank { -//! #[serde(rename = "N")] -//! North, -//! -//! #[serde(rename = "W")] -//! West, -//! -//! #[serde(rename = "E")] -//! East, -//! -//! #[serde(rename = "S")] -//! South, -//! } -//! -//! #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)] -//! enum Person { -//! #[serde(rename = "c1_OF_PERSON")] -//! Cannibal1, -//! -//! #[serde(rename = "c2_OF_PERSON")] -//! Cannibal2, -//! -//! #[serde(rename = "m1_OF_PERSON")] -//! Missionary1, -//! -//! #[serde(rename = "m2_OF_PERSON")] -//! Missionary2, -//! } -//! -//! #[derive(Clone, Debug, Deserialize)] -//! struct State { -//! pub bank_of_boat: Bank, -//! pub who_is_on_bank: ItfMap>, -//! } -//! -//! let data = include_str!("../tests/fixtures/MissionariesAndCannibals.itf.json"); -//! let trace: Trace = itf::trace_from_str(data).unwrap(); -//! -//! dbg!(trace); -//! ``` -//! -//! **Output:** -//! -//! ```rust -//! trace = Trace { -//! meta: TraceMeta { -//! description: None, -//! source: Some( -//! "MC_MissionariesAndCannibalsTyped.tla", -//! ), -//! var_types: { -//! "bank_of_boat": "Str", -//! "who_is_on_bank": "Str -> Set(PERSON)", -//! }, -//! format: None, -//! format_description: None, -//! other: {}, -//! }, -//! params: [], -//! vars: [ -//! "bank_of_boat", -//! "who_is_on_bank", -//! ], -//! loop_index: None, -//! states: [ -//! State { -//! meta: StateMeta { -//! index: Some( -//! 0, -//! ), -//! other: {}, -//! }, -//! value: State { -//! bank_of_boat: East, -//! who_is_on_bank: { -//! West: {}, -//! East: { -//! Missionary2, -//! Cannibal1, -//! Cannibal2, -//! Missionary1, -//! }, -//! }, -//! }, -//! }, -//! State { -//! meta: StateMeta { -//! index: Some( -//! 1, -//! ), -//! other: {}, -//! }, -//! value: State { -//! bank_of_boat: West, -//! who_is_on_bank: { -//! West: { -//! Missionary2, -//! Cannibal2, -//! }, -//! East: { -//! Missionary1, -//! Cannibal1, -//! }, -//! }, -//! }, -//! }, -//! State { -//! meta: StateMeta { -//! index: Some( -//! 2, -//! ), -//! other: {}, -//! }, -//! value: State { -//! bank_of_boat: East, -//! who_is_on_bank: { -//! West: { -//! Cannibal2, -//! }, -//! East: { -//! Missionary2, -//! Cannibal1, -//! Missionary1, -//! }, -//! }, -//! }, -//! }, -//! State { -//! meta: StateMeta { -//! index: Some( -//! 3, -//! ), -//! other: {}, -//! }, -//! value: State { -//! bank_of_boat: West, -//! who_is_on_bank: { -//! West: { -//! Missionary1, -//! Cannibal2, -//! Missionary2, -//! }, -//! East: { -//! Cannibal1, -//! }, -//! }, -//! }, -//! }, -//! State { -//! meta: StateMeta { -//! index: Some( -//! 4, -//! ), -//! other: {}, -//! }, -//! value: State { -//! bank_of_boat: East, -//! who_is_on_bank: { -//! East: { -//! Cannibal2, -//! Cannibal1, -//! }, -//! West: { -//! Missionary1, -//! Missionary2, -//! }, -//! }, -//! }, -//! }, -//! State { -//! meta: StateMeta { -//! index: Some( -//! 5, -//! ), -//! other: {}, -//! }, -//! value: State { -//! bank_of_boat: West, -//! who_is_on_bank: { -//! East: {}, -//! West: { -//! Cannibal1, -//! Cannibal2, -//! Missionary1, -//! Missionary2, -//! }, -//! }, -//! }, -//! }, -//! ], -//! } -//! ``` +#![doc = include_str!("../../README.md")] use serde::de::DeserializeOwned; use serde::Deserialize; From b06e4b597457ce2cdaa82d647254b1462a04fb0e Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 20 Nov 2023 17:54:49 +0100 Subject: [PATCH 39/50] Split `Deserializer` impl and `Error` into their own modules --- itf/src/de.rs | 521 +------------------------------------ itf/src/de/deserializer.rs | 490 ++++++++++++++++++++++++++++++++++ itf/src/de/error.rs | 27 ++ 3 files changed, 523 insertions(+), 515 deletions(-) create mode 100644 itf/src/de/deserializer.rs create mode 100644 itf/src/de/error.rs diff --git a/itf/src/de.rs b/itf/src/de.rs index 4b3bf96..40adaf4 100644 --- a/itf/src/de.rs +++ b/itf/src/de.rs @@ -1,14 +1,11 @@ -use std::fmt; +use serde::de::DeserializeOwned; -use num_traits::ToPrimitive; -use serde::de::value::{MapDeserializer, SeqDeserializer}; -use serde::de::{ - DeserializeOwned, DeserializeSeed, Deserializer, EnumAccess, Error as SerdeError, Expected, - IntoDeserializer, Unexpected, VariantAccess, Visitor, -}; -use serde::Deserialize; +use crate::Value; -use crate::value::{BigInt, Map, Set, Tuple, Value}; +mod error; +pub use error::Error; + +mod deserializer; #[doc(hidden)] pub fn decode_value(value: Value) -> Result @@ -17,509 +14,3 @@ where { T::deserialize(value) } - -#[derive(Debug)] -pub enum Error { - Custom(String), - UnsupportedType(&'static str), -} - -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::Custom(msg) => msg.fmt(f), - Error::UnsupportedType(ty) => write!(f, "unsupported type: {ty}"), - } - } -} - -impl SerdeError for Error { - fn custom(msg: T) -> Self - where - T: fmt::Display, - { - Self::Custom(msg.to_string()) - } -} - -impl Value { - fn invalid_type(&self, exp: &dyn Expected) -> E - where - E: serde::de::Error, - { - serde::de::Error::invalid_type(self.unexpected(), exp) - } - - fn unexpected(&self) -> Unexpected { - match self { - Value::Bool(b) => Unexpected::Bool(*b), - Value::Number(n) => Unexpected::Signed(*n), - Value::String(s) => Unexpected::Str(s), - Value::List(_) => Unexpected::Seq, - Value::Map(_) => Unexpected::Map, - Value::Record(_) => Unexpected::Other("record"), - Value::BigInt(_) => Unexpected::Other("bigint"), - Value::Tuple(_) => Unexpected::Other("tuple"), - Value::Set(_) => Unexpected::Other("set"), - Value::Unserializable(_) => Unexpected::Other("unserializable"), - } - } -} - -macro_rules! deserialize_number { - ($ty:ty, $to:ident, $visit:ident, $method:ident) => { - fn $method(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self { - Value::Number(n) => { - let num = <$ty>::try_from(n).map_err(|_| { - serde::de::Error::invalid_type(Unexpected::Signed(n), &stringify!($ty)) - })?; - - visitor.$visit(num) - } - Value::BigInt(b) => { - let num = b.get().$to().ok_or_else(|| { - serde::de::Error::invalid_type( - Unexpected::Other("bigint"), - &stringify!($ty), - ) - })?; - - visitor.$visit(num) - } - _ => Err(self.invalid_type(&visitor)), - } - } - }; -} - -impl<'de> IntoDeserializer<'de, Error> for Value { - type Deserializer = Self; - - fn into_deserializer(self) -> Self::Deserializer { - self - } -} - -impl<'de> Deserializer<'de> for Value { - type Error = Error; - - fn deserialize_any(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self { - Value::Bool(v) => visitor.visit_bool(v), - Value::Number(v) => visitor.visit_i64(v), - Value::String(v) => visitor.visit_string(v), - Value::BigInt(v) => visit_bigint(v, visitor), - Value::List(v) => visit_list(v, visitor), - Value::Tuple(v) => visit_tuple(v, visitor), - Value::Set(v) => visit_set(v, visitor), - Value::Record(v) => visit_record(v, visitor), - Value::Map(v) => visit_map(v, visitor), - Value::Unserializable(_) => Err(Error::UnsupportedType("unserializable")), - } - } - - deserialize_number!(i8, to_i8, visit_i8, deserialize_i8); - deserialize_number!(i16, to_i16, visit_i16, deserialize_i16); - deserialize_number!(i32, to_i32, visit_i32, deserialize_i32); - deserialize_number!(i64, to_i64, visit_i64, deserialize_i64); - deserialize_number!(i128, to_i128, visit_i128, deserialize_i128); - deserialize_number!(u8, to_u8, visit_u8, deserialize_u8); - deserialize_number!(u16, to_u16, visit_u16, deserialize_u16); - deserialize_number!(u32, to_u32, visit_u32, deserialize_u32); - deserialize_number!(u64, to_u64, visit_u64, deserialize_u64); - deserialize_number!(u128, to_u128, visit_u128, deserialize_u128); - - fn deserialize_option(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_some(self) - } - - fn deserialize_bool(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self { - Value::Bool(v) => visitor.visit_bool(v), - _ => Err(self.invalid_type(&visitor)), - } - } - - fn deserialize_char(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_string(visitor) - } - - fn deserialize_str(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_string(visitor) - } - - fn deserialize_string(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self { - Value::String(v) => visitor.visit_string(v), - _ => Err(self.invalid_type(&visitor)), - } - } - - fn deserialize_bytes(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_byte_buf(visitor) - } - - fn deserialize_byte_buf(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self { - Value::String(v) => visitor.visit_string(v), - Value::List(v) => visit_list(v, visitor), - _ => Err(self.invalid_type(&visitor)), - } - } - - fn deserialize_unit(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(self.invalid_type(&visitor)) - } - - fn deserialize_f32(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(self.invalid_type(&visitor)) - } - - fn deserialize_f64(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(self.invalid_type(&visitor)) - } - - fn deserialize_unit_struct( - self, - _name: &'static str, - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - self.deserialize_unit(visitor) - } - - fn deserialize_newtype_struct( - self, - _name: &'static str, - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_newtype_struct(self) - } - - fn deserialize_seq(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self { - Value::List(v) => visit_list(v, visitor), - Value::Tuple(v) => visit_tuple(v, visitor), - Value::Set(v) => visit_set(v, visitor), - _ => Err(self.invalid_type(&visitor)), - } - } - - fn deserialize_tuple(self, _len: usize, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self { - Value::BigInt(v) => visit_bigint(v, visitor), - Value::Number(v) => visit_bigint(BigInt::new(v), visitor), - _ => self.deserialize_seq(visitor), - } - } - - fn deserialize_tuple_struct( - self, - _name: &'static str, - _len: usize, - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - self.deserialize_seq(visitor) - } - - fn deserialize_map(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self { - Value::Map(v) => visit_map(v, visitor), - _ => Err(self.invalid_type(&visitor)), - } - } - - fn deserialize_struct( - self, - _name: &'static str, - _fields: &'static [&'static str], - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - match self { - Value::Record(v) => visit_record(v, visitor), - _ => Err(self.invalid_type(&visitor)), - } - } - - fn deserialize_enum( - self, - _name: &'static str, - _variants: &'static [&'static str], - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - let (variant, value) = match self { - Value::Record(value) => { - let mut iter = value.into_iter(); - let (variant, value) = match iter.next() { - Some(v) => v, - None => { - return Err(serde::de::Error::invalid_value( - Unexpected::Map, - &"map with a single key", - )); - } - }; - if iter.next().is_some() { - return Err(serde::de::Error::invalid_value( - Unexpected::Map, - &"map with a single key", - )); - } - (variant, Some(value)) - } - Value::String(variant) => (variant, None), - other => { - return Err(serde::de::Error::invalid_type( - other.unexpected(), - &"string or map", - )); - } - }; - - visitor.visit_enum(EnumDeserializer { variant, value }) - } - - fn deserialize_identifier(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_string(visitor) - } - - fn deserialize_ignored_any(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - drop(self); - visitor.visit_unit() - } -} - -fn visit_bigint<'de, V>(v: BigInt, visitor: V) -> Result -where - V: Visitor<'de>, -{ - let (sign, digits) = v.into_inner().to_u32_digits(); - - let sign_value = match sign { - num_bigint::Sign::Minus => -1, - num_bigint::Sign::NoSign => 0, - num_bigint::Sign::Plus => 1, - }; - - let digit_value = digits - .into_iter() - .map(i64::from) - .map(Value::Number) - .collect(); - - let serialized = [Value::Number(sign_value), Value::List(digit_value)]; - - let deserializer = SeqDeserializer::new(serialized.into_iter()); - visitor.visit_seq(deserializer) -} - -fn visit_map<'de, V>(v: Map, visitor: V) -> Result -where - V: Visitor<'de>, -{ - let mut deserializer = MapDeserializer::new(v.into_iter()); - let map = visitor.visit_map(&mut deserializer)?; - Ok(map) -} - -fn visit_record<'de, V>(record: Map, visitor: V) -> Result -where - V: Visitor<'de>, -{ - let mut deserializer = MapDeserializer::new(record.into_iter()); - let map = visitor.visit_map(&mut deserializer)?; - Ok(map) -} - -fn visit_set<'de, V>(v: Set, visitor: V) -> Result -where - V: Visitor<'de>, -{ - let mut deserializer = SeqDeserializer::new(v.into_iter()); - let seq = visitor.visit_seq(&mut deserializer)?; - Ok(seq) -} - -fn visit_tuple<'de, V>(v: Tuple, visitor: V) -> Result -where - V: Visitor<'de>, -{ - let mut deserializer = SeqDeserializer::new(v.into_iter()); - let seq = visitor.visit_seq(&mut deserializer)?; - Ok(seq) -} - -fn visit_list<'de, V>(v: Vec, visitor: V) -> Result -where - V: Visitor<'de>, -{ - let mut deserializer = SeqDeserializer::new(v.into_iter()); - let seq = visitor.visit_seq(&mut deserializer)?; - Ok(seq) -} - -struct EnumDeserializer { - variant: String, - value: Option, -} - -impl<'de> EnumAccess<'de> for EnumDeserializer { - type Error = Error; - type Variant = VariantDeserializer; - - fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Error> - where - V: DeserializeSeed<'de>, - { - let variant = self.variant.into_deserializer(); - let visitor = VariantDeserializer { value: self.value }; - seed.deserialize(variant).map(|v| (v, visitor)) - } -} - -struct VariantDeserializer { - value: Option, -} - -impl<'de> VariantAccess<'de> for VariantDeserializer { - type Error = Error; - - fn unit_variant(self) -> Result<(), Error> { - match self.value { - Some(value) => Deserialize::deserialize(value), - None => Ok(()), - } - } - - fn newtype_variant_seed(self, seed: T) -> Result - where - T: DeserializeSeed<'de>, - { - match self.value { - Some(value) => seed.deserialize(value), - None => Err(serde::de::Error::invalid_type( - Unexpected::UnitVariant, - &"newtype variant", - )), - } - } - - fn tuple_variant(self, _len: usize, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self.value { - Some(Value::Tuple(v)) => { - if v.is_empty() { - visitor.visit_unit() - } else { - visit_tuple(v, visitor) - } - } - // Some(Value::List(v)) => { - // if v.is_empty() { - // visitor.visit_unit() - // } else { - // visit_list(v, visitor) - // } - // } - Some(other) => Err(serde::de::Error::invalid_type( - other.unexpected(), - &"tuple variant", - )), - None => Err(serde::de::Error::invalid_type( - Unexpected::UnitVariant, - &"tuple variant", - )), - } - } - - fn struct_variant( - self, - _fields: &'static [&'static str], - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - match self.value { - Some(Value::Record(v)) => visit_record(v, visitor), - Some(other) => Err(serde::de::Error::invalid_type( - other.unexpected(), - &"struct variant", - )), - None => Err(serde::de::Error::invalid_type( - Unexpected::UnitVariant, - &"struct variant", - )), - } - } -} diff --git a/itf/src/de/deserializer.rs b/itf/src/de/deserializer.rs new file mode 100644 index 0000000..f890105 --- /dev/null +++ b/itf/src/de/deserializer.rs @@ -0,0 +1,490 @@ +use num_traits::ToPrimitive; +use serde::de::value::{MapDeserializer, SeqDeserializer}; +use serde::de::{ + DeserializeSeed, Deserializer, EnumAccess, Expected, IntoDeserializer, Unexpected, + VariantAccess, Visitor, +}; +use serde::Deserialize; + +use crate::de::Error; +use crate::value::{BigInt, Map, Set, Tuple, Value}; + +impl Value { + fn invalid_type(&self, exp: &dyn Expected) -> E + where + E: serde::de::Error, + { + serde::de::Error::invalid_type(self.unexpected(), exp) + } + + fn unexpected(&self) -> Unexpected { + match self { + Value::Bool(b) => Unexpected::Bool(*b), + Value::Number(n) => Unexpected::Signed(*n), + Value::String(s) => Unexpected::Str(s), + Value::List(_) => Unexpected::Seq, + Value::Map(_) => Unexpected::Map, + Value::Record(_) => Unexpected::Other("record"), + Value::BigInt(_) => Unexpected::Other("bigint"), + Value::Tuple(_) => Unexpected::Other("tuple"), + Value::Set(_) => Unexpected::Other("set"), + Value::Unserializable(_) => Unexpected::Other("unserializable"), + } + } +} + +macro_rules! deserialize_number { + ($ty:ty, $to:ident, $visit:ident, $method:ident) => { + fn $method(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self { + Value::Number(n) => { + let num = <$ty>::try_from(n).map_err(|_| { + serde::de::Error::invalid_type(Unexpected::Signed(n), &stringify!($ty)) + })?; + + visitor.$visit(num) + } + Value::BigInt(b) => { + let num = b.get().$to().ok_or_else(|| { + serde::de::Error::invalid_type( + Unexpected::Other("bigint"), + &stringify!($ty), + ) + })?; + + visitor.$visit(num) + } + _ => Err(self.invalid_type(&visitor)), + } + } + }; +} + +impl<'de> IntoDeserializer<'de, Error> for Value { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +impl<'de> Deserializer<'de> for Value { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self { + Value::Bool(v) => visitor.visit_bool(v), + Value::Number(v) => visitor.visit_i64(v), + Value::String(v) => visitor.visit_string(v), + Value::BigInt(v) => visit_bigint(v, visitor), + Value::List(v) => visit_list(v, visitor), + Value::Tuple(v) => visit_tuple(v, visitor), + Value::Set(v) => visit_set(v, visitor), + Value::Record(v) => visit_record(v, visitor), + Value::Map(v) => visit_map(v, visitor), + Value::Unserializable(_) => Err(Error::UnsupportedType("unserializable")), + } + } + + deserialize_number!(i8, to_i8, visit_i8, deserialize_i8); + deserialize_number!(i16, to_i16, visit_i16, deserialize_i16); + deserialize_number!(i32, to_i32, visit_i32, deserialize_i32); + deserialize_number!(i64, to_i64, visit_i64, deserialize_i64); + deserialize_number!(i128, to_i128, visit_i128, deserialize_i128); + deserialize_number!(u8, to_u8, visit_u8, deserialize_u8); + deserialize_number!(u16, to_u16, visit_u16, deserialize_u16); + deserialize_number!(u32, to_u32, visit_u32, deserialize_u32); + deserialize_number!(u64, to_u64, visit_u64, deserialize_u64); + deserialize_number!(u128, to_u128, visit_u128, deserialize_u128); + + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_some(self) + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self { + Value::Bool(v) => visitor.visit_bool(v), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_char(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_string(visitor) + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_string(visitor) + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self { + Value::String(v) => visitor.visit_string(v), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_byte_buf(visitor) + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self { + Value::String(v) => visitor.visit_string(v), + Value::List(v) => visit_list(v, visitor), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(self.invalid_type(&visitor)) + } + + fn deserialize_f32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(self.invalid_type(&visitor)) + } + + fn deserialize_f64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(self.invalid_type(&visitor)) + } + + fn deserialize_unit_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_unit(visitor) + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self { + Value::List(v) => visit_list(v, visitor), + Value::Tuple(v) => visit_tuple(v, visitor), + Value::Set(v) => visit_set(v, visitor), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self { + Value::BigInt(v) => visit_bigint(v, visitor), + Value::Number(v) => visit_bigint(BigInt::new(v), visitor), + _ => self.deserialize_seq(visitor), + } + } + + fn deserialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self { + Value::Map(v) => visit_map(v, visitor), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + match self { + Value::Record(v) => visit_record(v, visitor), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + let (variant, value) = match self { + Value::Record(value) => { + let mut iter = value.into_iter(); + let (variant, value) = match iter.next() { + Some(v) => v, + None => { + return Err(serde::de::Error::invalid_value( + Unexpected::Map, + &"map with a single key", + )); + } + }; + if iter.next().is_some() { + return Err(serde::de::Error::invalid_value( + Unexpected::Map, + &"map with a single key", + )); + } + (variant, Some(value)) + } + Value::String(variant) => (variant, None), + other => { + return Err(serde::de::Error::invalid_type( + other.unexpected(), + &"string or map", + )); + } + }; + + visitor.visit_enum(EnumDeserializer { variant, value }) + } + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_string(visitor) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + drop(self); + visitor.visit_unit() + } +} + +fn visit_bigint<'de, V>(v: BigInt, visitor: V) -> Result +where + V: Visitor<'de>, +{ + let (sign, digits) = v.into_inner().to_u32_digits(); + + let sign_value = match sign { + num_bigint::Sign::Minus => -1, + num_bigint::Sign::NoSign => 0, + num_bigint::Sign::Plus => 1, + }; + + let digit_value = digits + .into_iter() + .map(i64::from) + .map(Value::Number) + .collect(); + + let serialized = [Value::Number(sign_value), Value::List(digit_value)]; + + let deserializer = SeqDeserializer::new(serialized.into_iter()); + visitor.visit_seq(deserializer) +} + +fn visit_map<'de, V>(v: Map, visitor: V) -> Result +where + V: Visitor<'de>, +{ + let mut deserializer = MapDeserializer::new(v.into_iter()); + let map = visitor.visit_map(&mut deserializer)?; + Ok(map) +} + +fn visit_record<'de, V>(record: Map, visitor: V) -> Result +where + V: Visitor<'de>, +{ + let mut deserializer = MapDeserializer::new(record.into_iter()); + let map = visitor.visit_map(&mut deserializer)?; + Ok(map) +} + +fn visit_set<'de, V>(v: Set, visitor: V) -> Result +where + V: Visitor<'de>, +{ + let mut deserializer = SeqDeserializer::new(v.into_iter()); + let seq = visitor.visit_seq(&mut deserializer)?; + Ok(seq) +} + +fn visit_tuple<'de, V>(v: Tuple, visitor: V) -> Result +where + V: Visitor<'de>, +{ + let mut deserializer = SeqDeserializer::new(v.into_iter()); + let seq = visitor.visit_seq(&mut deserializer)?; + Ok(seq) +} + +fn visit_list<'de, V>(v: Vec, visitor: V) -> Result +where + V: Visitor<'de>, +{ + let mut deserializer = SeqDeserializer::new(v.into_iter()); + let seq = visitor.visit_seq(&mut deserializer)?; + Ok(seq) +} + +struct EnumDeserializer { + variant: String, + value: Option, +} + +impl<'de> EnumAccess<'de> for EnumDeserializer { + type Error = Error; + type Variant = VariantDeserializer; + + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Error> + where + V: DeserializeSeed<'de>, + { + let variant = self.variant.into_deserializer(); + let visitor = VariantDeserializer { value: self.value }; + seed.deserialize(variant).map(|v| (v, visitor)) + } +} + +struct VariantDeserializer { + value: Option, +} + +impl<'de> VariantAccess<'de> for VariantDeserializer { + type Error = Error; + + fn unit_variant(self) -> Result<(), Error> { + match self.value { + Some(value) => Deserialize::deserialize(value), + None => Ok(()), + } + } + + fn newtype_variant_seed(self, seed: T) -> Result + where + T: DeserializeSeed<'de>, + { + match self.value { + Some(value) => seed.deserialize(value), + None => Err(serde::de::Error::invalid_type( + Unexpected::UnitVariant, + &"newtype variant", + )), + } + } + + fn tuple_variant(self, _len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.value { + Some(Value::Tuple(v)) => { + if v.is_empty() { + visitor.visit_unit() + } else { + visit_tuple(v, visitor) + } + } + // Some(Value::List(v)) => { + // if v.is_empty() { + // visitor.visit_unit() + // } else { + // visit_list(v, visitor) + // } + // } + Some(other) => Err(serde::de::Error::invalid_type( + other.unexpected(), + &"tuple variant", + )), + None => Err(serde::de::Error::invalid_type( + Unexpected::UnitVariant, + &"tuple variant", + )), + } + } + + fn struct_variant( + self, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + match self.value { + Some(Value::Record(v)) => visit_record(v, visitor), + Some(other) => Err(serde::de::Error::invalid_type( + other.unexpected(), + &"struct variant", + )), + None => Err(serde::de::Error::invalid_type( + Unexpected::UnitVariant, + &"struct variant", + )), + } + } +} diff --git a/itf/src/de/error.rs b/itf/src/de/error.rs new file mode 100644 index 0000000..0f4dd49 --- /dev/null +++ b/itf/src/de/error.rs @@ -0,0 +1,27 @@ +use std::fmt; + +#[derive(Debug)] +pub enum Error { + Custom(String), + UnsupportedType(&'static str), +} + +impl std::error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Custom(msg) => msg.fmt(f), + Error::UnsupportedType(ty) => write!(f, "unsupported type: {ty}"), + } + } +} + +impl serde::de::Error for Error { + fn custom(msg: T) -> Self + where + T: fmt::Display, + { + Self::Custom(msg.to_string()) + } +} From a2eef53c28f230d1ec0ec24578815f7b2ab3b132 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 20 Nov 2023 17:55:49 +0100 Subject: [PATCH 40/50] Add a `itf::de::from_bigint` deserializer function to convert from `BigInt` via `#[serde(deserialize_with = "...")]` --- itf/src/de.rs | 3 +++ itf/src/de/helpers.rs | 15 +++++++++++++++ itf/tests/regression.rs | 16 ++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 itf/src/de/helpers.rs diff --git a/itf/src/de.rs b/itf/src/de.rs index 40adaf4..11cb271 100644 --- a/itf/src/de.rs +++ b/itf/src/de.rs @@ -5,6 +5,9 @@ use crate::Value; mod error; pub use error::Error; +mod helpers; +pub use helpers::from_bigint; + mod deserializer; #[doc(hidden)] diff --git a/itf/src/de/helpers.rs b/itf/src/de/helpers.rs new file mode 100644 index 0000000..f4ea039 --- /dev/null +++ b/itf/src/de/helpers.rs @@ -0,0 +1,15 @@ +use std::fmt::Display; + +use num_bigint::BigInt; +use serde::de::Error; +use serde::{Deserialize, Deserializer}; + +pub fn from_bigint<'de, A, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, + A: TryFrom, + >::Error: Display, +{ + let bigint = BigInt::deserialize(deserializer).map_err(D::Error::custom)?; + A::try_from(bigint).map_err(D::Error::custom) +} diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index c991346..63febc1 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -153,6 +153,22 @@ fn test_enum_deserialization_failure() { assert!(itf::from_value::(itf.clone()).is_err()); + #[derive(Deserialize, Debug)] + #[serde(tag = "typ")] + enum FooBarWithInt { + // try to deserialize _foo as i64, via a conversion from BigInt + Foo { + #[serde(deserialize_with = "itf::de::from_bigint")] + _foo: i64, + }, + Bar { + _bar: String, + }, + } + itf::from_value::(itf.clone()).unwrap(); + + assert!(itf::from_value::(itf.clone()).is_ok()); + #[derive(Deserialize, Debug)] #[serde(tag = "typ")] enum FooBarBigInt { From bb0658f443cc928b2b99e5ec3f163b4c0fc47b39 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Tue, 21 Nov 2023 12:22:29 +0100 Subject: [PATCH 41/50] disallow implicit deser from bigint to (u)int --- itf/src/de/deserializer.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/itf/src/de/deserializer.rs b/itf/src/de/deserializer.rs index f890105..12e47cf 100644 --- a/itf/src/de/deserializer.rs +++ b/itf/src/de/deserializer.rs @@ -1,4 +1,3 @@ -use num_traits::ToPrimitive; use serde::de::value::{MapDeserializer, SeqDeserializer}; use serde::de::{ DeserializeSeed, Deserializer, EnumAccess, Expected, IntoDeserializer, Unexpected, @@ -47,16 +46,6 @@ macro_rules! deserialize_number { visitor.$visit(num) } - Value::BigInt(b) => { - let num = b.get().$to().ok_or_else(|| { - serde::de::Error::invalid_type( - Unexpected::Other("bigint"), - &stringify!($ty), - ) - })?; - - visitor.$visit(num) - } _ => Err(self.invalid_type(&visitor)), } } From 2888463f6a6276e2b4f6d9d9910de44770767c74 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Tue, 21 Nov 2023 12:22:53 +0100 Subject: [PATCH 42/50] disallow implicit deser from int to bigint --- itf/src/de/deserializer.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/itf/src/de/deserializer.rs b/itf/src/de/deserializer.rs index 12e47cf..bbeaeef 100644 --- a/itf/src/de/deserializer.rs +++ b/itf/src/de/deserializer.rs @@ -212,7 +212,6 @@ impl<'de> Deserializer<'de> for Value { { match self { Value::BigInt(v) => visit_bigint(v, visitor), - Value::Number(v) => visit_bigint(BigInt::new(v), visitor), _ => self.deserialize_seq(visitor), } } From 87eb1d0be6471be588d11de44e321ac157fd6f66 Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Tue, 21 Nov 2023 12:25:24 +0100 Subject: [PATCH 43/50] use Integer as DeserializeAs to deserialize from BigInt to (u)int --- itf/Cargo.toml | 1 + itf/src/de.rs | 2 +- itf/src/de/helpers.rs | 17 ++++++++++++----- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/itf/Cargo.toml b/itf/Cargo.toml index 84942e5..bdfc786 100644 --- a/itf/Cargo.toml +++ b/itf/Cargo.toml @@ -16,3 +16,4 @@ num-bigint = { version = "0.4", features = ["serde"] } num-traits = { version = "0.2" } serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["raw_value"] } +serde_with = { version = "3.4.0" } diff --git a/itf/src/de.rs b/itf/src/de.rs index 11cb271..996d9d9 100644 --- a/itf/src/de.rs +++ b/itf/src/de.rs @@ -6,7 +6,7 @@ mod error; pub use error::Error; mod helpers; -pub use helpers::from_bigint; +pub use helpers::Integer; mod deserializer; diff --git a/itf/src/de/helpers.rs b/itf/src/de/helpers.rs index f4ea039..77fb91d 100644 --- a/itf/src/de/helpers.rs +++ b/itf/src/de/helpers.rs @@ -2,14 +2,21 @@ use std::fmt::Display; use num_bigint::BigInt; use serde::de::Error; -use serde::{Deserialize, Deserializer}; +use serde::Deserialize; +use serde_with::DeserializeAs; -pub fn from_bigint<'de, A, D>(deserializer: D) -> Result +pub struct Integer; + +impl<'de, A> DeserializeAs<'de, A> for Integer where - D: Deserializer<'de>, A: TryFrom, >::Error: Display, { - let bigint = BigInt::deserialize(deserializer).map_err(D::Error::custom)?; - A::try_from(bigint).map_err(D::Error::custom) + fn deserialize_as(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bigint = BigInt::deserialize(deserializer).map_err(D::Error::custom)?; + A::try_from(bigint).map_err(D::Error::custom) + } } From 54aee5ada65f067d4dfd49e59dfcde3ff8bddf4c Mon Sep 17 00:00:00 2001 From: Ranadeep Biswas Date: Tue, 21 Nov 2023 12:31:39 +0100 Subject: [PATCH 44/50] update tests for explicit serialization from BigInt to (u)int --- itf/tests/regression.rs | 92 +++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 31 deletions(-) diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index 63febc1..f39f77f 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -27,7 +27,7 @@ fn test_set() { fn test_num_bigint() { let itf = serde_json::json!([-1, [99]]); - // successful cases + // successful case; only BigInt assert_eq!( num_bigint::BigInt::from(-99), itf::from_value::(itf.clone()).unwrap() @@ -47,14 +47,14 @@ fn test_num_bigint() { fn test_bigint_deser() { let itf = serde_json::json!({"#bigint": "-99"}); - // successful cases - assert_eq!(-99, itf::from_value::(itf.clone()).unwrap()); + // successful case; only BigInt assert_eq!( num_bigint::BigInt::from(-99), itf::from_value(itf.clone()).unwrap() ); // unsuccessful cases + assert!(itf::from_value::(itf.clone()).is_err()); assert!(itf::from_value::(itf.clone()).is_err()); assert!(itf::from_value::(itf.clone()).is_err()); assert!(!matches!( @@ -67,15 +67,15 @@ fn test_bigint_deser() { fn test_biguint_deser() { let itf = serde_json::json!({"#bigint": "99"}); - // successful cases - assert_eq!(99, itf::from_value::(itf.clone()).unwrap()); - assert_eq!(99, itf::from_value::(itf.clone()).unwrap()); + // successful case; only BigInt assert_eq!( num_bigint::BigInt::from(99), itf::from_value(itf.clone()).unwrap() ); // unsuccessful cases + assert!(itf::from_value::(itf.clone()).is_err()); + assert!(itf::from_value::(itf.clone()).is_err()); assert!(itf::from_value::(itf.clone()).is_err()); assert!(itf::from_value::(itf.clone()).is_err()); assert!(!matches!( @@ -137,47 +137,79 @@ fn test_bigint_to_int() { } #[test] -fn test_enum_deserialization_failure() { - let itf = serde_json::json!({ - "_foo": {"#bigint": "1"}, +fn test_deserialize_any() { + use itf::de::Integer; + use num_bigint::BigInt; + use serde_with::As; + use std::collections::HashMap; + + let itf = serde_json::json!([{ + "_foo": {"#map": [[{"#bigint": "1"}, {"#bigint": "2"}]]}, "typ": "Foo", - }); + }, + { + "_bar": [[[{"#bigint": "1"}, {"#bigint": "2"}]]], + "typ": "Bar", + } + + ]); + // deserialize as bigints #[derive(Deserialize, Debug)] #[serde(tag = "typ")] - enum FooBarInt { - // try to deserialize _foo as i64, instead of BigInt - Foo { _foo: i64 }, - Bar { _bar: String }, + enum FooBarBigInt { + Foo { _foo: HashMap }, + Bar { _bar: Vec> }, } + itf::from_value::>(itf.clone()).unwrap(); - assert!(itf::from_value::(itf.clone()).is_err()); - + // deserialize as i64 #[derive(Deserialize, Debug)] #[serde(tag = "typ")] - enum FooBarWithInt { - // try to deserialize _foo as i64, via a conversion from BigInt + enum FooBarInt { + // try to deserialize _foo as i64, instead of BigInt Foo { - #[serde(deserialize_with = "itf::de::from_bigint")] - _foo: i64, + #[serde(with = "As::>")] + _foo: HashMap, }, Bar { - _bar: String, + #[serde(with = "As::>>")] + _bar: Vec>, }, } - itf::from_value::(itf.clone()).unwrap(); - - assert!(itf::from_value::(itf.clone()).is_ok()); + itf::from_value::>(itf.clone()).unwrap(); + // deserialize as mix #[derive(Deserialize, Debug)] #[serde(tag = "typ")] - enum FooBarBigInt { - // can deserialize _foo to BigInt - Foo { _foo: num_bigint::BigInt }, - Bar { _bar: String }, + enum FooBarMixInt { + // try to deserialize _foo as i64, instead of BigInt + Foo { + #[serde(with = "As::>")] + _foo: HashMap, + }, + Bar { + #[serde(with = "As::>>")] + _bar: Vec>, + }, } + itf::from_value::>(itf.clone()).unwrap(); +} + +#[test] +fn test_failed_bare_bigint_to_int() { + use itf::de::Integer; + use serde_with::de::DeserializeAsWrap; + + let itf = serde_json::json!({ + "#bigint": "12", + }); + + let itf_value = serde_json::from_value::(itf.clone()).unwrap(); + + assert!(i64::deserialize(itf_value.clone()).is_err()); - assert!(itf::from_value::(itf).is_ok()); + assert!(DeserializeAsWrap::::deserialize(itf_value).is_ok()); } #[test] @@ -197,8 +229,6 @@ fn test_complete() { _number: i64, _str: String, _bigint: num_bigint::BigInt, - _int_from_bigint: i64, - _bigint_from_int: num_bigint::BigInt, _list: Vec, _tuple: (String, num_bigint::BigInt), _set: HashSet, From c71bcc993b89ff2c45bc22b57d60b46cdd3e1dd4 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 21 Nov 2023 14:37:38 +0100 Subject: [PATCH 45/50] Re-export `serde_with::As` under `itf::de::As` to enable its use without explicitly depending on `serde-with` --- itf/src/de.rs | 1 + itf/src/de/helpers.rs | 2 ++ itf/tests/regression.rs | 3 +-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/itf/src/de.rs b/itf/src/de.rs index 996d9d9..a8513ec 100644 --- a/itf/src/de.rs +++ b/itf/src/de.rs @@ -6,6 +6,7 @@ mod error; pub use error::Error; mod helpers; +pub use helpers::As; pub use helpers::Integer; mod deserializer; diff --git a/itf/src/de/helpers.rs b/itf/src/de/helpers.rs index 77fb91d..f1d937c 100644 --- a/itf/src/de/helpers.rs +++ b/itf/src/de/helpers.rs @@ -5,6 +5,8 @@ use serde::de::Error; use serde::Deserialize; use serde_with::DeserializeAs; +pub use serde_with::As; + pub struct Integer; impl<'de, A> DeserializeAs<'de, A> for Integer diff --git a/itf/tests/regression.rs b/itf/tests/regression.rs index f39f77f..86d1431 100644 --- a/itf/tests/regression.rs +++ b/itf/tests/regression.rs @@ -138,9 +138,8 @@ fn test_bigint_to_int() { #[test] fn test_deserialize_any() { - use itf::de::Integer; + use itf::de::{As, Integer}; use num_bigint::BigInt; - use serde_with::As; use std::collections::HashMap; let itf = serde_json::json!([{ From 1ce39433e61a37e6b0fee021608182e29259672c Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 21 Nov 2023 14:46:07 +0100 Subject: [PATCH 46/50] Add doc comments to the helpers --- README.md | 8 ++++-- itf/src/de/helpers.rs | 67 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1f7ca4c..ee61e63 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,14 @@ struct State { } let data = include_str!("../tests/fixtures/MissionariesAndCannibals.itf.json"); -let trace: Trace = itf::trace_from_str(data).unwrap(); +let trace: itf::Trace = itf::trace_from_str(data).unwrap(); dbg!(trace); ``` **Output:** -```rust +```rust,ignore [itf/examples/cannibals.rs:45] trace = Trace { meta: Meta { format: None, @@ -228,7 +228,9 @@ Copyright © 2023 Informal Systems Inc. and itf-rs authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use the files in this repository except in compliance with the License. You may obtain a copy of the License at - https://www.apache.org/licenses/LICENSE-2.0 +```text +https://www.apache.org/licenses/LICENSE-2.0 +``` Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/itf/src/de/helpers.rs b/itf/src/de/helpers.rs index f1d937c..06f005e 100644 --- a/itf/src/de/helpers.rs +++ b/itf/src/de/helpers.rs @@ -7,6 +7,73 @@ use serde_with::DeserializeAs; pub use serde_with::As; +/// Helper for `serde` to deserialize a `BigInt` to +/// any type which implements `TryFrom`. +/// +/// ## Example +/// +/// ```rust +/// use std::collections::HashMap; +/// +/// use num_bigint::BigInt; +/// use serde::Deserialize; +/// +/// use itf::Trace; +/// use itf::de::{As, Integer}; +/// +/// let json = serde_json::json!([ +/// { +/// "_foo": {"#map": [[{"#bigint": "1"}, {"#bigint": "2"}]]}, +/// "typ": "Foo", +/// }, +/// { +/// "_bar": [[[{"#bigint": "1"}, {"#bigint": "2"}]]], +/// "typ": "Bar", +/// } +/// ]); +/// +/// // Deserialize as `num_bigint::BigInt` +/// #[derive(Deserialize, Debug)] +/// #[serde(tag = "typ")] +/// enum FooBarBigInt { +/// Foo { _foo: HashMap }, +/// Bar { _bar: Vec> }, +/// } +/// itf::from_value::>(json.clone()).unwrap(); +/// +/// // Deserialize as `i64` +/// #[derive(Deserialize, Debug)] +/// #[serde(tag = "typ")] +/// enum FooBarInt { +/// // try to deserialize _foo as i64, instead of BigInt +/// Foo { +/// #[serde(with = "As::>")] +/// _foo: HashMap, +/// }, +/// Bar { +/// #[serde(with = "As::>>")] +/// _bar: Vec>, +/// }, +/// } +/// itf::from_value::>(json.clone()).unwrap(); +/// +/// // Deserialize as a mix +/// #[derive(Deserialize, Debug)] +/// #[serde(tag = "typ")] +/// enum FooBarMixInt { +/// // try to deserialize _foo as i64, instead of BigInt +/// Foo { +/// #[serde(with = "As::>")] +/// _foo: HashMap, +/// }, +/// Bar { +/// #[serde(with = "As::>>")] +/// _bar: Vec>, +/// }, +/// } +/// itf::from_value::>(json.clone()).unwrap(); +/// ``` + pub struct Integer; impl<'de, A> DeserializeAs<'de, A> for Integer From 7090e9e8f7554e174eadd8fc79720f83460b96ec Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 21 Nov 2023 14:49:51 +0100 Subject: [PATCH 47/50] Document public items --- itf/src/lib.rs | 4 ++++ itf/src/state.rs | 2 ++ itf/src/trace.rs | 2 ++ 3 files changed, 8 insertions(+) diff --git a/itf/src/lib.rs b/itf/src/lib.rs index b59038f..3f2ae27 100644 --- a/itf/src/lib.rs +++ b/itf/src/lib.rs @@ -18,6 +18,7 @@ pub mod value; #[doc(hidden)] pub use value::Value; +/// Deserialize a [`Trace`] over states `S` from an ITF JSON string. pub fn trace_from_str(str: &str) -> Result, Error> where S: for<'de> Deserialize<'de>, @@ -26,6 +27,7 @@ where trace_value.decode() } +/// Deserialize a [`Trace`] over states `S` from an ITF JSON [`serde_json::Value`]. pub fn trace_from_value(value: serde_json::Value) -> Result, Error> where S: DeserializeOwned, @@ -34,6 +36,7 @@ where trace_value.decode() } +/// Deserialize an ITF-encoded expression `S` from an ITF JSON string. pub fn from_str(str: &str) -> Result where S: for<'de> Deserialize<'de>, @@ -43,6 +46,7 @@ where Ok(data) } +/// Deserialize an ITF-encoded expression `S` from an ITF JSON [`serde_json::Value`]. pub fn from_value(value: serde_json::Value) -> Result where S: DeserializeOwned, diff --git a/itf/src/state.rs b/itf/src/state.rs index 3123209..205a97d 100644 --- a/itf/src/state.rs +++ b/itf/src/state.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::error::Error; use crate::value::Value; +/// Metada for an ITF [`State`]. #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Meta { #[serde(default)] @@ -15,6 +16,7 @@ pub struct Meta { pub other: BTreeMap, } +/// An ITF state of type `S`. #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct State { #[serde(rename = "#meta")] diff --git a/itf/src/trace.rs b/itf/src/trace.rs index d6054bc..caf63f7 100644 --- a/itf/src/trace.rs +++ b/itf/src/trace.rs @@ -7,6 +7,7 @@ use crate::error::Error; use crate::state::State; use crate::value::Value; +/// Metadata for an ITF [`Trace`]. #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Meta { #[serde(default)] @@ -31,6 +32,7 @@ pub struct Meta { pub other: BTreeMap, } +/// An ITF trace over states of type `S`. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Trace { #[serde(rename = "#meta")] From e39f14168a5fe069dfc47b33a6c7a5619155438b Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 21 Nov 2023 14:50:47 +0100 Subject: [PATCH 48/50] Remove unused `Error::Io` variant --- itf/src/error.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/itf/src/error.rs b/itf/src/error.rs index d424aa6..90abc56 100644 --- a/itf/src/error.rs +++ b/itf/src/error.rs @@ -1,16 +1,10 @@ +/// Error type for the library. #[derive(Debug)] pub enum Error { - Io(std::io::Error), Json(serde_json::Error), Decode(crate::de::Error), } -impl From for Error { - fn from(v: std::io::Error) -> Self { - Self::Io(v) - } -} - impl From for Error { fn from(v: serde_json::Error) -> Self { Self::Json(v) @@ -26,7 +20,6 @@ impl From for Error { impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - Error::Io(e) => write!(f, "I/O error: {}", e), Error::Json(e) => write!(f, "JSON error: {}", e), Error::Decode(e) => write!(f, "decoding error: {}", e), } @@ -36,7 +29,6 @@ impl std::fmt::Display for Error { impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Error::Io(e) => Some(e), Error::Json(e) => Some(e), Error::Decode(e) => Some(e), } From 9d62110549d50bd83b36921c342d2b759f20e7d1 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 21 Nov 2023 14:51:28 +0100 Subject: [PATCH 49/50] Document `Error` --- itf/src/error.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/itf/src/error.rs b/itf/src/error.rs index 90abc56..0183f35 100644 --- a/itf/src/error.rs +++ b/itf/src/error.rs @@ -1,7 +1,10 @@ /// Error type for the library. #[derive(Debug)] pub enum Error { + /// An error occured when deserializing the ITF-encoded JSON Json(serde_json::Error), + + /// An error occured when decoding an ITF value into a Rust value Decode(crate::de::Error), } From e2aef1c56d8183c5be97952d28869987c1be5fe6 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 21 Nov 2023 14:53:29 +0100 Subject: [PATCH 50/50] Redefine `itf::de::Integer` as `TryFromInto` --- itf/src/de/helpers.rs | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/itf/src/de/helpers.rs b/itf/src/de/helpers.rs index 06f005e..20dc369 100644 --- a/itf/src/de/helpers.rs +++ b/itf/src/de/helpers.rs @@ -1,15 +1,12 @@ -use std::fmt::Display; - use num_bigint::BigInt; -use serde::de::Error; -use serde::Deserialize; -use serde_with::DeserializeAs; pub use serde_with::As; /// Helper for `serde` to deserialize a `BigInt` to /// any type which implements `TryFrom`. /// +/// To be used in conjunction with [`As`]. +/// /// ## Example /// /// ```rust @@ -73,19 +70,4 @@ pub use serde_with::As; /// } /// itf::from_value::>(json.clone()).unwrap(); /// ``` - -pub struct Integer; - -impl<'de, A> DeserializeAs<'de, A> for Integer -where - A: TryFrom, - >::Error: Display, -{ - fn deserialize_as(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let bigint = BigInt::deserialize(deserializer).map_err(D::Error::custom)?; - A::try_from(bigint).map_err(D::Error::custom) - } -} +pub type Integer = serde_with::TryFromInto;