160 lines
5.1 KiB
Go
160 lines
5.1 KiB
Go
package configkit
|
|
|
|
import (
|
|
"encoding"
|
|
"math"
|
|
"reflect"
|
|
)
|
|
|
|
// secretCloneMode distinguishes fresh unresolved references from publication of
|
|
// already validated private state, which must not trigger another lookup.
|
|
type secretCloneMode uint8
|
|
|
|
const (
|
|
cloneUnresolvedSecrets secretCloneMode = iota
|
|
cloneValidatedSecrets
|
|
)
|
|
|
|
func cloneConfiguration(schema *schemaDescriptor, value reflect.Value, limits projectionLimits) (reflect.Value, *projectionIssue) {
|
|
return cloneConfigurationWithMode(schema, value, limits, cloneUnresolvedSecrets)
|
|
}
|
|
|
|
func cloneConfigurationWithMode(schema *schemaDescriptor, value reflect.Value, limits projectionLimits, mode secretCloneMode) (reflect.Value, *projectionIssue) {
|
|
if schema == nil || schema.root == nil || !value.IsValid() || value.Type() != schema.root.typeOf {
|
|
return reflect.Value{}, &projectionIssue{code: CodeSchemaUnsupportedType, message: "configuration value does not match its schema"}
|
|
}
|
|
operation := valueOperation{schema: schema, limits: limits, active: make(map[valueVisit]bool), cloneMode: mode}
|
|
return operation.clone(value, schema.root, Path{}, 0)
|
|
}
|
|
|
|
func (o *valueOperation) clone(value reflect.Value, descriptor *typeDescriptor, path Path, depth int) (reflect.Value, *projectionIssue) {
|
|
if issue := o.checkDepth(path, depth); issue != nil {
|
|
return reflect.Value{}, issue
|
|
}
|
|
if descriptor.kind == descriptorPointer {
|
|
if value.IsNil() {
|
|
return reflect.Zero(value.Type()), nil
|
|
}
|
|
leave, issue := o.enter(value, path)
|
|
if issue != nil {
|
|
return reflect.Value{}, issue
|
|
}
|
|
defer leave()
|
|
child, issue := o.clone(value.Elem(), descriptor.element, path, depth)
|
|
if issue != nil {
|
|
return reflect.Value{}, issue
|
|
}
|
|
result := reflect.New(value.Type().Elem())
|
|
result.Elem().Set(child)
|
|
return result, nil
|
|
}
|
|
|
|
switch descriptor.kind {
|
|
case descriptorStruct:
|
|
result := reflect.New(value.Type()).Elem()
|
|
for _, member := range descriptor.members {
|
|
memberPath, memberDepth := path, depth
|
|
if !member.inline {
|
|
memberPath, memberDepth = path.Key(member.name), depth+1
|
|
}
|
|
cloned, issue := o.clone(value.Field(member.index), member.descriptor, memberPath, memberDepth)
|
|
if issue != nil {
|
|
return reflect.Value{}, issue
|
|
}
|
|
result.Field(member.index).Set(cloned)
|
|
}
|
|
return result, nil
|
|
case descriptorMap:
|
|
if value.IsNil() {
|
|
return reflect.Zero(value.Type()), nil
|
|
}
|
|
leave, issue := o.enter(value, path)
|
|
if issue != nil {
|
|
return reflect.Value{}, issue
|
|
}
|
|
defer leave()
|
|
result := reflect.MakeMapWithSize(value.Type(), value.Len())
|
|
for _, key := range value.MapKeys() {
|
|
cloned, issue := o.clone(value.MapIndex(key), descriptor.element, path.Key(key.String()), depth+1)
|
|
if issue != nil {
|
|
return reflect.Value{}, issue
|
|
}
|
|
result.SetMapIndex(key, cloned)
|
|
}
|
|
return result, nil
|
|
case descriptorSlice:
|
|
if value.IsNil() {
|
|
return reflect.Zero(value.Type()), nil
|
|
}
|
|
leave, issue := o.enter(value, path)
|
|
if issue != nil {
|
|
return reflect.Value{}, issue
|
|
}
|
|
defer leave()
|
|
result := reflect.MakeSlice(value.Type(), value.Len(), value.Len())
|
|
for i := 0; i < value.Len(); i++ {
|
|
cloned, issue := o.clone(value.Index(i), descriptor.element, path.Index(i), depth+1)
|
|
if issue != nil {
|
|
return reflect.Value{}, issue
|
|
}
|
|
result.Index(i).Set(cloned)
|
|
}
|
|
return result, nil
|
|
case descriptorArray:
|
|
result := reflect.New(value.Type()).Elem()
|
|
for i := 0; i < value.Len(); i++ {
|
|
cloned, issue := o.clone(value.Index(i), descriptor.element, path.Index(i), depth+1)
|
|
if issue != nil {
|
|
return reflect.Value{}, issue
|
|
}
|
|
result.Index(i).Set(cloned)
|
|
}
|
|
return result, nil
|
|
case descriptorDynamic:
|
|
if value.IsNil() {
|
|
return reflect.Zero(value.Type()), nil
|
|
}
|
|
concrete := value.Elem()
|
|
dynamic, issue := o.dynamicDescriptor(concrete.Type(), path)
|
|
if issue != nil {
|
|
return reflect.Value{}, issue
|
|
}
|
|
cloned, issue := o.clone(concrete, dynamic, path, depth)
|
|
if issue != nil {
|
|
return reflect.Value{}, issue
|
|
}
|
|
result := reflect.New(value.Type()).Elem()
|
|
result.Set(cloned)
|
|
return result, nil
|
|
case descriptorSecret:
|
|
result := reflect.New(value.Type()).Elem()
|
|
reference := value.Interface().(Secret)
|
|
if o.cloneMode == cloneValidatedSecrets {
|
|
result.Set(value)
|
|
} else if reference.specified {
|
|
result.Set(reflect.ValueOf(SecretEnv(reference.EnvName())))
|
|
}
|
|
return result, nil
|
|
case descriptorText:
|
|
text, issue := marshalCanonicalText(value, path)
|
|
if issue != nil {
|
|
return reflect.Value{}, issue
|
|
}
|
|
result := reflect.New(value.Type()).Elem()
|
|
unmarshaler := result.Addr().Interface().(encoding.TextUnmarshaler)
|
|
if err := unmarshaler.UnmarshalText([]byte(text)); err != nil {
|
|
return reflect.Value{}, &projectionIssue{code: CodeSchemaCodec, path: path.clone(), message: "custom text scalar could not be reconstructed"}
|
|
}
|
|
return result, nil
|
|
case descriptorFloat:
|
|
if math.IsNaN(value.Float()) || math.IsInf(value.Float(), 0) {
|
|
return reflect.Value{}, &projectionIssue{code: CodeSchemaNonFinite, path: path.clone(), message: "configuration float must be finite"}
|
|
}
|
|
fallthrough
|
|
default:
|
|
result := reflect.New(value.Type()).Elem()
|
|
result.Set(value)
|
|
return result, nil
|
|
}
|
|
}
|