Bind engine secret handling to facade types

This commit is contained in:
2026-09-12 16:04:31 +00:00
parent 9b4f9ac50e
commit da02a19f2c
28 changed files with 348 additions and 232 deletions

View File

@@ -29,8 +29,9 @@ const (
)
type schemaDescriptor struct {
root *typeDescriptor
types map[reflect.Type]*typeDescriptor
root *typeDescriptor
types map[reflect.Type]*typeDescriptor
secrets SecretBinding
}
type typeDescriptor struct {
@@ -65,20 +66,39 @@ type schemaIssue struct {
message string
}
// SecretState is the private operation state for one exact-type secret value.
// It is supplied by the facade and never enters inspection records.
type SecretState struct {
Name string
Value string
Specified bool
Resolved bool
}
// SecretBinding adapts the facade-owned exact secret type for one load.
// Bindings are operation data: callers must supply the type and conversions
// together, and a binding never becomes package-global state.
type SecretBinding struct {
Type reflect.Type
Read func(reflect.Value) SecretState
Make func(SecretState) reflect.Value
}
var (
durationType = reflect.TypeFor[time.Duration]()
secretType = reflect.TypeFor[Secret]()
textMarshalerType = reflect.TypeFor[encoding.TextMarshaler]()
textUnmarshalerType = reflect.TypeFor[encoding.TextUnmarshaler]()
)
func buildSchema(root reflect.Type) (*schemaDescriptor, *schemaIssue) {
func buildSchema(root reflect.Type, secrets SecretBinding) (*schemaDescriptor, *schemaIssue) {
secrets.validate()
if root == nil || (root.Kind() != reflect.Struct && !isStringMap(root)) {
return nil, &schemaIssue{code: CodeSchemaRoot, message: "configuration root must be a struct or string-keyed map"}
}
builder := schemaBuilder{
cache: make(map[reflect.Type]*typeDescriptor),
building: make(map[reflect.Type]bool),
secrets: secrets,
}
descriptor, issue := builder.describe(root, Path{})
if issue != nil {
@@ -87,12 +107,13 @@ func buildSchema(root reflect.Type) (*schemaDescriptor, *schemaIssue) {
if descriptor.kind != descriptorStruct && descriptor.kind != descriptorMap {
return nil, &schemaIssue{code: CodeSchemaRoot, message: "configuration root must be a structural struct or string-keyed map"}
}
return &schemaDescriptor{root: descriptor, types: builder.cache}, nil
return &schemaDescriptor{root: descriptor, types: builder.cache, secrets: secrets}, nil
}
type schemaBuilder struct {
cache map[reflect.Type]*typeDescriptor
building map[reflect.Type]bool
secrets SecretBinding
}
func (b *schemaBuilder) describe(current reflect.Type, path Path) (*typeDescriptor, *schemaIssue) {
@@ -122,7 +143,7 @@ func (b *schemaBuilder) describe(current reflect.Type, path Path) (*typeDescript
descriptor.kind = descriptorDuration
return descriptor, nil
}
if current == secretType {
if b.secrets.Type != nil && current == b.secrets.Type {
descriptor.kind = descriptorSecret
return descriptor, nil
}
@@ -189,6 +210,15 @@ func (b *schemaBuilder) describe(current reflect.Type, path Path) (*typeDescript
return descriptor, nil
}
func (binding SecretBinding) validate() {
if binding.Type == nil {
return
}
if binding.Read == nil || binding.Make == nil {
panic("configkit engine: incomplete secret binding")
}
}
func (b *schemaBuilder) describeFields(descriptor *typeDescriptor, current reflect.Type, path Path) *schemaIssue {
for i := 0; i < current.NumField(); i++ {
field := current.Field(i)