sg/internal/result/result.go (70 lines of code) (raw):
package result
import (
"fmt"
"github.com/open-policy-agent/opa/rego"
)
func empty(query string) Result {
return Result{Query: query}
}
func fromString(query string, message string) Result {
return Result{
Query: query,
Message: message,
}
}
func fromMetadata(query string, metadata map[string]interface{}) (Result, error) {
const msgField = "msg"
if _, ok := metadata[msgField]; !ok {
return Result{}, fmt.Errorf("rule missing %s field: %v", msgField, metadata)
}
if _, ok := metadata[msgField].(string); !ok {
return Result{}, fmt.Errorf("%s field must be string: %v", msgField, metadata)
}
rv := fromString(query, metadata[msgField].(string))
rv.Metadata = make(map[string]interface{})
for k, v := range metadata {
if k == msgField {
continue
}
rv.Metadata[k] = v
}
return rv, nil
}
// FromRegoExpression resolves result from rego expression.
// Based on: https://github.com/open-policy-agent/conftest/blob/f18b7bbde2fdbd766c8348dff3a0a24792eb98c7/policy/engine.go#L439-L443
func FromRegoExpression(
query string,
expression *rego.ExpressionValue,
) ([]Result, error) {
// Rego rules that are intended for evaluation should return a slice of values.
// For example, deny[msg] or violation[{"msg": msg}].
//
// When an expression does not have a slice of values, the expression did not
// evaluate to true, and no message was returned.
var expressionValues []interface{}
if v, ok := expression.Value.([]interface{}); ok {
expressionValues = v
}
if len(expressionValues) == 0 {
return []Result{empty(query)}, nil
}
var rv []Result
for _, v := range expressionValues {
switch val := v.(type) {
// Policies that only return a single string (e.g. deny[msg])
case string:
rv = append(rv, fromString(query, val))
// Policies that return metadata (e.g. deny[{"msg": msg}])
case map[string]interface{}:
v, err := fromMetadata(query, val)
if err != nil {
return nil, fmt.Errorf("failed to load from metadata: %w", err)
}
rv = append(rv, v)
}
}
return rv, nil
}
// Passed tells if the result is passed.
func (r Result) Passed() bool {
return r.Message == ""
}
// Merge merges two results into a new one.
// The new result uses Source from the first result.
func (qr QueryResults) Merge(other QueryResults) QueryResults {
return QueryResults{
Source: qr.Source,
Successes: qr.Successes + other.Successes,
Failures: append(qr.Failures, other.Failures...),
Warnings: append(qr.Warnings, other.Warnings...),
Exceptions: append(qr.Exceptions, other.Exceptions...),
}
}