Skip to content

Commit

Permalink
docs/howto: use the built-in function "matchN"
Browse files Browse the repository at this point in the history
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.

Also: tweak installation page so that its install-from-source language
and examples include those required by the new guide -- which is the
first page on the site to demonstrate an unreleased feature, and hence
needs to refer to installation of tip from source.

Fixes cue-lang/docs-and-content#176.

Preview-Path: /docs/howto/use-the-built-in-function-matchn/
Preview-Path: /docs/introduction/installation/
Signed-off-by: Jonathan Matthews <[email protected]>
Change-Id: Ibc59f98dfc1f7bda95a5aa1baf13c64bf8b3d753
Dispatch-Trailer: {"type":"trybot","CL":1199805,"patchset":6,"ref":"refs/changes/05/1199805/6","targetBranch":"master"}
  • Loading branch information
jpluscplusm authored and cueckoo committed Aug 23, 2024
1 parent 54018cc commit 4e00fcd
Show file tree
Hide file tree
Showing 7 changed files with 739 additions and 11 deletions.
234 changes: 234 additions & 0 deletions content/docs/howto/use-the-built-in-function-matchn/en.md
Original file line number Diff line number Diff line change
@@ -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}}}

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

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**:

<!-- We use upload/script pairs because code blocks can't access non-default
versions of CUE cf. https://cuelang.org/issues/3265 -->
{{{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 {{<issue 3389/>}}.
156 changes: 156 additions & 0 deletions content/docs/howto/use-the-built-in-function-matchn/gen_cache.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package site
{
content: {
docs: {
howto: {
"use-the-built-in-function-matchn": {
page: {
cache: {
upload: {
basic: "SfLeiaEtBbY2Z1uAKQxqPgZRnVnAigkNGVGGEjBd8Hg="
"one of": "KsOHLEak2geBiFpCZ8M02L0T0D2Jk24FUmKgfKRV94g="
"any of": "fLYBc+EKqae7YFdt/9S0VwqrhMLnQ/RVqu7wa9PnZ+s="
"all of": "y/gGG/+4Fu/gODhEr8q/Y07mwrRFDH5WJF6e6vtcdII="
not: "vCbSEI3lBLMhfGaEgSg5ycOhU3n8Vb+6GWR6uS2DniU="
"all but one": "HVVlWZKcUdaGkpX6YiZnUXDmAPt1A/+jKEU2J3u/ASg="
composite: "TlhZD5Iho4AKKbK698YckQj71zwTj2P6ebS9oeSDERM="
}
multi_step: {
hash: "TOBO1JHJ8991VBLJJFK2M5CJVJB2E8NF5VAKNUH9ISLGTNVI558G===="
scriptHash: "JUDK13N4VCGVDLNM7Q2I29M46032UM3OS69SQSNOUJML5J3ONPB0===="
steps: [{
doc: ""
cmd: "export PATH=/cues/v0.11.0-0.dev.0.20240823082259-4967eb4077a9:$PATH"
exitCode: 0
output: ""
}, {
doc: "#ellipsis 1"
cmd: "cue version"
exitCode: 0
output: """
cue version v0.11.0-0.dev.0.20240823082259-4967eb4077a9
...
"""
}, {
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
"""
}]
}
}
}
}
}
}
}
}
3 changes: 3 additions & 0 deletions content/docs/howto/use-the-built-in-function-matchn/page.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package site

content: docs: howto: "use-the-built-in-function-matchn": page: _
Loading

0 comments on commit 4e00fcd

Please sign in to comment.