alertmanager/vendor/github.com/go-openapi/validate/validator.go
Max Leonard Inden f1b920bcc9
api: Implement OpenAPI generated Alertmanager API V2
The current Alertmanager API v1 is undocumented and written by hand.
This patch introduces a new Alertmanager API - v2. The API is fully
generated via an OpenAPI 2.0 [1] specification (see
`api/v2/openapi.yaml`) with the exception of the http handlers itself.

Pros:
- Generated server code
- Ability to generate clients in all major languages
  (Go, Java, JS, Python, Ruby, Haskell, *elm* [3] ...)
    - Strict contract (OpenAPI spec) between server and clients.
    - Instant feedback on frontend-breaking changes, due to strictly
      typed frontend language elm.
- Generated documentation (See Alertmanager online Swagger UI [4])

Cons:
- Dependency on open api ecosystem including go-swagger [2]

In addition this patch includes the following changes.

- README.md: Add API section

- test: Duplicate acceptance test to API v1 & API v2 version

  The Alertmanager acceptance test framework has a decent test coverage
  on the Alertmanager API. Introducing the Alertmanager API v2 does not go
  hand in hand with deprecating API v1. They should live alongside each
  other for a couple of minor Alertmanager versions.

  Instead of porting the acceptance test framework to use the new API v2,
  this patch duplicates the acceptance tests, one using the API v1, the
  other API v2.

  Once API v1 is removed we can simply remove `test/with_api_v1` and bring
  `test/with_api_v2` to `test/`.

[1]
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md

[2] https://github.com/go-swagger/go-swagger/

[3] https://github.com/ahultgren/swagger-elm

[4]
http://petstore.swagger.io/?url=https://raw.githubusercontent.com/mxinden/alertmanager/apiv2/api/v2/openapi.yaml

Signed-off-by: Max Leonard Inden <IndenML@gmail.com>
2018-09-04 13:38:34 +02:00

639 lines
18 KiB
Go

