Skip to content

Commit

Permalink
json&cue: integrate ParsingDuration
Browse files Browse the repository at this point in the history
Integrate the new jsontypes.ParsingDuration type into the cue and json
decoders via the `SingleTypeSubstitutionMangler`.
dfinkel committed May 19, 2024

Verified

This commit was signed with the committer’s verified signature.
1 parent 7c8d66f commit fe58cd1
Showing 4 changed files with 62 additions and 9 deletions.
24 changes: 23 additions & 1 deletion decoders/cue/cue.go
Original file line number Diff line number Diff line change
@@ -4,18 +4,32 @@ import (
"fmt"
"io"
"reflect"
"time"

"cuelang.org/go/cue/cuecontext"

"github.com/vimeo/dials"
"github.com/vimeo/dials/common"
"github.com/vimeo/dials/decoders/json/jsontypes"
"github.com/vimeo/dials/tagformat"
"github.com/vimeo/dials/transform"
)

// Decoder is a decoder that knows how to work with configs written in Cue
type Decoder struct{}

func must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}

// pre-declare the time.Duration -> jsontypes.ParsingDuration mangler at
// package-scope, so we don't have to construct a new one every time Decode is
// called.
var parsingDurMangler = must(transform.NewSingleTypeSubstitutionMangler[time.Duration, jsontypes.ParsingDuration]())

