Home

ffjsonnet @main - refs - log -
-
https://git.jolheiser.com/ffjsonnet.git
jsonnet parser for peterbourgon/ff
tree log patch
use cuesonnet Signed-off-by: jolheiser <git@jolheiser.com>
Signature
-----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgBTEvCQk6VqUAdN2RuH6bj1dNkY oOpbPWj+jw4ua1B1cAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQGxpsD4Aa6aHMmZmV/VK6vbdzW4652HJHt/pPulHaoGqEnXPFoG99m+PEhwUmeR8Sk 8JeXZbdbSDNIXn/y8zcQo= -----END SSH SIGNATURE-----
jolheiser <git@jolheiser.com>
4 hours ago
5 changed files, 156 additions(+), 32 deletions(-)
ffjsonnet.goffjsonnet_test.gogo.modgo.sumtestdata/bad_duration.json
M ffjsonnet.go -> ffjsonnet.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
diff --git a/ffjsonnet.go b/ffjsonnet.go
index 8a3bad030157e69b85ba3dd5db4a0ceaecfabc96..422b8b83aae464f29097a7575a2695ec43d29917 100644
--- a/ffjsonnet.go
+++ b/ffjsonnet.go
@@ -1,12 +1,10 @@
 package ffjsonnet
 
 import (
-	"encoding/json"
 	"fmt"
 	"io"
-	"strings"
 
-	"github.com/google/go-jsonnet"
+	"go.jolheiser.com/cuesonnet"
 )
 
 // Parser is a helper function that uses a default ParseConfig.
@@ -19,6 +17,8 @@ type ParseConfig struct {
 	// Delimiter is used when concatenating nested node keys into a flag name.
 	// The default delimiter is ".".
 	Delimiter string
+	// Schema is a CUE schema to validate the incoming (JSON)net
+	Schema string
 }
 
 // Parse a  document from the provided io.Reader, using the provided set
@@ -28,23 +28,14 @@ func (pc *ParseConfig) Parse(r io.Reader, set func(name, value string) error) error {
 	if pc.Delimiter == "" {
 		pc.Delimiter = "."
 	}
-
-	data, err := io.ReadAll(r)
-	if err != nil {
-		return err
+	if pc.Schema == "" {
+		pc.Schema = "_"
 	}
 
-	vm := jsonnet.MakeVM()
-	jsonData, err := vm.EvaluateAnonymousSnippet("config", string(data))
-	if err != nil {
-		return err
-	}
+	schema := cuesonnet.Schema(pc.Schema)
 
-	d := json.NewDecoder(strings.NewReader(jsonData))
-	d.UseNumber() // required for stringifying values
-
-	var m map[string]interface{}
-	if err := d.Decode(&m); err != nil {
+	var m map[string]any
+	if err := schema.Decode(r, &m); err != nil {
 		return ParseError{Inner: err}
 	}
 
M ffjsonnet_test.go -> ffjsonnet_test.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
diff --git a/ffjsonnet_test.go b/ffjsonnet_test.go
index 50a76e249aba598ae01f5b74d1c9cf38948a799e..7e6234cff11dea5cf132a971ed96ee8eee1e4116 100644
--- a/ffjsonnet_test.go
+++ b/ffjsonnet_test.go
@@ -9,9 +9,58 @@ 	"github.com/peterbourgon/ff/v3/fftest"
 	"go.jolheiser.com/ffjsonnet"
 )
 
-func TestJSONParser(t *testing.T) {
-	t.Parallel()
+func TestSchema(t *testing.T) {
+	for _, testcase := range []struct {
+		name   string
+		file   string
+		schema string
+		want   fftest.Vars
+	}{
+		{
+			name:   "valid schema",
+			file:   "testdata/basic.json",
+			schema: "import \"time\"\n{ s: string, i: int, b: bool, d: time.Duration }",
+			want:   fftest.Vars{S: "s", I: 10, B: true, D: 5 * time.Second},
+		},
+		{
+			name:   "valid schema (jsonnet)",
+			file:   "testdata/basic.jsonnet",
+			schema: "import \"time\"\n{ s: string, i: int, b: bool, d: time.Duration }",
+			want:   fftest.Vars{S: "s", I: 10, B: true, D: 5 * time.Second},
+		},
+		{
+			name:   "invalid schema constraint",
+			file:   "testdata/basic.json",
+			schema: "i: >100",
+			want:   fftest.Vars{WantParseErrorString: "i:"},
+		},
+		{
+			name:   "invalid schema type",
+			file:   "testdata/basic.json",
+			schema: "s: int",
+			want:   fftest.Vars{WantParseErrorString: "s:"},
+		},
+		{
+			name:   "invalid duration",
+			file:   "testdata/bad_duration.json",
+			schema: "import \"time\"\n{ d: time.Duration }",
+			want:   fftest.Vars{WantParseErrorString: "d:"},
+		},
+	} {
+		t.Run(testcase.name, func(t *testing.T) {
+			t.Parallel()
+			fs, vars := fftest.Pair()
+			pc := &ffjsonnet.ParseConfig{Schema: testcase.schema}
+			vars.ParseError = ff.Parse(fs, nil,
+				ff.WithConfigFile(testcase.file),
+				ff.WithConfigFileParser(pc.Parse),
+			)
+			fftest.Compare(t, &testcase.want, vars)
+		})
+	}
+}
 
+func TestJSONParser(t *testing.T) {
 	for _, testcase := range []struct {
 		name string
 		args []string
@@ -48,8 +97,15 @@ 			args: []string{},
 			file: "testdata/bad.json",
 			want: fftest.Vars{WantParseErrorString: "end of file"},
 		},
+		{
+			name: "invalid duration",
+			args: []string{},
+			file: "testdata/bad_duration.json",
+			want: fftest.Vars{WantParseErrorString: "parse error"},
+		},
 	} {
 		t.Run(testcase.name, func(t *testing.T) {
+			t.Parallel()
 			fs, vars := fftest.Pair()
 			vars.ParseError = ff.Parse(fs, testcase.args,
 				ff.WithConfigFile(testcase.file),
M go.mod -> go.mod
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
diff --git a/go.mod b/go.mod
index 95f0e3296f2813d201374bc4569a5383a2908444..f02cf2d9e677f37ffa385692c045c64f884ea1ec 100644
--- a/go.mod
+++ b/go.mod
@@ -1,13 +1,27 @@
 module go.jolheiser.com/ffjsonnet
 
-go 1.22.3
+go 1.25.0
 
 require (
-	github.com/google/go-jsonnet v0.20.0
 	github.com/peterbourgon/ff/v3 v3.4.0
+	go.jolheiser.com/cuesonnet v0.0.0-20260608154847-ad92adf766cc
 )
 
 require (
-	gopkg.in/yaml.v2 v2.4.0 // indirect
-	sigs.k8s.io/yaml v1.1.0 // indirect
+	cuelang.org/go v0.16.1 // indirect
+	github.com/cockroachdb/apd/v3 v3.2.3 // indirect
+	github.com/emicklei/proto v1.14.3 // indirect
+	github.com/google/go-jsonnet v0.22.0 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/mitchellh/go-wordwrap v1.0.1 // indirect
+	github.com/pelletier/go-toml/v2 v2.3.1 // indirect
+	github.com/protocolbuffers/txtpbfmt v0.0.0-20260420112717-c39628bde8b5 // indirect
+	go.yaml.in/yaml/v2 v2.4.4 // indirect
+	go.yaml.in/yaml/v3 v3.0.4 // indirect
+	golang.org/x/crypto v0.52.0 // indirect
+	golang.org/x/net v0.55.0 // indirect
+	golang.org/x/sys v0.46.0 // indirect
+	golang.org/x/text v0.37.0 // indirect
+	google.golang.org/protobuf v1.36.11 // indirect
+	sigs.k8s.io/yaml v1.6.0 // indirect
 )
M go.sum -> go.sum
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
diff --git a/go.sum b/go.sum
index 5e3bbf39a84788869a3244b924808923d0fdafcb..08fe3fcde02b064e56481d1984d44110133876c0 100644
--- a/go.sum
+++ b/go.sum
@@ -1,12 +1,69 @@
-github.com/google/go-jsonnet v0.20.0 h1:WG4TTSARuV7bSm4PMB4ohjxe33IHT5WVTrJSU33uT4g=
-github.com/google/go-jsonnet v0.20.0/go.mod h1:VbgWF9JX7ztlv770x/TolZNGGFfiHEVx9G6ca2eUmeA=
+cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819 h1:Zh+Ur3OsoWpvALHPLT45nOekHkgOt+IOfutBbPqM17I=
+cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819/go.mod h1:WjmQxb+W6nVNCgj8nXrF24lIz95AHwnSl36tpjDZSU8=
+cuelang.org/go v0.16.1 h1:iPN1lHZd2J0hjcr8hfq9PnIGk7VfPkKFfxH4de+m9sE=
+cuelang.org/go v0.16.1/go.mod h1:/aW3967FeWC5Hc1cDrN4Z4ICVApdMi83wO5L3uF/1hM=
+github.com/cockroachdb/apd/v3 v3.2.3 h1:4Zx+I3R35bFXMnltzmjP79i2cravE4jTRL6ps9Aux80=
+github.com/cockroachdb/apd/v3 v3.2.3/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
+github.com/emicklei/proto v1.14.3 h1:zEhlzNkpP8kN6utonKMzlPfIvy82t5Kb9mufaJxSe1Q=
+github.com/emicklei/proto v1.14.3/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
+github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
+github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/go-jsonnet v0.22.0 h1:o0bOAIE+9SIfRZ7FXQPuta0mHLLE0AwbY/L5GTH5CH8=
+github.com/google/go-jsonnet v0.22.0/go.mod h1:pLhKpu0/ODjL2Zev4y+CmCoHKAgONT1gSLQyriuYh9w=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
+github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
+github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
+github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
+github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
+github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
 github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
 github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
-github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
-github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+github.com/protocolbuffers/txtpbfmt v0.0.0-20260420112717-c39628bde8b5 h1:Mckui8l+Wqz2Ve7XQvsE8SbHNmDWu8NA7Xce5NFJ/kM=
+github.com/protocolbuffers/txtpbfmt v0.0.0-20260420112717-c39628bde8b5/go.mod h1:JSbkp0BviKovYYt9XunS95M3mLPibE9bGg+Y95DsEEY=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
+github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
+go.jolheiser.com/cuesonnet v0.0.0-20260608154847-ad92adf766cc h1:pYFHXKY2BOjdK6ag9yr3XTNMTvfovHeldtndZmuo4tg=
+go.jolheiser.com/cuesonnet v0.0.0-20260608154847-ad92adf766cc/go.mod h1:Vbhj9tI/M7a/p32d/PD3v4FgsL5lQWrcxq8ckYZfYCc=
+go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
+go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
+golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
+golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
+golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
+golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
+golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
+golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
+golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
+golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
+golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
+golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
+golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
-sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
+sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
I testdata/bad_duration.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
diff --git a/testdata/bad_duration.json b/testdata/bad_duration.json
new file mode 100644
index 0000000000000000000000000000000000000000..b6738ac519920e002bbe2a5ca3b14a90e1b2ddf2
--- /dev/null
+++ b/testdata/bad_duration.json
@@ -0,0 +1,6 @@
+{
+    "s": "s",
+    "i": 10,
+    "b": true,
+    "d": "notaduration"
+}