Skip to content

Commit

Permalink
Merge pull request #706 from vmware-tanzu/assert-one-of
Browse files Browse the repository at this point in the history
Add `one_of()` / `one_of=` — a value is within an enumeration
  • Loading branch information
pivotaljohn committed Jul 21, 2022
2 parents ac148ff + eaf68ab commit 36681d2
Show file tree
Hide file tree
Showing 15 changed files with 158 additions and 1 deletion.
7 changes: 7 additions & 0 deletions pkg/validations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
KwargMax string = "max"
KwargNotNull string = "not_null"
KwargOneNotNull string = "one_not_null"
KwargOneOf string = "one_of"
)

// ProcessAssertValidateAnns checks Assert annotations on data values and stores them on a Node as Validations.
Expand Down Expand Up @@ -179,6 +180,12 @@ func newValidationKwargs(kwargs []starlark.Tuple, annPos *filepos.Position) (val
default:
return validationKwargs{}, fmt.Errorf("expected True or a sequence of keys, but was a '%s'", value[1].Type())
}
case KwargOneOf:
v, ok := value[1].(starlark.Sequence)
if !ok {
return validationKwargs{}, fmt.Errorf("expected keyword argument %s to be a sequence, but was %s", KwargOneOf, value[1].Type())
}
processedKwargs.oneOf = v
default:
return validationKwargs{}, fmt.Errorf("unknown keyword argument %q (at %s)", kwargName, annPos.AsCompactString())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#@assert/validate max=10
foo: 11

+++

