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 {{}}.