package engine import "reflect" func (b *schemaBuilder) describeOrdinaryField(owner *typeDescriptor, field reflect.StructField, index int, name string, secret bool, path Path) *schemaIssue { fieldPath := path.Key(name) fieldType, issue := b.describe(field.Type, fieldPath) if issue != nil { return issue } if issue := addField(owner, fieldDescriptor{name: name, index: []int{index}, descriptor: fieldType, secret: secret}, path); issue != nil { return issue } owner.members = append(owner.members, structMember{index: index, name: name, descriptor: fieldType}) return nil } func (b *schemaBuilder) describeInlineField(owner *typeDescriptor, field reflect.StructField, index int, secret bool, path Path) *schemaIssue { inlineType, issue := inlineFieldType(field.Type, path) if issue != nil { return issue } if inlineType.Kind() == reflect.Map && field.Type.Kind() == reflect.Pointer { return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline maps cannot be pointers"} } switch inlineType.Kind() { case reflect.Struct: return b.describeInlineStruct(owner, field, inlineType, index, secret, path) case reflect.Map: return b.describeInlineMap(owner, inlineType, index, secret, path) default: return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline field must be a struct, pointer to struct, or string-keyed map"} } } func inlineFieldType(fieldType reflect.Type, path Path) (reflect.Type, *schemaIssue) { current := fieldType seenPointers := make(map[reflect.Type]bool) for current.Kind() == reflect.Pointer { if seenPointers[current] { return nil, &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline pointer chain must terminate in a struct"} } seenPointers[current] = true current = current.Elem() } return current, nil } func (b *schemaBuilder) describeInlineStruct(owner *typeDescriptor, field reflect.StructField, inlineType reflect.Type, index int, secret bool, path Path) *schemaIssue { if b.building[inlineType] { return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "recursive inline struct is not supported"} } child, issue := b.describe(inlineType, path) if issue != nil { return issue } if child.kind != descriptorStruct { return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline field must be a structural struct or string-keyed map"} } for _, nested := range child.fields { nested.index = prependIndex(index, nested.index) nested.secret = secret || nested.secret if issue := addField(owner, nested, path); issue != nil { return issue } } if child.inlineMap != nil { nested := *child.inlineMap nested.index = prependIndex(index, nested.index) nested.secret = secret || nested.secret if owner.inlineMap != nil { return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "configuration object has more than one inline map"} } owner.inlineMap = &nested } memberType, issue := b.describe(field.Type, path) if issue != nil { return issue } owner.members = append(owner.members, structMember{index: index, descriptor: memberType, inline: true}) return nil } func (b *schemaBuilder) describeInlineMap(owner *typeDescriptor, inlineType reflect.Type, index int, secret bool, path Path) *schemaIssue { if inlineType.Key().Kind() != reflect.String { return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline map must have string keys"} } if owner.inlineMap != nil { return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "configuration object has more than one inline map"} } fieldPath := path.Key("*") fieldType, issue := b.describe(inlineType, fieldPath) if issue != nil { return issue } owner.inlineMap = &fieldDescriptor{index: []int{index}, descriptor: fieldType, secret: secret, inlineMap: true} owner.members = append(owner.members, structMember{index: index, descriptor: fieldType, inline: true}) return nil }