summaryrefslogtreecommitdiff
path: root/data
diff options
context:
space:
mode:
Diffstat (limited to 'data')
-rw-r--r--data/data.go63
-rw-r--r--data/data_test.go117
-rw-r--r--data/datasource.go3
-rw-r--r--data/mimetypes.go1
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