// Decode is a decoder that decodes the Cue config from an io.Reader into the
// appropriate struct.
func (d *Decoder) Decode(r io.Reader, t *dials.Type) (reflect.Value, error) {
@@ -27,7 +41,9 @@ func (d *Decoder) Decode(r io.Reader, t *dials.Type) (reflect.Value, error) {
const jsonTagName = "json"

// If there aren't any json tags, copy over from any dials tags.
// Also, convert any time.Duration fields to jsontypes.ParsingDuration so we can decode those values as strings.
tfmr := transform.NewTransformer(t.Type(),
parsingDurMangler,
&tagformat.TagCopyingMangler{
SrcTag: common.DialsTagName, NewTag: jsonTagName})
reflVal, tfmErr := tfmr.Translate()
@@ -43,5 +59,11 @@ func (d *Decoder) Decode(r io.Reader, t *dials.Type) (reflect.Value, error) {
if decErr := val.Decode(reflVal.Addr().Interface()); decErr != nil {
return reflect.Value{}, fmt.Errorf("failed to decode cue value into dials struct: %w", decErr)
}
return reflVal, nil

unmangledVal, unmangleErr := tfmr.ReverseTranslate(reflVal)
if unmangleErr != nil {
return reflect.Value{}, unmangleErr
}

return unmangledVal, nil
}
21 changes: 16 additions & 5 deletions decoders/cue/cue_test.go
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import (
"context"
"net"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -82,13 +83,17 @@ func TestDeeplyNestedCueJSON(t *testing.T) {
Username string `dials:"username"`
Password string `dials:"password"`
OtherStuff struct {
Something string `dials:"something"`
IPAddress net.IP `dials:"ip_address"`
Something string `dials:"something"`
IPAddress net.IP `dials:"ip_address"`
SomeTimeout time.Duration `dials:"some_timeout"`
SomeOtherTimeout time.Duration `dials:"some_other_timeout"`
SomeLifetime time.Duration `dials:"some_lifetime_ns"`
} `dials:"other_stuff"`
} `dials:"database_user"`
}

jsonData := `{
cueData := `
import "time"
"database_name": "something",
"database_address": "127.0.0.1",
"database_user": {
@@ -97,15 +102,18 @@ func TestDeeplyNestedCueJSON(t *testing.T) {
"other_stuff": {
"something": "asdf",
"ip_address": "123.10.11.121"
"some_timeout": "13s"
"some_other_timeout": 87 * time.Second,
"some_lifetime_ns": 378,
}
}
}`
`

myConfig := &testConfig{}
d, err := dials.Config(
context.Background(),
myConfig,
&static.StringSource{Data: jsonData, Decoder: &Decoder{}},
&static.StringSource{Data: cueData, Decoder: &Decoder{}},
)
require.NoError(t, err)

@@ -115,6 +123,9 @@ func TestDeeplyNestedCueJSON(t *testing.T) {
assert.Equal(t, "test", c.DatabaseUser.Username)
assert.Equal(t, "password", c.DatabaseUser.Password)
assert.Equal(t, "asdf", c.DatabaseUser.OtherStuff.Something)
assert.Equal(t, time.Second*13, c.DatabaseUser.OtherStuff.SomeTimeout)
assert.Equal(t, time.Second*87, c.DatabaseUser.OtherStuff.SomeOtherTimeout)
assert.Equal(t, time.Nanosecond*378, c.DatabaseUser.OtherStuff.SomeLifetime)
assert.Equal(t, net.IPv4(123, 10, 11, 121), c.DatabaseUser.OtherStuff.IPAddress)

}
15 changes: 15 additions & 0 deletions decoders/json/json.go
Original file line number Diff line number Diff line change
@@ -5,9 +5,11 @@ import (
"fmt"
"io"
"reflect"
"time"

"github.com/vimeo/dials"
"github.com/vimeo/dials/common"
"github.com/vimeo/dials/decoders/json/jsontypes"
"github.com/vimeo/dials/tagformat"
"github.com/vimeo/dials/transform"
)
@@ -18,6 +20,18 @@ const JSONTagName = "json"
// Decoder is a decoder that knows how to work with text encoded in JSON
type Decoder struct{}

func must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}

// pre-declare the time.Duration -> jsontypes.ParsingDuration mangler at
// package-scope, so we don't have to construct a new one every time Decode is
// called.
var parsingDurMangler = must(transform.NewSingleTypeSubstitutionMangler[time.Duration, jsontypes.ParsingDuration]())

// Decode is a decoder that decodes the JSON from an io.Reader into the
// appropriate struct.
func (d *Decoder) Decode(r io.Reader, t *dials.Type) (reflect.Value, error) {
@@ -28,6 +42,7 @@ func (d *Decoder) Decode(r io.Reader, t *dials.Type) (reflect.Value, error) {

// If there aren't any json tags, copy over from any dials tags.
tfmr := transform.NewTransformer(t.Type(),
parsingDurMangler,
&tagformat.TagCopyingMangler{
SrcTag: common.DialsTagName, NewTag: JSONTagName})
val, tfmErr := tfmr.Translate()
11 changes: 8 additions & 3 deletions decoders/json/json_test.go
Original file line number Diff line number Diff line change
@@ -4,9 +4,11 @@ import (
"context"
"net"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/vimeo/dials"
"github.com/vimeo/dials/sources/static"
)
@@ -81,8 +83,9 @@ func TestDeeplyNestedJSON(t *testing.T) {
Username string `dials:"username"`
Password string `dials:"password"`
OtherStuff struct {
Something string `dials:"something"`
IPAddress net.IP `dials:"ip_address"`
Something string `dials:"something"`
IPAddress net.IP `dials:"ip_address"`
SomeTimeout time.Duration `dials:"some_timeout"`
} `dials:"other_stuff"`
} `dials:"database_user"`
}
@@ -95,7 +98,8 @@ func TestDeeplyNestedJSON(t *testing.T) {
"password": "password",
"other_stuff": {
"something": "asdf",
"ip_address": "123.10.11.121"
"ip_address": "123.10.11.121",
"some_timeout": "13s"
}
}
}`
@@ -114,6 +118,7 @@ func TestDeeplyNestedJSON(t *testing.T) {
assert.Equal(t, "test", c.DatabaseUser.Username)
assert.Equal(t, "password", c.DatabaseUser.Password)
assert.Equal(t, "asdf", c.DatabaseUser.OtherStuff.Something)
assert.Equal(t, time.Second*13, c.DatabaseUser.OtherStuff.SomeTimeout)
assert.Equal(t, net.IPv4(123, 10, 11, 121), c.DatabaseUser.OtherStuff.IPAddress)

}

0 comments on commit fe58cd1

Please sign in to comment.