diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 978f679..cf57e95 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -21,7 +21,7 @@ jobs: test: strategy: matrix: - go-version: [ stable, oldstable ] + go-version: [ 1.17.x, stable, oldstable ] runs-on: ubuntu-latest steps: - name: Install Go diff --git a/value.go b/value.go new file mode 100644 index 0000000..509af79 --- /dev/null +++ b/value.go @@ -0,0 +1,56 @@ +package vars + +import ( + "encoding/json" + "fmt" + "strconv" +) + +// Infer parses a value from a string into a suitable type. +// If s only contains digits, int64 is returned. +// If s is parsed as float, float64 is returned. +// If s has unquoted values of true or false, bool is returned. +// If s is equal to unquoted null, nil is returned. +// If s starts with double quote ", string decoded from JSON is returned (mind JSON escaping rules). +// If s starts with [ or {, any decoded from JSON is returned. +// Otherwise, s is returned as is. +func Infer(s string) interface{} { + switch s { + case "": + return "" + case "true": + return true + case "false": + return false + case "null": + return nil + } + + if s[0] == '"' { + var v string + if err := json.Unmarshal([]byte(s), &v); err != nil { + return fmt.Errorf("infer string value %s: %w", s, err) + } + + return v + } + + if s[0] == '[' || s[0] == '{' { + var v interface{} + if err := json.Unmarshal([]byte(s), &v); err != nil { + return fmt.Errorf("infer JSON value %s: %w", s, err) + } + + return v + } + + if i, err := strconv.ParseInt(s, 10, 64); err == nil { + return i + } + + if f, err := strconv.ParseFloat(s, 64); err == nil { + return f + } + + return s +} diff --git a/value_test.go b/value_test.go new file mode 100644 index 0000000..f52b0a7 --- /dev/null +++ b/value_test.go @@ -0,0 +1,36 @@ +package vars_test + +import ( + "testing" + + "github.com/godogx/vars" + "github.com/stretchr/testify/assert" +) + +func TestInfer(t *testing.T) { + for _, tc := range []struct { + s string + v interface{} + }{ + {"", ""}, + {"123", int64(123)}, + {"123.45", 123.45}, + {"true", true}, + {"false", false}, + {"null", nil}, + {`"bla\nbla"`, "bla\nbla"}, + {`"bla\nbl...`, "infer string value \"bla\\nbl...: unexpected end of JSON input"}, + {`{"foo":"bar"}`, map[string]interface{}{"foo": "bar"}}, + {`["abc", 1, false, null]`, []interface{}{"abc", 1.0, false, nil}}, + {`{"foo":"ba....`, "infer JSON value {\"foo\":\"ba....: unexpected end of JSON input"}, + } { + t.Run(tc.s, func(t *testing.T) { + v := vars.Infer(tc.s) + if err, ok := v.(error); ok { + v = err.Error() + } + + assert.Equal(t, tc.v, v) + }) + } +}