mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 03:41:45 +00:00
Add models from cel-policy-templates
This commit is contained in:
parent
d73403dc12
commit
a37dfa7f0e
303
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/decisions.go
vendored
Normal file
303
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/decisions.go
vendored
Normal file
@ -0,0 +1,303 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
)
|
||||
|
||||
// NewDecision returns an empty Decision instance.
|
||||
func NewDecision() *Decision {
|
||||
return &Decision{}
|
||||
}
|
||||
|
||||
// Decision contains a decision name, or reference to a decision name, and an output expression.
|
||||
type Decision struct {
|
||||
Name string
|
||||
Reference *cel.Ast
|
||||
Output *cel.Ast
|
||||
}
|
||||
|
||||
// DecisionValue represents a named decision and value.
|
||||
type DecisionValue interface {
|
||||
fmt.Stringer
|
||||
|
||||
// Name returns the decision name.
|
||||
Name() string
|
||||
|
||||
// IsFinal returns whether the decision value will change with additional rule evaluations.
|
||||
//
|
||||
// When a decision is final, additional productions and rules which may also trigger the same
|
||||
// decision may be skipped.
|
||||
IsFinal() bool
|
||||
}
|
||||
|
||||
// SingleDecisionValue extends the DecisionValue which contains a single decision value as well
|
||||
// as some metadata about the evaluation details and the rule that spawned the value.
|
||||
type SingleDecisionValue interface {
|
||||
DecisionValue
|
||||
|
||||
// Value returns the single value for the decision.
|
||||
Value() ref.Val
|
||||
|
||||
// Details returns the evaluation details, if present, that produced the value.
|
||||
Details() *cel.EvalDetails
|
||||
|
||||
// RuleID indicate which policy rule id within an instance that produced the decision.
|
||||
RuleID() int64
|
||||
}
|
||||
|
||||
// MultiDecisionValue extends the DecisionValue which contains a set of decision values as well as
|
||||
// the corresponding metadata about how each value was produced.
|
||||
type MultiDecisionValue interface {
|
||||
DecisionValue
|
||||
|
||||
// Values returns the collection of values produced for the decision.
|
||||
Values() []ref.Val
|
||||
|
||||
// Details returns the evaluation details for each value in the decision.
|
||||
// The value index correponds to the details index. The details may be nil.
|
||||
Details() []*cel.EvalDetails
|
||||
|
||||
// RulesIDs returns the rule id within an instance which produce the decision values.
|
||||
// The value index corresponds to the rule id index.
|
||||
RuleIDs() []int64
|
||||
}
|
||||
|
||||
// DecisionSelector determines whether the given decision is the decision set requested by the
|
||||
// caller.
|
||||
type DecisionSelector func(decision string) bool
|
||||
|
||||
// NewBoolDecisionValue returns a boolean decision with an initial value.
|
||||
func NewBoolDecisionValue(name string, value types.Bool) *BoolDecisionValue {
|
||||
return &BoolDecisionValue{
|
||||
name: name,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// BoolDecisionValue represents the decision value type associated with a decision.
|
||||
type BoolDecisionValue struct {
|
||||
name string
|
||||
value ref.Val
|
||||
isFinal bool
|
||||
details *cel.EvalDetails
|
||||
ruleID int64
|
||||
}
|
||||
|
||||
// And logically ANDs the current decision value with the incoming CEL value.
|
||||
//
|
||||
// And follows CEL semantics with respect to errors and unknown values where errors may be
|
||||
// absorbed or short-circuited away by subsequent 'false' values. When unkonwns are encountered
|
||||
// the unknown values combine and aggregate within the decision. Unknowns may also be absorbed
|
||||
// per CEL semantics.
|
||||
func (dv *BoolDecisionValue) And(other ref.Val) *BoolDecisionValue {
|
||||
v, vBool := dv.value.(types.Bool)
|
||||
if vBool && v == types.False {
|
||||
return dv
|
||||
}
|
||||
o, oBool := other.(types.Bool)
|
||||
if oBool && o == types.False {
|
||||
dv.value = types.False
|
||||
return dv
|
||||
}
|
||||
if vBool && oBool {
|
||||
return dv
|
||||
}
|
||||
dv.value = logicallyMergeUnkErr(dv.value, other)
|
||||
return dv
|
||||
}
|
||||
|
||||
// Details implements the SingleDecisionValue interface method.
|
||||
func (dv *BoolDecisionValue) Details() *cel.EvalDetails {
|
||||
return dv.details
|
||||
}
|
||||
|
||||
// Finalize marks the decision as immutable with additional input and indicates the rule and
|
||||
// evaluation details which triggered the finalization.
|
||||
func (dv *BoolDecisionValue) Finalize(details *cel.EvalDetails, rule Rule) DecisionValue {
|
||||
dv.details = details
|
||||
if rule != nil {
|
||||
dv.ruleID = rule.GetID()
|
||||
}
|
||||
dv.isFinal = true
|
||||
return dv
|
||||
}
|
||||
|
||||
// IsFinal returns whether the decision is final.
|
||||
func (dv *BoolDecisionValue) IsFinal() bool {
|
||||
return dv.isFinal
|
||||
}
|
||||
|
||||
// Or logically ORs the decision value with the incoming CEL value.
|
||||
//
|
||||
// The ORing logic follows CEL semantics with respect to errors and unknown values.
|
||||
// Errors may be absorbed or short-circuited away by subsequent 'true' values. When unkonwns are
|
||||
// encountered the unknown values combine and aggregate within the decision. Unknowns may also be
|
||||
// absorbed per CEL semantics.
|
||||
func (dv *BoolDecisionValue) Or(other ref.Val) *BoolDecisionValue {
|
||||
v, vBool := dv.value.(types.Bool)
|
||||
if vBool && v == types.True {
|
||||
return dv
|
||||
}
|
||||
o, oBool := other.(types.Bool)
|
||||
if oBool && o == types.True {
|
||||
dv.value = types.True
|
||||
return dv
|
||||
}
|
||||
if vBool && oBool {
|
||||
return dv
|
||||
}
|
||||
dv.value = logicallyMergeUnkErr(dv.value, other)
|
||||
return dv
|
||||
}
|
||||
|
||||
// Name implements the DecisionValue interface method.
|
||||
func (dv *BoolDecisionValue) Name() string {
|
||||
return dv.name
|
||||
}
|
||||
|
||||
// RuleID implements the SingleDecisionValue interface method.
|
||||
func (dv *BoolDecisionValue) RuleID() int64 {
|
||||
return dv.ruleID
|
||||
}
|
||||
|
||||
// String renders the decision value to a string for debug purposes.
|
||||
func (dv *BoolDecisionValue) String() string {
|
||||
var buf strings.Builder
|
||||
buf.WriteString(dv.name)
|
||||
buf.WriteString(": ")
|
||||
buf.WriteString(fmt.Sprintf("rule[%d] -> ", dv.ruleID))
|
||||
buf.WriteString(fmt.Sprintf("%v", dv.value))
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Value implements the SingleDecisionValue interface method.
|
||||
func (dv *BoolDecisionValue) Value() ref.Val {
|
||||
return dv.value
|
||||
}
|
||||
|
||||
// NewListDecisionValue returns a named decision value which contains a list of CEL values produced
|
||||
// by one or more policy instances and / or production rules.
|
||||
func NewListDecisionValue(name string) *ListDecisionValue {
|
||||
return &ListDecisionValue{
|
||||
name: name,
|
||||
values: []ref.Val{},
|
||||
details: []*cel.EvalDetails{},
|
||||
ruleIDs: []int64{},
|
||||
}
|
||||
}
|
||||
|
||||
// ListDecisionValue represents a named decision which collects into a list of values.
|
||||
type ListDecisionValue struct {
|
||||
name string
|
||||
values []ref.Val
|
||||
isFinal bool
|
||||
details []*cel.EvalDetails
|
||||
ruleIDs []int64
|
||||
}
|
||||
|
||||
// Append accumulates the incoming CEL value into the decision's value list.
|
||||
func (dv *ListDecisionValue) Append(val ref.Val, det *cel.EvalDetails, rule Rule) {
|
||||
dv.values = append(dv.values, val)
|
||||
dv.details = append(dv.details, det)
|
||||
// Rule ids may be null if the policy is a singleton.
|
||||
ruleID := int64(0)
|
||||
if rule != nil {
|
||||
ruleID = rule.GetID()
|
||||
}
|
||||
dv.ruleIDs = append(dv.ruleIDs, ruleID)
|
||||
}
|
||||
|
||||
// Details returns the list of evaluation details observed in computing the values in the decision.
|
||||
// The details indices correlate 1:1 with the value indices.
|
||||
func (dv *ListDecisionValue) Details() []*cel.EvalDetails {
|
||||
return dv.details
|
||||
}
|
||||
|
||||
// Finalize marks the list decision complete.
|
||||
func (dv *ListDecisionValue) Finalize() DecisionValue {
|
||||
dv.isFinal = true
|
||||
return dv
|
||||
}
|
||||
|
||||
// IsFinal implements the DecisionValue interface method.
|
||||
func (dv *ListDecisionValue) IsFinal() bool {
|
||||
return dv.isFinal
|
||||
}
|
||||
|
||||
// Name implements the DecisionValue interface method.
|
||||
func (dv *ListDecisionValue) Name() string {
|
||||
return dv.name
|
||||
}
|
||||
|
||||
// RuleIDs returns the list of rule ids which produced the evaluation results.
|
||||
// The indices of the ruleIDs correlate 1:1 with the value indices.
|
||||
func (dv *ListDecisionValue) RuleIDs() []int64 {
|
||||
return dv.ruleIDs
|
||||
}
|
||||
|
||||
func (dv *ListDecisionValue) String() string {
|
||||
var buf strings.Builder
|
||||
buf.WriteString(dv.name)
|
||||
buf.WriteString(": ")
|
||||
for i, v := range dv.values {
|
||||
if len(dv.ruleIDs) == len(dv.values) {
|
||||
buf.WriteString(fmt.Sprintf("rule[%d] -> ", dv.ruleIDs[i]))
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf("%v", v))
|
||||
buf.WriteString("\n")
|
||||
if i < len(dv.values)-1 {
|
||||
buf.WriteString("\t")
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Values implements the MultiDecisionValue interface method.
|
||||
func (dv *ListDecisionValue) Values() []ref.Val {
|
||||
return dv.values
|
||||
}
|
||||
|
||||
func logicallyMergeUnkErr(value, other ref.Val) ref.Val {
|
||||
vUnk := types.IsUnknown(value)
|
||||
oUnk := types.IsUnknown(other)
|
||||
if vUnk && oUnk {
|
||||
merged := types.Unknown{}
|
||||
merged = append(merged, value.(types.Unknown)...)
|
||||
merged = append(merged, other.(types.Unknown)...)
|
||||
return merged
|
||||
}
|
||||
if vUnk {
|
||||
return value
|
||||
}
|
||||
if oUnk {
|
||||
return other
|
||||
}
|
||||
if types.IsError(value) {
|
||||
return value
|
||||
}
|
||||
if types.IsError(other) {
|
||||
return other
|
||||
}
|
||||
return types.NewErr(
|
||||
"got values (%v, %v), wanted boolean values",
|
||||
value, other)
|
||||
}
|
121
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/decisions_test.go
vendored
Normal file
121
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/decisions_test.go
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
)
|
||||
|
||||
func TestBoolDecisionValue_And(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value types.Bool
|
||||
ands []ref.Val
|
||||
result ref.Val
|
||||
}{
|
||||
{
|
||||
name: "init_false_end_false",
|
||||
value: types.False,
|
||||
ands: []ref.Val{types.NewErr("err"), types.True},
|
||||
result: types.False,
|
||||
},
|
||||
{
|
||||
name: "init_true_end_false",
|
||||
value: types.True,
|
||||
ands: []ref.Val{types.NewErr("err"), types.False},
|
||||
result: types.False,
|
||||
},
|
||||
{
|
||||
name: "init_true_end_err",
|
||||
value: types.True,
|
||||
ands: []ref.Val{types.True, types.NewErr("err")},
|
||||
result: types.NewErr("err"),
|
||||
},
|
||||
{
|
||||
name: "init_true_end_unk",
|
||||
value: types.True,
|
||||
ands: []ref.Val{types.True, types.Unknown{1}, types.NewErr("err"), types.Unknown{2}},
|
||||
result: types.Unknown{1, 2},
|
||||
},
|
||||
}
|
||||
for _, tst := range tests {
|
||||
tc := tst
|
||||
t.Run(tc.name, func(tt *testing.T) {
|
||||
v := NewBoolDecisionValue(tc.name, tc.value)
|
||||
for _, av := range tc.ands {
|
||||
v = v.And(av)
|
||||
}
|
||||
v.Finalize(nil, nil)
|
||||
if !reflect.DeepEqual(v.Value(), tc.result) {
|
||||
tt.Errorf("decision AND failed. got %v, wanted %v", v.Value(), tc.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolDecisionValue_Or(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value types.Bool
|
||||
ors []ref.Val
|
||||
result ref.Val
|
||||
}{
|
||||
{
|
||||
name: "init_false_end_true",
|
||||
value: types.False,
|
||||
ors: []ref.Val{types.NewErr("err"), types.Unknown{1}, types.True},
|
||||
result: types.True,
|
||||
},
|
||||
{
|
||||
name: "init_true_end_true",
|
||||
value: types.True,
|
||||
ors: []ref.Val{types.NewErr("err"), types.False},
|
||||
result: types.True,
|
||||
},
|
||||
{
|
||||
name: "init_false_end_err",
|
||||
value: types.False,
|
||||
ors: []ref.Val{types.False, types.NewErr("err1"), types.NewErr("err2")},
|
||||
result: types.NewErr("err1"),
|
||||
},
|
||||
{
|
||||
name: "init_false_end_unk",
|
||||
value: types.False,
|
||||
ors: []ref.Val{types.False, types.Unknown{1}, types.NewErr("err"), types.Unknown{2}},
|
||||
result: types.Unknown{1, 2},
|
||||
},
|
||||
}
|
||||
for _, tst := range tests {
|
||||
tc := tst
|
||||
t.Run(tc.name, func(tt *testing.T) {
|
||||
v := NewBoolDecisionValue(tc.name, tc.value)
|
||||
for _, av := range tc.ors {
|
||||
v = v.Or(av)
|
||||
}
|
||||
// Test finalization
|
||||
v.Finalize(nil, nil)
|
||||
// Ensure that calling string on the value doesn't error.
|
||||
_ = v.String()
|
||||
// Compare the output result
|
||||
if !reflect.DeepEqual(v.Value(), tc.result) {
|
||||
tt.Errorf("decision OR failed. got %v, wanted %v", v.Value(), tc.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
209
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/env.go
vendored
Normal file
209
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/env.go
vendored
Normal file
@ -0,0 +1,209 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
// NewEnv creates an empty Env instance with a fully qualified name that may be referenced
|
||||
// within templates.
|
||||
func NewEnv(name string) *Env {
|
||||
return &Env{
|
||||
Name: name,
|
||||
Functions: []*Function{},
|
||||
Vars: []*Var{},
|
||||
Types: map[string]*DeclType{},
|
||||
}
|
||||
}
|
||||
|
||||
// Env declares a set of variables, functions, and types available to a given set of CEL
|
||||
// expressions.
|
||||
//
|
||||
// The Env name must be fully qualified as it will be referenced within template evaluators,
|
||||
// validators, and possibly within the metadata of the instance rule schema.
|
||||
//
|
||||
// Note, the Types values currently only holds type definitions associated with a variable
|
||||
// declaration. Any type mentioned in the environment which does not have a definition is
|
||||
// treated as a reference to a type which must be supplied in the base CEL environment provided
|
||||
// by the policy engine.
|
||||
type Env struct {
|
||||
Name string
|
||||
Container string
|
||||
Functions []*Function
|
||||
Vars []*Var
|
||||
Types map[string]*DeclType
|
||||
}
|
||||
|
||||
// ExprEnvOptions returns a set of CEL environment options to be used when extending the base
|
||||
// policy engine CEL environment.
|
||||
func (e *Env) ExprEnvOptions() []cel.EnvOption {
|
||||
opts := []cel.EnvOption{}
|
||||
if e.Container != "" {
|
||||
opts = append(opts, cel.Container(e.Container))
|
||||
}
|
||||
if len(e.Vars) > 0 {
|
||||
vars := make([]*exprpb.Decl, len(e.Vars))
|
||||
for i, v := range e.Vars {
|
||||
vars[i] = v.ExprDecl()
|
||||
}
|
||||
opts = append(opts, cel.Declarations(vars...))
|
||||
}
|
||||
if len(e.Functions) > 0 {
|
||||
funcs := make([]*exprpb.Decl, len(e.Functions))
|
||||
for i, f := range e.Functions {
|
||||
funcs[i] = f.ExprDecl()
|
||||
}
|
||||
opts = append(opts, cel.Declarations(funcs...))
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// NewVar creates a new variable with a name and a type.
|
||||
func NewVar(name string, dt *DeclType) *Var {
|
||||
return &Var{
|
||||
Name: name,
|
||||
Type: dt,
|
||||
}
|
||||
}
|
||||
|
||||
// Var represents a named instanced of a type.
|
||||
type Var struct {
|
||||
Name string
|
||||
Type *DeclType
|
||||
}
|
||||
|
||||
// ExprDecl produces a CEL proto declaration for the variable.
|
||||
func (v *Var) ExprDecl() *exprpb.Decl {
|
||||
return decls.NewVar(v.Name, v.Type.ExprType())
|
||||
}
|
||||
|
||||
// NewFunction creates a Function instance with a simple function name and a set of overload
|
||||
// signatures.
|
||||
func NewFunction(name string, overloads ...*Overload) *Function {
|
||||
return &Function{
|
||||
Name: name,
|
||||
Overloads: overloads,
|
||||
}
|
||||
}
|
||||
|
||||
// Function represents a simple name and a set of overload signatures.
|
||||
type Function struct {
|
||||
Name string
|
||||
Overloads []*Overload
|
||||
}
|
||||
|
||||
// ExprDecl produces a CEL proto declaration for the function and its overloads.
|
||||
func (f *Function) ExprDecl() *exprpb.Decl {
|
||||
overloadDecls := make([]*exprpb.Decl_FunctionDecl_Overload, len(f.Overloads))
|
||||
for i, o := range f.Overloads {
|
||||
overloadDecls[i] = o.overloadDecl()
|
||||
}
|
||||
return decls.NewFunction(f.Name, overloadDecls...)
|
||||
}
|
||||
|
||||
// NewOverload returns a receiver-style overload declaration for a given function.
|
||||
//
|
||||
// The overload name must follow the conventions laid out within the CEL overloads.go file.
|
||||
//
|
||||
// // Receiver-style overload name:
|
||||
// <receiver_type>_<func>_<arg_type0>_<arg_typeN>
|
||||
//
|
||||
// Within this function, the first type supplied is the receiver type, and the last type supplied
|
||||
// is used as the return type. At least two types must be specified for a zero-arity receiver
|
||||
// function.
|
||||
func NewOverload(name string, first *DeclType, rest ...*DeclType) *Overload {
|
||||
argTypes := make([]*DeclType, 1+len(rest))
|
||||
argTypes[0] = first
|
||||
for i := 1; i < len(rest)+1; i++ {
|
||||
argTypes[i] = rest[i-1]
|
||||
}
|
||||
returnType := argTypes[len(argTypes)-1]
|
||||
argTypes = argTypes[0 : len(argTypes)-1]
|
||||
return newOverload(name, false, argTypes, returnType)
|
||||
}
|
||||
|
||||
// NewFreeFunctionOverload returns a free function overload for a given function name.
|
||||
//
|
||||
// The overload name must follow the conventions laid out within the CEL overloads.go file:
|
||||
//
|
||||
// // Free function style overload name:
|
||||
// <func>_<arg_type0>_<arg_typeN>
|
||||
//
|
||||
// When the function name is global, <func> will refer to the simple function name. When the
|
||||
// function has a qualified name, replace the '.' characters in the fully-qualified name with
|
||||
// underscores.
|
||||
//
|
||||
// Within this function, the last type supplied is used as the return type. At least one type must
|
||||
// be specified for a zero-arity free function.
|
||||
func NewFreeFunctionOverload(name string, first *DeclType, rest ...*DeclType) *Overload {
|
||||
argTypes := make([]*DeclType, 1+len(rest))
|
||||
argTypes[0] = first
|
||||
for i := 1; i < len(rest)+1; i++ {
|
||||
argTypes[i] = rest[i-1]
|
||||
}
|
||||
returnType := argTypes[len(argTypes)-1]
|
||||
argTypes = argTypes[0 : len(argTypes)-1]
|
||||
return newOverload(name, true, argTypes, returnType)
|
||||
}
|
||||
|
||||
func newOverload(name string,
|
||||
freeFunction bool,
|
||||
argTypes []*DeclType,
|
||||
returnType *DeclType) *Overload {
|
||||
return &Overload{
|
||||
Name: name,
|
||||
FreeFunction: freeFunction,
|
||||
Args: argTypes,
|
||||
ReturnType: returnType,
|
||||
}
|
||||
}
|
||||
|
||||
// Overload represents a single function overload signature.
|
||||
type Overload struct {
|
||||
Name string
|
||||
FreeFunction bool
|
||||
Args []*DeclType
|
||||
ReturnType *DeclType
|
||||
}
|
||||
|
||||
func (o *Overload) overloadDecl() *exprpb.Decl_FunctionDecl_Overload {
|
||||
typeParams := map[string]struct{}{}
|
||||
argExprTypes := make([]*exprpb.Type, len(o.Args))
|
||||
for i, a := range o.Args {
|
||||
if a.TypeParam {
|
||||
typeParams[a.TypeName()] = struct{}{}
|
||||
}
|
||||
argExprTypes[i] = a.ExprType()
|
||||
}
|
||||
returnType := o.ReturnType.ExprType()
|
||||
if len(typeParams) == 0 {
|
||||
if o.FreeFunction {
|
||||
return decls.NewOverload(o.Name, argExprTypes, returnType)
|
||||
}
|
||||
return decls.NewInstanceOverload(o.Name, argExprTypes, returnType)
|
||||
}
|
||||
typeParamNames := make([]string, 0, len(typeParams))
|
||||
for param := range typeParams {
|
||||
typeParamNames = append(typeParamNames, param)
|
||||
}
|
||||
if o.FreeFunction {
|
||||
return decls.NewParameterizedOverload(o.Name, argExprTypes, returnType, typeParamNames)
|
||||
}
|
||||
return decls.NewParameterizedInstanceOverload(o.Name, argExprTypes, returnType, typeParamNames)
|
||||
}
|
73
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/env_test.go
vendored
Normal file
73
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/env_test.go
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
)
|
||||
|
||||
func TestEnv_Vars(t *testing.T) {
|
||||
env := NewEnv("test.v1.Environment")
|
||||
env.Container = "test.v1"
|
||||
env.Vars = []*Var{
|
||||
NewVar("greeting", StringType),
|
||||
NewVar("replies", NewListType(StringType)),
|
||||
}
|
||||
expr := `greeting == 'hello' && replies.size() > 0`
|
||||
stdEnv, _ := cel.NewEnv()
|
||||
ast, iss := stdEnv.Compile(expr)
|
||||
if iss.Err() == nil {
|
||||
t.Errorf("got ast %v, expected error", ast)
|
||||
}
|
||||
custEnv, err := stdEnv.Extend(env.ExprEnvOptions()...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, iss = custEnv.Compile(expr)
|
||||
if iss.Err() != nil {
|
||||
t.Errorf("got error %v, wanted ast", iss)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnv_Funcs(t *testing.T) {
|
||||
env := NewEnv("test.v1.Environment")
|
||||
env.Container = "test.v1"
|
||||
env.Functions = []*Function{
|
||||
NewFunction("greeting",
|
||||
NewOverload("string_greeting_string", StringType, StringType, BoolType),
|
||||
NewFreeFunctionOverload("greeting_string", StringType, BoolType)),
|
||||
NewFunction("getOrDefault",
|
||||
NewOverload("map_get_or_default_param",
|
||||
NewMapType(NewTypeParam("K"), NewTypeParam("V")),
|
||||
NewTypeParam("K"), NewTypeParam("V"),
|
||||
NewTypeParam("V"))),
|
||||
}
|
||||
expr := `greeting('hello') && 'jim'.greeting('hello') && {'a': 0}.getOrDefault('b', 1) == 1`
|
||||
stdEnv, _ := cel.NewEnv()
|
||||
ast, iss := stdEnv.Compile(expr)
|
||||
if iss.Err() == nil {
|
||||
t.Errorf("got ast %v, expected error", ast)
|
||||
}
|
||||
custEnv, err := stdEnv.Extend(env.ExprEnvOptions()...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, iss = custEnv.Compile(expr)
|
||||
if iss.Err() != nil {
|
||||
t.Errorf("got error %v, wanted ast", iss)
|
||||
}
|
||||
}
|
148
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/instance.go
vendored
Normal file
148
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/instance.go
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package model contains abstract representations of policy template and instance config objects.
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewInstance returns an empty policy instance.
|
||||
func NewInstance(info SourceMetadata) *Instance {
|
||||
return &Instance{
|
||||
Metadata: &InstanceMetadata{},
|
||||
Selectors: []Selector{},
|
||||
Rules: []Rule{},
|
||||
Meta: info,
|
||||
}
|
||||
}
|
||||
|
||||
// Instance represents the compiled, type-checked, and validated policy instance.
|
||||
type Instance struct {
|
||||
APIVersion string
|
||||
Kind string
|
||||
Metadata *InstanceMetadata
|
||||
Description string
|
||||
|
||||
// Selectors determine whether the instance applies to the current evaluation context.
|
||||
// All Selector values must return true for the policy instance to be included in policy
|
||||
// evaluation step.
|
||||
Selectors []Selector
|
||||
|
||||
// Rules represent reference data to be used in evaluation policy decisions.
|
||||
// Depending on the nature of the decisions being emitted, some or all Rules may be evaluated
|
||||
// and the results aggregated according to the decision types being emitted.
|
||||
Rules []Rule
|
||||
|
||||
// Meta represents the source metadata from the input instance.
|
||||
Meta SourceMetadata
|
||||
}
|
||||
|
||||
// MetadataMap returns the metadata name to value map, which can be used in evaluation.
|
||||
// Only "name" field is supported for now.
|
||||
func (i *Instance) MetadataMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"name": i.Metadata.Name,
|
||||
}
|
||||
}
|
||||
|
||||
// InstanceMetadata contains standard metadata which may be associated with an instance.
|
||||
type InstanceMetadata struct {
|
||||
UID string
|
||||
Name string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// Selector interface indicates a pre-formatted instance selection condition.
|
||||
//
|
||||
// The implementations of such conditions are expected to be platform specific.
|
||||
//
|
||||
// Note, if there is a clear need to tailor selection more heavily, then the schema definition
|
||||
// for a selector should be moved into the Template schema.
|
||||
type Selector interface {
|
||||
isSelector()
|
||||
}
|
||||
|
||||
// LabelSelector matches key, value pairs of labels associated with the evaluation context.
|
||||
//
|
||||
// In Kubernetes, the such labels are provided as 'resource.labels'.
|
||||
type LabelSelector struct {
|
||||
// LabelValues provides a map of the string keys and values expected.
|
||||
LabelValues map[string]string
|
||||
}
|
||||
|
||||
func (*LabelSelector) isSelector() {}
|
||||
|
||||
// ExpressionSelector matches a label against an existence condition.
|
||||
type ExpressionSelector struct {
|
||||
// Label name being matched.
|
||||
Label string
|
||||
|
||||
// Operator determines the evaluation behavior. Must be one of Exists, NotExists, In, or NotIn.
|
||||
Operator string
|
||||
|
||||
// Values set, optional, to be used in the NotIn, In set membership tests.
|
||||
Values []interface{}
|
||||
}
|
||||
|
||||
func (*ExpressionSelector) isSelector() {}
|
||||
|
||||
// Rule interface indicates the value types that may be used as Rule instances.
|
||||
//
|
||||
// Note, the code within the main repo deals exclusively with custom, yaml-based rules, but it
|
||||
// is entirely possible to use a protobuf message as the rule container.
|
||||
type Rule interface {
|
||||
isRule()
|
||||
GetID() int64
|
||||
GetFieldID(field string) int64
|
||||
}
|
||||
|
||||
// CustomRule embeds the DynValue and represents rules whose type definition is provided in the
|
||||
// policy template.
|
||||
type CustomRule struct {
|
||||
*DynValue
|
||||
}
|
||||
|
||||
func (*CustomRule) isRule() {}
|
||||
|
||||
// GetID returns the parse-time generated ID of the rule node.
|
||||
func (c *CustomRule) GetID() int64 {
|
||||
return c.ID
|
||||
}
|
||||
|
||||
// GetFieldID returns the parse-time generated ID pointing to the rule field. If field is not
|
||||
// specified or is not found, falls back to the ID of the rule node.
|
||||
func (c *CustomRule) GetFieldID(field string) int64 {
|
||||
if field == "" {
|
||||
return c.GetID()
|
||||
}
|
||||
paths := strings.Split(field, ".")
|
||||
val := c.DynValue
|
||||
for _, path := range paths {
|
||||
var f *Field
|
||||
var ok bool
|
||||
switch v := val.Value().(type) {
|
||||
case *ObjectValue:
|
||||
f, ok = v.GetField(path)
|
||||
case *MapValue:
|
||||
f, ok = v.GetField(path)
|
||||
}
|
||||
if !ok {
|
||||
return c.GetID()
|
||||
}
|
||||
val = f.Ref
|
||||
}
|
||||
return val.ID
|
||||
}
|
189
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/registry.go
vendored
Normal file
189
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/registry.go
vendored
Normal file
@ -0,0 +1,189 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
)
|
||||
|
||||
// Resolver declares methods to find policy templates and related configuration objects.
|
||||
type Resolver interface {
|
||||
// FindEnv returns an Env object by its fully-qualified name, if present.
|
||||
FindEnv(name string) (*Env, bool)
|
||||
|
||||
// FindExprEnv returns a CEL expression environment by its fully-qualified name, if present.
|
||||
//
|
||||
// Note, the CEL expression environment name corresponds with the model Environment name;
|
||||
// however, the expression environment may inherit configuration via the CEL env.Extend method.
|
||||
FindExprEnv(name string) (*cel.Env, bool)
|
||||
|
||||
// FindSchema returns an Open API Schema instance by name, if present.
|
||||
//
|
||||
// Schema names start with a `#` sign as this method is only used to resolve references to
|
||||
// relative schema elements within `$ref` schema nodes.
|
||||
FindSchema(name string) (*OpenAPISchema, bool)
|
||||
|
||||
// FindTemplate returns a Template by its fully-qualified name, if present.
|
||||
FindTemplate(name string) (*Template, bool)
|
||||
|
||||
// FindType returns a DeclType instance corresponding to the given fully-qualified name, if
|
||||
// present.
|
||||
FindType(name string) (*DeclType, bool)
|
||||
}
|
||||
|
||||
// NewRegistry create a registry for keeping track of environments, schemas, templates, and more
|
||||
// from a base cel.Env expression environment.
|
||||
func NewRegistry(stdExprEnv *cel.Env) *Registry {
|
||||
return &Registry{
|
||||
envs: map[string]*Env{},
|
||||
exprEnvs: map[string]*cel.Env{"": stdExprEnv},
|
||||
schemas: map[string]*OpenAPISchema{
|
||||
"#anySchema": AnySchema,
|
||||
"#envSchema": envSchema,
|
||||
"#instanceSchema": instanceSchema,
|
||||
"#openAPISchema": schemaDef,
|
||||
"#templateSchema": templateSchema,
|
||||
},
|
||||
templates: map[string]*Template{},
|
||||
types: map[string]*DeclType{
|
||||
AnyType.TypeName(): AnyType,
|
||||
BoolType.TypeName(): BoolType,
|
||||
BytesType.TypeName(): BytesType,
|
||||
DoubleType.TypeName(): DoubleType,
|
||||
DurationType.TypeName(): DurationType,
|
||||
IntType.TypeName(): IntType,
|
||||
NullType.TypeName(): NullType,
|
||||
PlainTextType.TypeName(): PlainTextType,
|
||||
StringType.TypeName(): StringType,
|
||||
TimestampType.TypeName(): TimestampType,
|
||||
UintType.TypeName(): UintType,
|
||||
ListType.TypeName(): ListType,
|
||||
MapType.TypeName(): MapType,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Registry defines a repository of environment, schema, template, and type definitions.
|
||||
//
|
||||
// Registry instances are concurrency-safe.
|
||||
type Registry struct {
|
||||
rwMux sync.RWMutex
|
||||
envs map[string]*Env
|
||||
exprEnvs map[string]*cel.Env
|
||||
schemas map[string]*OpenAPISchema
|
||||
templates map[string]*Template
|
||||
types map[string]*DeclType
|
||||
}
|
||||
|
||||
// FindEnv implements the Resolver interface method.
|
||||
func (r *Registry) FindEnv(name string) (*Env, bool) {
|
||||
r.rwMux.RLock()
|
||||
defer r.rwMux.RUnlock()
|
||||
env, found := r.envs[name]
|
||||
return env, found
|
||||
}
|
||||
|
||||
// FindExprEnv implements the Resolver interface method.
|
||||
func (r *Registry) FindExprEnv(name string) (*cel.Env, bool) {
|
||||
r.rwMux.RLock()
|
||||
defer r.rwMux.RUnlock()
|
||||
exprEnv, found := r.exprEnvs[name]
|
||||
return exprEnv, found
|
||||
}
|
||||
|
||||
// FindSchema implements the Resolver interface method.
|
||||
func (r *Registry) FindSchema(name string) (*OpenAPISchema, bool) {
|
||||
r.rwMux.RLock()
|
||||
defer r.rwMux.RUnlock()
|
||||
schema, found := r.schemas[name]
|
||||
return schema, found
|
||||
}
|
||||
|
||||
// FindTemplate implements the Resolver interface method.
|
||||
func (r *Registry) FindTemplate(name string) (*Template, bool) {
|
||||
r.rwMux.RLock()
|
||||
defer r.rwMux.RUnlock()
|
||||
tmpl, found := r.templates[name]
|
||||
return tmpl, found
|
||||
}
|
||||
|
||||
// FindType implements the Resolver interface method.
|
||||
func (r *Registry) FindType(name string) (*DeclType, bool) {
|
||||
r.rwMux.RLock()
|
||||
defer r.rwMux.RUnlock()
|
||||
typ, found := r.types[name]
|
||||
if found {
|
||||
return typ, true
|
||||
}
|
||||
return typ, found
|
||||
}
|
||||
|
||||
// SetEnv registers an environment description by fully qualified name.
|
||||
func (r *Registry) SetEnv(name string, env *Env) error {
|
||||
r.rwMux.Lock()
|
||||
defer r.rwMux.Unlock()
|
||||
// Cleanup environment related artifacts when the env is reset.
|
||||
priorEnv, found := r.envs[name]
|
||||
if found {
|
||||
for typeName := range priorEnv.Types {
|
||||
delete(r.types, typeName)
|
||||
}
|
||||
}
|
||||
// Configure the new environment.
|
||||
baseExprEnv, found := r.exprEnvs[""]
|
||||
if !found {
|
||||
return fmt.Errorf("missing default expression environment")
|
||||
}
|
||||
exprEnv, err := baseExprEnv.Extend(env.ExprEnvOptions()...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.exprEnvs[name] = exprEnv
|
||||
r.envs[name] = env
|
||||
for typeName, typ := range env.Types {
|
||||
r.types[typeName] = typ
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSchema registers an OpenAPISchema fragment by its relative name so that it may be referenced
|
||||
// as a reusable schema unit within other OpenAPISchema instances.
|
||||
//
|
||||
// Name format: '#<simpleName>'.
|
||||
func (r *Registry) SetSchema(name string, schema *OpenAPISchema) error {
|
||||
r.rwMux.Lock()
|
||||
defer r.rwMux.Unlock()
|
||||
r.schemas[name] = schema
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTemplate registers a template by its fully qualified name.
|
||||
func (r *Registry) SetTemplate(name string, tmpl *Template) error {
|
||||
r.rwMux.Lock()
|
||||
defer r.rwMux.Unlock()
|
||||
r.templates[name] = tmpl
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetType registers a DeclType descriptor by its fully qualified name.
|
||||
func (r *Registry) SetType(name string, declType *DeclType) error {
|
||||
r.rwMux.Lock()
|
||||
defer r.rwMux.Unlock()
|
||||
r.types[name] = declType
|
||||
return nil
|
||||
}
|
451
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas.go
vendored
Normal file
451
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas.go
vendored
Normal file
@ -0,0 +1,451 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// NewOpenAPISchema returns an empty instance of an OpenAPISchema object.
|
||||
func NewOpenAPISchema() *OpenAPISchema {
|
||||
return &OpenAPISchema{
|
||||
Enum: []interface{}{},
|
||||
Metadata: map[string]string{},
|
||||
Properties: map[string]*OpenAPISchema{},
|
||||
Required: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
// OpenAPISchema declares a struct capable of representing a subset of Open API Schemas
|
||||
// supported by Kubernetes which can also be specified within Protocol Buffers.
|
||||
//
|
||||
// There are a handful of notable differences:
|
||||
// - The validating constructs `allOf`, `anyOf`, `oneOf`, `not`, and type-related restrictsion are
|
||||
// not supported as they can be better validated in the template 'validator' block.
|
||||
// - The $ref field supports references to other schema definitions, but such aliases
|
||||
// should be removed before being serialized.
|
||||
// - The `additionalProperties` and `properties` fields are not currently mutually exclusive as is
|
||||
// the case for Kubernetes.
|
||||
//
|
||||
// See: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#validation
|
||||
type OpenAPISchema struct {
|
||||
Title string `yaml:"title,omitempty"`
|
||||
Description string `yaml:"description,omitempty"`
|
||||
Type string `yaml:"type,omitempty"`
|
||||
TypeParam string `yaml:"type_param,omitempty"`
|
||||
TypeRef string `yaml:"$ref,omitempty"`
|
||||
DefaultValue interface{} `yaml:"default,omitempty"`
|
||||
Enum []interface{} `yaml:"enum,omitempty"`
|
||||
Format string `yaml:"format,omitempty"`
|
||||
Items *OpenAPISchema `yaml:"items,omitempty"`
|
||||
Metadata map[string]string `yaml:"metadata,omitempty"`
|
||||
Required []string `yaml:"required,omitempty"`
|
||||
Properties map[string]*OpenAPISchema `yaml:"properties,omitempty"`
|
||||
AdditionalProperties *OpenAPISchema `yaml:"additionalProperties,omitempty"`
|
||||
}
|
||||
|
||||
// DeclTypes constructs a top-down set of DeclType instances whose name is derived from the root
|
||||
// type name provided on the call, if not set to a custom type.
|
||||
func (s *OpenAPISchema) DeclTypes(maybeRootType string) (*DeclType, map[string]*DeclType) {
|
||||
root := s.DeclType().MaybeAssignTypeName(maybeRootType)
|
||||
types := FieldTypeMap(maybeRootType, root)
|
||||
return root, types
|
||||
}
|
||||
|
||||
// DeclType returns the CEL Policy Templates type name associated with the schema element.
|
||||
func (s *OpenAPISchema) DeclType() *DeclType {
|
||||
if s.TypeParam != "" {
|
||||
return NewTypeParam(s.TypeParam)
|
||||
}
|
||||
declType, found := openAPISchemaTypes[s.Type]
|
||||
if !found {
|
||||
return NewObjectTypeRef("*error*")
|
||||
}
|
||||
switch declType.TypeName() {
|
||||
case ListType.TypeName():
|
||||
return NewListType(s.Items.DeclType())
|
||||
case MapType.TypeName():
|
||||
if s.AdditionalProperties != nil {
|
||||
return NewMapType(StringType, s.AdditionalProperties.DeclType())
|
||||
}
|
||||
fields := make(map[string]*DeclField, len(s.Properties))
|
||||
required := make(map[string]struct{}, len(s.Required))
|
||||
for _, name := range s.Required {
|
||||
required[name] = struct{}{}
|
||||
}
|
||||
for name, prop := range s.Properties {
|
||||
_, isReq := required[name]
|
||||
fields[name] = &DeclField{
|
||||
Name: name,
|
||||
Required: isReq,
|
||||
Type: prop.DeclType(),
|
||||
defaultValue: prop.DefaultValue,
|
||||
enumValues: prop.Enum,
|
||||
}
|
||||
}
|
||||
customType, hasCustomType := s.Metadata["custom_type"]
|
||||
if !hasCustomType {
|
||||
return NewObjectType("object", fields)
|
||||
}
|
||||
return NewObjectType(customType, fields)
|
||||
case StringType.TypeName():
|
||||
switch s.Format {
|
||||
case "byte", "binary":
|
||||
return BytesType
|
||||
case "google-duration":
|
||||
return DurationType
|
||||
case "date", "date-time", "google-datetime":
|
||||
return TimestampType
|
||||
case "int64":
|
||||
return IntType
|
||||
case "uint64":
|
||||
return UintType
|
||||
}
|
||||
}
|
||||
|
||||
return declType
|
||||
}
|
||||
|
||||
// FindProperty returns the Open API Schema type for the given property name.
|
||||
//
|
||||
// A property may either be explicitly defined in a `properties` map or implicitly defined in an
|
||||
// `additionalProperties` block.
|
||||
func (s *OpenAPISchema) FindProperty(name string) (*OpenAPISchema, bool) {
|
||||
if s.DeclType() == AnyType {
|
||||
return s, true
|
||||
}
|
||||
if s.Properties != nil {
|
||||
prop, found := s.Properties[name]
|
||||
if found {
|
||||
return prop, true
|
||||
}
|
||||
}
|
||||
if s.AdditionalProperties != nil {
|
||||
return s.AdditionalProperties, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var (
|
||||
// SchemaDef defines an Open API Schema definition in terms of an Open API Schema.
|
||||
schemaDef *OpenAPISchema
|
||||
|
||||
// AnySchema indicates that the value may be of any type.
|
||||
AnySchema *OpenAPISchema
|
||||
|
||||
// EnvSchema defines the schema for CEL environments referenced within Policy Templates.
|
||||
envSchema *OpenAPISchema
|
||||
|
||||
// InstanceSchema defines a basic schema for defining Policy Instances where the instance rule
|
||||
// references a TemplateSchema derived from the Instance's template kind.
|
||||
instanceSchema *OpenAPISchema
|
||||
|
||||
// TemplateSchema defines a schema for defining Policy Templates.
|
||||
templateSchema *OpenAPISchema
|
||||
|
||||
openAPISchemaTypes map[string]*DeclType = map[string]*DeclType{
|
||||
"boolean": BoolType,
|
||||
"number": DoubleType,
|
||||
"integer": IntType,
|
||||
"null": NullType,
|
||||
"string": StringType,
|
||||
"google-duration": DurationType,
|
||||
"google-datetime": TimestampType,
|
||||
"date": TimestampType,
|
||||
"date-time": TimestampType,
|
||||
"array": ListType,
|
||||
"object": MapType,
|
||||
"": AnyType,
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
schemaDefYaml = `
|
||||
type: object
|
||||
properties:
|
||||
$ref:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type_param: # prohibited unless used within an environment.
|
||||
type: string
|
||||
format:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
required:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enumDescriptions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default: {}
|
||||
items:
|
||||
$ref: "#openAPISchema"
|
||||
properties:
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: "#openAPISchema"
|
||||
additionalProperties:
|
||||
$ref: "#openAPISchema"
|
||||
metadata:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
`
|
||||
|
||||
templateSchemaYaml = `
|
||||
type: object
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- metadata
|
||||
- evaluator
|
||||
properties:
|
||||
apiVersion:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
uid:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
default: "default"
|
||||
etag:
|
||||
type: string
|
||||
labels:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
pluralName:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
schema:
|
||||
$ref: "#openAPISchema"
|
||||
validator:
|
||||
type: object
|
||||
required:
|
||||
- productions
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
environment:
|
||||
type: string
|
||||
terms:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
productions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- message
|
||||
properties:
|
||||
match:
|
||||
type: string
|
||||
default: true
|
||||
field:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
details: {}
|
||||
evaluator:
|
||||
type: object
|
||||
required:
|
||||
- productions
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
environment:
|
||||
type: string
|
||||
ranges:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- in
|
||||
properties:
|
||||
in:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
index:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
terms:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
productions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
match:
|
||||
type: string
|
||||
default: "true"
|
||||
decision:
|
||||
type: string
|
||||
decisionRef:
|
||||
type: string
|
||||
output: {}
|
||||
decisions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- output
|
||||
properties:
|
||||
decision:
|
||||
type: string
|
||||
decisionRef:
|
||||
type: string
|
||||
output: {}
|
||||
`
|
||||
|
||||
instanceSchemaYaml = `
|
||||
type: object
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- metadata
|
||||
properties:
|
||||
apiVersion:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
selector:
|
||||
type: object
|
||||
properties:
|
||||
matchLabels:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
matchExpressions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
operator:
|
||||
type: string
|
||||
enum: ["DoesNotExist", "Exists", "In", "NotIn"]
|
||||
values:
|
||||
type: array
|
||||
items: {}
|
||||
default: []
|
||||
rule:
|
||||
$ref: "#templateRuleSchema"
|
||||
rules:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#templateRuleSchema"
|
||||
`
|
||||
|
||||
// TODO: support subsetting of built-in functions and macros
|
||||
// TODO: support naming anonymous types within rule schema and making them accessible to
|
||||
// declarations.
|
||||
// TODO: consider supporting custom macros
|
||||
envSchemaYaml = `
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
container:
|
||||
type: string
|
||||
variables:
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: "#openAPISchema"
|
||||
functions:
|
||||
type: object
|
||||
properties:
|
||||
extensions:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object # function name
|
||||
additionalProperties:
|
||||
type: object # overload name
|
||||
required:
|
||||
- return
|
||||
properties:
|
||||
free_function:
|
||||
type: boolean
|
||||
args:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#openAPISchema"
|
||||
return:
|
||||
$ref: "#openAPISchema"
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
AnySchema = NewOpenAPISchema()
|
||||
|
||||
instanceSchema = NewOpenAPISchema()
|
||||
in := strings.ReplaceAll(instanceSchemaYaml, "\t", " ")
|
||||
err := yaml.Unmarshal([]byte(in), instanceSchema)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
envSchema = NewOpenAPISchema()
|
||||
in = strings.ReplaceAll(envSchemaYaml, "\t", " ")
|
||||
err = yaml.Unmarshal([]byte(in), envSchema)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
schemaDef = NewOpenAPISchema()
|
||||
in = strings.ReplaceAll(schemaDefYaml, "\t", " ")
|
||||
err = yaml.Unmarshal([]byte(in), schemaDef)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
templateSchema = NewOpenAPISchema()
|
||||
in = strings.ReplaceAll(templateSchemaYaml, "\t", " ")
|
||||
err = yaml.Unmarshal([]byte(in), templateSchema)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
203
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas_test.go
vendored
Normal file
203
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas_test.go
vendored
Normal file
@ -0,0 +1,203 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestSchemaDeclType(t *testing.T) {
|
||||
ts := testSchema()
|
||||
cust := ts.DeclType()
|
||||
if cust.TypeName() != "CustomObject" {
|
||||
t.Errorf("incorrect type name, got %v, wanted CustomObject", cust.TypeName())
|
||||
}
|
||||
if len(cust.Fields) != 4 {
|
||||
t.Errorf("incorrect number of fields, got %d, wanted 4", len(cust.Fields))
|
||||
}
|
||||
for _, f := range cust.Fields {
|
||||
prop, found := ts.FindProperty(f.Name)
|
||||
if !found {
|
||||
t.Errorf("type field not found in schema, field: %s", f.Name)
|
||||
}
|
||||
fdv := f.DefaultValue()
|
||||
if prop.DefaultValue != nil {
|
||||
pdv := types.DefaultTypeAdapter.NativeToValue(prop.DefaultValue)
|
||||
if !reflect.DeepEqual(fdv, pdv) {
|
||||
t.Errorf("field and schema do not agree on default value, field: %s", f.Name)
|
||||
}
|
||||
}
|
||||
if prop.Enum == nil && len(f.EnumValues()) != 0 {
|
||||
t.Errorf("field had more enum values than the property. field: %s", f.Name)
|
||||
}
|
||||
if prop.Enum != nil {
|
||||
fevs := f.EnumValues()
|
||||
for _, fev := range fevs {
|
||||
found := false
|
||||
for _, pev := range prop.Enum {
|
||||
pev = types.DefaultTypeAdapter.NativeToValue(pev)
|
||||
if reflect.DeepEqual(fev, pev) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf(
|
||||
"could not find field enum value in property definition. field: %s, enum: %v",
|
||||
f.Name, fev)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range ts.Required {
|
||||
df, found := cust.FindField(name)
|
||||
if !found {
|
||||
t.Errorf("custom type missing required field. field=%s", name)
|
||||
}
|
||||
if !df.Required {
|
||||
t.Errorf("field marked as required in schema, but optional in type. field=%s", df.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchemaDeclTypes(t *testing.T) {
|
||||
ts := testSchema()
|
||||
cust, typeMap := ts.DeclTypes("mock_template")
|
||||
nested, _ := cust.FindField("nested")
|
||||
metadata, _ := cust.FindField("metadata")
|
||||
metadataElem := metadata.Type.ElemType
|
||||
expectedObjTypeMap := map[string]*DeclType{
|
||||
"CustomObject": cust,
|
||||
"CustomObject.nested": nested.Type,
|
||||
"CustomObject.metadata.@elem": metadataElem,
|
||||
}
|
||||
objTypeMap := map[string]*DeclType{}
|
||||
for name, t := range typeMap {
|
||||
if t.IsObject() {
|
||||
objTypeMap[name] = t
|
||||
}
|
||||
}
|
||||
if len(objTypeMap) != len(expectedObjTypeMap) {
|
||||
t.Errorf("got different type set. got=%v, wanted=%v", typeMap, expectedObjTypeMap)
|
||||
}
|
||||
for exp, expType := range expectedObjTypeMap {
|
||||
actType, found := objTypeMap[exp]
|
||||
if !found {
|
||||
t.Errorf("missing type in rule types: %s", exp)
|
||||
continue
|
||||
}
|
||||
if !proto.Equal(expType.ExprType(), actType.ExprType()) {
|
||||
t.Errorf("incompatible CEL types. got=%v, wanted=%v", actType.ExprType(), expType.ExprType())
|
||||
}
|
||||
}
|
||||
|
||||
metaExprType := metadata.Type.ExprType()
|
||||
expectedMetaExprType := decls.NewMapType(
|
||||
decls.String,
|
||||
decls.NewObjectType("CustomObject.metadata.@elem"))
|
||||
if !proto.Equal(expectedMetaExprType, metaExprType) {
|
||||
t.Errorf("got metadata CEL type %v, wanted %v", metaExprType, expectedMetaExprType)
|
||||
}
|
||||
}
|
||||
|
||||
func testSchema() *OpenAPISchema {
|
||||
// Manual construction of a schema with the following definition:
|
||||
//
|
||||
// schema:
|
||||
// type: object
|
||||
// metadata:
|
||||
// custom_type: "CustomObject"
|
||||
// required:
|
||||
// - name
|
||||
// - value
|
||||
// properties:
|
||||
// name:
|
||||
// type: string
|
||||
// nested:
|
||||
// type: object
|
||||
// properties:
|
||||
// subname:
|
||||
// type: string
|
||||
// flags:
|
||||
// type: object
|
||||
// additionalProperties:
|
||||
// type: boolean
|
||||
// dates:
|
||||
// type: array
|
||||
// items:
|
||||
// type: string
|
||||
// format: date-time
|
||||
// metadata:
|
||||
// type: object
|
||||
// additionalProperties:
|
||||
// type: object
|
||||
// properties:
|
||||
// key:
|
||||
// type: string
|
||||
// values:
|
||||
// type: array
|
||||
// items: string
|
||||
// value:
|
||||
// type: integer
|
||||
// format: int64
|
||||
// default: 1
|
||||
// enum: [1,2,3]
|
||||
nameField := NewOpenAPISchema()
|
||||
nameField.Type = "string"
|
||||
valueField := NewOpenAPISchema()
|
||||
valueField.Type = "integer"
|
||||
valueField.Format = "int64"
|
||||
valueField.DefaultValue = int64(1)
|
||||
valueField.Enum = []interface{}{int64(1), int64(2), int64(3)}
|
||||
nestedObjField := NewOpenAPISchema()
|
||||
nestedObjField.Type = "object"
|
||||
nestedObjField.Properties["subname"] = NewOpenAPISchema()
|
||||
nestedObjField.Properties["subname"].Type = "string"
|
||||
nestedObjField.Properties["flags"] = NewOpenAPISchema()
|
||||
nestedObjField.Properties["flags"].Type = "object"
|
||||
nestedObjField.Properties["flags"].AdditionalProperties = NewOpenAPISchema()
|
||||
nestedObjField.Properties["flags"].AdditionalProperties.Type = "boolean"
|
||||
nestedObjField.Properties["dates"] = NewOpenAPISchema()
|
||||
nestedObjField.Properties["dates"].Type = "array"
|
||||
nestedObjField.Properties["dates"].Items = NewOpenAPISchema()
|
||||
nestedObjField.Properties["dates"].Items.Type = "string"
|
||||
nestedObjField.Properties["dates"].Items.Format = "date-time"
|
||||
metadataKeyValue := NewOpenAPISchema()
|
||||
metadataKeyValue.Type = "object"
|
||||
metadataKeyValue.Properties["key"] = NewOpenAPISchema()
|
||||
metadataKeyValue.Properties["key"].Type = "string"
|
||||
metadataKeyValue.Properties["values"] = NewOpenAPISchema()
|
||||
metadataKeyValue.Properties["values"].Type = "array"
|
||||
metadataKeyValue.Properties["values"].Items = NewOpenAPISchema()
|
||||
metadataKeyValue.Properties["values"].Items.Type = "string"
|
||||
metadataObjField := NewOpenAPISchema()
|
||||
metadataObjField.Type = "object"
|
||||
metadataObjField.AdditionalProperties = metadataKeyValue
|
||||
ts := NewOpenAPISchema()
|
||||
ts.Type = "object"
|
||||
ts.Metadata["custom_type"] = "CustomObject"
|
||||
ts.Required = []string{"name", "value"}
|
||||
ts.Properties["name"] = nameField
|
||||
ts.Properties["value"] = valueField
|
||||
ts.Properties["nested"] = nestedObjField
|
||||
ts.Properties["metadata"] = metadataObjField
|
||||
return ts
|
||||
}
|
190
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/source.go
vendored
Normal file
190
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/source.go
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/common"
|
||||
)
|
||||
|
||||
// ByteSource converts a byte sequence and location description to a model.Source.
|
||||
func ByteSource(contents []byte, location string) *Source {
|
||||
return StringSource(string(contents), location)
|
||||
}
|
||||
|
||||
// StringSource converts a string and location description to a model.Source.
|
||||
func StringSource(contents, location string) *Source {
|
||||
return &Source{
|
||||
Source: common.NewStringSource(contents, location),
|
||||
}
|
||||
}
|
||||
|
||||
// Source represents the contents of a single source file.
|
||||
type Source struct {
|
||||
common.Source
|
||||
}
|
||||
|
||||
// Relative produces a RelativeSource object for the content provided at the absolute location
|
||||
// within the parent Source as indicated by the line and column.
|
||||
func (src *Source) Relative(content string, line, col int) *RelativeSource {
|
||||
return &RelativeSource{
|
||||
Source: src.Source,
|
||||
localSrc: common.NewStringSource(content, src.Description()),
|
||||
absLoc: common.NewLocation(line, col),
|
||||
}
|
||||
}
|
||||
|
||||
// RelativeSource represents an embedded source element within a larger source.
|
||||
type RelativeSource struct {
|
||||
common.Source
|
||||
localSrc common.Source
|
||||
absLoc common.Location
|
||||
}
|
||||
|
||||
// AbsoluteLocation returns the location within the parent Source where the RelativeSource starts.
|
||||
func (rel *RelativeSource) AbsoluteLocation() common.Location {
|
||||
return rel.absLoc
|
||||
}
|
||||
|
||||
// Content returns the embedded source snippet.
|
||||
func (rel *RelativeSource) Content() string {
|
||||
return rel.localSrc.Content()
|
||||
}
|
||||
|
||||
// OffsetLocation returns the absolute location given the relative offset, if found.
|
||||
func (rel *RelativeSource) OffsetLocation(offset int32) (common.Location, bool) {
|
||||
absOffset, found := rel.Source.LocationOffset(rel.absLoc)
|
||||
if !found {
|
||||
return common.NoLocation, false
|
||||
}
|
||||
return rel.Source.OffsetLocation(absOffset + offset)
|
||||
}
|
||||
|
||||
// NewLocation creates an absolute common.Location based on a local line, column
|
||||
// position from a relative source.
|
||||
func (rel *RelativeSource) NewLocation(line, col int) common.Location {
|
||||
localLoc := common.NewLocation(line, col)
|
||||
relOffset, found := rel.localSrc.LocationOffset(localLoc)
|
||||
if !found {
|
||||
return common.NoLocation
|
||||
}
|
||||
offset, _ := rel.Source.LocationOffset(rel.absLoc)
|
||||
absLoc, _ := rel.Source.OffsetLocation(offset + relOffset)
|
||||
return absLoc
|
||||
}
|
||||
|
||||
// NewSourceInfo creates SourceInfo metadata from a Source object.
|
||||
func NewSourceInfo(src common.Source) *SourceInfo {
|
||||
return &SourceInfo{
|
||||
Comments: make(map[int64][]*Comment),
|
||||
LineOffsets: src.LineOffsets(),
|
||||
Description: src.Description(),
|
||||
Offsets: make(map[int64]int32),
|
||||
}
|
||||
}
|
||||
|
||||
// SourceInfo contains metadata about the Source such as comments, line positions, and source
|
||||
// element offsets.
|
||||
type SourceInfo struct {
|
||||
// Comments mapped by source element id to a comment set.
|
||||
Comments map[int64][]*Comment
|
||||
|
||||
// LineOffsets contains the list of character offsets where newlines occur in the source.
|
||||
LineOffsets []int32
|
||||
|
||||
// Description indicates something about the source, such as its file name.
|
||||
Description string
|
||||
|
||||
// Offsets map from source element id to the character offset where the source element starts.
|
||||
Offsets map[int64]int32
|
||||
}
|
||||
|
||||
// SourceMetadata enables the lookup for expression source metadata by expression id.
|
||||
type SourceMetadata interface {
|
||||
// CommentsByID returns the set of comments associated with the expression id, if present.
|
||||
CommentsByID(int64) ([]*Comment, bool)
|
||||
|
||||
// LocationByID returns the CEL common.Location of the expression id, if present.
|
||||
LocationByID(int64) (common.Location, bool)
|
||||
}
|
||||
|
||||
// CommentsByID returns the set of comments by expression id, if present.
|
||||
func (info *SourceInfo) CommentsByID(id int64) ([]*Comment, bool) {
|
||||
comments, found := info.Comments[id]
|
||||
return comments, found
|
||||
}
|
||||
|
||||
// LocationByID returns the line and column location of source node by its id.
|
||||
func (info *SourceInfo) LocationByID(id int64) (common.Location, bool) {
|
||||
charOff, found := info.Offsets[id]
|
||||
if !found {
|
||||
return common.NoLocation, false
|
||||
}
|
||||
ln, lnOff := info.findLine(charOff)
|
||||
return common.NewLocation(int(ln), int(charOff-lnOff)), true
|
||||
}
|
||||
|
||||
func (info *SourceInfo) findLine(characterOffset int32) (int32, int32) {
|
||||
var line int32 = 1
|
||||
for _, lineOffset := range info.LineOffsets {
|
||||
if lineOffset > characterOffset {
|
||||
break
|
||||
} else {
|
||||
line++
|
||||
}
|
||||
}
|
||||
if line == 1 {
|
||||
return line, 0
|
||||
}
|
||||
return line, info.LineOffsets[line-2]
|
||||
}
|
||||
|
||||
// CommentStyle type used to indicate where a comment occurs.
|
||||
type CommentStyle int
|
||||
|
||||
const (
|
||||
// HeadComment indicates that the comment is defined in the lines preceding the source element.
|
||||
HeadComment CommentStyle = iota + 1
|
||||
|
||||
// LineComment indicates that the comment occurs on the same line after the source element.
|
||||
LineComment
|
||||
|
||||
// FootComment indicates that the comment occurs after the source element with at least one
|
||||
// blank line before the next source element.
|
||||
FootComment
|
||||
)
|
||||
|
||||
// NewHeadComment creates a new HeadComment from the text.
|
||||
func NewHeadComment(txt string) *Comment {
|
||||
return &Comment{Text: txt, Style: HeadComment}
|
||||
}
|
||||
|
||||
// NewLineComment creates a new LineComment from the text.
|
||||
func NewLineComment(txt string) *Comment {
|
||||
return &Comment{Text: txt, Style: LineComment}
|
||||
}
|
||||
|
||||
// NewFootComment creates a new FootComment from the text.
|
||||
func NewFootComment(txt string) *Comment {
|
||||
return &Comment{Text: txt, Style: FootComment}
|
||||
}
|
||||
|
||||
// Comment represents a comment within source.
|
||||
type Comment struct {
|
||||
// Text contains the comment text.
|
||||
Text string
|
||||
|
||||
// Style indicates where the comment appears relative to a source element.
|
||||
Style CommentStyle
|
||||
}
|
159
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/template.go
vendored
Normal file
159
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/template.go
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/cel"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
// NewTemplate produces an empty policy Template instance.
|
||||
func NewTemplate(info SourceMetadata) *Template {
|
||||
return &Template{
|
||||
Metadata: NewTemplateMetadata(),
|
||||
Evaluator: NewEvaluator(),
|
||||
Meta: info,
|
||||
}
|
||||
}
|
||||
|
||||
// Template represents the compiled and type-checked policy template.
|
||||
type Template struct {
|
||||
APIVersion string
|
||||
Kind string
|
||||
Metadata *TemplateMetadata
|
||||
Description string
|
||||
RuleTypes *RuleTypes
|
||||
Validator *Evaluator
|
||||
Evaluator *Evaluator
|
||||
Meta SourceMetadata
|
||||
}
|
||||
|
||||
// EvaluatorDecisionCount returns the number of decisions which can be produced by the template
|
||||
// evaluator production rules.
|
||||
func (t *Template) EvaluatorDecisionCount() int {
|
||||
return t.Evaluator.DecisionCount()
|
||||
}
|
||||
|
||||
// MetadataMap returns the metadata name to value map, which can be used in evaluation.
|
||||
// Only "name" field is supported for now.
|
||||
func (t *Template) MetadataMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"name": t.Metadata.Name,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTemplateMetadata returns an empty *TemplateMetadata instance.
|
||||
func NewTemplateMetadata() *TemplateMetadata {
|
||||
return &TemplateMetadata{
|
||||
Properties: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// TemplateMetadata contains the top-level information about the Template, including its name and
|
||||
// namespace.
|
||||
type TemplateMetadata struct {
|
||||
UID string
|
||||
Name string
|
||||
Namespace string
|
||||
|
||||
// PluralMame is the plural form of the template name to use when managing a collection of
|
||||
// template instances.
|
||||
PluralName string
|
||||
|
||||
// Properties contains an optional set of key-value information which external applications
|
||||
// might find useful.
|
||||
Properties map[string]string
|
||||
}
|
||||
|
||||
// NewEvaluator returns an empty instance of a Template Evaluator.
|
||||
func NewEvaluator() *Evaluator {
|
||||
return &Evaluator{
|
||||
Terms: []*Term{},
|
||||
Productions: []*Production{},
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluator contains a set of production rules used to validate policy templates or
|
||||
// evaluate template instances.
|
||||
//
|
||||
// The evaluator may optionally specify a named and versioned Environment as the basis for the
|
||||
// variables and functions exposed to the CEL expressions within the Evaluator, and an optional
|
||||
// set of terms.
|
||||
//
|
||||
// Terms are like template-local variables. Terms may rely on other terms which precede them.
|
||||
// Term order matters, and no cycles are permitted among terms by design and convention.
|
||||
type Evaluator struct {
|
||||
Environment string
|
||||
Ranges []*Range
|
||||
Terms []*Term
|
||||
Productions []*Production
|
||||
}
|
||||
|
||||
// DecisionCount returns the number of possible decisions which could be emitted by this evaluator.
|
||||
func (e *Evaluator) DecisionCount() int {
|
||||
decMap := map[string]struct{}{}
|
||||
for _, p := range e.Productions {
|
||||
for _, d := range p.Decisions {
|
||||
decMap[d.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
return len(decMap)
|
||||
}
|
||||
|
||||
// Range expresses a looping condition where the key (or index) and value can be extracted from the
|
||||
// range CEL expression.
|
||||
type Range struct {
|
||||
ID int64
|
||||
Key *exprpb.Decl
|
||||
Value *exprpb.Decl
|
||||
Expr *cel.Ast
|
||||
}
|
||||
|
||||
// NewTerm produces a named Term instance associated with a CEL Ast and a list of the input
|
||||
// terms needed to evaluate the Ast successfully.
|
||||
func NewTerm(id int64, name string, expr *cel.Ast) *Term {
|
||||
return &Term{
|
||||
ID: id,
|
||||
Name: name,
|
||||
Expr: expr,
|
||||
}
|
||||
}
|
||||
|
||||
// Term is a template-local variable whose name may shadow names in the Template environment and
|
||||
// which may depend on preceding terms as input.
|
||||
type Term struct {
|
||||
ID int64
|
||||
Name string
|
||||
Expr *cel.Ast
|
||||
}
|
||||
|
||||
// NewProduction returns an empty instance of a Production rule which minimally contains a single
|
||||
// Decision.
|
||||
func NewProduction(id int64, match *cel.Ast) *Production {
|
||||
return &Production{
|
||||
ID: id,
|
||||
Match: match,
|
||||
Decisions: []*Decision{},
|
||||
}
|
||||
}
|
||||
|
||||
// Production describes an match-decision pair where the match, if set, indicates whether the
|
||||
// Decision is applicable, and the decision indicates its name and output value.
|
||||
type Production struct {
|
||||
ID int64
|
||||
Match *cel.Ast
|
||||
Decisions []*Decision
|
||||
}
|
562
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types.go
vendored
Normal file
562
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types.go
vendored
Normal file
@ -0,0 +1,562 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
// NewListType returns a parameterized list type with a specified element type.
|
||||
func NewListType(elem *DeclType) *DeclType {
|
||||
return &DeclType{
|
||||
name: "list",
|
||||
ElemType: elem,
|
||||
exprType: decls.NewListType(elem.ExprType()),
|
||||
defaultValue: NewListValue(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewMapType returns a parameterized map type with the given key and element types.
|
||||
func NewMapType(key, elem *DeclType) *DeclType {
|
||||
return &DeclType{
|
||||
name: "map",
|
||||
KeyType: key,
|
||||
ElemType: elem,
|
||||
exprType: decls.NewMapType(key.ExprType(), elem.ExprType()),
|
||||
defaultValue: NewMapValue(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewObjectType creates an object type with a qualified name and a set of field declarations.
|
||||
func NewObjectType(name string, fields map[string]*DeclField) *DeclType {
|
||||
t := &DeclType{
|
||||
name: name,
|
||||
Fields: fields,
|
||||
exprType: decls.NewObjectType(name),
|
||||
traitMask: traits.FieldTesterType | traits.IndexerType,
|
||||
}
|
||||
t.defaultValue = NewObjectValue(t)
|
||||
return t
|
||||
}
|
||||
|
||||
// NewObjectTypeRef returns a reference to an object type by name
|
||||
func NewObjectTypeRef(name string) *DeclType {
|
||||
t := &DeclType{
|
||||
name: name,
|
||||
exprType: decls.NewObjectType(name),
|
||||
traitMask: traits.FieldTesterType | traits.IndexerType,
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// NewTypeParam creates a type parameter type with a simple name.
|
||||
//
|
||||
// Type parameters are resolved at compilation time to concrete types, or CEL 'dyn' type if no
|
||||
// type assignment can be inferred.
|
||||
func NewTypeParam(name string) *DeclType {
|
||||
return &DeclType{
|
||||
name: name,
|
||||
TypeParam: true,
|
||||
exprType: decls.NewTypeParamType(name),
|
||||
}
|
||||
}
|
||||
|
||||
func newSimpleType(name string, exprType *exprpb.Type, zeroVal ref.Val) *DeclType {
|
||||
return &DeclType{
|
||||
name: name,
|
||||
exprType: exprType,
|
||||
defaultValue: zeroVal,
|
||||
}
|
||||
}
|
||||
|
||||
// DeclType represents the universal type descriptor for Policy Templates.
|
||||
type DeclType struct {
|
||||
fmt.Stringer
|
||||
|
||||
name string
|
||||
Fields map[string]*DeclField
|
||||
KeyType *DeclType
|
||||
ElemType *DeclType
|
||||
TypeParam bool
|
||||
Metadata map[string]string
|
||||
|
||||
exprType *exprpb.Type
|
||||
traitMask int
|
||||
defaultValue ref.Val
|
||||
}
|
||||
|
||||
// MaybeAssignTypeName attempts to set the DeclType name to a fully qualified name, if the type
|
||||
// is of `object` type.
|
||||
//
|
||||
// The DeclType must return true for `IsObject` or this assignment will error.
|
||||
func (t *DeclType) MaybeAssignTypeName(name string) *DeclType {
|
||||
if t.IsObject() {
|
||||
objUpdated := false
|
||||
if t.name != "object" {
|
||||
name = t.name
|
||||
} else {
|
||||
objUpdated = true
|
||||
}
|
||||
fieldMap := make(map[string]*DeclField, len(t.Fields))
|
||||
for fieldName, field := range t.Fields {
|
||||
fieldType := field.Type
|
||||
fieldTypeName := fmt.Sprintf("%s.%s", name, fieldName)
|
||||
updated := fieldType.MaybeAssignTypeName(fieldTypeName)
|
||||
if updated == fieldType {
|
||||
fieldMap[fieldName] = field
|
||||
continue
|
||||
}
|
||||
objUpdated = true
|
||||
fieldMap[fieldName] = &DeclField{
|
||||
Name: fieldName,
|
||||
Type: updated,
|
||||
Required: field.Required,
|
||||
enumValues: field.enumValues,
|
||||
defaultValue: field.defaultValue,
|
||||
}
|
||||
}
|
||||
if !objUpdated {
|
||||
return t
|
||||
}
|
||||
return &DeclType{
|
||||
name: name,
|
||||
Fields: fieldMap,
|
||||
KeyType: t.KeyType,
|
||||
ElemType: t.ElemType,
|
||||
TypeParam: t.TypeParam,
|
||||
Metadata: t.Metadata,
|
||||
exprType: decls.NewObjectType(name),
|
||||
traitMask: t.traitMask,
|
||||
defaultValue: t.defaultValue,
|
||||
}
|
||||
}
|
||||
if t.IsMap() {
|
||||
elemTypeName := fmt.Sprintf("%s.@elem", name)
|
||||
updated := t.ElemType.MaybeAssignTypeName(elemTypeName)
|
||||
if updated == t.ElemType {
|
||||
return t
|
||||
}
|
||||
return NewMapType(t.KeyType, updated)
|
||||
}
|
||||
if t.IsList() {
|
||||
elemTypeName := fmt.Sprintf("%s.@idx", name)
|
||||
updated := t.ElemType.MaybeAssignTypeName(elemTypeName)
|
||||
if updated == t.ElemType {
|
||||
return t
|
||||
}
|
||||
return NewListType(updated)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ExprType returns the CEL expression type of this declaration.
|
||||
func (t *DeclType) ExprType() *exprpb.Type {
|
||||
return t.exprType
|
||||
}
|
||||
|
||||
// FindField returns the DeclField with the given name if present.
|
||||
func (t *DeclType) FindField(name string) (*DeclField, bool) {
|
||||
f, found := t.Fields[name]
|
||||
return f, found
|
||||
}
|
||||
|
||||
// HasTrait implements the CEL ref.Type interface making this type declaration suitable for use
|
||||
// within the CEL evaluator.
|
||||
func (t *DeclType) HasTrait(trait int) bool {
|
||||
if t.traitMask&trait == trait {
|
||||
return true
|
||||
}
|
||||
if t.defaultValue == nil {
|
||||
return false
|
||||
}
|
||||
_, isDecl := t.defaultValue.Type().(*DeclType)
|
||||
if isDecl {
|
||||
return false
|
||||
}
|
||||
return t.defaultValue.Type().HasTrait(trait)
|
||||
}
|
||||
|
||||
// IsList returns whether the declaration is a `list` type which defines a parameterized element
|
||||
// type, but not a parameterized key type or fields.
|
||||
func (t *DeclType) IsList() bool {
|
||||
return t.KeyType == nil && t.ElemType != nil && t.Fields == nil
|
||||
}
|
||||
|
||||
// IsMap returns whether the declaration is a 'map' type which defines parameterized key and
|
||||
// element types, but not fields.
|
||||
func (t *DeclType) IsMap() bool {
|
||||
return t.KeyType != nil && t.ElemType != nil && t.Fields == nil
|
||||
}
|
||||
|
||||
// IsObject returns whether the declartion is an 'object' type which defined a set of typed fields.
|
||||
func (t *DeclType) IsObject() bool {
|
||||
return t.KeyType == nil && t.ElemType == nil && t.Fields != nil
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer interface method.
|
||||
func (t *DeclType) String() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// TypeName returns the fully qualified type name for the DeclType.
|
||||
func (t *DeclType) TypeName() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// DefaultValue returns the CEL ref.Val representing the default value for this object type,
|
||||
// if one exists.
|
||||
func (t *DeclType) DefaultValue() ref.Val {
|
||||
return t.defaultValue
|
||||
}
|
||||
|
||||
// FieldTypeMap constructs a map of the field and object types nested within a given type.
|
||||
func FieldTypeMap(path string, t *DeclType) map[string]*DeclType {
|
||||
if t.IsObject() && t.TypeName() != "object" {
|
||||
path = t.TypeName()
|
||||
}
|
||||
types := make(map[string]*DeclType)
|
||||
buildDeclTypes(path, t, types)
|
||||
return types
|
||||
}
|
||||
|
||||
func buildDeclTypes(path string, t *DeclType, types map[string]*DeclType) {
|
||||
// Ensure object types are properly named according to where they appear in the schema.
|
||||
if t.IsObject() {
|
||||
// Hack to ensure that names are uniquely qualified and work well with the type
|
||||
// resolution steps which require fully qualified type names for field resolution
|
||||
// to function properly.
|
||||
types[t.TypeName()] = t
|
||||
for name, field := range t.Fields {
|
||||
fieldPath := fmt.Sprintf("%s.%s", path, name)
|
||||
buildDeclTypes(fieldPath, field.Type, types)
|
||||
}
|
||||
}
|
||||
// Map element properties to type names if needed.
|
||||
if t.IsMap() {
|
||||
mapElemPath := fmt.Sprintf("%s.@elem", path)
|
||||
buildDeclTypes(mapElemPath, t.ElemType, types)
|
||||
types[path] = t
|
||||
}
|
||||
// List element properties.
|
||||
if t.IsList() {
|
||||
listIdxPath := fmt.Sprintf("%s.@idx", path)
|
||||
buildDeclTypes(listIdxPath, t.ElemType, types)
|
||||
types[path] = t
|
||||
}
|
||||
}
|
||||
|
||||
// DeclField describes the name, ordinal, and optionality of a field declaration within a type.
|
||||
type DeclField struct {
|
||||
Name string
|
||||
Type *DeclType
|
||||
Required bool
|
||||
enumValues []interface{}
|
||||
defaultValue interface{}
|
||||
}
|
||||
|
||||
// TypeName returns the string type name of the field.
|
||||
func (f *DeclField) TypeName() string {
|
||||
return f.Type.TypeName()
|
||||
}
|
||||
|
||||
// DefaultValue returns the zero value associated with the field.
|
||||
func (f *DeclField) DefaultValue() ref.Val {
|
||||
if f.defaultValue != nil {
|
||||
return types.DefaultTypeAdapter.NativeToValue(f.defaultValue)
|
||||
}
|
||||
return f.Type.DefaultValue()
|
||||
}
|
||||
|
||||
// EnumValues returns the set of values that this field may take.
|
||||
func (f *DeclField) EnumValues() []ref.Val {
|
||||
if f.enumValues == nil || len(f.enumValues) == 0 {
|
||||
return []ref.Val{}
|
||||
}
|
||||
ev := make([]ref.Val, len(f.enumValues))
|
||||
for i, e := range f.enumValues {
|
||||
ev[i] = types.DefaultTypeAdapter.NativeToValue(e)
|
||||
}
|
||||
return ev
|
||||
}
|
||||
|
||||
// NewRuleTypes returns an Open API Schema-based type-system which is CEL compatible.
|
||||
func NewRuleTypes(kind string,
|
||||
schema *OpenAPISchema,
|
||||
res Resolver) (*RuleTypes, error) {
|
||||
// Note, if the schema indicates that it's actually based on another proto
|
||||
// then prefer the proto definition. For expressions in the proto, a new field
|
||||
// annotation will be needed to indicate the expected environment and type of
|
||||
// the expression.
|
||||
schemaTypes, err := newSchemaTypeProvider(kind, schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &RuleTypes{
|
||||
Schema: schema,
|
||||
ruleSchemaDeclTypes: schemaTypes,
|
||||
resolver: res,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RuleTypes extends the CEL ref.TypeProvider interface and provides an Open API Schema-based
|
||||
// type-system.
|
||||
type RuleTypes struct {
|
||||
ref.TypeProvider
|
||||
Schema *OpenAPISchema
|
||||
ruleSchemaDeclTypes *schemaTypeProvider
|
||||
typeAdapter ref.TypeAdapter
|
||||
resolver Resolver
|
||||
}
|
||||
|
||||
// EnvOptions returns a set of cel.EnvOption values which includes the Template's declaration set
|
||||
// as well as a custom ref.TypeProvider.
|
||||
//
|
||||
// Note, the standard declaration set includes 'rule' which is defined as the top-level rule-schema
|
||||
// type if one is configured.
|
||||
//
|
||||
// If the RuleTypes value is nil, an empty []cel.EnvOption set is returned.
|
||||
func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
if rt == nil {
|
||||
return []cel.EnvOption{}, nil
|
||||
}
|
||||
var ta ref.TypeAdapter = types.DefaultTypeAdapter
|
||||
tpa, ok := tp.(ref.TypeAdapter)
|
||||
if ok {
|
||||
ta = tpa
|
||||
}
|
||||
rtWithTypes := &RuleTypes{
|
||||
TypeProvider: tp,
|
||||
typeAdapter: ta,
|
||||
Schema: rt.Schema,
|
||||
ruleSchemaDeclTypes: rt.ruleSchemaDeclTypes,
|
||||
resolver: rt.resolver,
|
||||
}
|
||||
for name, declType := range rt.ruleSchemaDeclTypes.types {
|
||||
tpType, found := tp.FindType(name)
|
||||
if found && !proto.Equal(tpType, declType.ExprType()) {
|
||||
return nil, fmt.Errorf(
|
||||
"type %s definition differs between CEL environment and template", name)
|
||||
}
|
||||
}
|
||||
return []cel.EnvOption{
|
||||
cel.CustomTypeProvider(rtWithTypes),
|
||||
cel.CustomTypeAdapter(rtWithTypes),
|
||||
cel.Declarations(
|
||||
decls.NewVar("rule", rt.ruleSchemaDeclTypes.root.ExprType()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FindType attempts to resolve the typeName provided from the template's rule-schema, or if not
|
||||
// from the embedded ref.TypeProvider.
|
||||
//
|
||||
// FindType overrides the default type-finding behavior of the embedded TypeProvider.
|
||||
//
|
||||
// Note, when the type name is based on the Open API Schema, the name will reflect the object path
|
||||
// where the type definition appears.
|
||||
func (rt *RuleTypes) FindType(typeName string) (*exprpb.Type, bool) {
|
||||
if rt == nil {
|
||||
return nil, false
|
||||
}
|
||||
declType, found := rt.findDeclType(typeName)
|
||||
if found {
|
||||
return declType.ExprType(), found
|
||||
}
|
||||
return rt.TypeProvider.FindType(typeName)
|
||||
}
|
||||
|
||||
// FindDeclType returns the CPT type description which can be mapped to a CEL type.
|
||||
func (rt *RuleTypes) FindDeclType(typeName string) (*DeclType, bool) {
|
||||
if rt == nil {
|
||||
return nil, false
|
||||
}
|
||||
return rt.findDeclType(typeName)
|
||||
}
|
||||
|
||||
// FindFieldType returns a field type given a type name and field name, if found.
|
||||
//
|
||||
// Note, the type name for an Open API Schema type is likely to be its qualified object path.
|
||||
// If, in the future an object instance rather than a type name were provided, the field
|
||||
// resolution might more accurately reflect the expected type model. However, in this case
|
||||
// concessions were made to align with the existing CEL interfaces.
|
||||
func (rt *RuleTypes) FindFieldType(typeName, fieldName string) (*ref.FieldType, bool) {
|
||||
st, found := rt.findDeclType(typeName)
|
||||
if !found {
|
||||
return rt.TypeProvider.FindFieldType(typeName, fieldName)
|
||||
}
|
||||
|
||||
f, found := st.Fields[fieldName]
|
||||
if found {
|
||||
ft := f.Type
|
||||
return &ref.FieldType{
|
||||
Type: ft.ExprType(),
|
||||
}, true
|
||||
}
|
||||
// This could be a dynamic map.
|
||||
if st.IsMap() {
|
||||
et := st.ElemType
|
||||
return &ref.FieldType{
|
||||
Type: et.ExprType(),
|
||||
}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// ConvertToRule transforms an untyped DynValue into a typed object.
|
||||
//
|
||||
// Conversion is done deeply and will traverse the object graph represented by the dyn value.
|
||||
func (rt *RuleTypes) ConvertToRule(dyn *DynValue) Rule {
|
||||
ruleSchemaType := rt.ruleSchemaDeclTypes.root
|
||||
// TODO: handle conversions to protobuf types.
|
||||
dyn = rt.convertToCustomType(dyn, ruleSchemaType)
|
||||
return &CustomRule{DynValue: dyn}
|
||||
}
|
||||
|
||||
// NativeToValue is an implementation of the ref.TypeAdapater interface which supports conversion
|
||||
// of policy template values to CEL ref.Val instances.
|
||||
func (rt *RuleTypes) NativeToValue(val interface{}) ref.Val {
|
||||
switch v := val.(type) {
|
||||
case *CustomRule:
|
||||
return v.ExprValue()
|
||||
default:
|
||||
return rt.typeAdapter.NativeToValue(val)
|
||||
}
|
||||
}
|
||||
|
||||
// TypeNames returns the list of type names declared within the RuleTypes object.
|
||||
func (rt *RuleTypes) TypeNames() []string {
|
||||
typeNames := make([]string, len(rt.ruleSchemaDeclTypes.types))
|
||||
i := 0
|
||||
for name := range rt.ruleSchemaDeclTypes.types {
|
||||
typeNames[i] = name
|
||||
i++
|
||||
}
|
||||
return typeNames
|
||||
}
|
||||
|
||||
func (rt *RuleTypes) findDeclType(typeName string) (*DeclType, bool) {
|
||||
declType, found := rt.ruleSchemaDeclTypes.types[typeName]
|
||||
if found {
|
||||
return declType, true
|
||||
}
|
||||
declType, found = rt.resolver.FindType(typeName)
|
||||
if found {
|
||||
return declType, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (rt *RuleTypes) convertToCustomType(dyn *DynValue, declType *DeclType) *DynValue {
|
||||
switch v := dyn.Value().(type) {
|
||||
case *MapValue:
|
||||
if declType.IsObject() {
|
||||
obj := v.ConvertToObject(declType)
|
||||
for name, f := range obj.fieldMap {
|
||||
field := declType.Fields[name]
|
||||
f.Ref = rt.convertToCustomType(f.Ref, field.Type)
|
||||
}
|
||||
dyn.SetValue(obj)
|
||||
return dyn
|
||||
}
|
||||
// TODO: handle complex map types which have non-string keys.
|
||||
fieldType := declType.ElemType
|
||||
for _, f := range v.fieldMap {
|
||||
f.Ref = rt.convertToCustomType(f.Ref, fieldType)
|
||||
}
|
||||
return dyn
|
||||
case *ListValue:
|
||||
for i := 0; i < len(v.Entries); i++ {
|
||||
elem := v.Entries[i]
|
||||
elem = rt.convertToCustomType(elem, declType.ElemType)
|
||||
v.Entries[i] = elem
|
||||
}
|
||||
return dyn
|
||||
default:
|
||||
return dyn
|
||||
}
|
||||
}
|
||||
|
||||
func newSchemaTypeProvider(kind string, schema *OpenAPISchema) (*schemaTypeProvider, error) {
|
||||
root := schema.DeclType().MaybeAssignTypeName(kind)
|
||||
types := FieldTypeMap(kind, root)
|
||||
return &schemaTypeProvider{
|
||||
root: root,
|
||||
types: types,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type schemaTypeProvider struct {
|
||||
root *DeclType
|
||||
types map[string]*DeclType
|
||||
}
|
||||
|
||||
var (
|
||||
// AnyType is equivalent to the CEL 'protobuf.Any' type in that the value may have any of the
|
||||
// types supported by CEL Policy Templates.
|
||||
AnyType = newSimpleType("any", decls.Any, nil)
|
||||
|
||||
// BoolType is equivalent to the CEL 'bool' type.
|
||||
BoolType = newSimpleType("bool", decls.Bool, types.False)
|
||||
|
||||
// BytesType is equivalent to the CEL 'bytes' type.
|
||||
BytesType = newSimpleType("bytes", decls.Bytes, types.Bytes([]byte{}))
|
||||
|
||||
// DoubleType is equivalent to the CEL 'double' type which is a 64-bit floating point value.
|
||||
DoubleType = newSimpleType("double", decls.Double, types.Double(0))
|
||||
|
||||
// DurationType is equivalent to the CEL 'duration' type.
|
||||
DurationType = newSimpleType("duration", decls.Duration, types.Duration{Duration: time.Duration(0)})
|
||||
|
||||
// DynType is the equivalent of the CEL 'dyn' concept which indicates that the type will be
|
||||
// determined at runtime rather than compile time.
|
||||
DynType = newSimpleType("dyn", decls.Dyn, nil)
|
||||
|
||||
// IntType is equivalent to the CEL 'int' type which is a 64-bit signed int.
|
||||
IntType = newSimpleType("int", decls.Int, types.IntZero)
|
||||
|
||||
// NullType is equivalent to the CEL 'null_type'.
|
||||
NullType = newSimpleType("null_type", decls.Null, types.NullValue)
|
||||
|
||||
// StringType is equivalent to the CEL 'string' type which is expected to be a UTF-8 string.
|
||||
// StringType values may either be string literals or expression strings.
|
||||
StringType = newSimpleType("string", decls.String, types.String(""))
|
||||
|
||||
// PlainTextType is equivalent to the CEL 'string' type, but which has been specifically
|
||||
// designated as a string literal.
|
||||
PlainTextType = newSimpleType("string_lit", decls.String, types.String(""))
|
||||
|
||||
// TimestampType corresponds to the well-known protobuf.Timestamp type supported within CEL.
|
||||
TimestampType = newSimpleType("timestamp", decls.Timestamp, types.Timestamp{Time: time.Time{}})
|
||||
|
||||
// UintType is equivalent to the CEL 'uint' type.
|
||||
UintType = newSimpleType("uint", decls.Uint, types.Uint(0))
|
||||
|
||||
// ListType is equivalent to the CEL 'list' type.
|
||||
ListType = NewListType(AnyType)
|
||||
|
||||
// MapType is equivalent to the CEL 'map' type.
|
||||
MapType = NewMapType(AnyType, AnyType)
|
||||
)
|
155
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types_test.go
vendored
Normal file
155
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types_test.go
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
func TestTypes_ListType(t *testing.T) {
|
||||
list := NewListType(StringType)
|
||||
if !list.IsList() {
|
||||
t.Error("list type not identifiable as list")
|
||||
}
|
||||
if list.TypeName() != "list" {
|
||||
t.Errorf("got %s, wanted list", list.TypeName())
|
||||
}
|
||||
if list.DefaultValue() == nil {
|
||||
t.Error("got nil zero value for list type")
|
||||
}
|
||||
if list.ElemType.TypeName() != "string" {
|
||||
t.Errorf("got %s, wanted elem type of string", list.ElemType.TypeName())
|
||||
}
|
||||
if list.ExprType().GetListType() == nil {
|
||||
t.Errorf("got %v, wanted CEL list type", list.ExprType())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypes_MapType(t *testing.T) {
|
||||
mp := NewMapType(StringType, IntType)
|
||||
if !mp.IsMap() {
|
||||
t.Error("map type not identifiable as map")
|
||||
}
|
||||
if mp.TypeName() != "map" {
|
||||
t.Errorf("got %s, wanted map", mp.TypeName())
|
||||
}
|
||||
if mp.DefaultValue() == nil {
|
||||
t.Error("got nil zero value for map type")
|
||||
}
|
||||
if mp.KeyType.TypeName() != "string" {
|
||||
t.Errorf("got %s, wanted key type of string", mp.KeyType.TypeName())
|
||||
}
|
||||
if mp.ElemType.TypeName() != "int" {
|
||||
t.Errorf("got %s, wanted elem type of int", mp.ElemType.TypeName())
|
||||
}
|
||||
if mp.ExprType().GetMapType() == nil {
|
||||
t.Errorf("got %v, wanted CEL map type", mp.ExprType())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypes_RuleTypesFieldMapping(t *testing.T) {
|
||||
stdEnv, _ := cel.NewEnv()
|
||||
reg := NewRegistry(stdEnv)
|
||||
rt, err := NewRuleTypes("mock_template", testSchema(), reg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
nestedFieldType, found := rt.FindFieldType("CustomObject", "nested")
|
||||
if !found {
|
||||
t.Fatal("got field not found for 'CustomObject.nested', wanted found")
|
||||
}
|
||||
if nestedFieldType.Type.GetMessageType() != "CustomObject.nested" {
|
||||
t.Errorf("got field type %v, wanted mock_template.nested", nestedFieldType.Type)
|
||||
}
|
||||
subnameFieldType, found := rt.FindFieldType("CustomObject.nested", "subname")
|
||||
if !found {
|
||||
t.Fatal("got field not found for 'CustomObject.nested.subname', wanted found")
|
||||
}
|
||||
if subnameFieldType.Type.GetPrimitive() != exprpb.Type_STRING {
|
||||
t.Errorf("got field type %v, wanted string", subnameFieldType.Type)
|
||||
}
|
||||
flagsFieldType, found := rt.FindFieldType("CustomObject.nested", "flags")
|
||||
if !found {
|
||||
t.Fatal("got field not found for 'CustomObject.nested.flags', wanted found")
|
||||
}
|
||||
if flagsFieldType.Type.GetMapType() == nil {
|
||||
t.Errorf("got field type %v, wanted map", flagsFieldType.Type)
|
||||
}
|
||||
flagFieldType, found := rt.FindFieldType("CustomObject.nested.flags", "my_flag")
|
||||
if !found {
|
||||
t.Fatal("got field not found for 'CustomObject.nested.flags.my_flag', wanted found")
|
||||
}
|
||||
if flagFieldType.Type.GetPrimitive() != exprpb.Type_BOOL {
|
||||
t.Errorf("got field type %v, wanted bool", flagFieldType.Type)
|
||||
}
|
||||
|
||||
// Manually constructed instance of the schema.
|
||||
name := NewField(1, "name")
|
||||
name.Ref = testValue(t, 2, "test-instance")
|
||||
nestedVal := NewMapValue()
|
||||
flags := NewField(5, "flags")
|
||||
flagsVal := NewMapValue()
|
||||
myFlag := NewField(6, "my_flag")
|
||||
myFlag.Ref = testValue(t, 7, true)
|
||||
flagsVal.AddField(myFlag)
|
||||
flags.Ref = testValue(t, 8, flagsVal)
|
||||
dates := NewField(9, "dates")
|
||||
dates.Ref = testValue(t, 10, NewListValue())
|
||||
nestedVal.AddField(flags)
|
||||
nestedVal.AddField(dates)
|
||||
nested := NewField(3, "nested")
|
||||
nested.Ref = testValue(t, 4, nestedVal)
|
||||
mapVal := NewMapValue()
|
||||
mapVal.AddField(name)
|
||||
mapVal.AddField(nested)
|
||||
rule := rt.ConvertToRule(testValue(t, 11, mapVal))
|
||||
if rule == nil {
|
||||
t.Error("map could not be converted to rule")
|
||||
}
|
||||
if rule.GetID() != 11 {
|
||||
t.Errorf("got %d as the rule id, wanted 11", rule.GetID())
|
||||
}
|
||||
ruleVal := rt.NativeToValue(rule)
|
||||
if ruleVal == nil {
|
||||
t.Error("got CEL rule value of nil, wanted non-nil")
|
||||
}
|
||||
|
||||
opts, err := rt.EnvOptions(stdEnv.TypeProvider())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ruleEnv, err := stdEnv.Extend(opts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
helloVal := ruleEnv.TypeAdapter().NativeToValue("hello")
|
||||
if helloVal.Equal(types.String("hello")) != types.True {
|
||||
t.Errorf("got %v, wanted types.String('hello')", helloVal)
|
||||
}
|
||||
}
|
||||
|
||||
func testValue(t *testing.T, id int64, val interface{}) *DynValue {
|
||||
t.Helper()
|
||||
dv, err := NewDynValue(id, val)
|
||||
if err != nil {
|
||||
t.Fatalf("model.NewDynValue(%d, %v) failed: %v", id, val, err)
|
||||
}
|
||||
return dv
|
||||
}
|
782
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/value.go
vendored
Normal file
782
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/value.go
vendored
Normal file
@ -0,0 +1,782 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
)
|
||||
|
||||
// EncodeStyle is a hint for string encoding of parsed values.
|
||||
type EncodeStyle int
|
||||
|
||||
const (
|
||||
// BlockValueStyle is the default string encoding which preserves whitespace and newlines.
|
||||
BlockValueStyle EncodeStyle = iota
|
||||
|
||||
// FlowValueStyle indicates that the string is an inline representation of complex types.
|
||||
FlowValueStyle
|
||||
|
||||
// FoldedValueStyle is a multiline string with whitespace and newlines trimmed to a single
|
||||
// a whitespace. Repeated newlines are replaced with a single newline rather than a single
|
||||
// whitespace.
|
||||
FoldedValueStyle
|
||||
|
||||
// LiteralStyle is a multiline string that preserves newlines, but trims all other whitespace
|
||||
// to a single character.
|
||||
LiteralStyle
|
||||
)
|
||||
|
||||
// ParsedValue represents a top-level object representing either a template or instance value.
|
||||
type ParsedValue struct {
|
||||
ID int64
|
||||
Value *MapValue
|
||||
Meta SourceMetadata
|
||||
}
|
||||
|
||||
// NewEmptyDynValue returns the zero-valued DynValue.
|
||||
func NewEmptyDynValue() *DynValue {
|
||||
// note: 0 is not a valid parse node identifier.
|
||||
dv, _ := NewDynValue(0, nil)
|
||||
return dv
|
||||
}
|
||||
|
||||
// NewDynValue returns a DynValue that corresponds to a parse node id and value.
|
||||
func NewDynValue(id int64, val interface{}) (*DynValue, error) {
|
||||
dv := &DynValue{ID: id}
|
||||
err := dv.SetValue(val)
|
||||
return dv, err
|
||||
}
|
||||
|
||||
// DynValue is a dynamically typed value used to describe unstructured content.
|
||||
// Whether the value has the desired type is determined by where it is used within the Instance or
|
||||
// Template, and whether there are schemas which might enforce a more rigid type definition.
|
||||
type DynValue struct {
|
||||
ID int64
|
||||
EncodeStyle EncodeStyle
|
||||
value interface{}
|
||||
exprValue ref.Val
|
||||
declType *DeclType
|
||||
}
|
||||
|
||||
// DeclType returns the policy model type of the dyn value.
|
||||
func (dv *DynValue) DeclType() *DeclType {
|
||||
return dv.declType
|
||||
}
|
||||
|
||||
// ConvertToNative is an implementation of the CEL ref.Val method used to adapt between CEL types
|
||||
// and Go-native types.
|
||||
//
|
||||
// The default behavior of this method is to first convert to a CEL type which has a well-defined
|
||||
// set of conversion behaviors and proxy to the CEL ConvertToNative method for the type.
|
||||
func (dv *DynValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
ev := dv.ExprValue()
|
||||
if types.IsError(ev) {
|
||||
return nil, ev.(*types.Err)
|
||||
}
|
||||
return ev.ConvertToNative(typeDesc)
|
||||
}
|
||||
|
||||
// Equal returns whether the dyn value is equal to a given CEL value.
|
||||
func (dv *DynValue) Equal(other ref.Val) ref.Val {
|
||||
dvType := dv.Type()
|
||||
otherType := other.Type()
|
||||
// Preserve CEL's homogeneous equality constraint.
|
||||
if dvType.TypeName() != otherType.TypeName() {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
switch v := dv.value.(type) {
|
||||
case ref.Val:
|
||||
return v.Equal(other)
|
||||
case PlainTextValue:
|
||||
return celBool(string(v) == other.Value().(string))
|
||||
case *MultilineStringValue:
|
||||
return celBool(v.Value == other.Value().(string))
|
||||
case time.Duration:
|
||||
otherDuration := other.Value().(time.Duration)
|
||||
return celBool(v == otherDuration)
|
||||
case time.Time:
|
||||
otherTimestamp := other.Value().(time.Time)
|
||||
return celBool(v.Equal(otherTimestamp))
|
||||
default:
|
||||
return celBool(reflect.DeepEqual(v, other.Value()))
|
||||
}
|
||||
}
|
||||
|
||||
// ExprValue converts the DynValue into a CEL value.
|
||||
func (dv *DynValue) ExprValue() ref.Val {
|
||||
return dv.exprValue
|
||||
}
|
||||
|
||||
// Value returns the underlying value held by this reference.
|
||||
func (dv *DynValue) Value() interface{} {
|
||||
return dv.value
|
||||
}
|
||||
|
||||
// SetValue updates the underlying value held by this reference.
|
||||
func (dv *DynValue) SetValue(value interface{}) error {
|
||||
dv.value = value
|
||||
var err error
|
||||
dv.exprValue, dv.declType, err = exprValue(value)
|
||||
return err
|
||||
}
|
||||
|
||||
// Type returns the CEL type for the given value.
|
||||
func (dv *DynValue) Type() ref.Type {
|
||||
return dv.ExprValue().Type()
|
||||
}
|
||||
|
||||
func exprValue(value interface{}) (ref.Val, *DeclType, error) {
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
return types.Bool(v), BoolType, nil
|
||||
case []byte:
|
||||
return types.Bytes(v), BytesType, nil
|
||||
case float64:
|
||||
return types.Double(v), DoubleType, nil
|
||||
case int64:
|
||||
return types.Int(v), IntType, nil
|
||||
case string:
|
||||
return types.String(v), StringType, nil
|
||||
case uint64:
|
||||
return types.Uint(v), UintType, nil
|
||||
case PlainTextValue:
|
||||
return types.String(string(v)), PlainTextType, nil
|
||||
case *MultilineStringValue:
|
||||
return types.String(v.Value), StringType, nil
|
||||
case time.Duration:
|
||||
return types.Duration{Duration: v}, DurationType, nil
|
||||
case time.Time:
|
||||
return types.Timestamp{Time: v}, TimestampType, nil
|
||||
case types.Null:
|
||||
return v, NullType, nil
|
||||
case *ListValue:
|
||||
return v, ListType, nil
|
||||
case *MapValue:
|
||||
return v, MapType, nil
|
||||
case *ObjectValue:
|
||||
return v, v.objectType, nil
|
||||
default:
|
||||
return nil, unknownType, fmt.Errorf("unsupported type: (%T)%v", v, v)
|
||||
}
|
||||
}
|
||||
|
||||
// PlainTextValue is a text string literal which must not be treated as an expression.
|
||||
type PlainTextValue string
|
||||
|
||||
// MultilineStringValue is a multiline string value which has been parsed in a way which omits
|
||||
// whitespace as well as a raw form which preserves whitespace.
|
||||
type MultilineStringValue struct {
|
||||
Value string
|
||||
Raw string
|
||||
}
|
||||
|
||||
func newStructValue() *structValue {
|
||||
return &structValue{
|
||||
Fields: []*Field{},
|
||||
fieldMap: map[string]*Field{},
|
||||
}
|
||||
}
|
||||
|
||||
type structValue struct {
|
||||
Fields []*Field
|
||||
fieldMap map[string]*Field
|
||||
}
|
||||
|
||||
// AddField appends a MapField to the MapValue and indexes the field by name.
|
||||
func (sv *structValue) AddField(field *Field) {
|
||||
sv.Fields = append(sv.Fields, field)
|
||||
sv.fieldMap[field.Name] = field
|
||||
}
|
||||
|
||||
// ConvertToNative converts the MapValue type to a native go types.
|
||||
func (sv *structValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
if typeDesc.Kind() != reflect.Map &&
|
||||
typeDesc.Kind() != reflect.Struct &&
|
||||
typeDesc.Kind() != reflect.Ptr &&
|
||||
typeDesc.Kind() != reflect.Interface {
|
||||
return nil, fmt.Errorf("type conversion error from object to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
// TODO: Special case handling for protobuf Struct and Any if needed
|
||||
|
||||
// Unwrap pointers, but track their use.
|
||||
isPtr := false
|
||||
if typeDesc.Kind() == reflect.Ptr {
|
||||
tk := typeDesc
|
||||
typeDesc = typeDesc.Elem()
|
||||
if typeDesc.Kind() == reflect.Ptr {
|
||||
return nil, fmt.Errorf("unsupported type conversion to '%v'", tk)
|
||||
}
|
||||
isPtr = true
|
||||
}
|
||||
|
||||
if typeDesc.Kind() == reflect.Map {
|
||||
keyType := typeDesc.Key()
|
||||
if keyType.Kind() != reflect.String && keyType.Kind() != reflect.Interface {
|
||||
return nil, fmt.Errorf("object fields cannot be converted to type '%v'", keyType)
|
||||
}
|
||||
elemType := typeDesc.Elem()
|
||||
sz := len(sv.fieldMap)
|
||||
ntvMap := reflect.MakeMapWithSize(typeDesc, sz)
|
||||
for name, val := range sv.fieldMap {
|
||||
refVal, err := val.Ref.ConvertToNative(elemType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ntvMap.SetMapIndex(reflect.ValueOf(name), reflect.ValueOf(refVal))
|
||||
}
|
||||
return ntvMap.Interface(), nil
|
||||
}
|
||||
|
||||
if typeDesc.Kind() == reflect.Struct {
|
||||
ntvObjPtr := reflect.New(typeDesc)
|
||||
ntvObj := ntvObjPtr.Elem()
|
||||
for name, val := range sv.fieldMap {
|
||||
f := ntvObj.FieldByName(name)
|
||||
if !f.IsValid() {
|
||||
return nil, fmt.Errorf("type conversion error, no such field %s in type %v",
|
||||
name, typeDesc)
|
||||
}
|
||||
fv, err := val.Ref.ConvertToNative(f.Type())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Set(reflect.ValueOf(fv))
|
||||
}
|
||||
if isPtr {
|
||||
return ntvObjPtr.Interface(), nil
|
||||
}
|
||||
return ntvObj.Interface(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("type conversion error from object to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
// GetField returns a MapField by name if one exists.
|
||||
func (sv *structValue) GetField(name string) (*Field, bool) {
|
||||
field, found := sv.fieldMap[name]
|
||||
return field, found
|
||||
}
|
||||
|
||||
// IsSet returns whether the given field, which is defined, has also been set.
|
||||
func (sv *structValue) IsSet(key ref.Val) ref.Val {
|
||||
k, ok := key.(types.String)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(key)
|
||||
}
|
||||
name := string(k)
|
||||
_, found := sv.fieldMap[name]
|
||||
return celBool(found)
|
||||
}
|
||||
|
||||
// NewObjectValue creates a struct value with a schema type and returns the empty ObjectValue.
|
||||
func NewObjectValue(sType *DeclType) *ObjectValue {
|
||||
return &ObjectValue{
|
||||
structValue: newStructValue(),
|
||||
objectType: sType,
|
||||
}
|
||||
}
|
||||
|
||||
// ObjectValue is a struct with a custom schema type which indicates the fields and types
|
||||
// associated with the structure.
|
||||
type ObjectValue struct {
|
||||
*structValue
|
||||
objectType *DeclType
|
||||
}
|
||||
|
||||
// ConvertToType is an implementation of the CEL ref.Val interface method.
|
||||
func (o *ObjectValue) ConvertToType(t ref.Type) ref.Val {
|
||||
if t == types.TypeType {
|
||||
return types.NewObjectTypeValue(o.objectType.TypeName())
|
||||
}
|
||||
if t.TypeName() == o.objectType.TypeName() {
|
||||
return o
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", o.Type(), t)
|
||||
}
|
||||
|
||||
// Equal returns true if the two object types are equal and their field values are equal.
|
||||
func (o *ObjectValue) Equal(other ref.Val) ref.Val {
|
||||
// Preserve CEL's homogeneous equality semantics.
|
||||
if o.objectType.TypeName() != other.Type().TypeName() {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
o2 := other.(traits.Indexer)
|
||||
for name := range o.objectType.Fields {
|
||||
k := types.String(name)
|
||||
v := o.Get(k)
|
||||
ov := o2.Get(k)
|
||||
vEq := v.Equal(ov)
|
||||
if vEq != types.True {
|
||||
return vEq
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
// Get returns the value of the specified field.
|
||||
//
|
||||
// If the field is set, its value is returned. If the field is not set, the default value for the
|
||||
// field is returned thus allowing for safe-traversal and preserving proto-like field traversal
|
||||
// semantics for Open API Schema backed types.
|
||||
func (o *ObjectValue) Get(name ref.Val) ref.Val {
|
||||
n, ok := name.(types.String)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(n)
|
||||
}
|
||||
nameStr := string(n)
|
||||
field, found := o.fieldMap[nameStr]
|
||||
if found {
|
||||
return field.Ref.ExprValue()
|
||||
}
|
||||
fieldDef, found := o.objectType.Fields[nameStr]
|
||||
if !found {
|
||||
return types.NewErr("no such field: %s", nameStr)
|
||||
}
|
||||
defValue := fieldDef.DefaultValue()
|
||||
if defValue != nil {
|
||||
return defValue
|
||||
}
|
||||
return types.NewErr("no default for type: %s", fieldDef.TypeName())
|
||||
}
|
||||
|
||||
// Type returns the CEL type value of the object.
|
||||
func (o *ObjectValue) Type() ref.Type {
|
||||
return o.objectType
|
||||
}
|
||||
|
||||
// Value returns the Go-native representation of the object.
|
||||
func (o *ObjectValue) Value() interface{} {
|
||||
return o
|
||||
}
|
||||
|
||||
// NewMapValue returns an empty MapValue.
|
||||
func NewMapValue() *MapValue {
|
||||
return &MapValue{
|
||||
structValue: newStructValue(),
|
||||
}
|
||||
}
|
||||
|
||||
// MapValue declares an object with a set of named fields whose values are dynamically typed.
|
||||
type MapValue struct {
|
||||
*structValue
|
||||
}
|
||||
|
||||
// ConvertToObject produces an ObjectValue from the MapValue with the associated schema type.
|
||||
//
|
||||
// The conversion is shallow and the memory shared between the Object and Map as all references
|
||||
// to the map are expected to be replaced with the Object reference.
|
||||
func (m *MapValue) ConvertToObject(declType *DeclType) *ObjectValue {
|
||||
return &ObjectValue{
|
||||
structValue: m.structValue,
|
||||
objectType: declType,
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns whether the given key is contained in the MapValue.
|
||||
func (m *MapValue) Contains(key ref.Val) ref.Val {
|
||||
v, found := m.Find(key)
|
||||
if v != nil && types.IsUnknownOrError(v) {
|
||||
return v
|
||||
}
|
||||
return celBool(found)
|
||||
}
|
||||
|
||||
// ConvertToType converts the MapValue to another CEL type, if possible.
|
||||
func (m *MapValue) ConvertToType(t ref.Type) ref.Val {
|
||||
switch t {
|
||||
case types.MapType:
|
||||
return m
|
||||
case types.TypeType:
|
||||
return types.MapType
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", m.Type(), t)
|
||||
}
|
||||
|
||||
// Equal returns true if the maps are of the same size, have the same keys, and the key-values
|
||||
// from each map are equal.
|
||||
func (m *MapValue) Equal(other ref.Val) ref.Val {
|
||||
oMap, isMap := other.(traits.Mapper)
|
||||
if !isMap {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
if m.Size() != oMap.Size() {
|
||||
return types.False
|
||||
}
|
||||
for name, field := range m.fieldMap {
|
||||
k := types.String(name)
|
||||
ov, found := oMap.Find(k)
|
||||
if !found {
|
||||
return types.False
|
||||
}
|
||||
v := field.Ref.ExprValue()
|
||||
vEq := v.Equal(ov)
|
||||
if vEq != types.True {
|
||||
return vEq
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
// Find returns the value for the key in the map, if found.
|
||||
func (m *MapValue) Find(name ref.Val) (ref.Val, bool) {
|
||||
// Currently only maps with string keys are supported as this is best aligned with JSON,
|
||||
// and also much simpler to support.
|
||||
n, ok := name.(types.String)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(n), true
|
||||
}
|
||||
nameStr := string(n)
|
||||
field, found := m.fieldMap[nameStr]
|
||||
if found {
|
||||
return field.Ref.ExprValue(), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Get returns the value for the key in the map, or error if not found.
|
||||
func (m *MapValue) Get(key ref.Val) ref.Val {
|
||||
v, found := m.Find(key)
|
||||
if found {
|
||||
return v
|
||||
}
|
||||
return types.ValOrErr(key, "no such key: %v", key)
|
||||
}
|
||||
|
||||
// Iterator produces a traits.Iterator which walks over the map keys.
|
||||
//
|
||||
// The Iterator is frequently used within comprehensions.
|
||||
func (m *MapValue) Iterator() traits.Iterator {
|
||||
keys := make([]ref.Val, len(m.fieldMap))
|
||||
i := 0
|
||||
for k := range m.fieldMap {
|
||||
keys[i] = types.String(k)
|
||||
i++
|
||||
}
|
||||
return &baseMapIterator{
|
||||
baseVal: &baseVal{},
|
||||
keys: keys,
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the number of keys in the map.
|
||||
func (m *MapValue) Size() ref.Val {
|
||||
return types.Int(len(m.Fields))
|
||||
}
|
||||
|
||||
// Type returns the CEL ref.Type for the map.
|
||||
func (m *MapValue) Type() ref.Type {
|
||||
return types.MapType
|
||||
}
|
||||
|
||||
// Value returns the Go-native representation of the MapValue.
|
||||
func (m *MapValue) Value() interface{} {
|
||||
return m
|
||||
}
|
||||
|
||||
type baseMapIterator struct {
|
||||
*baseVal
|
||||
keys []ref.Val
|
||||
idx int
|
||||
}
|
||||
|
||||
// HasNext implements the traits.Iterator interface method.
|
||||
func (it *baseMapIterator) HasNext() ref.Val {
|
||||
if it.idx < len(it.keys) {
|
||||
return types.True
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
// Next implements the traits.Iterator interface method.
|
||||
func (it *baseMapIterator) Next() ref.Val {
|
||||
key := it.keys[it.idx]
|
||||
it.idx++
|
||||
return key
|
||||
}
|
||||
|
||||
// Type implements the CEL ref.Val interface metohd.
|
||||
func (it *baseMapIterator) Type() ref.Type {
|
||||
return types.IteratorType
|
||||
}
|
||||
|
||||
// NewField returns a MapField instance with an empty DynValue that refers to the
|
||||
// specified parse node id and field name.
|
||||
func NewField(id int64, name string) *Field {
|
||||
return &Field{
|
||||
ID: id,
|
||||
Name: name,
|
||||
Ref: NewEmptyDynValue(),
|
||||
}
|
||||
}
|
||||
|
||||
// Field specifies a field name and a reference to a dynamic value.
|
||||
type Field struct {
|
||||
ID int64
|
||||
Name string
|
||||
Ref *DynValue
|
||||
}
|
||||
|
||||
// NewListValue returns an empty ListValue instance.
|
||||
func NewListValue() *ListValue {
|
||||
return &ListValue{
|
||||
Entries: []*DynValue{},
|
||||
}
|
||||
}
|
||||
|
||||
// ListValue contains a list of dynamically typed entries.
|
||||
type ListValue struct {
|
||||
Entries []*DynValue
|
||||
initValueSet sync.Once
|
||||
valueSet map[ref.Val]struct{}
|
||||
}
|
||||
|
||||
// Add concatenates two lists together to produce a new CEL list value.
|
||||
func (lv *ListValue) Add(other ref.Val) ref.Val {
|
||||
oArr, isArr := other.(traits.Lister)
|
||||
if !isArr {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
szRight := len(lv.Entries)
|
||||
szLeft := int(oArr.Size().(types.Int))
|
||||
sz := szRight + szLeft
|
||||
combo := make([]ref.Val, sz)
|
||||
for i := 0; i < szRight; i++ {
|
||||
combo[i] = lv.Entries[i].ExprValue()
|
||||
}
|
||||
for i := 0; i < szLeft; i++ {
|
||||
combo[i+szRight] = oArr.Get(types.Int(i))
|
||||
}
|
||||
return types.DefaultTypeAdapter.NativeToValue(combo)
|
||||
}
|
||||
|
||||
// Append adds another entry into the ListValue.
|
||||
func (lv *ListValue) Append(entry *DynValue) {
|
||||
lv.Entries = append(lv.Entries, entry)
|
||||
// The append resets all previously built indices.
|
||||
lv.initValueSet = sync.Once{}
|
||||
}
|
||||
|
||||
// Contains returns whether the input `val` is equal to an element in the list.
|
||||
//
|
||||
// If any pair-wise comparison between the input value and the list element is an error, the
|
||||
// operation will return an error.
|
||||
func (lv *ListValue) Contains(val ref.Val) ref.Val {
|
||||
if types.IsUnknownOrError(val) {
|
||||
return val
|
||||
}
|
||||
lv.initValueSet.Do(lv.finalizeValueSet)
|
||||
if lv.valueSet != nil {
|
||||
_, found := lv.valueSet[val]
|
||||
if found {
|
||||
return types.True
|
||||
}
|
||||
// Instead of returning false, ensure that CEL's heterogeneous equality constraint
|
||||
// is satisfied by allowing pair-wise equality behavior to determine the outcome.
|
||||
}
|
||||
var err ref.Val
|
||||
sz := len(lv.Entries)
|
||||
for i := 0; i < sz; i++ {
|
||||
elem := lv.Entries[i]
|
||||
cmp := elem.Equal(val)
|
||||
b, ok := cmp.(types.Bool)
|
||||
if !ok && err == nil {
|
||||
err = types.MaybeNoSuchOverloadErr(cmp)
|
||||
}
|
||||
if b == types.True {
|
||||
return types.True
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
// ConvertToNative is an implementation of the CEL ref.Val method used to adapt between CEL types
|
||||
// and Go-native array-like types.
|
||||
func (lv *ListValue) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
// Non-list conversion.
|
||||
if typeDesc.Kind() != reflect.Slice &&
|
||||
typeDesc.Kind() != reflect.Array &&
|
||||
typeDesc.Kind() != reflect.Interface {
|
||||
return nil, fmt.Errorf("type conversion error from list to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
// If the list is already assignable to the desired type return it.
|
||||
if reflect.TypeOf(lv).AssignableTo(typeDesc) {
|
||||
return lv, nil
|
||||
}
|
||||
|
||||
// List conversion.
|
||||
otherElem := typeDesc.Elem()
|
||||
|
||||
// Allow the element ConvertToNative() function to determine whether conversion is possible.
|
||||
sz := len(lv.Entries)
|
||||
nativeList := reflect.MakeSlice(typeDesc, int(sz), int(sz))
|
||||
for i := 0; i < sz; i++ {
|
||||
elem := lv.Entries[i]
|
||||
nativeElemVal, err := elem.ConvertToNative(otherElem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nativeList.Index(int(i)).Set(reflect.ValueOf(nativeElemVal))
|
||||
}
|
||||
return nativeList.Interface(), nil
|
||||
}
|
||||
|
||||
// ConvertToType converts the ListValue to another CEL type.
|
||||
func (lv *ListValue) ConvertToType(t ref.Type) ref.Val {
|
||||
switch t {
|
||||
case types.ListType:
|
||||
return lv
|
||||
case types.TypeType:
|
||||
return types.ListType
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", ListType, t)
|
||||
}
|
||||
|
||||
// Equal returns true if two lists are of the same size, and the values at each index are also
|
||||
// equal.
|
||||
func (lv *ListValue) Equal(other ref.Val) ref.Val {
|
||||
oArr, isArr := other.(traits.Lister)
|
||||
if !isArr {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
sz := types.Int(len(lv.Entries))
|
||||
if sz != oArr.Size() {
|
||||
return types.False
|
||||
}
|
||||
for i := types.Int(0); i < sz; i++ {
|
||||
cmp := lv.Get(i).Equal(oArr.Get(i))
|
||||
if cmp != types.True {
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
return types.True
|
||||
}
|
||||
|
||||
// Get returns the value at the given index.
|
||||
//
|
||||
// If the index is negative or greater than the size of the list, an error is returned.
|
||||
func (lv *ListValue) Get(idx ref.Val) ref.Val {
|
||||
iv, isInt := idx.(types.Int)
|
||||
if !isInt {
|
||||
return types.ValOrErr(idx, "unsupported index: %v", idx)
|
||||
}
|
||||
i := int(iv)
|
||||
if i < 0 || i >= len(lv.Entries) {
|
||||
return types.NewErr("index out of bounds: %v", idx)
|
||||
}
|
||||
return lv.Entries[i].ExprValue()
|
||||
}
|
||||
|
||||
// Iterator produces a traits.Iterator suitable for use in CEL comprehension macros.
|
||||
func (lv *ListValue) Iterator() traits.Iterator {
|
||||
return &baseListIterator{
|
||||
getter: lv.Get,
|
||||
sz: len(lv.Entries),
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the number of elements in the list.
|
||||
func (lv *ListValue) Size() ref.Val {
|
||||
return types.Int(len(lv.Entries))
|
||||
}
|
||||
|
||||
// Type returns the CEL ref.Type for the list.
|
||||
func (lv *ListValue) Type() ref.Type {
|
||||
return types.ListType
|
||||
}
|
||||
|
||||
// Value returns the Go-native value.
|
||||
func (lv *ListValue) Value() interface{} {
|
||||
return lv
|
||||
}
|
||||
|
||||
// finalizeValueSet inspects the ListValue entries in order to make internal optimizations once all list
|
||||
// entries are known.
|
||||
func (lv *ListValue) finalizeValueSet() {
|
||||
valueSet := make(map[ref.Val]struct{})
|
||||
for _, e := range lv.Entries {
|
||||
switch e.value.(type) {
|
||||
case bool, float64, int64, string, uint64, types.Null, PlainTextValue:
|
||||
if valueSet != nil {
|
||||
valueSet[e.ExprValue()] = struct{}{}
|
||||
}
|
||||
default:
|
||||
lv.valueSet = nil
|
||||
return
|
||||
}
|
||||
}
|
||||
lv.valueSet = valueSet
|
||||
}
|
||||
|
||||
type baseVal struct{}
|
||||
|
||||
func (*baseVal) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
return nil, fmt.Errorf("unsupported native conversion to: %v", typeDesc)
|
||||
}
|
||||
|
||||
func (*baseVal) ConvertToType(t ref.Type) ref.Val {
|
||||
return types.NewErr("unsupported type conversion to: %v", t)
|
||||
}
|
||||
|
||||
func (*baseVal) Equal(other ref.Val) ref.Val {
|
||||
return types.NewErr("unsupported equality test between instances")
|
||||
}
|
||||
|
||||
func (v *baseVal) Value() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
type baseListIterator struct {
|
||||
*baseVal
|
||||
getter func(idx ref.Val) ref.Val
|
||||
sz int
|
||||
idx int
|
||||
}
|
||||
|
||||
func (it *baseListIterator) HasNext() ref.Val {
|
||||
if it.idx < it.sz {
|
||||
return types.True
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
func (it *baseListIterator) Next() ref.Val {
|
||||
v := it.getter(types.Int(it.idx))
|
||||
it.idx++
|
||||
return v
|
||||
}
|
||||
|
||||
func (it *baseListIterator) Type() ref.Type {
|
||||
return types.IteratorType
|
||||
}
|
||||
|
||||
func celBool(pred bool) ref.Val {
|
||||
if pred {
|
||||
return types.True
|
||||
}
|
||||
return types.False
|
||||
}
|
||||
|
||||
var unknownType = &DeclType{name: "unknown"}
|
363
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/value_test.go
vendored
Normal file
363
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/value_test.go
vendored
Normal file
@ -0,0 +1,363 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
)
|
||||
|
||||
func TestConvertToType(t *testing.T) {
|
||||
objType := NewObjectType("TestObject", map[string]*DeclField{})
|
||||
tests := []struct {
|
||||
val interface{}
|
||||
typ ref.Type
|
||||
}{
|
||||
{true, types.BoolType},
|
||||
{float64(1.2), types.DoubleType},
|
||||
{int64(-42), types.IntType},
|
||||
{uint64(63), types.UintType},
|
||||
{PlainTextValue("plain text"), types.StringType},
|
||||
{&MultilineStringValue{Value: "multiline", Raw: "multi\nline"}, types.StringType},
|
||||
{time.Duration(300), types.DurationType},
|
||||
{time.Now().UTC(), types.TimestampType},
|
||||
{types.NullValue, types.NullType},
|
||||
{NewListValue(), types.ListType},
|
||||
{NewMapValue(), types.MapType},
|
||||
{[]byte("bytes"), types.BytesType},
|
||||
{NewObjectValue(objType), objType},
|
||||
}
|
||||
for i, tc := range tests {
|
||||
idx := i
|
||||
tst := tc
|
||||
t.Run(fmt.Sprintf("[%d]", i), func(t *testing.T) {
|
||||
dv := testValue(t, int64(idx), tst.val)
|
||||
ev := dv.ExprValue()
|
||||
if ev.ConvertToType(types.TypeType).(ref.Type).TypeName() != tst.typ.TypeName() {
|
||||
t.Errorf("got %v, wanted %v type", ev.ConvertToType(types.TypeType), tst.typ)
|
||||
}
|
||||
if ev.ConvertToType(tst.typ).Equal(ev) != types.True {
|
||||
t.Errorf("got %v, wanted input value %v", ev.ConvertToType(tst.typ), ev)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
vals := []interface{}{
|
||||
true, []byte("bytes"), float64(1.2), int64(-42), uint64(63), PlainTextValue("plain text"),
|
||||
&MultilineStringValue{Value: "multiline", Raw: "multi\nline"}, time.Duration(300),
|
||||
time.Now().UTC(), types.NullValue, NewListValue(), NewMapValue(),
|
||||
NewObjectValue(NewObjectType("TestObject", map[string]*DeclField{})),
|
||||
}
|
||||
for i, v := range vals {
|
||||
dv := testValue(t, int64(i), v)
|
||||
if dv.Equal(dv.ExprValue()) != types.True {
|
||||
t.Errorf("got %v, wanted dyn value %v equal to itself", dv.Equal(dv.ExprValue()), dv.ExprValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListValueAdd(t *testing.T) {
|
||||
lv := NewListValue()
|
||||
lv.Append(testValue(t, 1, "first"))
|
||||
ov := NewListValue()
|
||||
ov.Append(testValue(t, 2, "second"))
|
||||
ov.Append(testValue(t, 3, "third"))
|
||||
llv := NewListValue()
|
||||
llv.Append(testValue(t, 4, lv))
|
||||
lov := NewListValue()
|
||||
lov.Append(testValue(t, 5, ov))
|
||||
var v traits.Lister = llv.Add(lov).(traits.Lister)
|
||||
if v.Size() != types.Int(2) {
|
||||
t.Errorf("got list size %d, wanted 2", v.Size())
|
||||
}
|
||||
complex, err := v.ConvertToNative(reflect.TypeOf([][]string{}))
|
||||
complexList := complex.([][]string)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(complexList, [][]string{{"first"}, {"second", "third"}}) {
|
||||
t.Errorf("got %v, wanted [['first'], ['second', 'third']]", complexList)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListValueContains(t *testing.T) {
|
||||
lv := NewListValue()
|
||||
lv.Append(testValue(t, 1, "first"))
|
||||
lv.Append(testValue(t, 2, "second"))
|
||||
lv.Append(testValue(t, 3, "third"))
|
||||
for i := types.Int(0); i < lv.Size().(types.Int); i++ {
|
||||
e := lv.Get(i)
|
||||
contained := lv.Contains(e)
|
||||
if contained != types.True {
|
||||
t.Errorf("got %v, wanted list contains elem[%v] %v == true", contained, i, e)
|
||||
}
|
||||
}
|
||||
if lv.Contains(types.String("fourth")) != types.False {
|
||||
t.Errorf("got %v, wanted false 'fourth'", lv.Contains(types.String("fourth")))
|
||||
}
|
||||
if !types.IsError(lv.Contains(types.Int(-1))) {
|
||||
t.Errorf("got %v, wanted error for invalid type", lv.Contains(types.Int(-1)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestListValueContainsNestedList(t *testing.T) {
|
||||
lvA := NewListValue()
|
||||
lvA.Append(testValue(t, 1, int64(1)))
|
||||
lvA.Append(testValue(t, 2, int64(2)))
|
||||
|
||||
lvB := NewListValue()
|
||||
lvB.Append(testValue(t, 3, int64(3)))
|
||||
|
||||
elemA, elemB := testValue(t, 4, lvA), testValue(t, 5, lvB)
|
||||
lv := NewListValue()
|
||||
lv.Append(elemA)
|
||||
lv.Append(elemB)
|
||||
|
||||
contained := lv.Contains(elemA.ExprValue())
|
||||
if contained != types.True {
|
||||
t.Errorf("got %v, wanted elemA contained in list value", contained)
|
||||
}
|
||||
contained = lv.Contains(elemB.ExprValue())
|
||||
if contained != types.True {
|
||||
t.Errorf("got %v, wanted elemB contained in list value", contained)
|
||||
}
|
||||
contained = lv.Contains(types.DefaultTypeAdapter.NativeToValue([]int32{4}))
|
||||
if contained != types.False {
|
||||
t.Errorf("got %v, wanted empty list not contained", contained)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListValueConvertToNative(t *testing.T) {
|
||||
lv := NewListValue()
|
||||
none, err := lv.ConvertToNative(reflect.TypeOf([]interface{}{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(none, []interface{}{}) {
|
||||
t.Errorf("got %v, wanted empty list", none)
|
||||
}
|
||||
lv.Append(testValue(t, 1, "first"))
|
||||
one, err := lv.ConvertToNative(reflect.TypeOf([]string{}))
|
||||
oneList := one.([]string)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(oneList) != 1 {
|
||||
t.Errorf("got len(one) == %d, wanted 1", len(oneList))
|
||||
}
|
||||
if !reflect.DeepEqual(oneList, []string{"first"}) {
|
||||
t.Errorf("got %v, wanted string list", oneList)
|
||||
}
|
||||
ov := NewListValue()
|
||||
ov.Append(testValue(t, 2, "second"))
|
||||
ov.Append(testValue(t, 3, "third"))
|
||||
if ov.Size() != types.Int(2) {
|
||||
t.Errorf("got list size %d, wanted 2", ov.Size())
|
||||
}
|
||||
llv := NewListValue()
|
||||
llv.Append(testValue(t, 4, lv))
|
||||
llv.Append(testValue(t, 5, ov))
|
||||
if llv.Size() != types.Int(2) {
|
||||
t.Errorf("got list size %d, wanted 2", llv.Size())
|
||||
}
|
||||
complex, err := llv.ConvertToNative(reflect.TypeOf([][]string{}))
|
||||
complexList := complex.([][]string)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(complexList, [][]string{{"first"}, {"second", "third"}}) {
|
||||
t.Errorf("got %v, wanted [['first'], ['second', 'third']]", complexList)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListValueIterator(t *testing.T) {
|
||||
lv := NewListValue()
|
||||
lv.Append(testValue(t, 1, "first"))
|
||||
lv.Append(testValue(t, 2, "second"))
|
||||
lv.Append(testValue(t, 3, "third"))
|
||||
it := lv.Iterator()
|
||||
if it.Type() != types.IteratorType {
|
||||
t.Errorf("got type %v for iterator, wanted IteratorType", it.Type())
|
||||
}
|
||||
i := types.Int(0)
|
||||
for it.HasNext() == types.True {
|
||||
v := it.Next()
|
||||
if v.Equal(lv.Get(i)) != types.True {
|
||||
t.Errorf("iterator value %v and value %v at index %d not equal", v, lv.Get(i), i)
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapValueConvertToNative(t *testing.T) {
|
||||
mv := NewMapValue()
|
||||
none, err := mv.ConvertToNative(reflect.TypeOf(map[string]interface{}{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(none, map[string]interface{}{}) {
|
||||
t.Errorf("got %v, wanted empty map", none)
|
||||
}
|
||||
none, err = mv.ConvertToNative(reflect.TypeOf(map[interface{}]interface{}{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(none, map[interface{}]interface{}{}) {
|
||||
t.Errorf("got %v, wanted empty map", none)
|
||||
}
|
||||
mv.AddField(NewField(1, "Test"))
|
||||
tst, _ := mv.GetField("Test")
|
||||
tst.Ref = testValue(t, 2, uint64(12))
|
||||
mv.AddField(NewField(3, "Check"))
|
||||
chk, _ := mv.GetField("Check")
|
||||
chk.Ref = testValue(t, 4, uint64(34))
|
||||
if mv.Size() != types.Int(2) {
|
||||
t.Errorf("got size %d, wanted 2", mv.Size())
|
||||
}
|
||||
if mv.Contains(types.String("Test")) != types.True {
|
||||
t.Error("key 'Test' not found")
|
||||
}
|
||||
if mv.Contains(types.String("Check")) != types.True {
|
||||
t.Error("key 'Check' not found")
|
||||
}
|
||||
if mv.Contains(types.String("Checked")) != types.False {
|
||||
t.Error("key 'Checked' found, wanted not found")
|
||||
}
|
||||
it := mv.Iterator()
|
||||
for it.HasNext() == types.True {
|
||||
k := it.Next()
|
||||
v := mv.Get(k)
|
||||
if k == types.String("Test") && v != types.Uint(12) {
|
||||
t.Errorf("key 'Test' not equal to 12u")
|
||||
}
|
||||
if k == types.String("Check") && v != types.Uint(34) {
|
||||
t.Errorf("key 'Check' not equal to 34u")
|
||||
}
|
||||
}
|
||||
mpStrUint, err := mv.ConvertToNative(reflect.TypeOf(map[string]uint64{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(mpStrUint, map[string]uint64{
|
||||
"Test": uint64(12),
|
||||
"Check": uint64(34),
|
||||
}) {
|
||||
t.Errorf("got %v, wanted {'Test': 12u, 'Check': 34u}", mpStrUint)
|
||||
}
|
||||
tstStr, err := mv.ConvertToNative(reflect.TypeOf(&tstStruct{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(tstStr, &tstStruct{
|
||||
Test: uint64(12),
|
||||
Check: uint64(34),
|
||||
}) {
|
||||
t.Errorf("got %v, wanted tstStruct{Test: 12u, Check: 34u}", tstStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapValueEqual(t *testing.T) {
|
||||
mv := NewMapValue()
|
||||
name := NewField(1, "name")
|
||||
name.Ref = testValue(t, 2, "alert")
|
||||
priority := NewField(3, "priority")
|
||||
priority.Ref = testValue(t, 4, int64(4))
|
||||
mv.AddField(name)
|
||||
mv.AddField(priority)
|
||||
if mv.Equal(mv) != types.True {
|
||||
t.Fatalf("map.Equal(map) failed: %v", mv.Equal(mv))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapValueNotEqual(t *testing.T) {
|
||||
mv := NewMapValue()
|
||||
name := NewField(1, "name")
|
||||
name.Ref = testValue(t, 2, "alert")
|
||||
priority := NewField(3, "priority")
|
||||
priority.Ref = testValue(t, 4, int64(4))
|
||||
mv.AddField(name)
|
||||
mv.AddField(priority)
|
||||
|
||||
mv2 := NewMapValue()
|
||||
mv2.AddField(name)
|
||||
if mv.Equal(mv2) != types.False {
|
||||
t.Fatalf("mv.Equal(mv2) failed: %v", mv.Equal(mv2))
|
||||
}
|
||||
|
||||
priority2 := NewField(5, "priority")
|
||||
priority2.Ref = testValue(t, 6, int64(3))
|
||||
mv2.AddField(priority2)
|
||||
if mv.Equal(mv2) != types.False {
|
||||
t.Fatalf("mv.Equal(mv2) failed: %v", mv.Equal(mv2))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapValueIsSet(t *testing.T) {
|
||||
mv := NewMapValue()
|
||||
if mv.IsSet(types.String("name")) != types.False {
|
||||
t.Error("map.IsSet('name') returned true for unset key")
|
||||
}
|
||||
mv.AddField(NewField(1, "name"))
|
||||
if mv.IsSet(types.String("name")) != types.True {
|
||||
t.Error("map.IsSet('name') returned false for a set key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectValueEqual(t *testing.T) {
|
||||
objType := NewObjectType("Notice", map[string]*DeclField{
|
||||
"name": &DeclField{Name: "name", Type: StringType},
|
||||
"priority": &DeclField{Name: "priority", Type: IntType},
|
||||
"message": &DeclField{Name: "message", Type: PlainTextType, defaultValue: "<eom>"},
|
||||
})
|
||||
name := NewField(1, "name")
|
||||
name.Ref = testValue(t, 2, "alert")
|
||||
priority := NewField(3, "priority")
|
||||
priority.Ref = testValue(t, 4, int64(4))
|
||||
message := NewField(5, "message")
|
||||
message.Ref = testValue(t, 6, "call immediately")
|
||||
|
||||
mv1 := NewMapValue()
|
||||
mv1.AddField(name)
|
||||
mv1.AddField(priority)
|
||||
obj1 := mv1.ConvertToObject(objType)
|
||||
if obj1.Equal(obj1) != types.True {
|
||||
t.Errorf("obj1.Equal(obj1) failed, got: %v", obj1.Equal(obj1))
|
||||
}
|
||||
|
||||
mv2 := NewMapValue()
|
||||
mv2.AddField(name)
|
||||
mv2.AddField(priority)
|
||||
mv2.AddField(message)
|
||||
obj2 := mv2.ConvertToObject(objType)
|
||||
if obj1.Equal(obj2) == types.True {
|
||||
t.Error("obj1.Equal(obj2) returned true, wanted false")
|
||||
}
|
||||
if obj2.Equal(obj1) == types.True {
|
||||
t.Error("obj2.Equal(obj1) returned true, wanted false")
|
||||
}
|
||||
}
|
||||
|
||||
type tstStruct struct {
|
||||
Test uint64
|
||||
Check uint64
|
||||
}
|
Loading…
Reference in New Issue
Block a user