Skip to content

Commit

Permalink
add ForceJSON option for string decoding (#13)
Browse files Browse the repository at this point in the history
* add ForceJSON option for string decoding

* update lint job

* more go versions
  • Loading branch information
muir committed Mar 14, 2024
1 parent abaed17 commit 802665b
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Expand Up @@ -4,7 +4,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.16.x, 1.17.x, 1.18.x]
go-version: [1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
Expand Down
40 changes: 28 additions & 12 deletions .github/workflows/golangci-lint.yml
@@ -1,33 +1,49 @@
name: golangci-lint
on: [push ]
on: [push]
permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
# pull-requests: read

jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.21'
cache: false
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
uses: golangci/golangci-lint-action@v4
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest
# Require: The version of golangci-lint to use.
# When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version.
# When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit.
version: v1.54

# Optional: working directory, useful for monorepos
# working-directory: somedir
output: checkstyle

# Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0
# args: --out-format checkstyle
#
# Note: By default, the `.golangci.yml` file should be at the root of the repository.
# The location of the configuration file can be changed by using `--config=`
# args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0

# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true

# Optional: if set to true then the action will use pre-installed Go.
# skip-go-installation: true
# Optional: if set to true, then all caching functionality will be completely disabled,
# takes precedence over all other caching options.
# skip-cache: true

# Optional: if set to true then the action don't cache or restore ~/go/pkg.
# Optional: if set to true, then the action won't cache or restore ~/go/pkg.
# skip-pkg-cache: true

# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
# Optional: if set to true, then the action won't cache or restore ~/.cache/go-build.
# skip-build-cache: true

# Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'.
# install-mode: "goinstall"
28 changes: 26 additions & 2 deletions unpack.go
Expand Up @@ -2,6 +2,7 @@ package reflectutils

import (
"encoding"
"encoding/json"
"flag"
"reflect"
"strconv"
Expand All @@ -10,12 +11,15 @@ import (
"github.com/pkg/errors"
)

var textUnmarshallerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
var flagValueType = reflect.TypeOf((*flag.Value)(nil)).Elem()
var (
textUnmarshallerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
flagValueType = reflect.TypeOf((*flag.Value)(nil)).Elem()
)

type stringSetterOpts struct {
split string
sliceAppend bool
forceJSON bool
}

type StringSetterArg func(*stringSetterOpts)
Expand All @@ -41,6 +45,15 @@ func SliceAppend(b bool) StringSetterArg {
}
}

// ForceJSON controls if types will be decoded with JSON
// unmarshal. This overrides normal decoding patterns. The default
// is false.
func ForceJSON(b bool) StringSetterArg {
return func(o *stringSetterOpts) {
o.forceJSON = b
}
}

// MakeStringSetter handles setting a reflect.Value from a string.
// Based on type, it returns a function to do the work. It is assumed that the
// reflect.Type matches the reflect.Value. If not, panic is likely.
Expand All @@ -64,6 +77,17 @@ func MakeStringSetter(t reflect.Type, optArgs ...StringSetterArg) (func(target r
for _, f := range optArgs {
f(&opts)
}
if opts.forceJSON {
return func(target reflect.Value, value string) error {
p := reflect.New(t.Elem())
target.Set(p)
err := json.Unmarshal([]byte(value), target.Interface())
if err != nil {
return errors.WithStack(err)
}
return nil
}, nil
}
if setter, ok := settersByType[t]; ok {
return func(target reflect.Value, value string) error {
out := setter.Call([]reflect.Value{reflect.ValueOf(value)})
Expand Down
14 changes: 13 additions & 1 deletion unpack_test.go
Expand Up @@ -30,13 +30,18 @@ func (bp *Bar) Set(s string) error {
*bp = Bar(s + "/e")
return nil
}

func (bp Bar) String() string {
return "b/" + string(bp)
}

var _ flag.Value = func() *Bar { var x Bar; return &x }()

func TestStringSetter(t *testing.T) {
type J struct {
A int
B string
}
type tsType struct {
Int int `value:"38"`
Int8 int8 `value:"-9"`
Expand Down Expand Up @@ -75,7 +80,7 @@ func TestStringSetter(t *testing.T) {
FooP *Foo `value:"foo" want:"~foo~"`
Dur time.Duration `value:"30m" want:"30m0s"`
DurP *time.Duration `value:"15m" want:"15m0s"`
DurArray []time.Duration `value:"15m,45m" want:"[15m0s 45m0s]"`
DurArray []time.Duration `value:"15m,45m" want:"[15m0s 45m0s]"`
Bar Bar `value:"bar" want:"b/bar/e"`
BarArray [2]Bar `value:"a,b,c" want:"[b/a/e b/b,c/e]"`
BarP *Bar `value:"bar" want:"b/bar/e"`
Expand All @@ -89,6 +94,7 @@ func TestStringSetter(t *testing.T) {
SS5 []string `value:"foo" want:"[foo bar]" value2:"bar"`
SS6 []string `value:"foo" want:"[bar]" value2:"bar" sa:"f"`
RG01 *[]int `value:"823:29" want:"[823 29]" split:":"`
S *J `value:"{\"A\":10,\"B\":\"bar\"}" want:"{A:10 B:bar}" fj:"t"`
}
var ts tsType
vp := reflect.ValueOf(&ts)
Expand Down Expand Up @@ -116,6 +122,12 @@ func TestStringSetter(t *testing.T) {
t.Log(" slice append", b)
opts = append(opts, reflectutils.SliceAppend(b))
}
if fj, ok := f.Tag.Lookup("fj"); ok {
b, err := strconv.ParseBool(fj)
require.NoError(t, err, "parse fj")
t.Log(" force JSON", b)
opts = append(opts, reflectutils.ForceJSON(b))
}

fn, err := reflectutils.MakeStringSetter(f.Type, opts...)
if !assert.NoErrorf(t, err, "make string setter for %s", f.Name) {
Expand Down

0 comments on commit 802665b

Please sign in to comment.