Files
Israel BlancasandGitHub bd678a5834 [chore] Add schema combiner logic (#15293)
#### Description
Add combiner logic for the schemas.

Note there are many missing things here like preserving and merging
`$defs` and rewrite `$ref`. I will do this as part of following PRs.

This PR will allow to continue with the work related to `ocb` and
improvements to generate a good schema.

<!-- Issue number if applicable -->
#### Link to tracking issue
Updates #15010

Signed-off-by: Israel Blancas <[email protected]>
2026-05-22 22:26:38 +00:00

137 lines
8.5 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package schemagen // import "go.opentelemetry.io/collector/internal/schemagen"
import (
"encoding/json"
"errors"
"fmt"
"go.opentelemetry.io/collector/confmap"
)
// ConfigMetadata represents a JSON schema object, draft 2020-12 (limited), with additional custom fields.
type ConfigMetadata struct {
Schema string `mapstructure:"$schema,omitempty" json:"$schema,omitempty" yaml:"$schema,omitempty"`
ID string `mapstructure:"$id,omitempty" json:"$id,omitempty" yaml:"$id,omitempty"`
Title string `mapstructure:"title,omitempty" json:"title,omitempty" yaml:"title,omitempty"`
Description string `mapstructure:"description,omitempty" json:"description,omitempty" yaml:"description,omitempty"`
Comment string `mapstructure:"$comment,omitempty" json:"$comment,omitempty" yaml:"$comment,omitempty"`
Type string `mapstructure:"type,omitempty" json:"type,omitempty" yaml:"type,omitempty"`
Ref string `mapstructure:"$ref,omitempty" json:"-" yaml:"$ref,omitempty"`
Default any `mapstructure:"default,omitempty" json:"default,omitempty" yaml:"default,omitempty"`
Examples []any `mapstructure:"examples,omitempty" json:"examples,omitempty" yaml:"examples,omitempty"`
Deprecated bool `mapstructure:"deprecated,omitempty" json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Enum []any `mapstructure:"enum,omitempty" json:"enum,omitempty" yaml:"enum,omitempty"`
Const any `mapstructure:"const,omitempty" json:"const,omitempty" yaml:"const,omitempty"`
AllOf []*ConfigMetadata `mapstructure:"allOf,omitempty" json:"allOf,omitempty" yaml:"allOf,omitempty"`
Properties map[string]*ConfigMetadata `mapstructure:"properties,omitempty" json:"properties,omitempty" yaml:"properties,omitempty"`
AdditionalProperties *ConfigMetadata `mapstructure:"additionalProperties,omitempty" json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"`
PatternProperties map[string]*ConfigMetadata `mapstructure:"patternProperties,omitempty" json:"-" yaml:"patternProperties,omitempty"`
Required []string `mapstructure:"required,omitempty" json:"required,omitempty" yaml:"required,omitempty"`
MinProperties *int `mapstructure:"minProperties,omitempty" json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
MaxProperties *int `mapstructure:"maxProperties,omitempty" json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
Items *ConfigMetadata `mapstructure:"items,omitempty" json:"items,omitempty" yaml:"items,omitempty"`
MinItems *int `mapstructure:"minItems,omitempty" json:"minItems,omitempty" yaml:"minItems,omitempty"`
MaxItems *int `mapstructure:"maxItems,omitempty" json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
UniqueItems bool `mapstructure:"uniqueItems,omitempty" json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
MaxLength *int `mapstructure:"maxLength,omitempty" json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
MinLength *int `mapstructure:"minLength,omitempty" json:"minLength,omitempty" yaml:"minLength,omitempty"`
Pattern string `mapstructure:"pattern,omitempty" json:"pattern,omitempty" yaml:"pattern,omitempty"`
Format string `mapstructure:"format,omitempty" json:"format,omitempty" yaml:"format,omitempty"`
ContentMediaType string `mapstructure:"contentMediaType,omitempty" json:"contentMediaType,omitempty" yaml:"contentMediaType,omitempty"`
ContentEncoding string `mapstructure:"contentEncoding,omitempty" json:"contentEncoding,omitempty" yaml:"contentEncoding,omitempty"`
ContentSchema *ConfigMetadata `mapstructure:"contentSchema,omitempty" json:"contentSchema,omitempty" yaml:"contentSchema,omitempty"`
MultipleOf *float64 `mapstructure:"multipleOf,omitempty" json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
Maximum *float64 `mapstructure:"maximum,omitempty" json:"maximum,omitempty" yaml:"maximum,omitempty"`
ExclusiveMaximum *float64 `mapstructure:"exclusiveMaximum,omitempty" json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
Minimum *float64 `mapstructure:"minimum,omitempty" json:"minimum,omitempty" yaml:"minimum,omitempty"`
ExclusiveMinimum *float64 `mapstructure:"exclusiveMinimum,omitempty" json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
Defs map[string]*ConfigMetadata `mapstructure:"$defs,omitempty" json:"$defs,omitempty" yaml:"$defs,omitempty"`
// Additional custom fields
GoStruct GoStructConfig `mapstructure:"go_struct,omitempty" json:"-" yaml:"go_struct,omitempty"`
GoType string `mapstructure:"x-customType,omitempty" json:"-" yaml:"x-customType,omitempty"`
IsPointer bool `mapstructure:"x-pointer,omitempty" json:"-" yaml:"x-pointer,omitempty"`
IsOptional bool `mapstructure:"x-optional,omitempty" json:"-" yaml:"x-optional,omitempty"`
Embed bool `mapstructure:"embed,omitempty" json:"-" yaml:"embed,omitempty"`
// internal
ResolvedFrom string `mapstructure:"-" json:"-" yaml:"-"`
EmbeddedName string `mapstructure:"-" json:"-" yaml:"-"`
AdditionalPropertiesAllowed *bool `mapstructure:"-" json:"-" yaml:"-"`
InternalOnly bool `mapstructure:"-" json:"-" yaml:"-"`
}
type Metadata struct {
Config *ConfigMetadata `mapstructure:"config,omitempty" json:"config,omitempty" yaml:"config,omitempty"`
ExportedConfigs map[string]*ConfigMetadata `mapstructure:"exported_configs,omitempty" json:"exported_configs,omitempty" yaml:"exported_configs,omitempty"`
}
type GoStructConfig struct {
CustomValidator *CustomValidatorConfig `mapstructure:"custom_validator" json:"-" yaml:"custom_validator,omitempty"`
Anonymous bool `mapstructure:"anonymous" json:"-" yaml:"anonymous,omitempty"`
IgnoreDefault bool `mapstructure:"ignore_default" json:"-" yaml:"ignore_default,omitempty"`
}
type CustomValidatorConfig struct {
Name string `mapstructure:"name,omitempty" json:"-" yaml:"name,omitempty"`
}
func (g *GoStructConfig) Unmarshal(parser *confmap.Conf) error {
type goStructConfig GoStructConfig
if err := parser.Unmarshal((*goStructConfig)(g), confmap.WithIgnoreUnused()); err != nil {
return err
}
if !parser.IsSet("custom_validator") || g.CustomValidator != nil {
return nil
}
sub, err := parser.Sub("custom_validator")
if err != nil {
return fmt.Errorf("invalid custom_validator: %w", err)
}
g.CustomValidator = &CustomValidatorConfig{}
return sub.Unmarshal(g.CustomValidator)
}
func (md *ConfigMetadata) ToJSON() ([]byte, error) {
return json.MarshalIndent(md, "", " ")
}
func (md *ConfigMetadata) MarshalJSON() ([]byte, error) {
type alias ConfigMetadata
if len(md.PatternProperties) == 0 && md.AdditionalPropertiesAllowed == nil {
return json.Marshal((*alias)(md))
}
type withSpecialFields struct {
*alias
AdditionalProperties any `json:"additionalProperties,omitempty"`
PatternProperties map[string]*ConfigMetadata `json:"patternProperties,omitempty"`
}
out := withSpecialFields{
alias: (*alias)(md),
PatternProperties: md.PatternProperties,
}
if md.AdditionalPropertiesAllowed != nil {
out.AdditionalProperties = *md.AdditionalPropertiesAllowed
}
return json.Marshal(out)
}
func (md *ConfigMetadata) Validate() error {
var errs error
hasDefs := len(md.Defs) > 0
hasConfigFields := len(md.Properties) > 0 || len(md.AllOf) > 0
if md.Type != "object" && (md.Type != "" || !hasDefs || hasConfigFields) {
errs = errors.Join(errs, fmt.Errorf("config type must be \"object\", got %q", md.Type))
}
if !hasDefs && !hasConfigFields {
errs = errors.Join(errs, errors.New("config must not be empty"))
}
return errs
}