-
Notifications
You must be signed in to change notification settings - Fork 389
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(keeper): implement json primitive return #2949
base: master
Are you sure you want to change the base?
Conversation
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Codecov ReportAttention: Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall approach LGTM, let's roll with only supporting primitive types for now
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
gno.land/pkg/sdk/vm/handler.go
Outdated
QueryRender = "qrender" | ||
QueryFuncs = "qfuncs" | ||
QueryEval = "qeval" | ||
QueryEvalJSON = "qeval/json" // EXPERIMENTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I prefer a single call with a flag to specify the format.
Any other opinions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A solution could be to have "qeval" data
as a marshaled MsgEval
, but that would break the current behavior of qeval
until the current behavior is set as a fallback if unmarshal MsgEval
fail.
I'm hesitating; it would probably give more flexibility, but qeval/json
is really more straightforward.
wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. After some attempts with url.Url
using a query to parse the JSON flag or employing \n
as a flag separator, I found it problematic and difficult to escape. This issue arises when users include ?
or \n
within the expression's arguments. In my opinion, we should use qeval/json
as it looks like the most straightforward method to handle this for now, given the current implementation of the handler.
A possible compromise is to check the json
part of vm/qeval/json
within the queryEval
function, allowing the right part of the path to serve as a flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@moul I've ended up going with the path query flag; see the queryEval
function. We can probably set another separator than /
to distinguish between the specific path and the flag. However, having this as a distinct sub-path does especially chock me.
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Can you add a small txtar to illustrate the expected UX? |
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
fmt.Sprintf("%s(%d)", valChangesFn, app.LastBlockHeight()), | ||
) | ||
expr := fmt.Sprintf("%s(%d)", valChangesFn, app.LastBlockHeight()) | ||
msgEval := vm.NewMsgEval(vm.FormatDefault, valRealm, expr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zivkovicmilos, we don't need to port this module in this PR, but I'd like your opinion on what the diff will look like thanks to the new JSON built-in support.
gno.land/pkg/keyscli/query_json.go
Outdated
) | ||
} | ||
|
||
func execQuery(cfg *client.QueryCfg, args []string, io commands.IO) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func execQuery(cfg *client.QueryCfg, args []string, io commands.IO) error { | |
func execJSONQuery(cfg *client.QueryCfg, args []string, io commands.IO) error { |
} | ||
|
||
// XXX: this is probably too specific | ||
if cfg.Path == "vm/qeval/json" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be a preliminary check raising an error at the beginning of the function?
return IsImplementedBy(gStringerType, tv.T) | ||
} | ||
|
||
func (tv *TypedValue) IsError() bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that this check is universally seen as the way to indicate that a TypedValue is an error. It might make sense to rename it to ImplError(). Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, while IsStringer
makes more sense since Stringer
is universal for the String()
implementation, I agree that IsError
is not obvious and can be confusing. I can rename it to ImplError
, but if I do that, I should also rename IsStringer
to ImplStringer
for consistency.
case MsgEval: | ||
return vh.handleMsgEval(ctx, msg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
case MsgEval: | |
return vh.handleMsgEval(ctx, msg) |
Having a MsgEval
struct instead of using arguments may be sensible. However, I believe we should not register Eval
as a possible transaction type in that manner.
|
||
var _ std.Msg = MsgEval{} | ||
|
||
func NewMsgEval(format Format, pkgPath, expr string) MsgEval { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func NewMsgEval(format Format, pkgPath, expr string) MsgEval { | |
func NewMsgEval(pkgPath, expr string, format Format) MsgEval { |
Personal preference for this order.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the more i think about it, the more i'm for having such struct, but unexported, without New constructor, not in the msgs.go file.
and probably centralizing everything about the query in query.go.
func (msg MsgEval) GetSignBytes() []byte { | ||
return std.MustSortJSON(amino.MustMarshalJSON(msg)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I support removing this and preventing MsgCall from being included in a transaction. It's just a query.
@@ -46,7 +47,7 @@ const ( | |||
type VMKeeperI interface { | |||
AddPackage(ctx sdk.Context, msg MsgAddPackage) error | |||
Call(ctx sdk.Context, msg MsgCall) (res string, err error) | |||
QueryEval(ctx sdk.Context, pkgPath string, expr string) (res string, err error) | |||
Eval(ctx sdk.Context, msg MsgEval) (res string, err error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eval(ctx sdk.Context, msg MsgEval) (res string, err error) | |
QueryEval(ctx sdk.Context, pkgPath, expr string, format Format) (res string, err error) |
What about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eval(ctx sdk.Context, msg MsgEval) (res string, err error) | |
QueryEval(ctx sdk.Context, cfg EvalCfg) (res string, err error) |
To support optional flags, this is probably better.
} | ||
return res, nil | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't you add the two \n
here and remove the if condition here: https://github.com/gnolang/gno/pull/2949/files#diff-3663e30f0c0e01d8d47bb7974d605b4cf12d1a244970c6a9bbba55fb0be6acedR503 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As only Call
can handle multiple msgs
, I don't think we should do that; we don't want Queries to end with a useless \n
in their response.
const ( | ||
FormatMachine Format = "machine" // Default machine representation | ||
FormatString = "string" // Single string representation | ||
FormatJSON = "json" // XXX: EXPERIMENTAL, only supports primitive types for now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FormatJSON = "json" // XXX: EXPERIMENTAL, only supports primitive types for now | |
FormatJSON = "json" // XXX: EXPERIMENTAL, only supports primitive types for now | |
FormatJSONP = "jsonp" // XXX: EXPERIMENTAL, only supports primitive types for now |
I'm for adding right now the jsonp
alternative. Btw jsonp
takes an extra argument (the callback name).
// XXX: This field is experimental, use with care as output is likely to change | ||
// Default format is machine | ||
Format Format `json:"format" yaml:"format"` | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
FormatOpts string `json:"format-opts" yaml:"format-opts"` | |
} |
to support formatting options, such as JSONP' callback, and eventually other things later?
@@ -14,6 +14,7 @@ var Package = amino.RegisterPackage(amino.NewPackage( | |||
std.Package, | |||
gnovm.Package, | |||
).WithTypes( | |||
MsgEval{}, "m_eval", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MsgEval{}, "m_eval", |
cfg.Path = args[0] | ||
if cfg.Path == "vm/qeval" { | ||
// automatically add json suffix for qeval | ||
cfg.Path = cfg.Path + "/json" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks weird. what's the goal? maybe add a comment if there is a valid reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do. The reason for that is this jquery
is made to handle JSON, so you want your query to return JSON. Therefore, you should not have to specify the format for the query. The goal is to automatically pass the argument to produce a JSON output without having to specify it every time.
query vm/qeval abc.xy/r/toto
-> machine
jquery vm/qeval abc.xy/r/toto
-> JSON
slice := tv.V.(*gno.SliceValue) | ||
if data := slice.GetBase(nil).Data; data != nil { | ||
i := slice.GetLength() | ||
return `"` + base64.StdEncoding.EncodeToString(data[:i]) + `"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't we support slices and arrays just like this:
"[" + strings.Join(JSONPrimitiveValues(vals), ",") + "]"
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be cool, but not like this, as it would not handle cyclic dependencies. Setting a depth could be a solution, but I prefer handling this in a separate PR.
return res, nil | ||
} | ||
|
||
func stringifyResultValues(m *gno.Machine, format Format, values []gno.TypedValue) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
implement or add a comment to add gasmeter here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this method inherits the gno.Machine
, I think it naturally handles gas here.
@piux2 can you confirm ?
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
|
||
return commands.NewCommand( | ||
commands.Metadata{ | ||
Name: "jquery", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This name gives me shivers. Could we not do gnokey query --json
? Seems also easier to support in broadcast
and maketx
if it's just a flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What case do you see with broadcast/maketx and JSON?
I personally like "jquery"; it sounds like "anti-web 2.0." :)
} | ||
|
||
// Fallback on generic object handler | ||
if pv, ok := tv.V.(gno.PointerValue); ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we not also consider the case where tv.V is an Object?
switch v := tv.V.(type) {
case *PointerValue:
case Object:
default:
}
Yet another JSON PR 🎉
This is a simpler implementation of the JSON keeper result that introduces JSON primitive results in keeper MsgCall and MsgQuery result. To maintain backward compatibility, it's not enabled by default, and you have to specify the format in the request.
The result will be returned in the form of a parsable JSON array.
Currently, it will resolve types as follows:
Object -><oid:type>
<obj:TYPE:OID>
as string<type>
as stringThis PR also adds a small utility command in gnokey called
jquery
that outputs a parsable JSON response. This command is flagged as experimental, as are all JSON features present in the handler/keeper.TODOs
Contributors' checklist...
BREAKING CHANGE: xxx
message was included in the description