// Copyright 2015 go-swagger maintainers
//
// Licensed 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 validate
import (
"fmt"
"reflect"
"github.com/go-openapi/errors"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
)
// An EntityValidator is an interface for things that can validate entities
type EntityValidator interface {
Validate(interface{}) *Result
}
type valueValidator interface {
SetPath(path string)
Applies(interface{}, reflect.Kind) bool
Validate(interface{}) *Result
}
type itemsValidator struct {
items *spec.Items
root interface{}
path string
in string
validators []valueValidator
KnownFormats strfmt.Registry
}
func newItemsValidator(path, in string, items *spec.Items, root interface{}, formats strfmt.Registry) *itemsValidator {
iv := &itemsValidator{path: path, in: in, items: items, root: root, KnownFormats: formats}
iv.validators = []valueValidator{
&typeValidator{
Type: spec.StringOrArray([]string{items.Type}),
Format: items.Format,
In: in,
Path: path,
},
iv.stringValidator(),
iv.formatValidator(),
iv.numberValidator(),
iv.sliceValidator(),
iv.commonValidator(),
}
return iv
}
func (i *itemsValidator) Validate(index int, data interface{}) *Result {
tpe := reflect.TypeOf(data)
kind := tpe.Kind()
mainResult := new(Result)
path := fmt.Sprintf("%s.%d", i.path, index)
for _, validator := range i.validators {
validator.SetPath(path)
if validator.Applies(i.root, kind) {
result := validator.Validate(data)
mainResult.Merge(result)
mainResult.Inc()
if result != nil && result.HasErrors() {
return mainResult
}
}
}
return mainResult
}
func (i *itemsValidator) commonValidator() valueValidator {
return &basicCommonValidator{
In: i.in,
Default: i.items.Default,
Enum: i.items.Enum,
}
}
func (i *itemsValidator) sliceValidator() valueValidator {
return &basicSliceValidator{
In: i.in,
Default: i.items.Default,
MaxItems: i.items.MaxItems,
MinItems: i.items.MinItems,
UniqueItems: i.items.UniqueItems,
Source: i.root,
Items: i.items.Items,
KnownFormats: i.KnownFormats,
}
}
func (i *itemsValidator) numberValidator() valueValidator {
return &numberValidator{
In: i.in,
Default: i.items.Default,
MultipleOf: i.items.MultipleOf,
Maximum: i.items.Maximum,
ExclusiveMaximum: i.items.ExclusiveMaximum,
Minimum: i.items.Minimum,
ExclusiveMinimum: i.items.ExclusiveMinimum,
Type: i.items.Type,
Format: i.items.Format,
}
}
func (i *itemsValidator) stringValidator() valueValidator {
return &stringValidator{
In: i.in,
Default: i.items.Default,
MaxLength: i.items.MaxLength,
MinLength: i.items.MinLength,
Pattern: i.items.Pattern,
AllowEmptyValue: false,
}
}
func (i *itemsValidator) formatValidator() valueValidator {
return &formatValidator{
In: i.in,
//Default: i.items.Default,
Format: i.items.Format,
KnownFormats: i.KnownFormats,
}
}
type basicCommonValidator struct {
Path string
In string
Default interface{}
Enum []interface{}
}
func (b *basicCommonValidator) SetPath(path string) {
b.Path = path
}
func (b *basicCommonValidator) Applies(source interface{}, kind reflect.Kind) bool {
switch source.(type) {
case *spec.Parameter, *spec.Schema, *spec.Header:
return true
}
return false
}
func (b *basicCommonValidator) Validate(data interface{}) (res *Result) {
if len(b.Enum) > 0 {
for _, enumValue := range b.Enum {
actualType := reflect.TypeOf(enumValue)
if actualType != nil { // Safeguard
expectedValue := reflect.ValueOf(data)
if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) {
if reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), enumValue) {
return nil
}
}
}
}
return errorHelp.sErr(errors.EnumFail(b.Path, b.In, data, b.Enum))
}
return nil
}
// A HeaderValidator has very limited subset of validations to apply
type HeaderValidator struct {
name string
header *spec.Header
validators []valueValidator
KnownFormats strfmt.Registry
}
// NewHeaderValidator creates a new header validator object
func NewHeaderValidator(name string, header *spec.Header, formats strfmt.Registry) *HeaderValidator {
p := &HeaderValidator{name: name, header: header, KnownFormats: formats}
p.validators = []valueValidator{
&typeValidator{
Type: spec.StringOrArray([]string{header.Type}),
Format: header.Format,
In: "header",
Path: name,
},
p.stringValidator(),
p.formatValidator(),
p.numberValidator(),
p.sliceValidator(),
p.commonValidator(),
}
return p
}
// Validate the value of the header against its schema
func (p *HeaderValidator) Validate(data interface{}) *Result {
result := new(Result)
tpe := reflect.TypeOf(data)
kind := tpe.Kind()
for _, validator := range p.validators {
if validator.Applies(p.header, kind) {
if err := validator.Validate(data); err != nil {
result.Merge(err)
if err.HasErrors() {
return result
}
}
}
}
return nil
}
func (p *HeaderValidator) commonValidator() valueValidator {
return &basicCommonValidator{
Path: p.name,
In: "response",
Default: p.header.Default,
Enum: p.header.Enum,
}
}
func (p *HeaderValidator) sliceValidator() valueValidator {
return &basicSliceValidator{
Path: p.name,
In: "response",
Default: p.header.Default,
MaxItems: p.header.MaxItems,
MinItems: p.header.MinItems,
UniqueItems: p.header.UniqueItems,
Items: p.header.Items,
Source: p.header,
KnownFormats: p.KnownFormats,
}
}
func (p *HeaderValidator) numberValidator() valueValidator {
return &numberValidator{
Path: p.name,
In: "response",
Default: p.header.Default,
MultipleOf: p.header.MultipleOf,
Maximum: p.header.Maximum,
ExclusiveMaximum: p.header.ExclusiveMaximum,
Minimum: p.header.Minimum,
ExclusiveMinimum: p.header.ExclusiveMinimum,
Type: p.header.Type,
Format: p.header.Format,
}
}
func (p *HeaderValidator) stringValidator() valueValidator {
return &stringValidator{
Path: p.name,
In: "response",
Default: p.header.Default,
Required: true,
MaxLength: p.header.MaxLength,
MinLength: p.header.MinLength,
Pattern: p.header.Pattern,
AllowEmptyValue: false,
}
}
func (p *HeaderValidator) formatValidator() valueValidator {
return &formatValidator{
Path: p.name,
In: "response",
//Default: p.header.Default,
Format: p.header.Format,
KnownFormats: p.KnownFormats,
}
}
// A ParamValidator has very limited subset of validations to apply
type ParamValidator struct {
param *spec.Parameter
validators []valueValidator
KnownFormats strfmt.Registry
}
// NewParamValidator creates a new param validator object
func NewParamValidator(param *spec.Parameter, formats strfmt.Registry) *ParamValidator {
p := &ParamValidator{param: param, KnownFormats: formats}
p.validators = []valueValidator{
&typeValidator{
Type: spec.StringOrArray([]string{param.Type}),
Format: param.Format,
In: param.In,
Path: param.Name,
},
p.stringValidator(),
p.formatValidator(),
p.numberValidator(),
p.sliceValidator(),
p.commonValidator(),
}
return p
}
// Validate the data against the description of the parameter
func (p *ParamValidator) Validate(data interface{}) *Result {
result := new(Result)
tpe := reflect.TypeOf(data)
kind := tpe.Kind()
// TODO: validate type
for _, validator := range p.validators {
if validator.Applies(p.param, kind) {
if err := validator.Validate(data); err != nil {
result.Merge(err)
if err.HasErrors() {
return result
}
}
}
}
return nil
}
func (p *ParamValidator) commonValidator() valueValidator {
return &basicCommonValidator{
Path: p.param.Name,
In: p.param.In,
Default: p.param.Default,
Enum: p.param.Enum,
}
}
func (p *ParamValidator) sliceValidator() valueValidator {
return &basicSliceValidator{
Path: p.param.Name,
In: p.param.In,
Default: p.param.Default,
MaxItems: p.param.MaxItems,
MinItems: p.param.MinItems,
UniqueItems: p.param.UniqueItems,
Items: p.param.Items,
Source: p.param,
KnownFormats: p.KnownFormats,
}
}
func (p *ParamValidator) numberValidator() valueValidator {
return &numberValidator{
Path: p.param.Name,
In: p.param.In,
Default: p.param.Default,
MultipleOf: p.param.MultipleOf,
Maximum: p.param.Maximum,
ExclusiveMaximum: p.param.ExclusiveMaximum,
Minimum: p.param.Minimum,
ExclusiveMinimum: p.param.ExclusiveMinimum,
Type: p.param.Type,
Format: p.param.Format,
}
}
func (p *ParamValidator) stringValidator() valueValidator {
return &stringValidator{
Path: p.param.Name,
In: p.param.In,
Default: p.param.Default,
AllowEmptyValue: p.param.AllowEmptyValue,
Required: p.param.Required,
MaxLength: p.param.MaxLength,
MinLength: p.param.MinLength,
Pattern: p.param.Pattern,
}
}
func (p *ParamValidator) formatValidator() valueValidator {
return &formatValidator{
Path: p.param.Name,
In: p.param.In,
//Default: p.param.Default,
Format: p.param.Format,
KnownFormats: p.KnownFormats,
}
}
type basicSliceValidator struct {
Path string
In string
Default interface{}
MaxItems *int64
MinItems *int64
UniqueItems bool
Items *spec.Items
Source interface{}
itemsValidator *itemsValidator
KnownFormats strfmt.Registry
}
func (s *basicSliceValidator) SetPath(path string) {
s.Path = path
}
func (s *basicSliceValidator) Applies(source interface{}, kind reflect.Kind) bool {
switch source.(type) {
case *spec.Parameter, *spec.Items, *spec.Header:
return kind == reflect.Slice
}
return false
}
func (s *basicSliceValidator) Validate(data interface{}) *Result {
val := reflect.ValueOf(data)
size := int64(val.Len())
if s.MinItems != nil {
if err := MinItems(s.Path, s.In, size, *s.MinItems); err != nil {
return errorHelp.sErr(err)
}
}
if s.MaxItems != nil {
if err := MaxItems(s.Path, s.In, size, *s.MaxItems); err != nil {
return errorHelp.sErr(err)
}
}
if s.UniqueItems {
if err := UniqueItems(s.Path, s.In, data); err != nil {
return errorHelp.sErr(err)
}
}
if s.itemsValidator == nil && s.Items != nil {
s.itemsValidator = newItemsValidator(s.Path, s.In, s.Items, s.Source, s.KnownFormats)
}
if s.itemsValidator != nil {
for i := 0; i < int(size); i++ {
ele := val.Index(i)
if err := s.itemsValidator.Validate(i, ele.Interface()); err != nil && err.HasErrors() {
return err
}
}
}
return nil
}
func (s *basicSliceValidator) hasDuplicates(value reflect.Value, size int) bool {
dict := make(map[interface{}]struct{})
for i := 0; i < size; i++ {
ele := value.Index(i)
if _, ok := dict[ele.Interface()]; ok {
return true
}
dict[ele.Interface()] = struct{}{}
}
return false
}
type numberValidator struct {
Path string
In string
Default interface{}
MultipleOf *float64
Maximum *float64
ExclusiveMaximum bool
Minimum *float64
ExclusiveMinimum bool
// Allows for more accurate behavior regarding integers
Type string
Format string
}
func (n *numberValidator) SetPath(path string) {
n.Path = path
}
func (n *numberValidator) Applies(source interface{}, kind reflect.Kind) bool {
switch source.(type) {
case *spec.Parameter, *spec.Schema, *spec.Items, *spec.Header:
isInt := kind >= reflect.Int && kind <= reflect.Uint64
isFloat := kind == reflect.Float32 || kind == reflect.Float64
r := isInt || isFloat
debugLog("schema props validator for %q applies %t for %T (kind: %v) isInt=%t, isFloat=%t\n", n.Path, r, source, kind, isInt, isFloat)
return r
}
debugLog("schema props validator for %q applies %t for %T (kind: %v)\n", n.Path, false, source, kind)
return false
}
// Validate provides a validator for generic JSON numbers,
//
// By default, numbers are internally represented as float64.
// Formats float, or float32 may alter this behavior by mapping to float32.
// A special validation process is followed for integers, with optional "format":
// this is an attempt to provide a validation with native types.
//
// NOTE: since the constraint specified (boundary, multipleOf) is unmarshalled
// as float64, loss of information remains possible (e.g. on very large integers).
//
// Since this value directly comes from the unmarshalling, it is not possible
// at this stage of processing to check further and guarantee the correctness of such values.
//
// Normally, the JSON Number.MAX_SAFE_INTEGER (resp. Number.MIN_SAFE_INTEGER)
// would check we do not get such a loss.
//
// If this is the case, replace AddErrors() by AddWarnings() and IsValid() by !HasWarnings().
//
// TODO: consider replacing boundary check errors by simple warnings.
//
// TODO: default boundaries with MAX_SAFE_INTEGER are not checked (specific to json.Number?)
func (n *numberValidator) Validate(val interface{}) *Result {
res := new(Result)
resMultiple := new(Result)
resMinimum := new(Result)
resMaximum := new(Result)
// Used only to attempt to validate constraint on value,
// even though value or constraint specified do not match type and format
data := valueHelp.asFloat64(val)
// Is the provided value within the range of the specified numeric type and format?
res.AddErrors(IsValueValidAgainstRange(val, n.Type, n.Format, "Checked", n.Path))
if n.MultipleOf != nil {
// Is the constraint specifier within the range of the specific numeric type and format?
resMultiple.AddErrors(IsValueValidAgainstRange(*n.MultipleOf, n.Type, n.Format, "MultipleOf", n.Path))
if resMultiple.IsValid() {
// Constraint validated with compatible types
if err := MultipleOfNativeType(n.Path, n.In, val, *n.MultipleOf); err != nil {
resMultiple.Merge(errorHelp.sErr(err))
}
} else {
// Constraint nevertheless validated, converted as general number
if err := MultipleOf(n.Path, n.In, data, *n.MultipleOf); err != nil {
resMultiple.Merge(errorHelp.sErr(err))
}
}
}
if n.Maximum != nil {
// Is the constraint specifier within the range of the specific numeric type and format?
resMaximum.AddErrors(IsValueValidAgainstRange(*n.Maximum, n.Type, n.Format, "Maximum boundary", n.Path))
if resMaximum.IsValid() {
// Constraint validated with compatible types
if err := MaximumNativeType(n.Path, n.In, val, *n.Maximum, n.ExclusiveMaximum); err != nil {
resMaximum.Merge(errorHelp.sErr(err))
}
} else {
// Constraint nevertheless validated, converted as general number
if err := Maximum(n.Path, n.In, data, *n.Maximum, n.ExclusiveMaximum); err != nil {
resMaximum.Merge(errorHelp.sErr(err))
}
}
}
if n.Minimum != nil {
// Is the constraint specifier within the range of the specific numeric type and format?
resMinimum.AddErrors(IsValueValidAgainstRange(*n.Minimum, n.Type, n.Format, "Minimum boundary", n.Path))
if resMinimum.IsValid() {
// Constraint validated with compatible types
if err := MinimumNativeType(n.Path, n.In, val, *n.Minimum, n.ExclusiveMinimum); err != nil {
resMinimum.Merge(errorHelp.sErr(err))
}
} else {
// Constraint nevertheless validated, converted as general number
if err := Minimum(n.Path, n.In, data, *n.Minimum, n.ExclusiveMinimum); err != nil {
resMinimum.Merge(errorHelp.sErr(err))
}
}
}
res.Merge(resMultiple, resMinimum, resMaximum)
res.Inc()
return res
}
type stringValidator struct {
Default interface{}
Required bool
AllowEmptyValue bool
MaxLength *int64
MinLength *int64
Pattern string
Path string
In string
}
func (s *stringValidator) SetPath(path string) {
s.Path = path
}
func (s *stringValidator) Applies(source interface{}, kind reflect.Kind) bool {
switch source.(type) {
case *spec.Parameter, *spec.Schema, *spec.Items, *spec.Header:
r := kind == reflect.String
debugLog("string validator for %q applies %t for %T (kind: %v)\n", s.Path, r, source, kind)
return r
}
debugLog("string validator for %q applies %t for %T (kind: %v)\n", s.Path, false, source, kind)
return false
}
func (s *stringValidator) Validate(val interface{}) *Result {
data, ok := val.(string)
if !ok {
return errorHelp.sErr(errors.InvalidType(s.Path, s.In, "string", val))
}
if s.Required && !s.AllowEmptyValue && (s.Default == nil || s.Default == "") {
if err := RequiredString(s.Path, s.In, data); err != nil {
return errorHelp.sErr(err)
}
}
if s.MaxLength != nil {
if err := MaxLength(s.Path, s.In, data, *s.MaxLength); err != nil {
return errorHelp.sErr(err)
}
}
if s.MinLength != nil {
if err := MinLength(s.Path, s.In, data, *s.MinLength); err != nil {
return errorHelp.sErr(err)
}
}
if s.Pattern != "" {
if err := Pattern(s.Path, s.In, data, s.Pattern); err != nil {
return errorHelp.sErr(err)
}
}
return nil
}