From 74d687f0c7eebf698e3922367c8085bf41c727df Mon Sep 17 00:00:00 2001 From: Jonathan Matthews Date: Wed, 21 Aug 2024 11:27:41 +0100 Subject: [PATCH] docs/howto: use the built-in function "matchN" This adds a guide demonstrating different uses for the new "matchN" built-in function. The guide contains several separate sections which have use cases aligned with common phrases such as "all of" and "any of", insofar as they apply to the checking of some list of constraints. A final section captures a couple of more involved uses of the function which didn't make sense to include earlier on the page. A sub-optimal example demonstrating matchN validing a list value is included because I felt it was important to show that a list *can* be validated, but unfortunately I can't construct a list-based example that has /nice/ error reporting. This is tracked in cue-lang/cue#3389. Fixes cue-lang/docs-and-content#176. Preview-Path: /docs/howto/use-the-built-in-function-matchn/ Signed-off-by: Jonathan Matthews Change-Id: Ibc59f98dfc1f7bda95a5aa1baf13c64bf8b3d753 Dispatch-Trailer: {"type":"trybot","CL":1199805,"patchset":10,"ref":"refs/changes/05/1199805/10","targetBranch":"master"} --- .../use-the-built-in-function-matchn/en.md | 234 ++++++++++++++ .../gen_cache.cue | 156 +++++++++ .../use-the-built-in-function-matchn/page.cue | 3 + .../use-the-built-in-function-matchn/index.md | 295 ++++++++++++++++++ 4 files changed, 688 insertions(+) create mode 100644 content/docs/howto/use-the-built-in-function-matchn/en.md create mode 100644 content/docs/howto/use-the-built-in-function-matchn/gen_cache.cue create mode 100644 content/docs/howto/use-the-built-in-function-matchn/page.cue create mode 100644 hugo/content/en/docs/howto/use-the-built-in-function-matchn/index.md diff --git a/content/docs/howto/use-the-built-in-function-matchn/en.md b/content/docs/howto/use-the-built-in-function-matchn/en.md new file mode 100644 index 000000000..446fa5841 --- /dev/null +++ b/content/docs/howto/use-the-built-in-function-matchn/en.md @@ -0,0 +1,234 @@ +--- +title: Using the built-in function "matchN" as a field validator +tags: [commented cue] +authors: [jpluscplusm] +toc_hide: true +--- + +{{{with _script_ "en" "HIDDEN: access to cue tip"}}} +export PATH=/cues/$CUELANG_CUE_TIP:$PATH +{{{end}}} + +{{}} +This guide demonstrates a CUE feature that isn't yet available in a release. +It is only available in the `cue` command and Go API when they are +[installed from source]({{< relref "/docs/introduction/installation" >}}#install-from-source) +using the `@master` version selector. +{{}} + +This [Commented CUE]({{< relref "docs/howto#commented-cue-guides" >}}) +demonstrates how to use the +[built-in]({{< relref "docs/reference/glossary#built-in-functions" >}}) +function `matchN()` as a field validator. This flexible function allows CUE +constraints to be bound together in a wide range of combinations, including +"one of", "any of", "all of", and "none of"/"not". + +This guide uses the following unreleased version of CUE: + +{{{with script "en" "cue version"}}} +#ellipsis 1 +cue version +{{{end}}} + +## Basic use + +The `matchN()` function takes two arguments: + +1. a **number constraint**; +2. a **list of constraints**. + +The function validates a field's value by unifying the value with each item in +the **list of constraints** in turn, and keeping count of how many list items +the field's value is able to unify with. A field's value is valid if the count +unifies successfully with the **number constraint**: + + +{{{with upload "en" "basic"}}} +-- basic.cue -- +package basic + +A: 42 +// A validates successfully. +A: matchN(1, [int]) +A: matchN(2, [int, >10]) +A: matchN(2, [int, >10, >100]) + +B: 42 +// B fails to validate. +B: matchN(1, [int, >10]) +B: matchN(3, [int, >10, >100]) +{{{end}}} +{{{with script "en" "basic"}}} +! cue vet .:basic +{{{end}}} + +## "One of" + +With its **number constraint** set to `1` the `matchN()` function checks that a +field's value unifies successfully with just one of the **list of +constraints**: + +{{{with upload "en" "one of"}}} +-- one-of.cue -- +package oneOf + +A: 42 +// A validates successfully. +A: matchN(1, [int]) +A: matchN(1, [>10, >100, string]) + +B: 42 +// B fails to validate. +B: matchN(1, [int, >10]) +B: matchN(1, [string, >100]) +{{{end}}} +{{{with script "en" "one of"}}} +! cue vet .:oneOf +{{{end}}} + +## "Any of" + +If the **number constraint** is set to `>0`, the function checks that at least +one of the **list of constraints** unifies with the field's value: + +{{{with upload "en" "any of"}}} +-- any-of.cue -- +package anyOf + +A: 42 +// A validates successfully. +A: matchN(>0, [>10]) +A: matchN(>0, [int, >0, >100, string]) + +B: 42 +// B fails to validate. +B: matchN(1, [int, >0]) +B: matchN(>0, [string, >100]) +{{{end}}} +{{{with script "en" "any of"}}} +! cue vet .:anyOf +{{{end}}} + +## "All of" + +To check that a field's value unifies successfully with all of the **list of +constraints**, set the **number constraint** to a value matching the number of +items in the list: + +{{{with upload "en" "all of"}}} +-- all-of.cue -- +package allOf + +import "math" + +A: 42 +// A validates successfully. +A: matchN(1, [int]) +A: matchN(2, [int, >10]) +A: matchN(4, [int, >10, <100, math.MultipleOf(2)]) + +B: 42 +// B fails to validate. +B: matchN(3, [int, >10, >100]) +B: matchN(4, [int, >10, <100, math.MultipleOf(41)]) +{{{end}}} +{{{with script "en" "all of"}}} +! cue vet .:allOf +{{{end}}} + +## "Not" + +If you set the **number constraint** to `0` then `matchN()` checks that a +field's value doesn't unify successfully with any of the **list of +constraints**: + +{{{with upload "en" "not"}}} +-- not.cue -- +package not + +import ( + "strings" + "struct" +) + +A: 42 +// A validates successfully. +A: matchN(0, [string]) +A: matchN(0, [bytes, struct.MinFields(0)]) +A: matchN(0, [>100, strings.HasPrefix("4")]) + +B: 42 +// B fails to validate. +B: matchN(0, [int]) +B: matchN(0, [string, number]) +B: matchN(0, [42, >100, strings.HasSuffix(2)]) +{{{end}}} +{{{with script "en" "not"}}} +! cue vet .:not +{{{end}}} + +## More complex uses + +### References + +Either argument to `matchN()` can be resolved through a reference: + +{{{with upload "en" "all but one"}}} +-- all-but-one.cue -- +package allButOne + +// A validates successfully. +A: 42 +A: matchN(len(#C)-1, #C) + +// B fails to validate. +B: 42.0 +B: matchN(len(#C)-1, #C) + +#C: [number, int, >100] +{{{end}}} +{{{with script "en" "all but one"}}} +! cue vet .:allButOne +{{{end}}} + +### Composite data structures + +The `matchN()` function can validate composite data structures, not just +primitive values. Use it with both structs and lists: + +{{{with upload "en" "composite"}}} +-- composite.cue -- +package composite + +// A validates successfully. +A: matchN(>0, [#C1, #C2]) & { + x: 42 + y: "42" +} +// B fails to validate. +B: matchN(>0, [#C1, #C2]) & { + x: "4.2" + y: "4.2" + z: "4.2" +} +#C1: {x: int, ...} +#C2: { + z: float + y: string + ... +} + +// D validates successfully. +D: [1, 2, 3] & matchN(1, [#F1, #F2, #F3]) +// E fails to validate. +E: [11, 12, 13] & matchN(1, [#F1, #F2, #F3]) +#F1: [...>0] +#F2: [...>10] +#F3: [...>100] +{{{end}}} +{{{with script "en" "composite"}}} +! cue vet .:composite +{{{end}}} + +The sub-optimal error reporting for field `E` is tracked in {{}}. diff --git a/content/docs/howto/use-the-built-in-function-matchn/gen_cache.cue b/content/docs/howto/use-the-built-in-function-matchn/gen_cache.cue new file mode 100644 index 000000000..c3057ec7e --- /dev/null +++ b/content/docs/howto/use-the-built-in-function-matchn/gen_cache.cue @@ -0,0 +1,156 @@ +package site +{ + content: { + docs: { + howto: { + "use-the-built-in-function-matchn": { + page: { + cache: { + upload: { + basic: "BVCBRnKJG+wNRZrJOR04Rip8lj9gDNjO8nuavk2vYYg=" + "one of": "zFAI8Lz70zs7s8ltNgV2f7FidjP/XtkFyb8X3yz+0mI=" + "any of": "PSrMgOzunzkYDQ2cM614Gbo69iN6G5zQXKbsaEBWo7g=" + "all of": "nqmwdA3y+knBFRRrmIV59aIQo/D+z+OueCjyGin1/wQ=" + not: "zbtzhyLu7xy37xMBOo1JDbU0ibFAIL4wdNwhGQPX/DU=" + "all but one": "fWgky6e5aUCD5GXEXZuaL/byX7/u4TRLamKxR4g85tg=" + composite: "ZZjARSuVJMIo2mm7vc0nidivFMCtQtfp03GdBVC+6do=" + } + multi_step: { + hash: "OKNQ7BA00BLUULEN7NVHS2P18L4JENKTBUTNL4T26U33713CRP9G====" + scriptHash: "C2VPTFEKLRKCJQFHON0O2GLSQC0FCHTJGF4JKPJRCINNR5QQSPHG====" + steps: [{ + doc: "" + cmd: "export PATH=/cues/v0.11.0-0.dev.0.20240904084811-bb24c7ce1f04:$PATH" + exitCode: 0 + output: "" + }, { + doc: "#ellipsis 1" + cmd: "cue version" + exitCode: 0 + output: """ + cue version v0.11.0-0.dev.0.20240904084811-bb24c7ce1f04 + ... + + """ + }, { + doc: "" + cmd: "cue vet .:basic" + exitCode: 1 + output: """ + B: invalid value 42 (does not satisfy matchN(1, [int,>10])): 2 matched, expected 1: + ./basic.cue:11:4 + ./basic.cue:9:4 + ./basic.cue:11:11 + ./basic.cue:12:4 + B: invalid value 42 (does not satisfy matchN(3, [int,>10,>100])): 2 matched, expected 3: + ./basic.cue:12:4 + ./basic.cue:9:4 + ./basic.cue:11:4 + ./basic.cue:12:11 + + """ + }, { + doc: "" + cmd: "cue vet .:oneOf" + exitCode: 1 + output: """ + B: invalid value 42 (does not satisfy matchN(1, [string,>100])): 0 matched, expected 1: + ./one-of.cue:11:4 + ./one-of.cue:8:4 + ./one-of.cue:10:4 + ./one-of.cue:11:11 + + """ + }, { + doc: "" + cmd: "cue vet .:anyOf" + exitCode: 1 + output: """ + B: invalid value 42 (does not satisfy matchN(1, [int,>0])): 2 matched, expected 1: + ./any-of.cue:10:4 + ./any-of.cue:8:4 + ./any-of.cue:10:11 + ./any-of.cue:11:4 + B: invalid value 42 (does not satisfy matchN(>0, [string,>100])): 0 matched, expected >0: + ./any-of.cue:11:4 + ./any-of.cue:8:4 + ./any-of.cue:10:4 + ./any-of.cue:11:11 + + """ + }, { + doc: "" + cmd: "cue vet .:allOf" + exitCode: 1 + output: """ + B: invalid value 42 (does not satisfy matchN(3, [int,>10,>100])): 2 matched, expected 3: + ./all-of.cue:13:4 + ./all-of.cue:11:4 + ./all-of.cue:13:11 + ./all-of.cue:14:4 + B: invalid value 42 (does not satisfy matchN(4, [int,>10,<100,math.MultipleOf(41)])): 3 matched, expected 4: + ./all-of.cue:14:4 + ./all-of.cue:11:4 + ./all-of.cue:13:4 + ./all-of.cue:14:11 + + """ + }, { + doc: "" + cmd: "cue vet .:not" + exitCode: 1 + output: """ + B: invalid value 42 (does not satisfy matchN(0, [int])): 1 matched, expected 0: + ./not.cue:16:4 + ./not.cue:14:4 + ./not.cue:16:11 + ./not.cue:17:4 + ./not.cue:18:4 + B: invalid value 42 (does not satisfy matchN(0, [string,number])): 1 matched, expected 0: + ./not.cue:17:4 + ./not.cue:14:4 + ./not.cue:16:4 + ./not.cue:17:11 + ./not.cue:18:4 + B: invalid value 42 (does not satisfy matchN(0, [42,>100,strings.HasSuffix(2)])): 1 matched, expected 0: + ./not.cue:18:4 + ./not.cue:14:4 + ./not.cue:16:4 + ./not.cue:17:4 + ./not.cue:18:11 + + """ + }, { + doc: "" + cmd: "cue vet .:allButOne" + exitCode: 1 + output: """ + B: invalid value 42.0 (does not satisfy matchN(2, [number,int,>100])): 1 matched, expected 2: + ./all-but-one.cue:9:4 + ./all-but-one.cue:8:4 + ./all-but-one.cue:9:11 + + """ + }, { + doc: "" + cmd: "cue vet .:composite" + exitCode: 1 + output: """ + B: invalid value {x:"4.2",y:"4.2",z:"4.2"} (does not satisfy matchN(>0, [{x:int},{z:float,y:string}])): 0 matched, expected >0: + ./composite.cue:9:4 + ./composite.cue:9:11 + E: invalid value [11,12,13] (does not satisfy matchN(1, [[],[],[]])): 2 matched, expected 1: + ./composite.cue:24:19 + ./composite.cue:24:4 + ./composite.cue:24:26 + + """ + }] + } + } + } + } + } + } + } +} diff --git a/content/docs/howto/use-the-built-in-function-matchn/page.cue b/content/docs/howto/use-the-built-in-function-matchn/page.cue new file mode 100644 index 000000000..d1b59cb94 --- /dev/null +++ b/content/docs/howto/use-the-built-in-function-matchn/page.cue @@ -0,0 +1,3 @@ +package site + +content: docs: howto: "use-the-built-in-function-matchn": page: _ diff --git a/hugo/content/en/docs/howto/use-the-built-in-function-matchn/index.md b/hugo/content/en/docs/howto/use-the-built-in-function-matchn/index.md new file mode 100644 index 000000000..48df154b9 --- /dev/null +++ b/hugo/content/en/docs/howto/use-the-built-in-function-matchn/index.md @@ -0,0 +1,295 @@ +--- +title: Using the built-in function "matchN" as a field validator +tags: [commented cue] +authors: [jpluscplusm] +toc_hide: true +--- + +{{}} +This guide demonstrates a CUE feature that isn't yet available in a release. +It is only available in the `cue` command and Go API when they are +[installed from source]({{< relref "/docs/introduction/installation" >}}#install-from-source) +using the `@master` version selector. +{{}} + +This [Commented CUE]({{< relref "docs/howto#commented-cue-guides" >}}) +demonstrates how to use the +[built-in]({{< relref "docs/reference/glossary#built-in-functions" >}}) +function `matchN()` as a field validator. This flexible function allows CUE +constraints to be bound together in a wide range of combinations, including +"one of", "any of", "all of", and "none of"/"not". + +This guide uses the following unreleased version of CUE: + +```text { title="TERMINAL" type="terminal" codeToCopy="Y3VlIHZlcnNpb24=" } +$ cue version +cue version v0.11.0-0.dev.0.20240904084811-bb24c7ce1f04 +... +``` + +## Basic use + +The `matchN()` function takes two arguments: + +1. a **number constraint**; +2. a **list of constraints**. + +The function validates a field's value by unifying the value with each item in +the **list of constraints** in turn, and keeping count of how many list items +the field's value is able to unify with. A field's value is valid if the count +unifies successfully with the **number constraint**: + + +{{< code-tabs >}} +{{< code-tab name="basic.cue" language="cue" area="top-left" >}} +package basic + +A: 42 +// A validates successfully. +A: matchN(1, [int]) +A: matchN(2, [int, >10]) +A: matchN(2, [int, >10, >100]) + +B: 42 +// B fails to validate. +B: matchN(1, [int, >10]) +B: matchN(3, [int, >10, >100]) +{{< /code-tab >}}{{< /code-tabs >}} +```text { title="TERMINAL" type="terminal" codeToCopy="Y3VlIHZldCAuOmJhc2lj" } +$ cue vet .:basic +B: invalid value 42 (does not satisfy matchN(1, [int,>10])): 2 matched, expected 1: + ./basic.cue:11:4 + ./basic.cue:9:4 + ./basic.cue:11:11 + ./basic.cue:12:4 +B: invalid value 42 (does not satisfy matchN(3, [int,>10,>100])): 2 matched, expected 3: + ./basic.cue:12:4 + ./basic.cue:9:4 + ./basic.cue:11:4 + ./basic.cue:12:11 +``` + +## "One of" + +With its **number constraint** set to `1` the `matchN()` function checks that a +field's value unifies successfully with just one of the **list of +constraints**: + +{{< code-tabs >}} +{{< code-tab name="one-of.cue" language="cue" area="top-left" >}} +package oneOf + +A: 42 +// A validates successfully. +A: matchN(1, [int]) +A: matchN(1, [>10, >100, string]) + +B: 42 +// B fails to validate. +B: matchN(1, [int, >10]) +B: matchN(1, [string, >100]) +{{< /code-tab >}}{{< /code-tabs >}} +```text { title="TERMINAL" type="terminal" codeToCopy="Y3VlIHZldCAuOm9uZU9m" } +$ cue vet .:oneOf +B: invalid value 42 (does not satisfy matchN(1, [string,>100])): 0 matched, expected 1: + ./one-of.cue:11:4 + ./one-of.cue:8:4 + ./one-of.cue:10:4 + ./one-of.cue:11:11 +``` + +## "Any of" + +If the **number constraint** is set to `>0`, the function checks that at least +one of the **list of constraints** unifies with the field's value: + +{{< code-tabs >}} +{{< code-tab name="any-of.cue" language="cue" area="top-left" >}} +package anyOf + +A: 42 +// A validates successfully. +A: matchN(>0, [>10]) +A: matchN(>0, [int, >0, >100, string]) + +B: 42 +// B fails to validate. +B: matchN(1, [int, >0]) +B: matchN(>0, [string, >100]) +{{< /code-tab >}}{{< /code-tabs >}} +```text { title="TERMINAL" type="terminal" codeToCopy="Y3VlIHZldCAuOmFueU9m" } +$ cue vet .:anyOf +B: invalid value 42 (does not satisfy matchN(1, [int,>0])): 2 matched, expected 1: + ./any-of.cue:10:4 + ./any-of.cue:8:4 + ./any-of.cue:10:11 + ./any-of.cue:11:4 +B: invalid value 42 (does not satisfy matchN(>0, [string,>100])): 0 matched, expected >0: + ./any-of.cue:11:4 + ./any-of.cue:8:4 + ./any-of.cue:10:4 + ./any-of.cue:11:11 +``` + +## "All of" + +To check that a field's value unifies successfully with all of the **list of +constraints**, set the **number constraint** to a value matching the number of +items in the list: + +{{< code-tabs >}} +{{< code-tab name="all-of.cue" language="cue" area="top-left" >}} +package allOf + +import "math" + +A: 42 +// A validates successfully. +A: matchN(1, [int]) +A: matchN(2, [int, >10]) +A: matchN(4, [int, >10, <100, math.MultipleOf(2)]) + +B: 42 +// B fails to validate. +B: matchN(3, [int, >10, >100]) +B: matchN(4, [int, >10, <100, math.MultipleOf(41)]) +{{< /code-tab >}}{{< /code-tabs >}} +```text { title="TERMINAL" type="terminal" codeToCopy="Y3VlIHZldCAuOmFsbE9m" } +$ cue vet .:allOf +B: invalid value 42 (does not satisfy matchN(3, [int,>10,>100])): 2 matched, expected 3: + ./all-of.cue:13:4 + ./all-of.cue:11:4 + ./all-of.cue:13:11 + ./all-of.cue:14:4 +B: invalid value 42 (does not satisfy matchN(4, [int,>10,<100,math.MultipleOf(41)])): 3 matched, expected 4: + ./all-of.cue:14:4 + ./all-of.cue:11:4 + ./all-of.cue:13:4 + ./all-of.cue:14:11 +``` + +## "Not" + +If you set the **number constraint** to `0` then `matchN()` checks that a +field's value doesn't unify successfully with any of the **list of +constraints**: + +{{< code-tabs >}} +{{< code-tab name="not.cue" language="cue" area="top-left" >}} +package not + +import ( + "strings" + "struct" +) + +A: 42 +// A validates successfully. +A: matchN(0, [string]) +A: matchN(0, [bytes, struct.MinFields(0)]) +A: matchN(0, [>100, strings.HasPrefix("4")]) + +B: 42 +// B fails to validate. +B: matchN(0, [int]) +B: matchN(0, [string, number]) +B: matchN(0, [42, >100, strings.HasSuffix(2)]) +{{< /code-tab >}}{{< /code-tabs >}} +```text { title="TERMINAL" type="terminal" codeToCopy="Y3VlIHZldCAuOm5vdA==" } +$ cue vet .:not +B: invalid value 42 (does not satisfy matchN(0, [int])): 1 matched, expected 0: + ./not.cue:16:4 + ./not.cue:14:4 + ./not.cue:16:11 + ./not.cue:17:4 + ./not.cue:18:4 +B: invalid value 42 (does not satisfy matchN(0, [string,number])): 1 matched, expected 0: + ./not.cue:17:4 + ./not.cue:14:4 + ./not.cue:16:4 + ./not.cue:17:11 + ./not.cue:18:4 +B: invalid value 42 (does not satisfy matchN(0, [42,>100,strings.HasSuffix(2)])): 1 matched, expected 0: + ./not.cue:18:4 + ./not.cue:14:4 + ./not.cue:16:4 + ./not.cue:17:4 + ./not.cue:18:11 +``` + +## More complex uses + +### References + +Either argument to `matchN()` can be resolved through a reference: + +{{< code-tabs >}} +{{< code-tab name="all-but-one.cue" language="cue" area="top-left" >}} +package allButOne + +// A validates successfully. +A: 42 +A: matchN(len(#C)-1, #C) + +// B fails to validate. +B: 42.0 +B: matchN(len(#C)-1, #C) + +#C: [number, int, >100] +{{< /code-tab >}}{{< /code-tabs >}} +```text { title="TERMINAL" type="terminal" codeToCopy="Y3VlIHZldCAuOmFsbEJ1dE9uZQ==" } +$ cue vet .:allButOne +B: invalid value 42.0 (does not satisfy matchN(2, [number,int,>100])): 1 matched, expected 2: + ./all-but-one.cue:9:4 + ./all-but-one.cue:8:4 + ./all-but-one.cue:9:11 +``` + +### Composite data structures + +The `matchN()` function can validate composite data structures, not just +primitive values. Use it with both structs and lists: + +{{< code-tabs >}} +{{< code-tab name="composite.cue" language="cue" area="top-left" >}} +package composite + +// A validates successfully. +A: matchN(>0, [#C1, #C2]) & { + x: 42 + y: "42" +} +// B fails to validate. +B: matchN(>0, [#C1, #C2]) & { + x: "4.2" + y: "4.2" + z: "4.2" +} +#C1: {x: int, ...} +#C2: { + z: float + y: string + ... +} + +// D validates successfully. +D: [1, 2, 3] & matchN(1, [#F1, #F2, #F3]) +// E fails to validate. +E: [11, 12, 13] & matchN(1, [#F1, #F2, #F3]) +#F1: [...>0] +#F2: [...>10] +#F3: [...>100] +{{< /code-tab >}}{{< /code-tabs >}} +```text { title="TERMINAL" type="terminal" codeToCopy="Y3VlIHZldCAuOmNvbXBvc2l0ZQ==" } +$ cue vet .:composite +B: invalid value {x:"4.2",y:"4.2",z:"4.2"} (does not satisfy matchN(>0, [{x:int},{z:float,y:string}])): 0 matched, expected >0: + ./composite.cue:9:4 + ./composite.cue:9:11 +E: invalid value [11,12,13] (does not satisfy matchN(1, [[],[],[]])): 2 matched, expected 1: + ./composite.cue:24:19 + ./composite.cue:24:4 + ./composite.cue:24:26 +``` + +The sub-optimal error reporting for field `E` is tracked in {{}}.