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 } }