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`.
  • Loading branch information
dfinkel committed May 17, 2024
1 parent 7c8d66f commit 836bb49
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 6 deletions.
24 changes: 23 additions & 1 deletion decoders/cue/cue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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()
Expand All @@ -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
}
8 changes: 6 additions & 2 deletions decoders/cue/cue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"net"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -82,8 +83,9 @@ 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"`
} `dials:"other_stuff"`
} `dials:"database_user"`
}
Expand All @@ -97,6 +99,7 @@ func TestDeeplyNestedCueJSON(t *testing.T) {
"other_stuff": {
"something": "asdf",
"ip_address": "123.10.11.121"
"some_timeout": "13s"
}
}
}`
Expand All @@ -115,6 +118,7 @@ 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, net.IPv4(123, 10, 11, 121), c.DatabaseUser.OtherStuff.IPAddress)

}
Expand Down
15 changes: 15 additions & 0 deletions decoders/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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) {
Expand All @@ -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()
Expand Down
11 changes: 8 additions & 3 deletions decoders/json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"`
}
Expand All @@ -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"
}
}
}`
Expand All @@ -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)

}
Expand Down

0 comments on commit 836bb49

Please sign in to comment.