snapd,snapctl: decode json using Number #3569

Merged
merged 24 commits into from Aug 23, 2017

Conversation

Projects
None yet
5 participants
Contributor

stolowski commented Jul 6, 2017

Use json Decoder to avoid issues with big integers mentioned here: https://forum.snapcraft.io/t/snap-set-digits-as-string/1099 .

stolowski added some commits Jul 5, 2017

codecov-io commented Jul 6, 2017

Codecov Report

Merging #3569 into master will increase coverage by <.01%.
The diff coverage is 92.59%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3569      +/-   ##
==========================================
+ Coverage   75.85%   75.85%   +<.01%     
==========================================
  Files         399      400       +1     
  Lines       34662    34663       +1     
==========================================
+ Hits        26294    26295       +1     
  Misses       6496     6496              
  Partials     1872     1872
Impacted Files Coverage Δ
daemon/api.go 72.62% <0%> (+0.02%) ⬆️
jsonutil/json.go 100% <100%> (ø)
overlord/hookstate/ctlcmd/set.go 83.05% <100%> (-0.29%) ⬇️
cmd/snap/cmd_set.go 88% <100%> (-0.47%) ⬇️
overlord/configstate/config/helpers.go 71.33% <100%> (-0.38%) ⬇️
overlord/hookstate/context.go 87.03% <100%> (ø) ⬆️
client/client.go 79.64% <100%> (ø) ⬆️
overlord/configstate/config/transaction.go 85.18% <100%> (-0.22%) ⬇️
cmd/snap/cmd_aliases.go 93.33% <0%> (-1.67%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 50ca21b...67ecd34. Read the comment docs.

Looks good. Needs one change though.

client/client.go
@@ -256,7 +258,9 @@ func (client *Client) doSync(method, path string, query url.Values, headers map[
}
if v != nil {
- if err := json.Unmarshal(rsp.Result, v); err != nil {
+ dec := json.NewDecoder(strings.NewReader(string(rsp.Result)))
@chipaca

chipaca Jul 9, 2017

Member

the string here forces a spurious copy of rsp.Result. I think you should use a bytes.NewReader here instead.

You've got a lot of them; all instances of strings.NewReader(string(thing) where the thing is a []byte (o a json.RawMessage, same thing) should be a bytes.NewReader(thing) directly. As far as I can tell that's all instances of strings.Newreader(string(thing)) in this code.

@stolowski

stolowski Jul 10, 2017

Contributor

Thanks, you're totally right. Fixed.

Collaborator

mvo5 commented Jul 10, 2017

A bit of a meta-question: would it make sense to have a helper like util/json.go:UnmarshalWithNumber() - with a better name etc. This would make the diff smaller and we would only have a single place that we need to change if we ever need to tweak this again. Same for NewDecoder (would it make sense to also move this into a helper that sets UserNumber()) right away?

Contributor

stolowski commented Jul 10, 2017

@mvo5 as discussed in the HO, let's not do the helper at this point, we can do it later if there is a compelling reason.

The changes themselves LGTM, but can we have more unittests ensuring that this logic is in place so we'll be able to tell if something breaks?

I also wonder if some of those places that are today doing Decode and not checking for More are actually broken. My guess is that yes, it's broken.

client/client.go
@@ -227,6 +227,7 @@ func (client *Client) do(method, path string, query url.Values, headers map[stri
if v != nil {
dec := json.NewDecoder(rsp.Body)
+ dec.UseNumber()
@niemeyer

niemeyer Jul 12, 2017

Contributor

Can we have a unit test that breaks without those changes? Otherwise this is likely to be dropped at some point and we won't notice.

cmd/snap/cmd_set.go
+ dec := json.NewDecoder(strings.NewReader(parts[1]))
+ dec.UseNumber()
+ err := dec.Decode(&value)
+ if err == nil && !dec.More() {
@niemeyer

niemeyer Jul 12, 2017

Contributor

Same.

daemon/api.go
@@ -1552,6 +1552,7 @@ func setSnapConf(c *Command, r *http.Request, user *auth.UserState) Response {
var patchValues map[string]interface{}
decoder := json.NewDecoder(r.Body)
+ decoder.UseNumber()
@niemeyer

niemeyer Jul 12, 2017

Contributor

Same.

overlord/configstate/config/helpers.go
- err := json.Unmarshal([]byte(*config), &configm)
+ dec := json.NewDecoder(bytes.NewReader(*config))
+ dec.UseNumber()
+ err := dec.Decode(&configm)
@niemeyer

niemeyer Jul 12, 2017

Contributor

Same, and so forth for the rest of these changes.

Contributor

niemeyer commented Jul 12, 2017

I think the helper would be appropriate here, and perhaps avoid a little the need for so many unit tests. Also, the helper would do the More bit which is currently lacking in many of those cases. That may be a good solution to the above review question.

stolowski added some commits Jul 13, 2017

Contributor

stolowski commented Jul 13, 2017

@niemeyer Thanks for the review. I've pushed the decoding helper. Working on more tests, so it's not ready for re-review yet.

stolowski added some commits Jul 13, 2017

Contributor

stolowski commented Jul 14, 2017

The branch is ready for re-review; I've reverted the changes to state, task, snapstate as they were not needed after all, also added the helper and improved unit tests and the spread test.

Thanks for this, I like the helper approach, looks good to me, some nitpick/questions inside. Also thanks for the amount of tests in there :)

client/client.go
@@ -256,7 +257,8 @@ func (client *Client) doSync(method, path string, query url.Values, headers map[
}
if v != nil {
- if err := json.Unmarshal(rsp.Result, v); err != nil {
+ err := util.DecodeJsonWithNumbers(bytes.NewReader(rsp.Result), v)
@mvo5

mvo5 Jul 24, 2017

Collaborator

(nitpick) this can be written in a single line: if err := util.Decode...; err != nil {

@stolowski

stolowski Jul 31, 2017

Contributor

Done.

overlord/configstate/config/helpers.go
@@ -98,7 +100,9 @@ func GetFromChange(snapName string, subkeys []string, pos int, config map[string
if !ok {
raw = jsonRaw(value)
}
- err := json.Unmarshal([]byte(*raw), result)
+ dec := json.NewDecoder(bytes.NewReader(*raw))
@mvo5

mvo5 Jul 24, 2017

Collaborator

pardon my ignorance please, but why is this not using util.DecodeJsonWithNumbers() ? Maybe a small comment with the reason behind this?

@stolowski

stolowski Jul 31, 2017

Contributor

Totally valid comment. My oversight, thanks for spotting!

overlord/configstate/config/helpers.go
@@ -112,7 +116,9 @@ func GetFromChange(snapName string, subkeys []string, pos int, config map[string
if !ok {
raw = jsonRaw(value)
}
- err := json.Unmarshal([]byte(*raw), &configm)
+ dec := json.NewDecoder(bytes.NewReader(*raw))
@mvo5

mvo5 Jul 24, 2017

Collaborator

Same question as above

@stolowski

stolowski Jul 31, 2017

Contributor

My oversight again.. thanks!

@@ -62,7 +64,9 @@ func (op setGetOp) args() map[string]interface{} {
}
kv := strings.SplitN(pair, "=", 2)
var v interface{}
- err := json.Unmarshal([]byte(kv[1]), &v)
+ dec := json.NewDecoder(strings.NewReader(kv[1]))
@mvo5

mvo5 Jul 24, 2017

Collaborator

Would it make sense to use the helper here as well?

@stolowski

stolowski Jul 31, 2017

Contributor

Yes, fixed that too.

- err := json.Unmarshal([]byte(kv[1]), &v)
+ dec := json.NewDecoder(strings.NewReader(kv[1]))
+ dec.UseNumber()
+ err := dec.Decode(&v)
@mvo5

mvo5 Jul 24, 2017

Collaborator

Not related to the PR, but this could be written in a single line: if err := dec.Decode(&v); err != nil {

@stolowski

stolowski Jul 31, 2017

Contributor

Done.

overlord/hookstate/ctlcmd/set.go
@@ -154,7 +154,9 @@ func (s *setCommand) setInterfaceSetting(context *hookstate.Context, plugOrSlot
}
var value interface{}
- err := json.Unmarshal([]byte(parts[1]), &value)
+ dec := json.NewDecoder(strings.NewReader(parts[1]))
@mvo5

mvo5 Jul 24, 2017

Collaborator

Would it make sense to use the helper here? If not, maybe a small comment why not?

@stolowski

stolowski Jul 31, 2017

Contributor

Yes, another oversight. Fixed.

util/json.go
@@ -0,0 +1,40 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
@mvo5

mvo5 Jul 24, 2017

Collaborator

I'm not sure about util as a package name, it seems very generic. Maybe jsonutil ? I know it only carries a single helper for now, but I am concerned that it will become a bit of a grab-all package if we scoope it so wide.

@stolowski

stolowski Jul 31, 2017

Contributor

Fair point. I agree, let's not let it become "grab-all package". Changed to jsonutil.

stolowski added some commits Jul 31, 2017

@niemeyer niemeyer changed the title from snapd, snapctl: use json Decoder instead of Unmarshall to snapd,snapctl: decode json using Number Aug 16, 2017

Let's please just rename the function. Otherwise +1!

jsonutil/json.go
+ "io"
+)
+
+// DecodeJsonWithNumbers decodes input data using json.Decoder, ensuring numbers are preserved
@niemeyer

niemeyer Aug 16, 2017

Contributor

This seems better as DecodeWithNumber, the "Json" part is implied as this is inside jsonutil, and the "Number" here refers to the type name, json.Number, rather than to "numbers" in general.

jsonutil/json.go
+ return err
+ }
+ if dec.More() {
+ return fmt.Errorf("excess data found, not a valid json")
@niemeyer

niemeyer Aug 16, 2017

Contributor

People won't associate "excess data" as the reason why, say, 15:45 was not accepted (which excess data?). We ourselves were confused by that when debugging the problem.

I suggest simply saying "cannot parse json value".

stolowski added some commits Aug 18, 2017

@mvo5 mvo5 added this to the 2.28 milestone Aug 23, 2017

@mvo5 mvo5 merged commit bf55de6 into snapcore:master Aug 23, 2017

1 of 7 checks passed

xenial-ppc64el autopkgtest finished (failure)
Details
artful-amd64 autopkgtest running
Details
xenial-amd64 autopkgtest running
Details
xenial-i386 autopkgtest running
Details
yakkety-amd64 autopkgtest running
Details
zesty-amd64 autopkgtest running
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment