From eac6463f3a13239ba6c13b6197dc79efa6f2334f Mon Sep 17 00:00:00 2001 From: Paul Jolly Date: Tue, 14 May 2024 12:32:59 +0100 Subject: [PATCH] docs/howto: validate JSON using the Go API This adds a guide demonstrating how to validate JSON data files against an embedded CUE schema. A YAML counterpart is requested in cue-lang/docs-and-content#150, as it was discovered during this change that the YAML equivalent couldn't be created simply by performing "s/json/yaml/g" on the source. Preview-Path: /docs/howto/validate-json-using-go-api/ Signed-off-by: Paul Jolly Change-Id: I53c3112650f36a32b86a5d89f670f738d1b798c8 --- .../howto/validate-json-using-go-api/en.md | 169 +++++++++++++++++ .../validate-json-using-go-api/gen_cache.cue | 96 ++++++++++ .../howto/validate-json-using-go-api/page.cue | 3 + .../howto/validate-json-using-go-api/index.md | 174 ++++++++++++++++++ 4 files changed, 442 insertions(+) create mode 100644 content/docs/howto/validate-json-using-go-api/en.md create mode 100644 content/docs/howto/validate-json-using-go-api/gen_cache.cue create mode 100644 content/docs/howto/validate-json-using-go-api/page.cue create mode 100644 hugo/content/en/docs/howto/validate-json-using-go-api/index.md diff --git a/content/docs/howto/validate-json-using-go-api/en.md b/content/docs/howto/validate-json-using-go-api/en.md new file mode 100644 index 0000000000..314a240e15 --- /dev/null +++ b/content/docs/howto/validate-json-using-go-api/en.md @@ -0,0 +1,169 @@ +--- +title: Validate JSON using the Go API +tags: +- go api +- validation +- encodings +authors: +- myitcv +toc_hide: true +--- + +This guide demonstrates how to write a Go program that validates JSON files +using an embeded CUE schema. + +{{{with _script_ "en" "set caches to speed up re-running"}}} +export GOMODCACHE=/caches/gomodcache +export GOCACHE=/caches/gobuild +{{{end}}} + +## Set up some schema and data files + +{{{with step}}} +Create a CUE schema. + +You can use any schema that's relevant to your specific data, but our example uses this simple CUE: + +{{{with upload "en" "cue schema"}}} +-- schema.cue -- +#Schema: { + name?: string + age?: int +} +{{{end}}} +{{{end}}} + + +{{{with step}}} +Create some known-good and known-bad test data. + +You may already have some representative test data. This data is relevant to our example schema: + +{{{with upload "en" "good data"}}} +-- good.json -- +{ + "name": "Charlie Cartwright", + "age": 80 +} +{{{end}}} + +{{{with upload "en" "bad data"}}} +-- bad.json -- +{ + "name": [ + "Moby", + "Dick" + ], + "age": "173" +} +{{{end}}} +{{{end}}} + +{{{with step}}} +Verify that your schema accepts and rejects your test data appropriately. + +Our example schema is contained in the `#Schema` definition, which we reference explicitly: + +{{{with script "en" "test schema"}}} +cue vet schema.cue good.json -d '#Schema' +! cue vet schema.cue bad.json -d '#Schema' +{{{end}}} +{{{end}}} + +## Create a Go program + +{{{with step}}} +Initialize a Go module, or use an existing one if that's more suitable for your situation: + +{{{with script "en" "go mod init"}}} +#ellipsis 0 +go mod init go.example +{{{end}}} +{{{end}}} + +{{{with step}}} +Create a main program. + +You can use this example code as a starting point: + +{{{with upload "en" "main go"}}} +-- main.go -- +package main + +import ( + "fmt" + "log" + "os" + + _ "embed" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" + "cuelang.org/go/encoding/json" +) + +// Embed our schema in a Go string variable. +// +//go:embed schema.cue +var cueSource string + +func main() { + ctx := cuecontext.New() + + // Build the schema + schema := ctx.CompileString(cueSource).LookupPath(cue.ParsePath("#Schema")) + + // Load the JSON file specified (the program's sole argument) as a CUE value + dataFilename := os.Args[1] + dataFile, err := os.ReadFile(dataFilename) + if err != nil { + log.Fatal(err) + } + dataExpr, err := json.Extract(dataFilename, dataFile) + if err != nil { + log.Fatal(err) + } + dataAsCUE := ctx.BuildExpr(dataExpr) + + // Validate the JSON data using the schema + unified := schema.Unify(dataAsCUE) + if err := unified.Validate(); err != nil { + fmt.Println("❌ JSON: NOT ok") + log.Fatal(err) + } + + fmt.Println("✅ JSON: ok") +} +{{{end}}} + +This example code embeds the CUE from `schema.cue` and uses it to validate a +single JSON file, printing the validation result to its standard output stream. +{{{end}}} + +{{{with step}}} +Add a dependency on `cuelang.org/go` and ensure the Go module is tidy: +{{{with script "en" "deps and tidy"}}} +#ellipsis 0 +go get cuelang.org/go@${CUELANG_CUE_LATEST} +#ellipsis 0 +go mod tidy +{{{end}}} +{{{end}}} + +## Run the Go program + +{{{with step}}} +Verify that the Go program accepts and rejects your test data as expected: + +{{{with script "en" "verify go program"}}} +go run . good.json +! go run . bad.json +{{{end}}} +{{{end}}} + +## Related content + +- Go API: [`cue`](https://pkg.go.dev/cuelang.org/go/cue#section-documentation) -- package documentation +- Go API: [`cue/cuecontext`](https://pkg.go.dev/cuelang.org/go/cue/cuecontext#section-documentation) -- package documentation +- Go API: [`encoding/json`](https://pkg.go.dev/cuelang.org/go/encoding/json#section-documentation) -- package documentation +- Tag: {{< tag "go api" >}} -- pages explaining and exploring CUE's Go API diff --git a/content/docs/howto/validate-json-using-go-api/gen_cache.cue b/content/docs/howto/validate-json-using-go-api/gen_cache.cue new file mode 100644 index 0000000000..c7b962be91 --- /dev/null +++ b/content/docs/howto/validate-json-using-go-api/gen_cache.cue @@ -0,0 +1,96 @@ +package site +{ + content: { + docs: { + howto: { + "validate-json-using-go-api": { + page: { + cache: { + upload: { + "cue schema": "ZpSJCm9G7J+MjuBIzc3pueaF87YCQpvv3hx5r8UwLLw=" + "good data": "VxujqE1InR4iaC/qnP2xgIRqP1SFB8Khx9JCm/Z16Z8=" + "bad data": "o7UoF2uK9CROfVxOPxkx6d58DoiGlhqxyWJsiLQw+pA=" + "main go": "ZlWvRqx9OnyoDaf555iHnIXYgCPjz2igOpxpjyBF9Kc=" + } + multi_step: { + hash: "7VTM00G63VNCUJDBFFR80NS40QASA7JSQLGC93V16BUTKK750VR0====" + scriptHash: "D7AL3UDDJL6UIBJ1L3ASO3RB5JHESRQ6II1N9R88SETKGSR84LI0====" + steps: [{ + doc: "" + cmd: "export GOMODCACHE=/caches/gomodcache" + exitCode: 0 + output: "" + }, { + doc: "" + cmd: "export GOCACHE=/caches/gobuild" + exitCode: 0 + output: "" + }, { + doc: "" + cmd: "cue vet schema.cue good.json -d '#Schema'" + exitCode: 0 + output: "" + }, { + doc: "" + cmd: "cue vet schema.cue bad.json -d '#Schema'" + exitCode: 1 + output: """ + age: conflicting values "173" and int (mismatched types string and int): + ./bad.json:6:12 + ./schema.cue:3:9 + name: conflicting values string and ["Moby","Dick"] (mismatched types string and list): + ./bad.json:2:13 + ./schema.cue:2:9 + + """ + }, { + doc: "#ellipsis 0" + cmd: "go mod init go.example" + exitCode: 0 + output: """ + ... + + """ + }, { + doc: "#ellipsis 0" + cmd: "go get cuelang.org/go@v0.8.2" + exitCode: 0 + output: """ + ... + + """ + }, { + doc: "#ellipsis 0" + cmd: "go mod tidy" + exitCode: 0 + output: """ + ... + + """ + }, { + doc: "" + cmd: "go run . good.json" + exitCode: 0 + output: """ + ✅ JSON: ok + + """ + }, { + doc: "" + cmd: "go run . bad.json" + exitCode: 1 + output: """ + ❌ JSON: NOT ok + main.go:42: #Schema.name: conflicting values string and ["Moby","Dick"] (mismatched types string and list) (and 1 more errors) + exit status 1 + + """ + }] + } + } + } + } + } + } + } +} diff --git a/content/docs/howto/validate-json-using-go-api/page.cue b/content/docs/howto/validate-json-using-go-api/page.cue new file mode 100644 index 0000000000..408f282ef9 --- /dev/null +++ b/content/docs/howto/validate-json-using-go-api/page.cue @@ -0,0 +1,3 @@ +package site + +content: docs: howto: "validate-json-using-go-api": page: _ diff --git a/hugo/content/en/docs/howto/validate-json-using-go-api/index.md b/hugo/content/en/docs/howto/validate-json-using-go-api/index.md new file mode 100644 index 0000000000..ad0045ab07 --- /dev/null +++ b/hugo/content/en/docs/howto/validate-json-using-go-api/index.md @@ -0,0 +1,174 @@ +--- +title: Validate JSON using the Go API +tags: +- go api +- validation +- encodings +authors: +- myitcv +toc_hide: true +--- + +This guide demonstrates how to write a Go program that validates JSON files +using an embeded CUE schema. + +## Set up some schema and data files + +{{< step stepNumber="1" >}} +Create a CUE schema. + +You can use any schema that's relevant to your specific data, but our example uses this simple CUE: + +{{< code-tabs >}} +{{< code-tab name="schema.cue" language="cue" area="top-left" >}} +#Schema: { + name?: string + age?: int +} +{{< /code-tab >}}{{< /code-tabs >}} +{{< /step >}} + + +{{< step stepNumber="2" >}} +Create some known-good and known-bad test data. + +You may already have some representative test data. This data is relevant to our example schema: + +{{< code-tabs >}} +{{< code-tab name="good.json" language="json" area="top-left" >}} +{ + "name": "Charlie Cartwright", + "age": 80 +} +{{< /code-tab >}}{{< /code-tabs >}} + +{{< code-tabs >}} +{{< code-tab name="bad.json" language="json" area="top-left" >}} +{ + "name": [ + "Moby", + "Dick" + ], + "age": "173" +} +{{< /code-tab >}}{{< /code-tabs >}} +{{< /step >}} + +{{< step stepNumber="3" >}} +Verify that your schema accepts and rejects your test data appropriately. + +Our example schema is contained in the `#Schema` definition, which we reference explicitly: + +```text { title="TERMINAL" codeToCopy="Y3VlIHZldCBzY2hlbWEuY3VlIGdvb2QuanNvbiAtZCAnI1NjaGVtYScKY3VlIHZldCBzY2hlbWEuY3VlIGJhZC5qc29uIC1kICcjU2NoZW1hJw==" } +$ cue vet schema.cue good.json -d '#Schema' +$ cue vet schema.cue bad.json -d '#Schema' +age: conflicting values "173" and int (mismatched types string and int): + ./bad.json:6:12 + ./schema.cue:3:9 +name: conflicting values string and ["Moby","Dick"] (mismatched types string and list): + ./bad.json:2:13 + ./schema.cue:2:9 +``` +{{< /step >}} + +## Create a Go program + +{{< step stepNumber="4" >}} +Initialize a Go module, or use an existing one if that's more suitable for your situation: + +```text { title="TERMINAL" codeToCopy="Z28gbW9kIGluaXQgZ28uZXhhbXBsZQ==" } +$ go mod init go.example +... +``` +{{< /step >}} + +{{< step stepNumber="5" >}} +Create a main program. + +You can use this example code as a starting point: + +{{< code-tabs >}} +{{< code-tab name="main.go" language="go" area="top-left" >}} +package main + +import ( + "fmt" + "log" + "os" + + _ "embed" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" + "cuelang.org/go/encoding/json" +) + +// Embed our schema in a Go string variable. +// +//go:embed schema.cue +var cueSource string + +func main() { + ctx := cuecontext.New() + + // Build the schema + schema := ctx.CompileString(cueSource).LookupPath(cue.ParsePath("#Schema")) + + // Load the JSON file specified (the program's sole argument) as a CUE value + dataFilename := os.Args[1] + dataFile, err := os.ReadFile(dataFilename) + if err != nil { + log.Fatal(err) + } + dataExpr, err := json.Extract(dataFilename, dataFile) + if err != nil { + log.Fatal(err) + } + dataAsCUE := ctx.BuildExpr(dataExpr) + + // Validate the JSON data using the schema + unified := schema.Unify(dataAsCUE) + if err := unified.Validate(); err != nil { + fmt.Println("❌ JSON: NOT ok") + log.Fatal(err) + } + + fmt.Println("✅ JSON: ok") +} +{{< /code-tab >}}{{< /code-tabs >}} + +This example code embeds the CUE from `schema.cue` and uses it to validate a +single JSON file, printing the validation result to its standard output stream. +{{< /step >}} + +{{< step stepNumber="6" >}} +Add a dependency on `cuelang.org/go` and ensure the Go module is tidy: +```text { title="TERMINAL" codeToCopy="Z28gZ2V0IGN1ZWxhbmcub3JnL2dvQHYwLjguMgpnbyBtb2QgdGlkeQ==" } +$ go get cuelang.org/go@v0.8.2 +... +$ go mod tidy +... +``` +{{< /step >}} + +## Run the Go program + +{{< step stepNumber="7" >}} +Verify that the Go program accepts and rejects your test data as expected: + +```text { title="TERMINAL" codeToCopy="Z28gcnVuIC4gZ29vZC5qc29uCmdvIHJ1biAuIGJhZC5qc29u" } +$ go run . good.json +✅ JSON: ok +$ go run . bad.json +❌ JSON: NOT ok +main.go:42: #Schema.name: conflicting values string and ["Moby","Dick"] (mismatched types string and list) (and 1 more errors) +exit status 1 +``` +{{< /step >}} + +## Related content + +- Go API: [`cue`](https://pkg.go.dev/cuelang.org/go/cue#section-documentation) -- package documentation +- Go API: [`cue/cuecontext`](https://pkg.go.dev/cuelang.org/go/cue/cuecontext#section-documentation) -- package documentation +- Go API: [`encoding/json`](https://pkg.go.dev/cuelang.org/go/encoding/json#section-documentation) -- package documentation +- Tag: {{< tag "go api" >}} -- pages explaining and exploring CUE's Go API