Skip to content

Commit

Permalink
Check for output type agreement during the compile phase (#992)
Browse files Browse the repository at this point in the history
* Check for output type agreement during the compile phase
* Updated comment and nil safety of OutputType
  • Loading branch information
TristonianJones authored Aug 2, 2024
1 parent 1f51886 commit 5bcdb8b
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 3 deletions.
50 changes: 48 additions & 2 deletions policy/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ func (r *CompiledRule) Matches() []*CompiledMatch {
return r.matches[:]
}

// OutputType returns the output type of the first match clause as all match clauses
// are validated for agreement prior to construction fo the CompiledRule.
func (r *CompiledRule) OutputType() *cel.Type {
// It's a compilation error if the output types of the matches don't agree
for _, m := range r.Matches() {
return m.OutputType()
}
return cel.DynType
}

// CompiledVariable represents the variable name, expression, and associated type-check declaration.
type CompiledVariable struct {
id int64
Expand Down Expand Up @@ -105,6 +115,17 @@ func (m *CompiledMatch) NestedRule() *CompiledRule {
return m.nestedRule
}

// OutputType returns the cel.Type associated with output expression.
func (m *CompiledMatch) OutputType() *cel.Type {
if m.output != nil {
return m.output.Expr().OutputType()
}
if m.nestedRule != nil {
return m.nestedRule.OutputType()
}
return cel.DynType
}

// OutputValue represents the output expression associated with a match block.
type OutputValue struct {
id int64
Expand Down Expand Up @@ -263,11 +284,36 @@ func (c *compiler) compileRule(r *Rule, ruleEnv *cel.Env, iss *cel.Issues) (*Com
}
}
}
return &CompiledRule{

rule := &CompiledRule{
id: r.id,
variables: compiledVars,
matches: compiledMatches,
}, iss
}
// Validate type agreement between the different match outputs
c.checkMatchOutputTypesAgree(rule, iss)
return rule, iss
}

func (c *compiler) checkMatchOutputTypesAgree(rule *CompiledRule, iss *cel.Issues) {
var outputType *cel.Type
for _, m := range rule.Matches() {
if outputType == nil {
outputType = m.OutputType()
if outputType.TypeName() == "error" {
outputType = nil
continue
}
}
matchOutputType := m.OutputType()
if matchOutputType.TypeName() == "error" {
continue
}
if !outputType.IsAssignableType(matchOutputType) {
iss.ReportErrorAtID(m.Output().ID(), "incompatible output types: %s not assignable to %s", outputType, matchOutputType)
return
}
}
}

func (c *compiler) relSource(pstr ValueString) *RelativeSource {
Expand Down
5 changes: 4 additions & 1 deletion policy/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,10 @@ ERROR: testdata/errors/policy.yaml:31:75: Syntax error: extraneous input ']' exp
| ..........................................................................^
ERROR: testdata/errors/policy.yaml:34:67: undeclared reference to 'format' (in container '')
| "invalid values provided on one or more labels: %s".format([variables.invalid])
| ..................................................................^`,
| ..................................................................^
ERROR: testdata/errors/policy.yaml:38:16: incompatible output types: bool not assignable to string
| output: "'false'"
| ...............^`,
},
{
name: "limits",
Expand Down
4 changes: 4 additions & 0 deletions policy/testdata/errors/policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@ rule:
- condition: variables.invalid.size() > 0
output: |
"invalid values provided on one or more labels: %s".format([variables.invalid])
- condition: "1 > 0"
output: "true"
- condition: "1 < 0"
output: "'false'"

0 comments on commit 5bcdb8b

Please sign in to comment.