Skip to content

Commit

Permalink
modeldecoder/generator: enable additionalProperties subschema (elasti…
Browse files Browse the repository at this point in the history
…c#4464)

Handle map value rules without requiring patternProperties in
JSON Schema.

When value rules (e.g. inputTypesVals) are specified, but no
patternKeys rule is specified, then nest the generated schema
under additionalProperties.

This will enable us to remove the patternKeys rules from labels,
tags, etc. when moving label key sanitization into the server,
without dropping label value validation.
  • Loading branch information
axw committed Dec 14, 2020
1 parent cbe25ee commit db0f942
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 49 deletions.
4 changes: 4 additions & 0 deletions docs/spec/v2/metricset.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
"additionalProperties": false,
"patternProperties": {
"^[^*\"]*$": {
"type": [
"null",
"object"
],
"properties": {
"value": {
"description": "Value holds the value of a single metric sample.",
Expand Down
6 changes: 1 addition & 5 deletions model/modeldecoder/generator/jsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,6 @@ func (g *JSONSchemaGenerator) generate(st structType, key string, prop *property
default:
switch t := f.Type().Underlying().(type) {
case *types.Map:
if _, ok := tags[tagPatternKeys]; !ok && name == "" {
// ignore the field in case no json name and no patternProperties are given
continue
}
nestedProp := property{Properties: make(map[string]*property)}
if err = generateJSONPropertyMap(&info, prop, &childProp, &nestedProp); err != nil {
break
Expand Down Expand Up @@ -240,7 +236,7 @@ type property struct {
Type *propertyType `json:"type,omitempty"`
// AdditionalProperties should default to `true` and be set to `false`
// in case PatternProperties are set
AdditionalProperties *bool `json:"additionalProperties,omitempty"`
AdditionalProperties interface{} `json:"additionalProperties,omitempty"`
PatternProperties map[string]*property `json:"patternProperties,omitempty"`
Properties map[string]*property `json:"properties,omitempty"`
Items *property `json:"items,omitempty"`
Expand Down
80 changes: 36 additions & 44 deletions model/modeldecoder/generator/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,19 +159,23 @@ func generateJSONPropertyMap(info *fieldInfo, parent *property, child *property,
child.Type.add(TypeNameObject)
patternName, isPatternProp := info.tags[tagPatternKeys]
delete(info.tags, tagPatternKeys)
if !isPatternProp && name == "" {
// the object (map) can be places as a property with a defined key,
// or as a patternProperty with a defined key pattern
// if both are missing the field cannot be added
return fmt.Errorf("invalid combination: either json name or tag %s must be given", tagPatternKeys)
}
if !isPatternProp {
// if no pattern property is given, the child map will be nested directly as
// property inside the parent property, identified by it's json name
// e.g. {"parent":{"properties":{"jsonNameXY":{..}}}}

nestedParent := child
if name == "" {
// The map does not have a json name defined, in which case it is nested directly
// inside the parent object's patternProperties/additionalProperties
// e.g. {"parent":{"patternProperties":{"patternXY":{..}}}}
*nested = *child
parent.Properties[name] = nested
nestedParent = parent
} else {
// The map does have a json name defined, in which case it is nested as
// patternProperties/additionalProperties inside an object, which itself is nested
// inside the parent property, identified by its json name
// e.g. {"parent":{"properties":{"jsonNameXY":{"patternProperties":{"patternXY":{..}}}}}}
parent.Properties[name] = child
}

haveValueSchema := len(info.tags) > 0
if maxLen, ok := info.tags[tagMaxLengthVals]; ok {
nested.MaxLength = json.Number(maxLen)
delete(info.tags, tagMaxLengthVals)
Expand All @@ -183,42 +187,30 @@ func generateJSONPropertyMap(info *fieldInfo, parent *property, child *property,
}
delete(info.tags, tagInputTypesVals)
nested.Type = &propertyType{names: names}
}
if !isPatternProp {
// nothing more to do when no key pattern is given
return nil
}
pattern, ok := info.parsed.patternVariables[patternName]
if !ok {
return fmt.Errorf("unhandled %s tag value %s", tagPatternKeys, pattern)
}
// for map key patterns two options are supported:
// - the map does not have a json name defined, in which case it is nested directly
// inside the parent object's patternProperty
// e.g. {"parent":{"patternProperties":{"patternXY":{..}}}}
// - the map does have a json name defined, in which case it is nested as
// patternProperty inside an object, which itself is nested
// inside the parent property, identified by it's json name
// e.g. {"parent":{"properties":{"jsonNameXY":{"patternProperties":{"patternXY":{..}}}}}}
if name == "" {
if parent.PatternProperties == nil {
parent.PatternProperties = make(map[string]*property)
}
} else {
valueType := info.field.Type().Underlying().(*types.Map).Elem()
typeName, ok := propertyTypes[valueType.String()]
if !ok {
typeName = TypeNameObject
if !types.IsInterface(valueType) {
haveValueSchema = true
typeName, ok := propertyTypes[valueType.String()]
if !ok {
typeName = TypeNameObject
}
nested.Type = &propertyType{names: []propertyTypeName{typeName}}
}
nested.Type = &propertyType{names: []propertyTypeName{typeName}}
parent.PatternProperties[pattern] = nested
parent.AdditionalProperties = new(bool)
return nil
}
if child.PatternProperties == nil {
child.PatternProperties = make(map[string]*property)

if isPatternProp {
pattern, ok := info.parsed.patternVariables[patternName]
if !ok {
return fmt.Errorf("unhandled %s tag value %s", tagPatternKeys, pattern)
}
if nestedParent.PatternProperties == nil {
nestedParent.PatternProperties = make(map[string]*property)
}
nestedParent.PatternProperties[pattern] = nested
nestedParent.AdditionalProperties = false
} else if haveValueSchema {
nestedParent.AdditionalProperties = nested
}
child.PatternProperties[pattern] = nested
child.AdditionalProperties = new(bool)
parent.Properties[name] = child
return nil
}

0 comments on commit db0f942

Please sign in to comment.