Skip to content

Commit

Permalink
Merge pull request #140 from maxatome/smuggle
Browse files Browse the repository at this point in the history
Smuggle fields-path param can dereference maps, arrays & slices
  • Loading branch information
maxatome committed Apr 2, 2021
2 parents f69b59b + cc70b7b commit 81a65e8
Show file tree
Hide file tree
Showing 12 changed files with 659 additions and 115 deletions.
102 changes: 61 additions & 41 deletions internal/json/parser.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 31 additions & 19 deletions internal/json/parser.y
Expand Up @@ -65,25 +65,29 @@ value:
| PLACEHOLDER
;

object: '{' members '}'
object: '{' '}'
{
$$ = map[string]interface{}{}
}
| '{' members '}'
{
$$ = $2
}

members:
| '{' members ',' '}' // not JSON spec but useful
{
$$ = map[string]interface{}{}
$$ = $2
}
| member

members: member
{
$$ = map[string]interface{}{
$1.key: $1.value,
}
}
| member ',' members
| members ',' member
{
$3[$1.key] = $1.value
$$ = $3
$1[$3.key] = $3.value
$$ = $1
}

member: STRING ':' value
Expand All @@ -94,16 +98,20 @@ member: STRING ':' value
}
}

array: '[' elements ']'
array: '[' ']'
{
$$ = []interface{}{}
}
| '[' elements ']'
{
$$ = $2
}

elements:
| '[' elements ',' ']' // not JSON spec but useful
{
$$ = []interface{}{}
$$ = $2
}
| value

elements: value
{
$$ = []interface{}{$1}
}
Expand All @@ -112,22 +120,26 @@ elements:
$$ = append($1, $3)
}

op_params: elements
op_params: '(' ')'
{
$$ = $1
$$ = []interface{}{}
}
| elements ','
| '(' elements ')'
{
$$ = $1
$$ = $2
}
| '(' elements ',' ')'
{
$$ = $2
}

operator:
OPERATOR_SHORTCUT
| OPERATOR '(' op_params ')'
| OPERATOR op_params
{
j := yylex.(*json)
opPos := j.popPos()
op, err := j.getOperator(Operator{Name: $1, Params: $3}, opPos)
op, err := j.getOperator(Operator{Name: $1, Params: $2}, opPos)
if err != nil {
j.fatal(err.Error(), opPos)
return 1
Expand Down
28 changes: 28 additions & 0 deletions internal/json/parser_test.go
Expand Up @@ -82,6 +82,34 @@ func TestJSON(t *testing.T) {
}
})

t.Run("JSON spec infringements", func(t *testing.T) {
check := func(gotJSON, expectedJSON string) {
t.Helper()
var expected interface{}
err := ejson.Unmarshal([]byte(expectedJSON), &expected)
if err != nil {
t.Fatalf("bad JSON: %s", err)
}

got, err := json.Parse([]byte(gotJSON))
if !test.NoError(t, err, "json.Parse succeeds") {
return
}
if !reflect.DeepEqual(got, expected) {
test.EqualErrorMessage(t,
strings.TrimRight(spew.Sdump(got), "\n"),
strings.TrimRight(spew.Sdump(expected), "\n"),
"got matches expected",
)
}
}
// "," is accepted just before non-empty "}" or "]"
check(`{"foo": "bar", }`, `{"foo":"bar"}`)
check(`{"foo":"bar",}`, `{"foo":"bar"}`)
check(`[ 1, 2, 3, ]`, `[1,2,3]`)
check(`[ 1,2,3,]`, `[1,2,3]`)
})

t.Run("Special string cases", func(t *testing.T) {
for i, tst := range []struct{ in, expected string }{
{
Expand Down
10 changes: 10 additions & 0 deletions td/example_cmp_test.go
Expand Up @@ -2491,10 +2491,20 @@ func ExampleCmpSmuggle_field_path() {
ok = td.CmpSmuggle(t, got, "Body.Value.Num", td.Between(100, 200))
fmt.Println("check Num using an other fields-path:", ok)

// Note that maps and array/slices are supported
got.Request.Body.Value = map[string]interface{}{
"foo": []interface{}{
3: map[int]string{666: "bar"},
},
}
ok = td.CmpSmuggle(t, got, "Body.Value[foo][3][666]", "bar")
fmt.Println("check fields-path including maps/slices:", ok)

// Output:
// check Num by hand: true
// check Num using a fields-path: true
// check Num using an other fields-path: true
// check fields-path including maps/slices: true
}

func ExampleCmpSStruct() {
Expand Down
10 changes: 10 additions & 0 deletions td/example_t_test.go
Expand Up @@ -2491,10 +2491,20 @@ func ExampleT_Smuggle_field_path() {
ok = t.Smuggle(got, "Body.Value.Num", td.Between(100, 200))
fmt.Println("check Num using an other fields-path:", ok)

// Note that maps and array/slices are supported
got.Request.Body.Value = map[string]interface{}{
"foo": []interface{}{
3: map[int]string{666: "bar"},
},
}
ok = t.Smuggle(got, "Body.Value[foo][3][666]", "bar")
fmt.Println("check fields-path including maps/slices:", ok)

// Output:
// check Num by hand: true
// check Num using a fields-path: true
// check Num using an other fields-path: true
// check fields-path including maps/slices: true
}

func ExampleT_SStruct() {
Expand Down
10 changes: 10 additions & 0 deletions td/example_test.go
Expand Up @@ -2659,10 +2659,20 @@ func ExampleSmuggle_field_path() {
ok = td.Cmp(t, got, td.Smuggle("Body.Value.Num", td.Between(100, 200)))
fmt.Println("check Num using an other fields-path:", ok)

// Note that maps and array/slices are supported
got.Request.Body.Value = map[string]interface{}{
"foo": []interface{}{
3: map[int]string{666: "bar"},
},
}
ok = td.Cmp(t, got, td.Smuggle("Body.Value[foo][3][666]", "bar"))
fmt.Println("check fields-path including maps/slices:", ok)

// Output:
// check Num by hand: true
// check Num using a fields-path: true
// check Num using an other fields-path: true
// check fields-path including maps/slices: true
}

func ExampleString() {
Expand Down
2 changes: 1 addition & 1 deletion td/td_json_pointer.go
Expand Up @@ -79,7 +79,7 @@ var _ TestDeep = &tdJSONPointer{}
//
// td.Cmp(t, got, td.JSONPointer("/Next/Next", Item{Val: 3}))
//
// Contrary to Smuggle operator and its field-path feature, only
// Contrary to Smuggle operator and its fields-path feature, only
// public fields can be followed, as private ones are never (un)marshalled.
//
// There is no JSONHas nor JSONHasnt operators to only check a JSON
Expand Down

0 comments on commit 81a65e8

Please sign in to comment.