input/elasticapm/internal/modeldecoder/generator/map.go (171 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package generator import ( "encoding/json" "fmt" "go/types" "io" "strings" ) var mapSupportedTags = []string{tagMaxLengthVals, tagPatternKeys, tagRequired, tagInputTypesVals} func generateMapValidation(w io.Writer, fields []structField, f structField, isCustomStruct bool) error { typ := f.Type().Underlying().(*types.Map) // verify that validation rules for map kind exist switch typ.Elem().Underlying().(type) { case *types.Basic, *types.Interface: // do nothing special case *types.Struct: if !isCustomStruct { return fmt.Errorf("unhandled struct type %s", typ) } default: return fmt.Errorf("unhandled type %s", typ) } vTag, err := validationTag(f.tag) if err != nil { return err } // check if all configured tags are supported for k := range vTag { var supported bool for _, s := range mapSupportedTags { if k == s { supported = true break } } if !supported { return fmt.Errorf("unhandled tag rule '%v'", k) } } // validation rules must be run on map itself and its elements // 1. apply map validation rules: if ruleValue, ok := vTag[tagRequired]; ok { mapRuleRequired(w, f, validationRule{name: tagRequired, value: ruleValue}) if len(vTag) == 1 { return nil } } if len(vTag) == 0 { return nil } // 2. iterate over map and apply validation rules to its elements if _, ok := vTag[tagInputTypesVals]; ok || isCustomStruct { fmt.Fprintf(w, ` for k,v := range val.%s{ `[1:], f.Name()) } else { fmt.Fprintf(w, ` for k := range val.%s{ `[1:], f.Name()) } if isCustomStruct { // call validation on every item fmt.Fprintf(w, ` if err := v.validate(); err != nil{ return errors.Wrapf(err, "%s") } `[1:], jsonName(f)) } if patternKeysValue, ok := vTag[tagPatternKeys]; ok { mapRulePatternKeys(w, f, validationRule{name: tagPatternKeys, value: patternKeysValue}) } if typesValsValue, ok := vTag[tagInputTypesVals]; ok { mapRuleTypesVals(w, f, vTag, validationRule{name: tagInputTypesVals, value: typesValsValue}) } fmt.Fprint(w, ` } `[1:]) return nil } func mapRuleTypesVals(w io.Writer, f structField, rules map[string]string, rule validationRule) { fmt.Fprint(w, ` switch t := v.(type){ `[1:]) // if values are not required allow nil if _, ok := rules[tagRequired]; !ok { fmt.Fprint(w, ` case nil: `[1:]) } for _, typ := range strings.Split(rule.value, ";") { if typ == "number" { typ = "json.Number" } fmt.Fprintf(w, ` case %s: `[1:], typ) if typ == "string" { if maxValValue, ok := rules[tagMaxLengthVals]; ok { mapRuleMaxVals(w, f, validationRule{name: tagMaxLengthVals, value: maxValValue}) } } } fmt.Fprintf(w, ` default: return fmt.Errorf("'%s': validation rule '%s(%s)' violated for key %%s",k) } `[1:], jsonName(f), rule.name, rule.value) } func mapRuleRequired(w io.Writer, f structField, rule validationRule) { fmt.Fprintf(w, ` if len(val.%s) == 0{ return fmt.Errorf("'%s' required") } `[1:], f.Name(), jsonName(f)) } func mapRulePatternKeys(w io.Writer, f structField, rule validationRule) { fmt.Fprintf(w, ` if k != "" && !%sRegexp.MatchString(k){ return fmt.Errorf("'%s': validation rule '%s(%s)' violated") } `[1:], rule.value, jsonName(f), rule.name, rule.value) } func mapRuleMaxVals(w io.Writer, f structField, rule validationRule) { fmt.Fprintf(w, ` if utf8.RuneCountInString(t) > %s{ return fmt.Errorf("'%s': validation rule '%s(%s)' violated") } `[1:], rule.value, jsonName(f), rule.name, rule.value) } func generateJSONPropertyMap(info *fieldInfo, parent *property, child *property, nested *property) error { name := jsonSchemaName(info.field) child.Type.add(TypeNameObject) patternName, isPatternProp := info.tags[tagPatternKeys] delete(info.tags, tagPatternKeys) 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 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) } if inputTypes, ok := info.tags[tagInputTypesVals]; ok { names, err := propertyTypesFromTag(tagInputTypesVals, inputTypes) if err != nil { return err } delete(info.tags, tagInputTypesVals) nested.Type = &propertyType{names: names} } else { valueType := info.field.Type().Underlying().(*types.Map).Elem() if !types.IsInterface(valueType) { haveValueSchema = true typeName, ok := propertyTypes[valueType.String()] if !ok { typeName = TypeNameObject } nested.Type = &propertyType{names: []propertyTypeName{typeName}} } } 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 } return nil }