133 lines
3.8 KiB
Go
133 lines
3.8 KiB
Go
package engine
|
|
|
|
import "reflect"
|
|
|
|
type projectionLimits struct {
|
|
depth int
|
|
nodes int
|
|
}
|
|
|
|
func defaultProjectionLimits() projectionLimits {
|
|
return projectionLimits{depth: maxDocumentDepth, nodes: maxDocumentNodes}
|
|
}
|
|
|
|
type projectionIssue struct {
|
|
code Code
|
|
path Path
|
|
message string
|
|
}
|
|
|
|
type valueVisit struct {
|
|
typeOf reflect.Type
|
|
kind reflect.Kind
|
|
ptr uintptr
|
|
length int
|
|
}
|
|
|
|
// valueOperation owns one traversal's cycle and budget state and reuses the
|
|
// current load's descriptor cache for dynamic values.
|
|
type valueOperation struct {
|
|
cloneMode secretCloneMode
|
|
schema *schemaDescriptor
|
|
limits projectionLimits
|
|
active map[valueVisit]bool
|
|
nodes int
|
|
}
|
|
|
|
func (o *valueOperation) dynamicDescriptor(current reflect.Type, path Path) (*typeDescriptor, *projectionIssue) {
|
|
if o.schema.secrets.Type != nil && current == o.schema.secrets.Type {
|
|
return nil, &projectionIssue{code: CodeSchemaDynamicType, path: path.clone(), message: "dynamic configuration values cannot establish secret-reference semantics"}
|
|
}
|
|
builder := schemaBuilder{cache: o.schema.types, building: make(map[reflect.Type]bool), secrets: o.schema.secrets}
|
|
descriptor, issue := builder.describe(current, path)
|
|
if issue != nil {
|
|
builder.rollback()
|
|
return nil, &projectionIssue{code: CodeSchemaDynamicType, path: path.clone(), message: "dynamic configuration value has an unsupported type"}
|
|
}
|
|
if descriptorContainsSecret(descriptor, make(map[*typeDescriptor]bool)) {
|
|
builder.rollback()
|
|
return nil, &projectionIssue{code: CodeSchemaDynamicType, path: path.clone(), message: "dynamic configuration values cannot establish secret-reference semantics"}
|
|
}
|
|
return descriptor, nil
|
|
}
|
|
|
|
func descriptorContainsSecret(descriptor *typeDescriptor, visited map[*typeDescriptor]bool) bool {
|
|
if descriptor == nil || visited[descriptor] {
|
|
return false
|
|
}
|
|
if descriptor.kind == descriptorSecret {
|
|
return true
|
|
}
|
|
visited[descriptor] = true
|
|
if descriptorContainsSecret(descriptor.element, visited) {
|
|
return true
|
|
}
|
|
for _, member := range descriptor.members {
|
|
if descriptorContainsSecret(member.descriptor, visited) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (o *valueOperation) charge(path Path) *projectionIssue {
|
|
o.nodes++
|
|
if o.limits.nodes >= 0 && o.nodes > o.limits.nodes {
|
|
return &projectionIssue{code: CodeSchemaProjectionLimit, path: path.clone(), message: "configuration projection exceeds the node limit"}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (o *valueOperation) checkDepth(path Path, depth int) *projectionIssue {
|
|
if depth > o.limits.depth {
|
|
return &projectionIssue{code: CodeSchemaProjectionLimit, path: path.clone(), message: "configuration projection exceeds the depth limit"}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (o *valueOperation) enter(value reflect.Value, path Path) (func(), *projectionIssue) {
|
|
visit, ok := visitFor(value)
|
|
if !ok {
|
|
return func() {}, nil
|
|
}
|
|
if o.active[visit] {
|
|
return nil, &projectionIssue{code: CodeSchemaCycle, path: path.clone(), message: "configuration values must not contain cycles"}
|
|
}
|
|
o.active[visit] = true
|
|
return func() { delete(o.active, visit) }, nil
|
|
}
|
|
|
|
func visitFor(value reflect.Value) (valueVisit, bool) {
|
|
if isNilValue(value) {
|
|
return valueVisit{}, false
|
|
}
|
|
visit := valueVisit{typeOf: value.Type(), kind: value.Kind()}
|
|
switch value.Kind() {
|
|
case reflect.Pointer, reflect.Slice:
|
|
if value.Kind() == reflect.Slice && value.Len() == 0 {
|
|
return valueVisit{}, false
|
|
}
|
|
visit.ptr = value.Pointer()
|
|
if value.Kind() == reflect.Slice {
|
|
visit.length = value.Len()
|
|
}
|
|
case reflect.Map:
|
|
if value.Len() == 0 {
|
|
return valueVisit{}, false
|
|
}
|
|
visit.ptr = uintptr(value.UnsafePointer())
|
|
default:
|
|
return valueVisit{}, false
|
|
}
|
|
return visit, visit.ptr != 0
|
|
}
|
|
|
|
func isNilValue(value reflect.Value) bool {
|
|
switch value.Kind() {
|
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
|
|
return value.IsNil()
|
|
default:
|
|
return false
|
|
}
|
|
}
|