ERR:
- "foo" (stdin:2) requires "a value less than or equal to 10"; fail: 11 is more than 10 (by stdin:1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#@assert/validate max_len=5
value: "123456"

+++

ERR:
- "value" (stdin:2) requires "length less than or equal to 5"; fail: length of 6 is more than 5 (by stdin:1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#@assert/validate min=10
value: 9

+++

ERR:
- "value" (stdin:2) requires "a value greater or equal to 10"; fail: 9 is less than 10 (by stdin:1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#@assert/validate min_len=5
value: "1234"

+++

ERR:
- "value" (stdin:2) requires "length greater or equal to 5"; fail: length of 4 is less than 5 (by stdin:1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#@assert/validate not_null=True
value: null

+++

ERR:
- "value" (stdin:2) requires "not null"; fail: value is null (by stdin:1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#@assert/validate one_not_null=True
config:
foo: ""
bar: ""

+++

ERR:
- "config" (stdin:2) requires "exactly one child not null"; check: multiple values are not null ["foo" "bar"] (by stdin:1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#@assert/validate one_of=["debug","info","warning","error","fatal"]
foo: critical

+++

ERR:
- "foo" (stdin:2) requires "one of"; fail: critical not in ["debug", "info", "warning", "error", "fatal"] (by stdin:1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#@assert/validate one_of="one,two,three"
foo: two

+++

ERR: Invalid @assert/validate annotation - expected keyword argument one_of to be a sequence, but was string
7 changes: 7 additions & 0 deletions pkg/validations/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type validationKwargs struct {
max starlark.Value
notNull bool
oneNotNull starlark.Value // valid values are either starlark.Sequence or starlark.Bool
oneOf starlark.Sequence
}

// Run takes a root Node, and threadName, and validates each Node in the tree.
Expand Down Expand Up @@ -211,6 +212,12 @@ func (v validationKwargs) asRules() []rule {
assertion: assertion.CheckFunc(),
})
}
if v.oneOf != nil {
rules = append(rules, rule{
msg: fmt.Sprintf("one of"),
assertion: yttlibrary.NewAssertOneOf(v.oneOf).CheckFunc(),
})
}

return rules
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/validations/validations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestMain(m *testing.M) {
}

// TestValidations verifies that the validations mechanism correctly:
// 1. parses `@assert/validate` annotations,
// 1. parses `@assert/validate` annotations (and because schema package delegates, `@schema/validation` too),
// 2. decides which rules run under various conditions
// 3. checks the rules
// 4. combines rule results into an appropriate validation outcome (list of error or passing)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#@ load("@ytt:assert", "assert")


one_of: #@ assert.try_to(lambda: assert.one_of())
check: #@ assert.try_to(lambda: assert.one_of(1,2).check())

+++

one_of:
- null
- 'assert.one_of: got 0 arguments, want at least 1'
check:
- null
- function lambda missing 1 argument (val)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#@ load("@ytt:assert", "assert")

check: #@ assert.try_to(lambda: assert.one_of([1,2]).check(1,2))

+++

check:
- null
- function lambda accepts 1 positional argument (2 given)
31 changes: 31 additions & 0 deletions pkg/yamltemplate/filetests/ytt-library/assert/one_of/main.tpltest
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#@ load("@ytt:assert", "assert")

pass:
list: #@ assert.one_of(["aws", "azure", "vsphere"]).check("azure")
tuple: #@ assert.one_of(( "aws", "azure", "vsphere" )).check("azure")
heterogeneous: #@ assert.one_of("aws", 4, False, {"azure": "vsphere"}).check({"azure": "vsphere"})
positional_args: #@ assert.one_of("aws", "azure", "vsphere").check("azure")
fail:
not_in_enum: #@ assert.try_to(lambda: assert.one_of("aws", "azure", "vsphere").check("not-in-set"))
enum_not_a_sequence:
int: #@ assert.try_to(lambda: assert.one_of(4))
string: #@ assert.try_to(lambda: assert.one_of("aws,azure,vsphere"))

+++

pass:
list: true
tuple: true
heterogeneous: true
positional_args: true
fail:
not_in_enum:
- null
- 'fail: not-in-set not in ["aws", "azure", "vsphere"]'
enum_not_a_sequence:
int:
- null
- 'assert.one_of: expected a sequence, but was a ''int'''
string:
- null
- 'assert.one_of: expected a sequence, but was a ''string'''
32 changes: 32 additions & 0 deletions pkg/yttlibrary/assert.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func (m AssertModule) AsModule() starlark.StringDict {
members["max_len"] = starlark.NewBuiltin("assert.max_len", core.ErrWrapper(m.MaxLen))
members["not_null"] = starlark.NewBuiltin("assert.not_null", core.ErrWrapper(m.NotNull))
members["one_not_null"] = starlark.NewBuiltin("assert.one_not_null", core.ErrWrapper(m.OneNotNull))
members["one_of"] = starlark.NewBuiltin("assert.one_of", core.ErrWrapper(m.OneOf))
}
return starlark.StringDict{
"assert": &starlarkstruct.Module{
Expand Down Expand Up @@ -382,6 +383,37 @@ func (m AssertModule) oneNotNullCheck(keys starlark.Sequence) core.StarlarkFunc
}
}

// NewAssertOneOf produces an Assertion that a given value is one of a pre-defined set.
//
// see also:https://github.com/google/starlark-go/blob/master/doc/spec.md#membership-tests
func NewAssertOneOf(enum starlark.Sequence) *Assertion {
return NewAssertionFromSource(
"assert.one_of",
`lambda val: True if yaml.decode(yaml.encode(val)) in yaml.decode(yaml.encode(enum)) else fail("{} not in {}".format(yaml.decode(yaml.encode(val)), yaml.decode(yaml.encode(enum))))`,
starlark.StringDict{"enum": enum, "yaml": YAMLAPI["yaml"]},
)
}

// OneOf is a core.StarlarkFunc wrapping NewAssertOneOf()
func (m AssertModule) OneOf(thread *starlark.Thread, f *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if args.Len() == 0 {
return starlark.None, fmt.Errorf("got %d arguments, want at least %d", args.Len(), 1)
}

enum := args[0]
if args.Len() > 1 {
enum = args
}

seq, ok := enum.(starlark.Sequence)
if !ok {
return nil, fmt.Errorf("expected a sequence, but was a '%s'", enum.Type())
}

maxFunc := NewAssertOneOf(seq)
return maxFunc, nil
}

func (m AssertModule) maybeSuggestKey(given starlark.Value, expected starlark.Dict) string {
var keysAsStrings []string
for _, k := range expected.Keys() {
Expand Down

0 comments on commit 36681d2

Please sign in to comment.