Separate projection cloning and value comparison
This commit is contained in:
126
value_operation.go
Normal file
126
value_operation.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package configkit
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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 current == secretType {
|
||||
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)}
|
||||
descriptor, issue := builder.describe(current, path)
|
||||
if issue != nil {
|
||||
return nil, &projectionIssue{code: CodeSchemaDynamicType, path: path.clone(), message: "dynamic configuration value has an unsupported type"}
|
||||
}
|
||||
if descriptorContainsSecret(descriptor, make(map[*typeDescriptor]bool)) {
|
||||
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()
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user