diff options
Diffstat (limited to 'data')
| -rw-r--r-- | data/data.go | 63 | ||||
| -rw-r--r-- | data/data_test.go | 117 | ||||
| -rw-r--r-- | data/datasource.go | 3 | ||||
| -rw-r--r-- | data/mimetypes.go | 1 |
4 files changed, 184 insertions, 0 deletions
diff --git a/data/data.go b/data/data.go index adf6b2b7..d2fc32fb 100644 --- a/data/data.go +++ b/data/data.go @@ -12,6 +12,9 @@ import ( "io" "strings" + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" + "cuelang.org/go/cue/format" "github.com/Shopify/ejson" ejsonJson "github.com/Shopify/ejson/json" "github.com/hairyhenderson/gomplate/v4/conv" @@ -442,3 +445,63 @@ func ToTOML(in interface{}) (string, error) { } return buf.String(), nil } + +// CUE - Unmarshal a CUE expression into the appropriate type +func CUE(in string) (interface{}, error) { + cuectx := cuecontext.New() + val := cuectx.CompileString(in) + + if val.Err() != nil { + return nil, fmt.Errorf("unable to process CUE: %w", val.Err()) + } + + switch val.Kind() { + case cue.StructKind: + out := map[string]interface{}{} + err := val.Decode(&out) + return out, err + case cue.ListKind: + out := []interface{}{} + err := val.Decode(&out) + return out, err + case cue.BytesKind: + out := []byte{} + err := val.Decode(&out) + return out, err + case cue.StringKind: + out := "" + err := val.Decode(&out) + return out, err + case cue.IntKind: + out := 0 + err := val.Decode(&out) + return out, err + case cue.NumberKind, cue.FloatKind: + out := 0.0 + err := val.Decode(&out) + return out, err + case cue.BoolKind: + out := false + err := val.Decode(&out) + return out, err + case cue.NullKind: + return nil, nil + default: + return nil, fmt.Errorf("unsupported CUE type %q", val.Kind()) + } +} + +func ToCUE(in interface{}) (string, error) { + cuectx := cuecontext.New() + v := cuectx.Encode(in) + if v.Err() != nil { + return "", v.Err() + } + + bs, err := format.Node(v.Syntax()) + if err != nil { + return "", err + } + + return string(bs), nil +} diff --git a/data/data_test.go b/data/data_test.go index 2ea6365e..324a3600 100644 --- a/data/data_test.go +++ b/data/data_test.go @@ -660,3 +660,120 @@ func TestStringifyYAMLMapMapKeys(t *testing.T) { assert.EqualValues(t, c.want, c.input) } } + +func TestCUE(t *testing.T) { + in := `package foo +import "regexp" +matches: regexp.FindSubmatch(#"^([^:]*):(\d+)$"#, "localhost:443") +one: 1 +two: 2 +// A field using quotes. +"two-and-a-half": 2.5 +list: [ 1, 2, 3 ] +` + + expected := map[string]interface{}{ + "matches": []interface{}{ + "localhost:443", + "localhost", + "443", + }, + "one": 1, + "two": 2, + "two-and-a-half": 2.5, + "list": []interface{}{1, 2, 3}, + } + + out, err := CUE(in) + require.NoError(t, err) + assert.EqualValues(t, expected, out) + + out, err = CUE(`[1,2,3]`) + require.NoError(t, err) + assert.EqualValues(t, []interface{}{1, 2, 3}, out) + + out, err = CUE(`"hello world"`) + require.NoError(t, err) + assert.EqualValues(t, "hello world", out) + + out, err = CUE(`true`) + require.NoError(t, err) + assert.EqualValues(t, true, out) + + out, err = CUE(`'\x00\x01\x02\x03\x04'`) + require.NoError(t, err) + assert.EqualValues(t, []byte{0, 1, 2, 3, 4}, out) + + out, err = CUE(`42`) + require.NoError(t, err) + assert.EqualValues(t, 42, out) + + out, err = CUE(`42.0`) + require.NoError(t, err) + assert.EqualValues(t, 42.0, out) + + out, err = CUE(`null`) + require.NoError(t, err) + assert.EqualValues(t, nil, out) + + _, err = CUE(`>=0 & <=7 & >=3 & <=10`) + require.Error(t, err) +} + +func TestToCUE(t *testing.T) { + in := map[string]interface{}{ + "matches": []interface{}{ + "localhost:443", + "localhost", + "443", + }, + "one": 1, + "two": 2, + "two-and-a-half": 2.5, + "list": []interface{}{1, 2, 3}, + } + + expected := `{ + "two-and-a-half": 2.5 + list: [1, 2, 3] + two: 2 + one: 1 + matches: ["localhost:443", "localhost", "443"] +}` + + out, err := ToCUE(in) + require.NoError(t, err) + assert.EqualValues(t, expected, out) + + out, err = ToCUE([]interface{}{1, 2, 3}) + require.NoError(t, err) + assert.EqualValues(t, `[1, 2, 3]`, out) + + out, err = ToCUE("hello world") + require.NoError(t, err) + assert.EqualValues(t, `"hello world"`, out) + + out, err = ToCUE(true) + require.NoError(t, err) + assert.EqualValues(t, `true`, out) + + out, err = ToCUE([]byte{0, 1, 2, 3, 4}) + require.NoError(t, err) + assert.EqualValues(t, `'\x00\x01\x02\x03\x04'`, out) + + out, err = ToCUE(42) + require.NoError(t, err) + assert.EqualValues(t, `42`, out) + + out, err = ToCUE(42.0) + require.NoError(t, err) + assert.EqualValues(t, `42.0`, out) + + out, err = ToCUE(nil) + require.NoError(t, err) + assert.EqualValues(t, `null`, out) + + out, err = ToCUE(struct{}{}) + require.NoError(t, err) + assert.EqualValues(t, `{}`, out) +} diff --git a/data/datasource.go b/data/datasource.go index d16fe09a..fe3f0877 100644 --- a/data/datasource.go +++ b/data/datasource.go @@ -32,6 +32,7 @@ func init() { regExtension(".csv", csvMimetype) regExtension(".toml", tomlMimetype) regExtension(".env", envMimetype) + regExtension(".cue", cueMimetype) } // registerReaders registers the source-reader functions @@ -348,6 +349,8 @@ func parseData(mimeType, s string) (out interface{}, err error) { out, err = dotEnv(s) case textMimetype: out = s + case cueMimetype: + out, err = CUE(s) default: return nil, fmt.Errorf("datasources of type %s not yet supported", mimeType) } diff --git a/data/mimetypes.go b/data/mimetypes.go index bdc12ad4..1f243219 100644 --- a/data/mimetypes.go +++ b/data/mimetypes.go @@ -8,6 +8,7 @@ const ( tomlMimetype = "application/toml" yamlMimetype = "application/yaml" envMimetype = "application/x-env" + cueMimetype = "application/cue" ) // mimeTypeAliases defines a mapping for non-canonical mime types that are |
