input/elasticapm/internal/modeldecoder/generator/slice.go (127 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" "github.com/pkg/errors" ) func generateSliceValidation(w io.Writer, fields []structField, f structField, isCustomStruct bool) error { // call validation on every slice element when elements are of custom type if isCustomStruct { fmt.Fprintf(w, ` for _, elem := range val.%s{ if !elem.IsSet() { return fmt.Errorf("%s slice element required") } if err := elem.validate(); err != nil{ return errors.Wrapf(err, "%s") } } `[1:], f.Name(), jsonName(f), jsonName(f)) } // handle configured validation rules rules, err := validationRules(f.tag) if err != nil { return errors.Wrap(err, "slice") } for _, rule := range rules { switch rule.name { case tagMinLength, tagMaxLength: err = sliceRuleMinMaxLength(w, f, rule) case tagMinVals: err = sliceRuleMinVals(w, f, rule) case tagRequired: sliceRuleRequired(w, f, rule) case tagRequiredAnyOf: err = ruleRequiredOneOf(w, fields, rule.value) case tagRequiredIfAny: err = ruleRequiredIfAny(w, fields, f, rule.value) default: return errors.Wrap(errUnhandledTagRule(rule), "slice") } if err != nil { return errors.Wrap(err, "slice") } } return nil } func sliceRuleMinMaxLength(w io.Writer, f structField, rule validationRule) error { sliceT, ok := f.Type().Underlying().(*types.Slice) if !ok { return fmt.Errorf("unexpected error handling %s for slice", rule.name) } if basic, ok := sliceT.Elem().Underlying().(*types.Basic); ok { if basic.Kind() == types.String { fmt.Fprintf(w, ` for _, elem := range val.%s{ if utf8.RuneCountInString(elem) %s %s{ return fmt.Errorf("'%s': validation rule '%s(%s)' violated") } } `[1:], f.Name(), ruleMinMaxOperator(rule.name), rule.value, jsonName(f), rule.name, rule.value) return nil } } return fmt.Errorf("unhandled tag rule max for type %s", f.Type().Underlying()) } func sliceRuleMinVals(w io.Writer, f structField, rule validationRule) error { fmt.Fprintf(w, ` for _, elem := range val.%s{ if elem %s %s{ return fmt.Errorf("'%s': validation rule '%s(%s)' violated") } } `[1:], f.Name(), ruleMinMaxOperator(rule.name), rule.value, jsonName(f), rule.name, rule.value) return nil } func sliceRuleRequired(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 generateJSONPropertySlice(info *fieldInfo, parent *property, child *property) error { child.Type.add(TypeNameArray) var minItems int if child.Type.required { minItems = 1 } child.MinItems = &minItems parent.Properties[jsonSchemaName(info.field)] = child itemType := info.field.Type().Underlying().(*types.Slice).Elem() if _, ok := info.parsed.structTypes[itemType.String()]; ok { // parsed struct - no type specific tags will be handled return nil } // non-parsed struct // check if type is known, otherwise raise unhandled error itemsType, ok := propertyTypes[itemType.String()] if !ok { return fmt.Errorf("unhandled type %T", itemType) } // NOTE(simi): set required=true to be aligned with previous JSON schema definitions items := property{Type: &propertyType{names: []propertyTypeName{itemsType}, required: true}} switch itemsType { case TypeNameInteger: if err := setPropertyRulesInteger(info, &items); err != nil { return err } case TypeNameNumber: if err := setPropertyRulesNumber(info, &items); err != nil { return err } case TypeNameString: if err := setPropertyRulesString(info, &items); err != nil { return err } default: return fmt.Errorf("unhandled slice item type %s", itemsType) } if basic, ok := itemType.Underlying().(*types.Basic); ok { if items.Min == "" && (basic.Info()&types.IsUnsigned) != 0 { items.Min = json.Number("0") } } if minVals, ok := info.tags[tagMinVals]; ok { items.Min = json.Number(minVals) delete(info.tags, tagMinVals) } child.Items = &items return nil }