diff --git a/examples/gno.land/p/demo/teritori/dao_core/gno.mod b/examples/gno.land/p/demo/teritori/dao_core/gno.mod index d3ed739d2cf..5fc4bed1fdb 100644 --- a/examples/gno.land/p/demo/teritori/dao_core/gno.mod +++ b/examples/gno.land/p/demo/teritori/dao_core/gno.mod @@ -1,7 +1,7 @@ module gno.land/p/demo/teritori/dao_core require ( + gno.land/p/demo/json v0.0.0-latest gno.land/p/demo/teritori/dao_interfaces v0.0.0-latest gno.land/p/demo/teritori/markdown_utils v0.0.0-latest - gno.land/p/demo/teritori/ujson v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/teritori/dao_core/messages.gno b/examples/gno.land/p/demo/teritori/dao_core/messages.gno index 60493d2766b..d51bf73e21a 100644 --- a/examples/gno.land/p/demo/teritori/dao_core/messages.gno +++ b/examples/gno.land/p/demo/teritori/dao_core/messages.gno @@ -1,8 +1,8 @@ package core import ( + "gno.land/p/demo/json" dao_interfaces "gno.land/p/demo/teritori/dao_interfaces" - "gno.land/p/demo/teritori/ujson" ) // UpdateProposalModules @@ -12,6 +12,8 @@ type UpdateProposalModulesExecutableMessage struct { ToDisable []int } +var _ dao_interfaces.ExecutableMessage = &UpdateProposalModulesExecutableMessage{} + func (msg UpdateProposalModulesExecutableMessage) Type() string { return "gno.land/p/demo/teritori/dao_core.UpdateProposalModules" } @@ -20,11 +22,11 @@ func (msg *UpdateProposalModulesExecutableMessage) String() string { panic(ErrNotImplemented) } -func (msg *UpdateProposalModulesExecutableMessage) ToJSON() string { +func (msg *UpdateProposalModulesExecutableMessage) ToJSON() *json.Node { panic(ErrNotImplemented) } -func (msg *UpdateProposalModulesExecutableMessage) FromJSON(ast *ujson.JSONASTNode) { +func (msg *UpdateProposalModulesExecutableMessage) FromJSON(ast *json.Node) { panic(ErrNotImplemented) } @@ -32,6 +34,8 @@ type UpdateProposalModulesMessageHandler struct { dao dao_interfaces.IDAOCore } +var _ dao_interfaces.MessageHandler = &UpdateProposalModulesMessageHandler{} + func NewUpdateProposalModulesMessageHandler(dao dao_interfaces.IDAOCore) *UpdateProposalModulesMessageHandler { return &UpdateProposalModulesMessageHandler{dao: dao} } @@ -45,8 +49,8 @@ func (handler *UpdateProposalModulesMessageHandler) Execute(message dao_interfac handler.dao.UpdateProposalModules(msg.ToAdd, msg.ToDisable) } -func (handler *UpdateProposalModulesMessageHandler) MessageFromJSON(ast *ujson.JSONASTNode) dao_interfaces.ExecutableMessage { - panic(ErrNotSupported) +func (handler *UpdateProposalModulesMessageHandler) Instantiate() dao_interfaces.ExecutableMessage { + return &UpdateProposalModulesExecutableMessage{} } // UpdateVotingModule @@ -55,6 +59,8 @@ type UpdateVotingModuleExecutableMessage struct { Module dao_interfaces.IVotingModule } +var _ dao_interfaces.ExecutableMessage = &UpdateVotingModuleExecutableMessage{} + func (msg UpdateVotingModuleExecutableMessage) Type() string { return "gno.land/p/demo/teritori/dao_core.UpdateVotingModule" } @@ -63,11 +69,11 @@ func (msg *UpdateVotingModuleExecutableMessage) String() string { panic(ErrNotImplemented) } -func (msg *UpdateVotingModuleExecutableMessage) ToJSON() string { +func (msg *UpdateVotingModuleExecutableMessage) ToJSON() *json.Node { panic(ErrNotImplemented) } -func (msg *UpdateVotingModuleExecutableMessage) FromJSON(ast *ujson.JSONASTNode) { +func (msg *UpdateVotingModuleExecutableMessage) FromJSON(ast *json.Node) { panic(ErrNotImplemented) } @@ -75,6 +81,8 @@ type UpdateVotingModuleMessageHandler struct { dao dao_interfaces.IDAOCore } +var _ dao_interfaces.MessageHandler = &UpdateVotingModuleMessageHandler{} + func NewUpdateVotingModuleMessageHandler(dao dao_interfaces.IDAOCore) *UpdateVotingModuleMessageHandler { return &UpdateVotingModuleMessageHandler{dao: dao} } @@ -88,6 +96,6 @@ func (handler *UpdateVotingModuleMessageHandler) Execute(message dao_interfaces. handler.dao.UpdateVotingModule(msg.Module) } -func (handler *UpdateVotingModuleMessageHandler) MessageFromJSON(ast *ujson.JSONASTNode) dao_interfaces.ExecutableMessage { - panic(ErrNotSupported) +func (handler *UpdateVotingModuleMessageHandler) Instantiate() dao_interfaces.ExecutableMessage { + return &UpdateVotingModuleExecutableMessage{} } diff --git a/examples/gno.land/p/demo/teritori/dao_interfaces/gno.mod b/examples/gno.land/p/demo/teritori/dao_interfaces/gno.mod index 5d57f81771b..233d0f8c2d2 100644 --- a/examples/gno.land/p/demo/teritori/dao_interfaces/gno.mod +++ b/examples/gno.land/p/demo/teritori/dao_interfaces/gno.mod @@ -2,5 +2,5 @@ module gno.land/p/demo/teritori/dao_interfaces require ( gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/teritori/ujson v0.0.0-latest + gno.land/p/demo/json v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/teritori/dao_interfaces/messages.gno b/examples/gno.land/p/demo/teritori/dao_interfaces/messages.gno index 083156656fc..8b61ad2fe12 100644 --- a/examples/gno.land/p/demo/teritori/dao_interfaces/messages.gno +++ b/examples/gno.land/p/demo/teritori/dao_interfaces/messages.gno @@ -1,12 +1,12 @@ package dao_interfaces import ( - "gno.land/p/demo/teritori/ujson" + "gno.land/p/demo/json" ) type ExecutableMessage interface { - ujson.JSONAble - ujson.FromJSONAble + ToJSON() *json.Node + FromJSON(ast *json.Node) String() string Type() string @@ -14,7 +14,7 @@ type ExecutableMessage interface { type MessageHandler interface { Execute(message ExecutableMessage) - MessageFromJSON(ast *ujson.JSONASTNode) ExecutableMessage + Instantiate() ExecutableMessage Type() string } diff --git a/examples/gno.land/p/demo/teritori/dao_interfaces/messages_registry.gno b/examples/gno.land/p/demo/teritori/dao_interfaces/messages_registry.gno index 8372cb8d8ff..89bdea3c8b5 100644 --- a/examples/gno.land/p/demo/teritori/dao_interfaces/messages_registry.gno +++ b/examples/gno.land/p/demo/teritori/dao_interfaces/messages_registry.gno @@ -2,7 +2,7 @@ package dao_interfaces import ( "gno.land/p/demo/avl" - "gno.land/p/demo/teritori/ujson" + "gno.land/p/demo/json" ) type MessagesRegistry struct { @@ -24,21 +24,18 @@ func (r *MessagesRegistry) Remove(t string) { r.handlers.Remove(t) } -func (r *MessagesRegistry) MessagesFromJSON(messagesJSON string) []ExecutableMessage { - slice := ujson.ParseSlice(messagesJSON) - msgs := make([]ExecutableMessage, 0, len(slice)) - for _, child := range slice { - var messageType string - var payload *ujson.JSONASTNode - child.ParseObject([]*ujson.ParseKV{ - {Key: "type", Value: &messageType}, - {Key: "payload", Value: &payload}, - }) +func (r *MessagesRegistry) MessagesFromJSON(slice []*json.Node) []ExecutableMessage { + msgs := make([]ExecutableMessage, len(slice)) + for i, elem := range slice { + messageType := json.Must(elem.GetKey("type")).MustString() + payload := json.Must(elem.GetKey("payload")) h, ok := r.handlers.Get(messageType) if !ok { panic("invalid ExecutableMessage: invalid message type") } - msgs = append(msgs, h.(MessageHandler).MessageFromJSON(payload)) + instance := h.(MessageHandler).Instantiate() + instance.FromJSON(payload) + msgs[i] = instance } return msgs } @@ -61,15 +58,17 @@ type RegisterHandlerExecutableMessage struct { Handler MessageHandler } +var _ ExecutableMessage = &RegisterHandlerExecutableMessage{} + func (m RegisterHandlerExecutableMessage) Type() string { return "gno.land/p/demo/teritori/dao_interfaces.RegisterHandler" } -func (m *RegisterHandlerExecutableMessage) FromJSON(ast *ujson.JSONASTNode) { +func (m *RegisterHandlerExecutableMessage) FromJSON(ast *json.Node) { panic("not implemented") } -func (m *RegisterHandlerExecutableMessage) ToJSON() string { +func (m *RegisterHandlerExecutableMessage) ToJSON() *json.Node { panic("not implemented") } @@ -81,6 +80,8 @@ type RegisterHandlerExecutableMessageHandler struct { registry *MessagesRegistry } +var _ MessageHandler = &RegisterHandlerExecutableMessageHandler{} + func NewRegisterHandlerExecutableMessageHandler(registry *MessagesRegistry) *RegisterHandlerExecutableMessageHandler { return &RegisterHandlerExecutableMessageHandler{registry: registry} } @@ -89,8 +90,8 @@ func (h RegisterHandlerExecutableMessageHandler) Type() string { return RegisterHandlerExecutableMessage{}.Type() } -func (h *RegisterHandlerExecutableMessageHandler) MessageFromJSON(ast *ujson.JSONASTNode) ExecutableMessage { - panic("not implemented") +func (h *RegisterHandlerExecutableMessageHandler) Instantiate() ExecutableMessage { + return &RegisterHandlerExecutableMessage{} } func (h *RegisterHandlerExecutableMessageHandler) Execute(msg ExecutableMessage) { @@ -101,16 +102,18 @@ type RemoveHandlerExecutableMessage struct { HandlerType string } +var _ ExecutableMessage = &RemoveHandlerExecutableMessage{} + func (m RemoveHandlerExecutableMessage) Type() string { return "gno.land/p/demo/teritori/dao_interfaces.RemoveHandler" } -func (m *RemoveHandlerExecutableMessage) FromJSON(ast *ujson.JSONASTNode) { - ast.ParseAny(&m.HandlerType) +func (m *RemoveHandlerExecutableMessage) FromJSON(ast *json.Node) { + m.HandlerType = ast.MustString() } -func (m *RemoveHandlerExecutableMessage) ToJSON() string { - return ujson.FormatAny(m.HandlerType) +func (m *RemoveHandlerExecutableMessage) ToJSON() *json.Node { + return json.StringNode("", m.HandlerType) } func (m *RemoveHandlerExecutableMessage) String() string { @@ -121,6 +124,8 @@ type RemoveHandlerExecutableMessageHandler struct { registry *MessagesRegistry } +var _ MessageHandler = &RemoveHandlerExecutableMessageHandler{} + func NewRemoveHandlerExecutableMessageHandler(registry *MessagesRegistry) *RemoveHandlerExecutableMessageHandler { return &RemoveHandlerExecutableMessageHandler{registry: registry} } @@ -129,10 +134,8 @@ func (h RemoveHandlerExecutableMessageHandler) Type() string { return RemoveHandlerExecutableMessage{}.Type() } -func (h *RemoveHandlerExecutableMessageHandler) MessageFromJSON(ast *ujson.JSONASTNode) ExecutableMessage { - msg := &RemoveHandlerExecutableMessage{} - ast.ParseAny(msg) - return msg +func (h *RemoveHandlerExecutableMessageHandler) Instantiate() ExecutableMessage { + return &RemoveHandlerExecutableMessage{} } func (h *RemoveHandlerExecutableMessageHandler) Execute(msg ExecutableMessage) { diff --git a/examples/gno.land/p/demo/teritori/dao_interfaces/messages_registry_test.gno b/examples/gno.land/p/demo/teritori/dao_interfaces/messages_registry_test.gno index e9b4306c7a7..711107603dd 100644 --- a/examples/gno.land/p/demo/teritori/dao_interfaces/messages_registry_test.gno +++ b/examples/gno.land/p/demo/teritori/dao_interfaces/messages_registry_test.gno @@ -2,6 +2,8 @@ package dao_interfaces import ( "testing" + + "gno.land/p/demo/json" ) func TestRegistry(t *testing.T) { @@ -15,7 +17,7 @@ func TestRegistry(t *testing.T) { registry.Execute(registerMsg) // Test messages execution - msgs := registry.MessagesFromJSON(`[{"type":"CopyMessage","payload":"Hello"}]`) + msgs := registry.MessagesFromJSON(json.Must(json.Unmarshal(`[{"type":"CopyMessage","payload":"Hello"}]`)).MustArray()) if len(msgs) != 1 { t.Errorf("Expected 1 message, got %d", len(msgs)) } diff --git a/examples/gno.land/p/demo/teritori/dao_interfaces/messages_testing.gno b/examples/gno.land/p/demo/teritori/dao_interfaces/messages_testing.gno index 286e2c20d8f..e800cdc5992 100644 --- a/examples/gno.land/p/demo/teritori/dao_interfaces/messages_testing.gno +++ b/examples/gno.land/p/demo/teritori/dao_interfaces/messages_testing.gno @@ -1,7 +1,7 @@ package dao_interfaces import ( - "gno.land/p/demo/teritori/ujson" + "gno.land/p/demo/json" ) type CopyMessage struct { @@ -16,12 +16,12 @@ func (m *CopyMessage) String() string { return m.Value } -func (m *CopyMessage) FromJSON(ast *ujson.JSONASTNode) { - ast.ParseAny(&m.Value) +func (m *CopyMessage) FromJSON(ast *json.Node) { + m.Value = ast.MustString() } -func (m *CopyMessage) ToJSON() string { - return ujson.FormatString(m.Value) +func (m *CopyMessage) ToJSON() *json.Node { + return json.StringNode("", m.Value) } type CopyMessageHandler struct { @@ -47,8 +47,6 @@ func (h CopyMessageHandler) Type() string { return "CopyMessage" } -func (h *CopyMessageHandler) MessageFromJSON(ast *ujson.JSONASTNode) ExecutableMessage { - var msg CopyMessage - ast.ParseAny(&msg) - return &msg +func (h *CopyMessageHandler) Instantiate() ExecutableMessage { + return &CopyMessage{} } diff --git a/examples/gno.land/p/demo/teritori/dao_proposal_single/dao_proposal_single.gno b/examples/gno.land/p/demo/teritori/dao_proposal_single/dao_proposal_single.gno index e6c47ac62d4..9820c7172a3 100644 --- a/examples/gno.land/p/demo/teritori/dao_proposal_single/dao_proposal_single.gno +++ b/examples/gno.land/p/demo/teritori/dao_proposal_single/dao_proposal_single.gno @@ -5,9 +5,9 @@ import ( "strconv" "gno.land/p/demo/avl" + "gno.land/p/demo/json" "gno.land/p/demo/teritori/dao_interfaces" "gno.land/p/demo/teritori/dao_utils" - "gno.land/p/demo/teritori/ujson" ) type DAOProposalSingleOpts struct { @@ -43,14 +43,14 @@ type DAOProposalSingleOpts struct { CloseProposalOnExecutionFailure bool } -func (opts DAOProposalSingleOpts) ToJSON() string { - return ujson.FormatObject([]ujson.FormatKV{ - {Key: "threshold", Value: opts.Threshold}, - {Key: "maxVotingPeriod", Value: opts.MaxVotingPeriod}, - {Key: "minVotingPeriod", Value: opts.MinVotingPeriod}, - {Key: "onlyMembersExecute", Value: opts.OnlyMembersExecute}, - {Key: "allowRevoting", Value: opts.AllowRevoting}, - {Key: "closeProposalOnExecutionFailure", Value: opts.CloseProposalOnExecutionFailure}, +func (opts DAOProposalSingleOpts) ToJSON() *json.Node { + return json.ObjectNode("", map[string]*json.Node{ + "threshold": opts.Threshold.ToJSON(), + "maxVotingPeriod": opts.MaxVotingPeriod.ToJSON(), + "minVotingPeriod": opts.MinVotingPeriod.ToJSON(), + "onlyMembersExecute": json.BoolNode("", opts.OnlyMembersExecute), + "allowRevoting": json.BoolNode("", opts.AllowRevoting), + "closeProposalOnExecutionFailure": json.BoolNode("", opts.CloseProposalOnExecutionFailure), }) } @@ -201,7 +201,11 @@ func (d *DAOProposalSingle) Info() dao_interfaces.ModuleInfo { } func (d *DAOProposalSingle) ConfigJSON() string { - return ujson.FormatAny(d.opts) + b, err := json.Marshal(d.opts.ToJSON()) + if err != nil { + panic(err) + } + return string(b) } func (d *DAOProposalSingle) Propose(title string, description string, messages []dao_interfaces.ExecutableMessage) int { @@ -254,16 +258,15 @@ type VoteWithRationale struct { Rationale string } -func (v *VoteWithRationale) FromJSON(ast *ujson.JSONASTNode) { - ast.ParseObject([]*ujson.ParseKV{ - {Key: "vote", Value: &v.Vote}, - {Key: "rationale", Value: &v.Rationale}, - }) +func (v *VoteWithRationale) FromJSON(ast *json.Node) { + obj := ast.MustObject() + v.Vote.FromJSON(obj["vote"]) + v.Rationale = obj["rationale"].MustString() } func (d *DAOProposalSingle) VoteJSON(proposalID int, voteJSON string) { var v VoteWithRationale - ujson.ParseAny(voteJSON, &v) + v.FromJSON(json.Must(json.Unmarshal([]byte(voteJSON)))) voter := std.PrevRealm().Addr() @@ -317,21 +320,20 @@ func (d *DAOProposalSingle) Execute(proposalID int) { type ProposalRequest struct { Title string Description string - Messages *ujson.JSONASTNode + Messages []*json.Node } -func (pr *ProposalRequest) FromJSON(ast *ujson.JSONASTNode) { - ast.ParseObject([]*ujson.ParseKV{ - {Key: "title", Value: &pr.Title}, - {Key: "description", Value: &pr.Description}, - {Key: "messages", Value: &pr.Messages}, - }) +func (pr *ProposalRequest) FromJSON(ast *json.Node) { + obj := ast.MustObject() + pr.Title = obj["title"].MustString() + pr.Description = obj["description"].MustString() + pr.Messages = obj["messages"].MustArray() } func (d *DAOProposalSingle) ProposeJSON(proposalJSON string) int { var req ProposalRequest - ujson.ParseAny(proposalJSON, &req) - msgs := d.core.Registry().MessagesFromJSON(req.Messages.String()) // TODO: optimize + req.FromJSON(json.Must(json.Unmarshal([]byte(proposalJSON)))) + msgs := d.core.Registry().MessagesFromJSON(req.Messages) return d.Propose(req.Title, req.Description, msgs) } @@ -340,18 +342,18 @@ func (d *DAOProposalSingle) Proposals() []*Proposal { } func (d *DAOProposalSingle) ProposalsJSON(limit int, startAfter string, reverse bool) string { - iSlice := make([]interface{}, len(d.proposals)) + iSlice := make([]*json.Node, len(d.proposals)) for i, p := range d.proposals { - iSlice[i] = p + iSlice[i] = p.ToJSON() } - return ujson.FormatSlice(iSlice) + return json.ArrayNode("", iSlice).String() } func (d *DAOProposalSingle) ProposalJSON(proposalID int) string { if proposalID < 0 || proposalID >= len(d.proposals) { panic("proposal does not exist") } - return ujson.FormatAny(d.proposals[proposalID]) + return d.proposals[proposalID].ToJSON().String() } func (d *DAOProposalSingle) Threshold() Threshold { diff --git a/examples/gno.land/p/demo/teritori/dao_proposal_single/gno.mod b/examples/gno.land/p/demo/teritori/dao_proposal_single/gno.mod index 69255f7cf04..8292ec31502 100644 --- a/examples/gno.land/p/demo/teritori/dao_proposal_single/gno.mod +++ b/examples/gno.land/p/demo/teritori/dao_proposal_single/gno.mod @@ -2,7 +2,8 @@ module gno.land/p/demo/teritori/dao_proposal_single require ( gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/json v0.0.0-latest gno.land/p/demo/teritori/dao_interfaces v0.0.0-latest gno.land/p/demo/teritori/dao_utils v0.0.0-latest - gno.land/p/demo/teritori/ujson v0.0.0-latest + gno.land/p/demo/teritori/jsonutil v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/teritori/dao_proposal_single/proposal_test.gno b/examples/gno.land/p/demo/teritori/dao_proposal_single/proposal_test.gno index 5d1a13a8983..77b7548209d 100644 --- a/examples/gno.land/p/demo/teritori/dao_proposal_single/proposal_test.gno +++ b/examples/gno.land/p/demo/teritori/dao_proposal_single/proposal_test.gno @@ -4,9 +4,9 @@ import ( "testing" "gno.land/p/demo/avl" + "gno.land/p/demo/json" dao_interfaces "gno.land/p/demo/teritori/dao_interfaces" "gno.land/p/demo/teritori/dao_utils" - "gno.land/p/demo/teritori/ujson" ) type NoopMessage struct{} @@ -21,18 +21,56 @@ func (m NoopMessage) Type() string { return "noop-type" } -func (m NoopMessage) ToJSON() string { - return ujson.FormatString(m.String()) +func (m NoopMessage) ToJSON() *json.Node { + return json.StringNode("", m.String()) } -func (m NoopMessage) FromJSON(ast *ujson.JSONASTNode) { - var val string - ast.ParseAny(&val) - if val != m.String() { +func (m NoopMessage) FromJSON(ast *json.Node) { + if ast.MustString() != m.String() { panic("invalid noop message") } } +func TestThresholdToJSON(t *testing.T) { + tt := PercentageThresholdPercent(1) + tq := PercentageThresholdPercent(1) + threshold := &ThresholdThresholdQuorum{ + Threshold: &tt, + Quorum: &tq, + } + str := threshold.ToJSON().String() + expected := `{"thresholdQuorum":{"threshold":{"percent":1},"quorum":{"percent":1}}}` + if expected != str { + t.Fatalf("JSON does not match, expected %s, got %s", expected, str) + } +} + +func TestThresholdFromJSON(t *testing.T) { + s := ` {"thresholdQuorum": {"threshold" :{"percent":1},"quorum": {"percent":1}}}` + ival := ThresholdFromJSON(json.Must(json.Unmarshal([]byte(s)))) + + val, ok := ival.(*ThresholdThresholdQuorum) + if !ok { + t.Fatalf("expected ThresholdQuorum") + } + + tt, ok := val.Threshold.(*PercentageThresholdPercent) + if !ok { + t.Fatalf("expected Percent in threshold") + } + if *tt != 1 { + t.Fatalf("expected 1, got %d", *tt) + } + + tq, ok := val.Quorum.(*PercentageThresholdPercent) + if !ok { + t.Fatalf("expected Percent in quorum") + } + if *tq != 1 { + t.Fatalf("expected 1, got %d", *tq) + } +} + func TestProposalJSON(t *testing.T) { props := []Proposal{ { @@ -59,12 +97,12 @@ func TestProposalJSON(t *testing.T) { }, } props[0].Ballots.Set("0x1234567890", Ballot{Power: 1, Vote: VoteYes, Rationale: "test"}) - iSlice := make([]interface{}, len(props)) + iSlice := make([]*json.Node, len(props)) for i, p := range props { - iSlice[i] = p + iSlice[i] = p.ToJSON() } - str := ujson.FormatSlice(iSlice) - expected := `[{"id":0,"title":"Prop #0","description":"Wolol0\n\t\r","proposer":"0x1234567890","startHeight":0,"minVotingPeriod":null,"expiration":{"atHeight":1000},"threshold":null,"totalPower":0,"messages":[],"status":"Open","votes":{"yes":7,"no":21,"abstain":42},"allowRevoting":false,"ballots":{"0x1234567890":{"power":1,"vote":"Yes","rationale":"test"}}},{"id":1,"title":"Prop #1","description":"Wolol1\\\"","proposer":"0x1234567890","startHeight":0,"minVotingPeriod":null,"expiration":{"atHeight":2000},"threshold":null,"totalPower":0,"messages":[{"type":"noop-type","payload":"noop"},{"type":"noop-type","payload":"noop"},{"type":"noop-type","payload":"noop"}],"status":"Executed","votes":{"yes":0,"no":0,"abstain":0},"allowRevoting":false,"ballots":{}}]` + str := json.ArrayNode("", iSlice).String() + expected := `[{"id":"0","title":"Prop #0","description":"Wolol0\n\t\r","proposer":"0x1234567890","startHeight":"0","totalPower":"0","messages":[],"status":"Open","votes":{"yes":"7","no":"21","abstain":"42"},"allowRevoting":false,"ballots":{"0x1234567890":{"power":"1","vote":"Yes","rationale":"test"}},"expiration":{"atHeight":"1000"}},{"id":"1","title":"Prop #1","description":"Wolol1\\\"","proposer":"0x1234567890","startHeight":"0","totalPower":"0","messages":[{"type":"noop-type","payload":"noop"},{"type":"noop-type","payload":"noop"},{"type":"noop-type","payload":"noop"}],"status":"Executed","votes":{"yes":"0","no":"0","abstain":"0"},"allowRevoting":false,"ballots":{},"expiration":{"atHeight":"2000"}}]` if expected != str { t.Fatalf("JSON does not match, expected %s, got %s", expected, str) } diff --git a/examples/gno.land/p/demo/teritori/dao_proposal_single/threshold.gno b/examples/gno.land/p/demo/teritori/dao_proposal_single/threshold.gno index 5705da8a85c..be4a6f80362 100644 --- a/examples/gno.land/p/demo/teritori/dao_proposal_single/threshold.gno +++ b/examples/gno.land/p/demo/teritori/dao_proposal_single/threshold.gno @@ -3,7 +3,8 @@ package dao_proposal_single import ( "strconv" - "gno.land/p/demo/teritori/ujson" + "gno.land/p/demo/json" + "gno.land/p/demo/teritori/jsonutil" ) // ported from https://github.com/DA0-DA0/dao-contracts/blob/7776858e780f1ce9f038a3b06cce341dd41d2189/packages/dao-voting/src/threshold.rs @@ -11,15 +12,20 @@ import ( type PercentageThreshold interface { String() string Clone() PercentageThreshold - ToJSON() string -} - -func PercentageThresholdFromJSON(ast *ujson.JSONASTNode) PercentageThreshold { - p := PercentageThresholdPercent(0) - return ast.ParseUnion([]*ujson.ParseKV{ - {Key: "majority", Value: &PercentageThresholdMajority{}}, - {Key: "percent", Value: &p}, - }).(PercentageThreshold) + ToJSON() *json.Node +} + +func PercentageThresholdFromJSON(ast *json.Node) PercentageThreshold { + variant, valueNode := jsonutil.MustUnion(ast) + switch variant { + case "majority": + return &PercentageThresholdMajority{} + case "percent": + p := PercentageThresholdPercent(0) + p.FromJSON(valueNode) + return &p + } + panic("unknown PercentageThreshold variant") } type PercentageThresholdMajority struct{} @@ -32,8 +38,8 @@ func (p *PercentageThresholdMajority) Clone() PercentageThreshold { return &PercentageThresholdMajority{} } -func (p *PercentageThresholdMajority) ToJSON() string { - return ujson.FormatUnionMember("majority", "{}", true) +func (p *PercentageThresholdMajority) ToJSON() *json.Node { + return jsonutil.UnionNode("majority", jsonutil.EmptyObjectNode()) } type PercentageThresholdPercent uint16 // 4 decimals fixed point @@ -47,9 +53,8 @@ func (p *PercentageThresholdPercent) String() string { return s + "%" } -func (p *PercentageThresholdPercent) FromJSON(ast *ujson.JSONASTNode) { - var val uint32 - ujson.ParseAny(ast.Value, &val) +func (p *PercentageThresholdPercent) FromJSON(ast *json.Node) { + val := ast.MustNumeric() // FIXME: don't pass by float64 *p = PercentageThresholdPercent(val) } @@ -58,22 +63,30 @@ func (p *PercentageThresholdPercent) Clone() PercentageThreshold { return &c } -func (p *PercentageThresholdPercent) ToJSON() string { - return ujson.FormatUnionMember("percent", uint64(*p), false) +func (p *PercentageThresholdPercent) ToJSON() *json.Node { + return jsonutil.UnionNode("percent", json.NumberNode("", float64(*p))) } type Threshold interface { Clone() Threshold - ToJSON() string -} - -func ThresholdFromJSON(ast *ujson.JSONASTNode) Threshold { - ac := ThresholdAbsoluteCount(0) - return ast.ParseUnion([]*ujson.ParseKV{ - // TODO: {Key: "absolutePercentage"}, - {Key: "thresholdQuorum", Value: &ThresholdThresholdQuorum{}}, - {Key: "absoluteCount", Value: &ac}, - }).(Threshold) + ToJSON() *json.Node +} + +func ThresholdFromJSON(ast *json.Node) Threshold { + variant, valueNode := jsonutil.MustUnion(ast) + switch variant { + case "absolutePercentage": + panic("not implemented") + case "thresholdQuorum": + t := &ThresholdThresholdQuorum{} + t.FromJSON(valueNode) + return t + case "absoluteCount": + val := valueNode.MustNumeric() + ac := ThresholdAbsoluteCount(val) + return &ac + } + panic("unknown Threshold variant") } type ThresholdAbsolutePercentage struct { @@ -85,8 +98,8 @@ func (t ThresholdAbsolutePercentage) Clone() Threshold { return &ThresholdAbsolutePercentage{Value: c} } -func (t ThresholdAbsolutePercentage) ToJSON() string { - return ujson.FormatUnionMember("absolutePercentage", t.Value, false) +func (t ThresholdAbsolutePercentage) ToJSON() *json.Node { + return jsonutil.UnionNode("absolutePercentage", t.Value.ToJSON()) } type ThresholdThresholdQuorum struct { @@ -101,22 +114,19 @@ func (t *ThresholdThresholdQuorum) Clone() Threshold { } } -func (t *ThresholdThresholdQuorum) FromJSON(ast *ujson.JSONASTNode) { - ast.ParseObject([]*ujson.ParseKV{ - {Key: "threshold", CustomParser: func(ast *ujson.JSONASTNode) { - t.Threshold = PercentageThresholdFromJSON(ast) - }}, - {Key: "quorum", CustomParser: func(ast *ujson.JSONASTNode) { - t.Quorum = PercentageThresholdFromJSON(ast) - }}, - }) +// TODO: make union members json utilities bijective + +func (t *ThresholdThresholdQuorum) FromJSON(ast *json.Node) { + obj := ast.MustObject() + t.Threshold = PercentageThresholdFromJSON(obj["threshold"]) + t.Quorum = PercentageThresholdFromJSON(obj["quorum"]) } -func (t *ThresholdThresholdQuorum) ToJSON() string { - return ujson.FormatUnionMember("thresholdQuorum", ujson.FormatObject([]ujson.FormatKV{ - {Key: "threshold", Value: t.Threshold}, - {Key: "quorum", Value: t.Quorum}, - }), true) +func (t *ThresholdThresholdQuorum) ToJSON() *json.Node { + return jsonutil.UnionNode("thresholdQuorum", json.ObjectNode("", map[string]*json.Node{ + "threshold": t.Threshold.ToJSON(), + "quorum": t.Quorum.ToJSON(), + })) } type ThresholdAbsoluteCount uint64 @@ -126,6 +136,6 @@ func (t *ThresholdAbsoluteCount) Clone() Threshold { return &val } -func (t *ThresholdAbsoluteCount) ToJSON() string { - return ujson.FormatUnionMember("absoluteCount", uint64(*t), false) +func (t *ThresholdAbsoluteCount) ToJSON() *json.Node { + return jsonutil.UnionNode("absoluteCount", jsonutil.Uint64Node(uint64(*t))) } diff --git a/examples/gno.land/p/demo/teritori/dao_proposal_single/types.gno b/examples/gno.land/p/demo/teritori/dao_proposal_single/types.gno index 49db078aced..f292118572a 100644 --- a/examples/gno.land/p/demo/teritori/dao_proposal_single/types.gno +++ b/examples/gno.land/p/demo/teritori/dao_proposal_single/types.gno @@ -5,9 +5,10 @@ import ( "strconv" "gno.land/p/demo/avl" + "gno.land/p/demo/json" dao_interfaces "gno.land/p/demo/teritori/dao_interfaces" "gno.land/p/demo/teritori/dao_utils" - "gno.land/p/demo/teritori/ujson" + "gno.land/p/demo/teritori/jsonutil" ) type Ballot struct { @@ -16,11 +17,11 @@ type Ballot struct { Rationale string } -func (b Ballot) ToJSON() string { - return ujson.FormatObject([]ujson.FormatKV{ - {Key: "power", Value: b.Power}, - {Key: "vote", Value: b.Vote}, - {Key: "rationale", Value: b.Rationale}, +func (b Ballot) ToJSON() *json.Node { + return json.ObjectNode("", map[string]*json.Node{ + "power": jsonutil.Uint64Node(b.Power), + "vote": b.Vote.ToJSON(), + "rationale": json.StringNode("", b.Rationale), }) } @@ -60,11 +61,11 @@ func (v *Votes) Total() uint64 { return v.Yes + v.No + v.Abstain } -func (v Votes) ToJSON() string { - return ujson.FormatObject([]ujson.FormatKV{ - {Key: "yes", Value: v.Yes}, - {Key: "no", Value: v.No}, - {Key: "abstain", Value: v.Abstain}, +func (v Votes) ToJSON() *json.Node { + return json.ObjectNode("", map[string]*json.Node{ + "yes": jsonutil.Uint64Node(v.Yes), + "no": jsonutil.Uint64Node(v.No), + "abstain": jsonutil.Uint64Node(v.Abstain), }) } @@ -88,49 +89,54 @@ type Proposal struct { Ballots *avl.Tree } -var _ ujson.JSONAble = (*Proposal)(nil) - type messageWithType struct { Type string Message dao_interfaces.ExecutableMessage } -func (m *messageWithType) ToJSON() string { - return ujson.FormatObject([]ujson.FormatKV{ - {Key: "type", Value: m.Type}, - {Key: "payload", Value: m.Message}, +func (m *messageWithType) ToJSON() *json.Node { + return json.ObjectNode("", map[string]*json.Node{ + "type": json.StringNode("", m.Type), + "payload": m.Message.ToJSON(), }) } -func formatMessages(messages []dao_interfaces.ExecutableMessage) string { - var out []interface{} - for _, m := range messages { - out = append(out, &messageWithType{ +func formatMessages(messages []dao_interfaces.ExecutableMessage) []*json.Node { + out := make([]*json.Node, len(messages)) + for i, m := range messages { + mt := messageWithType{ Type: m.Type(), Message: m, - }) + } + out[i] = mt.ToJSON() } - return ujson.FormatSlice(out) -} - -func (p Proposal) ToJSON() string { - return ujson.FormatObject([]ujson.FormatKV{ - {Key: "id", Value: p.ID}, - {Key: "title", Value: p.Title}, - {Key: "description", Value: p.Description}, - {Key: "proposer", Value: p.Proposer}, - {Key: "startHeight", Value: p.StartHeight}, - {Key: "minVotingPeriod", Value: p.MinVotingPeriod}, - {Key: "expiration", Value: p.Expiration}, - {Key: "threshold", Value: p.Threshold}, - {Key: "totalPower", Value: p.TotalPower}, - {Key: "messages", Value: formatMessages(p.Messages), Raw: true}, - {Key: "status", Value: p.Status}, - {Key: "votes", Value: p.Votes}, - {Key: "allowRevoting", Value: p.AllowRevoting}, - - {Key: "ballots", Value: p.Ballots}, - }) + return out +} + +func (p Proposal) ToJSON() *json.Node { + fields := map[string]*json.Node{ + "id": jsonutil.IntNode(p.ID), + "title": json.StringNode("", p.Title), + "description": json.StringNode("", p.Description), + "proposer": jsonutil.AddressNode(p.Proposer), + "startHeight": jsonutil.Int64Node(p.StartHeight), + "totalPower": jsonutil.Uint64Node(p.TotalPower), + "messages": json.ArrayNode("", formatMessages(p.Messages)), + "status": p.Status.ToJSON(), + "votes": p.Votes.ToJSON(), + "allowRevoting": json.BoolNode("", p.AllowRevoting), + "ballots": jsonutil.AVLTreeNode(p.Ballots, func(v interface{}) *json.Node { return v.(Ballot).ToJSON() }), + } + if p.MinVotingPeriod != nil { + fields["minVotingPeriod"] = p.MinVotingPeriod.ToJSON() + } + if p.Expiration != nil { + fields["expiration"] = p.Expiration.ToJSON() + } + if p.Threshold != nil { + fields["threshold"] = p.Threshold.ToJSON() + } + return json.ObjectNode("", fields) } type ProposalStatus int @@ -141,8 +147,8 @@ const ( ProposalStatusExecuted ) -func (p ProposalStatus) ToJSON() string { - return ujson.FormatString(p.String()) +func (p ProposalStatus) ToJSON() *json.Node { + return json.StringNode("", p.String()) } func (p ProposalStatus) String() string { @@ -166,15 +172,25 @@ const ( VoteAbstain ) -func (v Vote) ToJSON() string { - return ujson.FormatString(v.String()) +func (v Vote) ToJSON() *json.Node { + return json.StringNode("", v.String()) } -func (v *Vote) FromJSON(ast *ujson.JSONASTNode) { - var val int - ast.ParseAny(&val) - // FIXME: validate - *v = Vote(val) +func (v *Vote) FromJSON(ast *json.Node) { + v.FromString(ast.MustString()) +} + +func (v *Vote) FromString(s string) { + switch s { + case "Yes": + *v = VoteYes + case "No": + *v = VoteNo + case "Abstain": + *v = VoteAbstain + default: + panic("unknown vote kind") + } } func (v Vote) String() string { diff --git a/examples/gno.land/p/demo/teritori/dao_proposal_single/update_settings.gno b/examples/gno.land/p/demo/teritori/dao_proposal_single/update_settings.gno index 011e0b42f19..140abb421d7 100644 --- a/examples/gno.land/p/demo/teritori/dao_proposal_single/update_settings.gno +++ b/examples/gno.land/p/demo/teritori/dao_proposal_single/update_settings.gno @@ -3,18 +3,18 @@ package dao_proposal_single import ( "strings" + "gno.land/p/demo/json" "gno.land/p/demo/teritori/dao_interfaces" - "gno.land/p/demo/teritori/ujson" ) -// TODO: convert to json - type UpdateSettingsMessage struct { dao_interfaces.ExecutableMessage Threshold Threshold } +var _ dao_interfaces.ExecutableMessage = (*UpdateSettingsMessage)(nil) + func (usm UpdateSettingsMessage) Type() string { return "gno.land/p/demo/teritori/dao_proposal_single.UpdateSettings" } @@ -30,18 +30,15 @@ func (usm *UpdateSettingsMessage) String() string { return strings.Join(ss, "\n--\n") } -func (usm *UpdateSettingsMessage) ToJSON() string { - return ujson.FormatObject([]ujson.FormatKV{ - {Key: "threshold", Value: usm.Threshold}, +func (usm *UpdateSettingsMessage) ToJSON() *json.Node { + return json.ObjectNode("", map[string]*json.Node{ + "threshold": usm.Threshold.ToJSON(), }) } -func (usm *UpdateSettingsMessage) FromJSON(ast *ujson.JSONASTNode) { - ast.ParseObject([]*ujson.ParseKV{ - {Key: "threshold", CustomParser: func(node *ujson.JSONASTNode) { - usm.Threshold = ThresholdFromJSON(node) - }}, - }) +func (usm *UpdateSettingsMessage) FromJSON(ast *json.Node) { + obj := ast.MustObject() + usm.Threshold = ThresholdFromJSON(obj["threshold"]) } func NewUpdateSettingsHandler(mod *DAOProposalSingle) dao_interfaces.MessageHandler { @@ -54,6 +51,8 @@ type updateSettingsHandler struct { mod *DAOProposalSingle } +var _ dao_interfaces.MessageHandler = (*updateSettingsHandler)(nil) + func (h *updateSettingsHandler) Execute(message dao_interfaces.ExecutableMessage) { usm := message.(*UpdateSettingsMessage) @@ -71,8 +70,6 @@ func (h updateSettingsHandler) Type() string { return UpdateSettingsMessage{}.Type() } -func (h *updateSettingsHandler) MessageFromJSON(ast *ujson.JSONASTNode) dao_interfaces.ExecutableMessage { - var usm UpdateSettingsMessage - ast.ParseAny(&usm) - return &usm +func (h *updateSettingsHandler) Instantiate() dao_interfaces.ExecutableMessage { + return &UpdateSettingsMessage{} } diff --git a/examples/gno.land/p/demo/teritori/dao_utils/expiration.gno b/examples/gno.land/p/demo/teritori/dao_utils/expiration.gno index 855e9330d35..6b26a650b03 100644 --- a/examples/gno.land/p/demo/teritori/dao_utils/expiration.gno +++ b/examples/gno.land/p/demo/teritori/dao_utils/expiration.gno @@ -5,7 +5,8 @@ import ( "strconv" "time" - "gno.land/p/demo/teritori/ujson" + "gno.land/p/demo/json" + "gno.land/p/demo/teritori/jsonutil" ) // loosely ported from https://github.com/CosmWasm/cw-utils/blob/7fce8a214f2f1e7763b8718dcbd2a6dd07f30988/src/expiration.rs @@ -13,7 +14,7 @@ import ( type ( Expiration interface { IsExpired() bool - ToJSON() string + ToJSON() *json.Node String() string } ExpirationAtHeight int64 @@ -25,8 +26,9 @@ func (e ExpirationAtHeight) IsExpired() bool { return std.GetHeight() >= int64(e) } -func (e ExpirationAtHeight) ToJSON() string { - return ujson.FormatUnionMember("atHeight", int64(e), false) +func (e ExpirationAtHeight) ToJSON() *json.Node { + val := jsonutil.Int64Node(int64(e)) + return jsonutil.UnionNode("atHeight", val) } func (e ExpirationAtHeight) String() string { @@ -39,8 +41,9 @@ func (e ExpirationAtTime) IsExpired() bool { return now.Equal(t) || now.After(t) } -func (e ExpirationAtTime) ToJSON() string { - return ujson.FormatUnionMember("atTime", time.Time(e), false) +func (e ExpirationAtTime) ToJSON() *json.Node { + val := jsonutil.TimeNode(time.Time(e)) + return jsonutil.UnionNode("atTime", val) } func (e ExpirationAtTime) String() string { @@ -51,8 +54,8 @@ func (e ExpirationNever) IsExpired() bool { return false } -func (e ExpirationNever) ToJSON() string { - return ujson.FormatUnionMember("never", "{}", true) +func (e ExpirationNever) ToJSON() *json.Node { + return jsonutil.UnionNode("never", jsonutil.EmptyObjectNode()) } func (e ExpirationNever) String() string { @@ -62,7 +65,7 @@ func (e ExpirationNever) String() string { type ( Duration interface { AfterCurrentBlock() Expiration - ToJSON() string + ToJSON() *json.Node String() string } DurationHeight int64 @@ -73,8 +76,9 @@ func (d DurationHeight) AfterCurrentBlock() Expiration { return ExpirationAtHeight(std.GetHeight() + int64(d)) } -func (d DurationHeight) ToJSON() string { - return ujson.FormatUnionMember("height", int64(d), false) +func (d DurationHeight) ToJSON() *json.Node { + val := json.NumberNode("", float64(d)) + return jsonutil.UnionNode("height", val) } func (d DurationHeight) String() string { @@ -85,8 +89,9 @@ func (d DurationTime) AfterCurrentBlock() Expiration { return ExpirationAtTime(time.Now().Add(time.Duration(d))) } -func (d DurationTime) ToJSON() string { - return ujson.FormatUnionMember("time", time.Duration(d), false) +func (d DurationTime) ToJSON() *json.Node { + val := jsonutil.DurationNode(time.Duration(d)) + return jsonutil.UnionNode("time", val) } func (d DurationTime) String() string { diff --git a/examples/gno.land/p/demo/teritori/dao_utils/expiration_test.gno b/examples/gno.land/p/demo/teritori/dao_utils/expiration_test.gno index 26fad60f58f..954558045ba 100644 --- a/examples/gno.land/p/demo/teritori/dao_utils/expiration_test.gno +++ b/examples/gno.land/p/demo/teritori/dao_utils/expiration_test.gno @@ -2,6 +2,9 @@ package dao_utils import ( "testing" + "time" + + "gno.land/p/demo/json" ) func TestMatch(t *testing.T) { @@ -10,6 +13,45 @@ func TestMatch(t *testing.T) { case ExpirationNever: t.Log("ExpirationNever") default: - t.Fatalf("expected a match") + t.Errorf("expected a match") + } +} + +func TestJSONNever(t *testing.T) { + ex := ExpirationNever{} + node := ex.ToJSON() + b, err := json.Marshal(node) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expected := `{"never":{}}` + if string(b) != expected { + t.Errorf("expected %v, got %v", expected, string(b)) + } +} + +func TestJSONAtTime(t *testing.T) { + ex := ExpirationAtTime(time.Unix(1712830928, 0)) + node := ex.ToJSON() + b, err := json.Marshal(node) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expected := `{"atTime":"2024-04-11T10:22:08Z"}` + if string(b) != expected { + t.Errorf("expected %v, got %v", expected, string(b)) + } +} + +func TestJSONAtHeight(t *testing.T) { + ex := ExpirationAtHeight(123456) + node := ex.ToJSON() + b, err := json.Marshal(node) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expected := `{"atHeight":"123456"}` + if string(b) != expected { + t.Errorf("expected %v, got %v", expected, string(b)) } } diff --git a/examples/gno.land/p/demo/teritori/dao_utils/gno.mod b/examples/gno.land/p/demo/teritori/dao_utils/gno.mod index 547ab0360f1..0bbd87b3605 100644 --- a/examples/gno.land/p/demo/teritori/dao_utils/gno.mod +++ b/examples/gno.land/p/demo/teritori/dao_utils/gno.mod @@ -1,3 +1,6 @@ module gno.land/p/demo/teritori/dao_utils -require gno.land/p/demo/teritori/ujson v0.0.0-latest +require ( + gno.land/p/demo/json v0.0.0-latest + gno.land/p/demo/teritori/jsonutil v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/teritori/dao_voting_group/gno.mod b/examples/gno.land/p/demo/teritori/dao_voting_group/gno.mod index 6d84d63a407..9c6b87864bc 100644 --- a/examples/gno.land/p/demo/teritori/dao_voting_group/gno.mod +++ b/examples/gno.land/p/demo/teritori/dao_voting_group/gno.mod @@ -1,8 +1,8 @@ module gno.land/p/demo/teritori/dao_voting_group require ( + gno.land/p/demo/json v0.0.0-latest gno.land/p/demo/teritori/dao_interfaces v0.0.0-latest gno.land/p/demo/teritori/markdown_utils v0.0.0-latest - gno.land/p/demo/teritori/ujson v0.0.0-latest gno.land/r/demo/teritori/groups v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/teritori/dao_voting_group/voting_group.gno b/examples/gno.land/p/demo/teritori/dao_voting_group/voting_group.gno index 99360d008db..039986edda4 100644 --- a/examples/gno.land/p/demo/teritori/dao_voting_group/voting_group.gno +++ b/examples/gno.land/p/demo/teritori/dao_voting_group/voting_group.gno @@ -3,9 +3,9 @@ package dao_voting_group import ( "std" + "gno.land/p/demo/json" dao_interfaces "gno.land/p/demo/teritori/dao_interfaces" "gno.land/p/demo/teritori/markdown_utils" - "gno.land/p/demo/teritori/ujson" "gno.land/r/demo/teritori/groups" ) @@ -27,9 +27,9 @@ func (v *VotingGroup) Info() dao_interfaces.ModuleInfo { } func (v *VotingGroup) ConfigJSON() string { - return ujson.FormatObject([]ujson.FormatKV{ - {Key: "groupId", Value: v.groupID}, - }) + return json.ObjectNode("", map[string]*json.Node{ + "groupId": v.groupID.ToJSON(), + }).String() } func (v *VotingGroup) VotingPowerAtHeight(addr std.Address, height int64) uint64 { diff --git a/examples/gno.land/p/demo/teritori/dao_voting_group/voting_group_test.gno b/examples/gno.land/p/demo/teritori/dao_voting_group/voting_group_test.gno index d4bfc6f0e79..43d6f520def 100644 --- a/examples/gno.land/p/demo/teritori/dao_voting_group/voting_group_test.gno +++ b/examples/gno.land/p/demo/teritori/dao_voting_group/voting_group_test.gno @@ -24,7 +24,7 @@ func TestVotingGroup(t *testing.T) { { conf := v.ConfigJSON() - expected := `{"groupId":1}` + expected := `{"groupId":"1"}` if conf != expected { t.Fatalf("expected %q, got %q.", expected, conf) } diff --git a/examples/gno.land/p/demo/teritori/ujson/gno.mod b/examples/gno.land/p/demo/teritori/jsonutil/gno.mod similarity index 52% rename from examples/gno.land/p/demo/teritori/ujson/gno.mod rename to examples/gno.land/p/demo/teritori/jsonutil/gno.mod index a6256d37a45..e6ad8e11649 100644 --- a/examples/gno.land/p/demo/teritori/ujson/gno.mod +++ b/examples/gno.land/p/demo/teritori/jsonutil/gno.mod @@ -1,6 +1,7 @@ -module gno.land/p/demo/teritori/ujson +module gno.land/p/demo/teritori/jsonutil require ( gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/json v0.0.0-latest gno.land/p/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/teritori/jsonutil/jsonutil.gno b/examples/gno.land/p/demo/teritori/jsonutil/jsonutil.gno new file mode 100644 index 00000000000..7447e707e5e --- /dev/null +++ b/examples/gno.land/p/demo/teritori/jsonutil/jsonutil.gno @@ -0,0 +1,116 @@ +package jsonutil + +import ( + "std" + "strconv" + "strings" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/json" + "gno.land/p/demo/users" +) + +func UnionNode(variant string, value *json.Node) *json.Node { + return json.ObjectNode("", map[string]*json.Node{ + variant: value, + }) +} + +func MustUnion(value *json.Node) (string, *json.Node) { + obj := value.MustObject() + for key, value := range obj { + return key, value + } + panic("no or more than one variant in union") +} + +func TimeNode(value time.Time) *json.Node { + j, err := value.MarshalJSON() + if err != nil { + panic(err) + } + return json.StringNode("", string(j[1:len(j)-1])) +} + +func MustTime(value *json.Node) time.Time { + t := time.Time{} + err := t.UnmarshalJSON([]byte(value.String())) + if err != nil { + panic(err) + } + return t +} + +func DurationNode(value time.Duration) *json.Node { + return Int64Node(value.Nanoseconds()) +} + +func EmptyObjectNode() *json.Node { + return json.ObjectNode("", nil) +} + +// int is always 64 bits in gno so we need a string to represent it without loss of precision on javascript side +func IntNode(value int) *json.Node { + return json.StringNode("", strconv.Itoa(value)) +} + +func MustInt(value *json.Node) int { + i, err := strconv.Atoi(value.MustString()) + if err != nil { + panic(err) + } + return i +} + +func Int64Node(value int64) *json.Node { + return json.StringNode("", strconv.FormatInt(value, 10)) +} + +func MustInt64(value *json.Node) int64 { + return int64(MustInt(value)) +} + +func MustUint64(value *json.Node) uint64 { + return uint64(MustInt(value)) // FIXME: full uint64 range support (currently limited to [-2^63, 2^63-1]) +} + +func Uint64Node(value uint64) *json.Node { + return json.StringNode("", strconv.FormatUint(value, 10)) +} + +func AVLTreeNode(root *avl.Tree, transform func(elem interface{}) *json.Node) *json.Node { + if root == nil { + return EmptyObjectNode() + } + fields := make(map[string]*json.Node) + root.Iterate("", "", func(key string, val interface{}) bool { + fields[key] = transform(val) + return false + }) + return json.ObjectNode("", fields) +} + +func AddressNode(addr std.Address) *json.Node { + return json.StringNode("", addr.String()) +} + +func MustAddress(value *json.Node) std.Address { + addr := std.Address(value.MustString()) + if !addr.IsValid() { + panic("invalid address") + } + return addr +} + +func AddressOrNameNode(aon users.AddressOrName) *json.Node { + return json.StringNode("", string(aon)) +} + +func MustAddressOrName(value *json.Node) users.AddressOrName { + aon := users.AddressOrName(value.MustString()) + if !aon.IsValid() { + panic("invalid address or name") + } + return aon +} diff --git a/examples/gno.land/p/demo/teritori/ujson/format.gno b/examples/gno.land/p/demo/teritori/ujson/format.gno deleted file mode 100644 index 38c2a0def15..00000000000 --- a/examples/gno.land/p/demo/teritori/ujson/format.gno +++ /dev/null @@ -1,146 +0,0 @@ -package ujson - -// This package strives to have the same behavior as json.Marshal but does not support all types and returns strings - -import ( - "errors" - "std" - "strconv" - "strings" - "time" - - "gno.land/p/demo/avl" - "gno.land/p/demo/users" -) - -type JSONAble interface { - ToJSON() string -} - -type FormatKV struct { - Key string - Value interface{} - Raw bool -} - -// does not work for slices, use FormatSlice instead -func FormatAny(p interface{}) string { - switch p.(type) { - case std.Address: - return FormatString(string(p.(std.Address))) - case *avl.Tree: - return FormatAVLTree(p.(*avl.Tree)) - case avl.Tree: - t := p.(avl.Tree) - return FormatAVLTree(&t) - case JSONAble: - return p.(JSONAble).ToJSON() - case string: - return FormatString(p.(string)) - case uint64: - return FormatUint64(p.(uint64)) - case uint32: - return FormatUint64(uint64(p.(uint32))) - case uint: - return FormatUint64(uint64(p.(uint))) - case int64: - return FormatInt64(p.(int64)) - case int32: - return FormatInt64(int64(p.(int32))) - case int: - return FormatInt64(int64(p.(int))) - case float32: - panic("float32 not implemented") - case float64: - panic("float64 not implemented") - case bool: - return FormatBool(p.(bool)) - case time.Time: - return FormatTime(p.(time.Time)) - case time.Duration: - return FormatInt64(int64(p.(time.Duration))) - case users.AddressOrName: - return FormatString(string(p.(users.AddressOrName))) - default: - return "null" - } -} - -// loosely ported from https://cs.opensource.google/go/go/+/master:src/time/time.go;l=1357?q=appendStrictRFC3339&ss=go%2Fgo -func FormatTime(t time.Time) string { - s := t.Format(time.RFC3339Nano) - b := []byte(s) - - // Not all valid Go timestamps can be serialized as valid RFC 3339. - // Explicitly check for these edge cases. - // See https://go.dev/issue/4556 and https://go.dev/issue/54580. - n0 := 0 - num2 := func(b []byte) byte { return 10*(b[0]-'0') + (b[1] - '0') } - switch { - case b[n0+len("9999")] != '-': // year must be exactly 4 digits wide - panic(errors.New("year outside of range [0,9999]")) - case b[len(b)-1] != 'Z': - c := b[len(b)-len("Z07:00")] - if ('0' <= c && c <= '9') || num2(b[len(b)-len("07:00"):]) >= 24 { - panic(errors.New("timezone hour outside of range [0,23]")) - } - } - return FormatString(string(b)) -} - -func FormatUint64(i uint64) string { - return strconv.FormatUint(i, 10) -} - -func FormatInt64(i int64) string { - return strconv.FormatInt(i, 10) -} - -func FormatSlice(s []interface{}) string { - elems := make([]string, len(s)) - for i, elem := range s { - elems[i] = FormatAny(elem) - } - return "[" + strings.Join(elems, ",") + "]" -} - -func FormatObject(kv []FormatKV) string { - elems := make([]string, len(kv)) - i := 0 - for _, elem := range kv { - var val string - if elem.Raw { - val = elem.Value.(string) - } else { - val = FormatAny(elem.Value) - } - elems[i] = FormatString(elem.Key) + ":" + val - i++ - } - return "{" + strings.Join(elems, ",") + "}" -} - -func FormatBool(b bool) string { - if b { - return "true" - } - return "false" -} - -func FormatAVLTree(t *avl.Tree) string { - if t == nil { - return "{}" - } - kv := make([]FormatKV, 0, t.Size()) - t.Iterate("", "", func(key string, value interface{}) bool { - kv = append(kv, FormatKV{key, value, false}) - return false - }) - return FormatObject(kv) -} - -func FormatUnionMember(name string, val interface{}, raw bool) string { - return FormatObject([]FormatKV{ - {Key: name, Value: val, Raw: raw}, - }) -} diff --git a/examples/gno.land/p/demo/teritori/ujson/parse.gno b/examples/gno.land/p/demo/teritori/ujson/parse.gno deleted file mode 100644 index 79ddd001f77..00000000000 --- a/examples/gno.land/p/demo/teritori/ujson/parse.gno +++ /dev/null @@ -1,594 +0,0 @@ -package ujson - -import ( - "std" - "strconv" - "strings" - "time" - - "gno.land/p/demo/avl" - "gno.land/p/demo/users" -) - -// https://stackoverflow.com/a/4150626 -const whitespaces = " \t\n\r" - -type FromJSONAble interface { - FromJSON(ast *JSONASTNode) -} - -// does not work for slices, use ast exploration instead -func (ast *JSONASTNode) ParseAny(ptr interface{}) { - switch ptr.(type) { - case *std.Address: - *ptr.(*std.Address) = std.Address(ParseString(ast.Value)) - case **avl.Tree: - panic("*avl.Tree not implemented, there is no way to know the type of the tree values, use a custom parser instead") - case *avl.Tree: - panic("avl.Tree not implemented, there is no way to know the type of the tree values, use a custom parser instead") - case *string: - if ast.Kind != JSONKindValue { - panic("not a value") - } - if ast.ValueKind != JSONTokenKindString { - panic("not a string") - } - *ptr.(*string) = ParseString(ast.Value) - case *uint64: - if ast.Kind != JSONKindValue { - panic("not a value") - } - if ast.ValueKind != JSONTokenKindNumber { - panic("not a number") - } - *ptr.(*uint64) = ParseUint64(ast.Value) - case *uint32: - if ast.Kind != JSONKindValue { - panic("not a value") - } - if ast.ValueKind != JSONTokenKindNumber { - panic("not a number") - } - *ptr.(*uint32) = uint32(ParseUint64(ast.Value)) - case *uint: - if ast.Kind != JSONKindValue { - panic("not a value") - } - if ast.ValueKind != JSONTokenKindNumber { - panic("not a number") - } - *ptr.(*uint) = uint(ParseUint64(ast.Value)) - case *int64: - if ast.Kind != JSONKindValue { - panic("not a value") - } - if ast.ValueKind != JSONTokenKindNumber { - panic("not a number") - } - *ptr.(*int64) = ParseInt64(ast.Value) - case *int32: - if ast.Kind != JSONKindValue { - panic("not a value") - } - if ast.ValueKind != JSONTokenKindNumber { - panic("not a number") - } - *ptr.(*int32) = int32(ParseInt64(ast.Value)) - case *int: - if ast.Kind != JSONKindValue { - panic("not a value") - } - if ast.ValueKind != JSONTokenKindNumber { - panic("not a number") - } - *ptr.(*int) = int(ParseInt64(ast.Value)) - case *float64: - panic("float64 not implemented") - case *float32: - panic("float32 not implemented") - case *bool: - if ast.Kind != JSONKindValue { - panic("not a value") - } - if ast.ValueKind != JSONTokenKindTrue && ast.ValueKind != JSONTokenKindFalse { - panic("not a bool") - } - *ptr.(*bool) = ast.ValueKind == JSONTokenKindTrue - case *FromJSONAble: - (*(ptr.(*FromJSONAble))).FromJSON(ast) - case FromJSONAble: - ptr.(FromJSONAble).FromJSON(ast) - case **JSONASTNode: - *ptr.(**JSONASTNode) = ast - case *time.Time: - ast.ParseTime(ptr.(*time.Time)) - case *time.Duration: - *ptr.(*time.Duration) = time.Duration(ParseInt64(ast.Value)) - case *users.AddressOrName: - s := ParseString(ast.Value) - *ptr.(*users.AddressOrName) = users.AddressOrName(s) - default: - if ast.Kind == JSONKindValue && ast.ValueKind == JSONTokenKindNull { - // *ptr.(*interface{}) = nil // TODO: handle nil - return - } - panic("type not defined for `" + ast.String() + "`") - } -} - -// loosely ported from https://cs.opensource.google/go/go/+/master:src/time/time.go;l=1370?q=appendStrictRFC3339&ss=go%2Fgo -// it's not a full port since it would require copying lot of utils -func (ast *JSONASTNode) ParseTime(t *time.Time) { - if ast.Kind != JSONKindValue && ast.ValueKind != JSONTokenKindString { - panic("time is not a string") - } - s := ParseString(ast.Value) - var err error - *t, err = time.Parse(time.RFC3339Nano, s) - if err != nil { - panic(err) - } -} - -func ParseUint64(s string) uint64 { - val, err := strconv.Atoi(s) - if err != nil { - panic(err) - } - return uint64(val) -} - -func ParseInt64(s string) int64 { - val, err := strconv.Atoi(s) - if err != nil { - panic(err) - } - return int64(val) -} - -type ParseKV struct { - Key string - Value interface{} - ArrayParser func(children []*JSONASTNode) - ObjectParser func(children []*JSONASTKV) - CustomParser func(node *JSONASTNode) -} - -func ParseAny(s string, val interface{}) { - tokens := tokenize(s) - if len(tokens) == 0 { - panic("empty json") - } - remainingTokens, ast := parseAST(tokens) - if len(remainingTokens) > 0 { - panic("invalid json") - } - ast.ParseAny(val) -} - -func (ast *JSONASTNode) ParseObject(kv []*ParseKV) { - if ast.Kind != JSONKindObject { - panic("not an object") - } - for _, elem := range kv { - for i, child := range ast.ObjectChildren { - if child.Key == elem.Key { - if elem.ArrayParser != nil { - if child.Value.Kind != JSONKindArray { - panic("not an array") - } - elem.ArrayParser(child.Value.ArrayChildren) - } else if elem.ObjectParser != nil { - if child.Value.Kind != JSONKindObject { - panic("not an object") - } - elem.ObjectParser(child.Value.ObjectChildren) - } else if elem.CustomParser != nil { - elem.CustomParser(child.Value) - } else { - child.Value.ParseAny(elem.Value) - } - break - } - if i == (len(ast.ObjectChildren) - 1) { - panic("invalid key `" + elem.Key + "` in object `" + ast.String() + "`") - } - } - } -} - -func (ast *JSONASTNode) ParseUnion(kv []*ParseKV) interface{} { - if ast.Kind != JSONKindObject { - panic("union is not an object") - } - if len(ast.ObjectChildren) != 1 { - panic("union object does not have exactly one field") - } - k, node := ast.ObjectChildren[0].Key, ast.ObjectChildren[0].Value - for _, kv := range kv { - if kv.Key == k { - node.ParseAny(kv.Value) - return kv.Value - } - } - panic("unknown union type") // TODO: expected one of ... -} - -func ParseSlice(s string) []*JSONASTNode { - ast := TokenizeAndParse(s) - return ast.ParseSlice() -} - -func (ast *JSONASTNode) ParseSlice() []*JSONASTNode { - if ast.Kind != JSONKindArray { - panic("not an array") - } - return ast.ArrayChildren -} - -func countWhitespaces(s string) int { - i := 0 - for i < len(s) { - if strings.ContainsRune(whitespaces, int32(s[i])) { - i++ - } else { - break - } - } - return i -} - -func JSONTokensString(tokens []*JSONToken) string { - s := "" - for _, token := range tokens { - s += token.Raw - } - return s -} - -func (node *JSONASTNode) String() string { - if node == nil { - return "nil" - } - switch node.Kind { - case JSONKindValue: - return node.Value - case JSONKindArray: - s := "[" - for i, child := range node.ArrayChildren { - if i > 0 { - s += "," - } - s += child.String() - } - s += "]" - return s - case JSONKindObject: - s := "{" - for i, child := range node.ObjectChildren { - if i > 0 { - s += "," - } - s += `"` + child.Key + `":` + child.Value.String() - } - s += "}" - return s - default: - panic("invalid json") - } -} - -func TokenizeAndParse(s string) *JSONASTNode { - tokens := tokenize(s) - if len(tokens) == 0 { - panic("empty json") - } - remainingTokens, ast := parseAST(tokens) - if len(remainingTokens) > 0 { - panic("invalid json") - } - return ast -} - -func parseAST(tokens []*JSONToken) (tkn []*JSONToken, tree *JSONASTNode) { - if len(tokens) == 0 { - panic("empty json") - } - - switch tokens[0].Kind { - - case JSONTokenKindString: - return tokens[1:], &JSONASTNode{Kind: JSONKindValue, ValueKind: tokens[0].Kind, Value: tokens[0].Raw} - case JSONTokenKindNumber: - return tokens[1:], &JSONASTNode{Kind: JSONKindValue, ValueKind: tokens[0].Kind, Value: tokens[0].Raw} - case JSONTokenKindTrue: - return tokens[1:], &JSONASTNode{Kind: JSONKindValue, ValueKind: tokens[0].Kind, Value: tokens[0].Raw} - case JSONTokenKindFalse: - return tokens[1:], &JSONASTNode{Kind: JSONKindValue, ValueKind: tokens[0].Kind, Value: tokens[0].Raw} - case JSONTokenKindNull: - return tokens[1:], &JSONASTNode{Kind: JSONKindValue, ValueKind: tokens[0].Kind, Value: tokens[0].Raw} - - case JSONTokenKindOpenArray: - arrayChildren := []*JSONASTNode{} - tokens = tokens[1:] - for len(tokens) > 0 { - if tokens[0].Kind == JSONTokenKindCloseArray { - return tokens[1:], &JSONASTNode{Kind: JSONKindArray, ArrayChildren: arrayChildren} - } - var child *JSONASTNode - tokens, child = parseAST(tokens) - arrayChildren = append(arrayChildren, child) - if len(tokens) == 0 { - panic("exepected more tokens in array") - } - if tokens[0].Kind == JSONTokenKindComma { - tokens = tokens[1:] - } else if tokens[0].Kind == JSONTokenKindCloseArray { - return tokens[1:], &JSONASTNode{Kind: JSONKindArray, ArrayChildren: arrayChildren} - } else { - panic("unexpected token in array after value `" + tokens[0].Raw + "`") - } - } - - case JSONTokenKindOpenObject: - objectChildren := []*JSONASTKV{} - if len(tokens) < 2 { - panic("objects must have at least 2 tokens") - } - tokens = tokens[1:] - for len(tokens) > 0 { - if tokens[0].Kind == JSONTokenKindCloseObject { - return tokens[1:], &JSONASTNode{Kind: JSONKindObject, ObjectChildren: objectChildren} - } - if tokens[0].Kind != JSONTokenKindString { - panic("invalid json") - } - key := tokens[0].Raw - tokens = tokens[1:] - if len(tokens) == 0 { - panic("exepected more tokens in object") - } - if tokens[0].Kind != JSONTokenKindColon { - panic("expected :") - } - tokens = tokens[1:] - if len(tokens) == 0 { - panic("exepected more tokens in object after :") - } - var value *JSONASTNode - tokens, value = parseAST(tokens) - objectChildren = append(objectChildren, &JSONASTKV{Key: ParseString(key), Value: value}) - if len(tokens) == 0 { - panic("exepected more tokens in object after value") - } - if tokens[0].Kind == JSONTokenKindComma { - tokens = tokens[1:] - } else if tokens[0].Kind == JSONTokenKindCloseObject { - return tokens[1:], &JSONASTNode{Kind: JSONKindObject, ObjectChildren: objectChildren} - } else { - panic("unexpected token in object after value `" + tokens[0].Raw + "`") - } - } - - default: - panic("unexpected token `" + tokens[0].Raw + "`") - } - - return -} - -func tokenize(s string) []*JSONToken { - tokens := []*JSONToken{} - for len(s) > 0 { - var token *JSONToken - s, token = tokenizeOne(s) - if token.Kind != JSONTokenKindSpaces { - tokens = append(tokens, token) - } - } - return tokens -} - -func tokenizeOne(s string) (string, *JSONToken) { - if len(s) == 0 { - panic("invalid token") - } - spacesCount := countWhitespaces(s) - if spacesCount > 0 { - spaces := s[:spacesCount] - return s[spacesCount:], &JSONToken{Kind: JSONTokenKindSpaces, Raw: spaces} - } - switch s[0] { - case '"': - return parseStringToken(s) - case 't': - return parseKeyword(s, "true", JSONTokenKindTrue) - case 'f': - return parseKeyword(s, "false", JSONTokenKindFalse) - case 'n': - return parseKeyword(s, "null", JSONTokenKindNull) - case '{': - return s[1:], &JSONToken{Kind: JSONTokenKindOpenObject, Raw: "{"} - case '[': - return s[1:], &JSONToken{Kind: JSONTokenKindOpenArray, Raw: "["} - case ':': - return s[1:], &JSONToken{Kind: JSONTokenKindColon, Raw: ":"} - case ',': - return s[1:], &JSONToken{Kind: JSONTokenKindComma, Raw: ","} - case ']': - return s[1:], &JSONToken{Kind: JSONTokenKindCloseArray, Raw: "]"} - case '}': - return s[1:], &JSONToken{Kind: JSONTokenKindCloseObject, Raw: "}"} - default: - return parseNumber(s) - } -} - -func parseKeyword(s string, keyword string, kind JSONTokenKind) (string, *JSONToken) { - if len(s) < len(keyword) { - panic("invalid keyword") - } - if s[:len(keyword)] != keyword { - panic("invalid keyword") - } - return s[len(keyword):], &JSONToken{Kind: kind, Raw: keyword} -} - -func parseStringToken(s string) (string, *JSONToken) { - if (len(s) < 2) || (s[0] != '"') { - panic("invalid string") - } - quote := false - for i := 1; i < len(s); i++ { - if !quote && s[i] == '\\' { - quote = true - continue - } - if !quote && s[i] == '"' { - return s[i+1:], &JSONToken{Kind: JSONTokenKindString, Raw: s[:i+1]} - } - quote = false - } - panic("invalid string") -} - -// copiloted -func parseNumber(s string) (string, *JSONToken) { - if len(s) == 0 { - panic("invalid number") - } - i := 0 - if s[i] == '-' { - i++ - } - if i == len(s) { - panic("invalid number") - } - if s[i] == '0' { - i++ - } else if ('1' <= s[i]) && (s[i] <= '9') { - i++ - for (i < len(s)) && ('0' <= s[i]) && (s[i] <= '9') { - i++ - } - } else { - panic("invalid number") - } - if i == len(s) { - return s[i:], &JSONToken{Kind: JSONTokenKindNumber, Raw: s} - } - if s[i] == '.' { - i++ - if i == len(s) { - panic("invalid number") - } - if ('0' <= s[i]) && (s[i] <= '9') { - i++ - for (i < len(s)) && ('0' <= s[i]) && (s[i] <= '9') { - i++ - } - } else { - panic("invalid number") - } - } - if i == len(s) { - return s[i:], &JSONToken{Kind: JSONTokenKindNumber, Raw: s} - } - if (s[i] == 'e') || (s[i] == 'E') { - i++ - if i == len(s) { - panic("invalid number") - } - if (s[i] == '+') || (s[i] == '-') { - i++ - } - if i == len(s) { - panic("invalid number") - } - if ('0' <= s[i]) && (s[i] <= '9') { - i++ - for (i < len(s)) && ('0' <= s[i]) && (s[i] <= '9') { - i++ - } - } else { - panic("invalid number") - } - } - return s[i:], &JSONToken{Kind: JSONTokenKindNumber, Raw: s[:i]} -} - -type JSONTokenKind int - -type JSONKind int - -const ( - JSONKindUnknown JSONKind = iota - JSONKindValue - JSONKindObject - JSONKindArray -) - -type JSONASTNode struct { - Kind JSONKind - ArrayChildren []*JSONASTNode - ObjectChildren []*JSONASTKV - ValueKind JSONTokenKind - Value string -} - -type JSONASTKV struct { - Key string - Value *JSONASTNode -} - -const ( - JSONTokenKindUnknown JSONTokenKind = iota - JSONTokenKindString - JSONTokenKindNumber - JSONTokenKindTrue - JSONTokenKindFalse - JSONTokenKindSpaces - JSONTokenKindComma - JSONTokenKindColon - JSONTokenKindOpenArray - JSONTokenKindCloseArray - JSONTokenKindOpenObject - JSONTokenKindCloseObject - JSONTokenKindNull -) - -func (k JSONTokenKind) String() string { - switch k { - case JSONTokenKindString: - return "string" - case JSONTokenKindNumber: - return "number" - case JSONTokenKindTrue: - return "true" - case JSONTokenKindFalse: - return "false" - case JSONTokenKindSpaces: - return "spaces" - case JSONTokenKindComma: - return "comma" - case JSONTokenKindColon: - return "colon" - case JSONTokenKindOpenArray: - return "open-array" - case JSONTokenKindCloseArray: - return "close-array" - case JSONTokenKindOpenObject: - return "open-object" - case JSONTokenKindCloseObject: - return "close-object" - case JSONTokenKindNull: - return "null" - default: - return "unknown" - } -} - -type JSONToken struct { - Kind JSONTokenKind - Raw string -} diff --git a/examples/gno.land/p/demo/teritori/ujson/strings.gno b/examples/gno.land/p/demo/teritori/ujson/strings.gno deleted file mode 100644 index 72f05509f45..00000000000 --- a/examples/gno.land/p/demo/teritori/ujson/strings.gno +++ /dev/null @@ -1,232 +0,0 @@ -package ujson - -import ( - "unicode/utf16" - "unicode/utf8" -) - -const ( - ReplacementChar = '\uFFFD' // Represents invalid code points. -) - -// Ported from https://cs.opensource.google/go/go/+/refs/tags/go1.20.6:src/encoding/json/encode.go -func FormatString(s string) string { - const escapeHTML = true - e := `"` // e.WriteByte('"') - start := 0 - for i := 0; i < len(s); { - if b := s[i]; b < utf8.RuneSelf { - if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) { - i++ - continue - } - if start < i { - e += s[start:i] // e.WriteString(s[start:i]) - } - e += "\\" // e.WriteByte('\\') - switch b { - case '\\', '"': - e += string(b) // e.WriteByte(b) - case '\n': - e += "n" // e.WriteByte('n') - case '\r': - e += "r" // e.WriteByte('r') - case '\t': - e += "t" // e.WriteByte('t') - default: - // This encodes bytes < 0x20 except for \t, \n and \r. - // If escapeHTML is set, it also escapes <, >, and & - // because they can lead to security holes when - // user-controlled strings are rendered into JSON - // and served to some browsers. - e += `u00` // e.WriteString(`u00`) - e += string(hex[b>>4]) // e.WriteByte(hex[b>>4]) - e += string(hex[b&0xF]) // e.WriteByte(hex[b&0xF]) - } - i++ - start = i - continue - } - c, size := utf8.DecodeRuneInString(s[i:]) - if c == utf8.RuneError && size == 1 { - if start < i { - e += s[start:i] // e.WriteString(s[start:i]) - } - e += `\ufffd` // e.WriteString(`\ufffd`) - i += size - start = i - continue - } - // U+2028 is LINE SEPARATOR. - // U+2029 is PARAGRAPH SEPARATOR. - // They are both technically valid characters in JSON strings, - // but don't work in JSONP, which has to be evaluated as JavaScript, - // and can lead to security holes there. It is valid JSON to - // escape them, so we do so unconditionally. - // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. - if c == '\u2028' || c == '\u2029' { - if start < i { - e += s[start:i] // e.WriteString(s[start:i]) - } - e += `\u202` // e.WriteString(`\u202`) - e += string(hex[c&0xF]) // e.WriteByte(hex[c&0xF]) - i += size - start = i - continue - } - i += size - } - if start < len(s) { - e += s[start:] // e.WriteString(s[start:]) - } - e += `"` // e.WriteByte('"') - return e -} - -// Ported from https://cs.opensource.google/go/go/+/refs/tags/go1.20.6:src/encoding/json/decode.go -// unquote converts a quoted JSON string literal s into an actual string t. -// The rules are different than for Go, so cannot use strconv.Unquote. -func ParseString(s string) string { - o, ok := unquoteBytes([]byte(s)) - if !ok { - panic("invalid string") - } - return string(o) -} - -func unquoteBytes(s []byte) (t []byte, ok bool) { - if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { - return - } - s = s[1 : len(s)-1] - - // Check for unusual characters. If there are none, - // then no unquoting is needed, so return a slice of the - // original bytes. - r := 0 - for r < len(s) { - c := s[r] - if c == '\\' || c == '"' || c < ' ' { - break - } - if c < utf8.RuneSelf { - r++ - continue - } - rr, size := utf8.DecodeRune(s[r:]) - if rr == utf8.RuneError && size == 1 { - break - } - r += size - } - if r == len(s) { - return s, true - } - - b := make([]byte, len(s)+2*utf8.UTFMax) - w := copy(b, s[0:r]) - for r < len(s) { - // Out of room? Can only happen if s is full of - // malformed UTF-8 and we're replacing each - // byte with RuneError. - if w >= len(b)-2*utf8.UTFMax { - nb := make([]byte, (len(b)+utf8.UTFMax)*2) - copy(nb, b[0:w]) - b = nb - } - switch c := s[r]; { - case c == '\\': - r++ - if r >= len(s) { - return - } - switch s[r] { - default: - return - case '"', '\\', '/', '\'': - b[w] = s[r] - r++ - w++ - case 'b': - b[w] = '\b' - r++ - w++ - case 'f': - b[w] = '\f' - r++ - w++ - case 'n': - b[w] = '\n' - r++ - w++ - case 'r': - b[w] = '\r' - r++ - w++ - case 't': - b[w] = '\t' - r++ - w++ - case 'u': - r-- - rr := getu4(s[r:]) - if rr < 0 { - return - } - r += 6 - if utf16.IsSurrogate(rr) { - rr1 := getu4(s[r:]) - if dec := utf16.DecodeRune(rr, rr1); dec != ReplacementChar { - // A valid pair; consume. - r += 6 - w += utf8.EncodeRune(b[w:], dec) - break - } - // Invalid surrogate; fall back to replacement rune. - rr = ReplacementChar - } - - } - - // Quote, control characters are invalid. - case c == '"', c < ' ': - return - - // ASCII - case c < utf8.RuneSelf: - b[w] = c - r++ - w++ - - // Coerce to well-formed UTF-8. - default: - rr, size := utf8.DecodeRune(s[r:]) - r += size - w += utf8.EncodeRune(b[w:], rr) - } - } - return b[0:w], true -} - -// getu4 decodes \uXXXX from the beginning of s, returning the hex value, -// or it returns -1. -func getu4(s []byte) rune { - if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { - return -1 - } - var r rune - for _, c := range s[2:6] { - switch { - case '0' <= c && c <= '9': - c = c - '0' - case 'a' <= c && c <= 'f': - c = c - 'a' + 10 - case 'A' <= c && c <= 'F': - c = c - 'A' + 10 - default: - return -1 - } - r = r*16 + rune(c) - } - return r -} diff --git a/examples/gno.land/p/demo/teritori/ujson/tables.gno b/examples/gno.land/p/demo/teritori/ujson/tables.gno deleted file mode 100644 index 1ec2db8d917..00000000000 --- a/examples/gno.land/p/demo/teritori/ujson/tables.gno +++ /dev/null @@ -1,216 +0,0 @@ -package ujson - -import "unicode/utf8" - -var hex = "0123456789abcdef" - -// safeSet holds the value true if the ASCII character with the given array -// position can be represented inside a JSON string without any further -// escaping. -// -// All values are true except for the ASCII control characters (0-31), the -// double quote ("), and the backslash character ("\"). -var safeSet = [utf8.RuneSelf]bool{ - ' ': true, - '!': true, - '"': false, - '#': true, - '$': true, - '%': true, - '&': true, - '\'': true, - '(': true, - ')': true, - '*': true, - '+': true, - ',': true, - '-': true, - '.': true, - '/': true, - '0': true, - '1': true, - '2': true, - '3': true, - '4': true, - '5': true, - '6': true, - '7': true, - '8': true, - '9': true, - ':': true, - ';': true, - '<': true, - '=': true, - '>': true, - '?': true, - '@': true, - 'A': true, - 'B': true, - 'C': true, - 'D': true, - 'E': true, - 'F': true, - 'G': true, - 'H': true, - 'I': true, - 'J': true, - 'K': true, - 'L': true, - 'M': true, - 'N': true, - 'O': true, - 'P': true, - 'Q': true, - 'R': true, - 'S': true, - 'T': true, - 'U': true, - 'V': true, - 'W': true, - 'X': true, - 'Y': true, - 'Z': true, - '[': true, - '\\': false, - ']': true, - '^': true, - '_': true, - '`': true, - 'a': true, - 'b': true, - 'c': true, - 'd': true, - 'e': true, - 'f': true, - 'g': true, - 'h': true, - 'i': true, - 'j': true, - 'k': true, - 'l': true, - 'm': true, - 'n': true, - 'o': true, - 'p': true, - 'q': true, - 'r': true, - 's': true, - 't': true, - 'u': true, - 'v': true, - 'w': true, - 'x': true, - 'y': true, - 'z': true, - '{': true, - '|': true, - '}': true, - '~': true, - '\u007f': true, -} - -// htmlSafeSet holds the value true if the ASCII character with the given -// array position can be safely represented inside a JSON string, embedded -// inside of HTML