Skip to content

Commit

Permalink
Add support for raw map[string]any type to riverjson encoding. (#41)
Browse files Browse the repository at this point in the history
* Add support for raw map[string]any type to riverjson encoding.

* use jsonBody type

* Update encoding/riverjson/riverjson.go

Co-authored-by: Paschalis Tsilias <[email protected]>

* add capsule test

---------

Co-authored-by: Paschalis Tsilias <[email protected]>
  • Loading branch information
wildum and tpaschalis authored Jan 23, 2024
1 parent 0f30d3c commit 9607531
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 19 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ internal API changes are not present.
Main (unreleased)
-----------------

### Features

- Add support for raw map[string]any type to riverjson encoding. (@wildum)

v0.3.0 (2023-10-26)
-------------------

Expand Down
58 changes: 39 additions & 19 deletions encoding/riverjson/riverjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
var goRiverDefaulter = reflect.TypeOf((*value.Defaulter)(nil)).Elem()

// MarshalBody marshals the provided Go value to a JSON representation of
// River. MarshalBody panics if not given a struct with River tags.
// River. MarshalBody panics if not given a struct with River tags or a map[string]any.
func MarshalBody(val interface{}) ([]byte, error) {
rv := reflect.ValueOf(val)
return json.Marshal(encodeStructAsBody(rv))
Expand All @@ -26,37 +26,57 @@ func MarshalBody(val interface{}) ([]byte, error) {
func encodeStructAsBody(rv reflect.Value) jsonBody {
for rv.Kind() == reflect.Pointer {
if rv.IsNil() {
return []jsonStatement{}
return jsonBody{}
}
rv = rv.Elem()
}

if rv.Kind() == reflect.Invalid {
return []jsonStatement{}
} else if rv.Kind() != reflect.Struct {
panic(fmt.Sprintf("river/encoding/riverjson: can only encode struct values to bodies, got %s", rv.Kind()))
return jsonBody{}
}

fields := rivertags.Get(rv.Type())
defaults := reflect.New(rv.Type()).Elem()
if defaults.CanAddr() && defaults.Addr().Type().Implements(goRiverDefaulter) {
defaults.Addr().Interface().(value.Defaulter).SetToDefault()
}
body := jsonBody{}

body := []jsonStatement{}
switch rv.Kind() {
case reflect.Struct:
fields := rivertags.Get(rv.Type())
defaults := reflect.New(rv.Type()).Elem()
if defaults.CanAddr() && defaults.Addr().Type().Implements(goRiverDefaulter) {
defaults.Addr().Interface().(value.Defaulter).SetToDefault()
}

for _, field := range fields {
fieldVal := reflectutil.Get(rv, field)
fieldValDefault := reflectutil.Get(defaults, field)
for _, field := range fields {
fieldVal := reflectutil.Get(rv, field)
fieldValDefault := reflectutil.Get(defaults, field)

var isEqual = fieldVal.Comparable() && fieldVal.Equal(fieldValDefault)
var isZero = fieldValDefault.IsZero() && fieldVal.IsZero()
isEqual := fieldVal.Comparable() && fieldVal.Equal(fieldValDefault)
isZero := fieldValDefault.IsZero() && fieldVal.IsZero()

if field.IsOptional() && (isEqual || isZero) {
continue
if field.IsOptional() && (isEqual || isZero) {
continue
}

body = append(body, encodeFieldAsStatements(nil, field, fieldVal)...)
}

body = append(body, encodeFieldAsStatements(nil, field, fieldVal)...)
case reflect.Map:
if rv.Type().Key().Kind() != reflect.String {
panic("river/encoding/riverjson: unsupported map type; expected map[string]T, got " + rv.Type().String())
}

iter := rv.MapRange()
for iter.Next() {
mapKey, mapValue := iter.Key(), iter.Value()

body = append(body, jsonAttr{
Name: mapKey.String(),
Type: "attr",
Value: buildJSONValue(value.FromRaw(mapValue)),
})
}

default:
panic(fmt.Sprintf("river/encoding/riverjson: can only encode struct or map[string]T values to bodies, got %s", rv.Kind()))
}

return body
Expand Down
28 changes: 28 additions & 0 deletions encoding/riverjson/riverjson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,31 @@ func TestMapBlocks(t *testing.T) {
require.NoError(t, err)
require.JSONEq(t, expect, string(bb))
}

func TestRawMap(t *testing.T) {
val := map[string]any{"field": "value"}

expect := `[{
"name": "field",
"type": "attr",
"value": { "type": "string", "value": "value" }
}]`

bb, err := riverjson.MarshalBody(val)
require.NoError(t, err)
require.JSONEq(t, expect, string(bb))
}

func TestRawMap_Capsule(t *testing.T) {
val := map[string]any{"capsule": rivertypes.Secret("foo")}

expect := `[{
"name": "capsule",
"type": "attr",
"value": { "type": "capsule", "value": "(secret)" }
}]`

bb, err := riverjson.MarshalBody(val)
require.NoError(t, err)
require.JSONEq(t, expect, string(bb))
}

0 comments on commit 9607531

Please sign in to comment.