alertmanager/vendor/github.com/go-openapi/validate/default_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

279 lines
9.2 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"
"strings"
"github.com/go-openapi/spec"
)
// defaultValidator validates default values in a spec.
// According to Swagger spec, default values MUST validate their schema.
type defaultValidator struct {
SpecValidator *SpecValidator
visitedSchemas map[string]bool
}
// resetVisited resets the internal state of visited schemas
func (d *defaultValidator) resetVisited() {
d.visitedSchemas = map[string]bool{}
}
// beingVisited asserts a schema is being visited
func (d *defaultValidator) beingVisited(path string) {
d.visitedSchemas[path] = true
}
// isVisited tells if a path has already been visited
func (d *defaultValidator) isVisited(path string) bool {
found := d.visitedSchemas[path]
if !found {
// search for overlapping paths
frags := strings.Split(path, ".")
if len(frags) < 2 {
// shortcut exit on smaller paths
return found
}
last := len(frags) - 1
var currentFragStr, parent string
for i := range frags {
if i == 0 {
currentFragStr = frags[last]
} else {
currentFragStr = strings.Join([]string{frags[last-i], currentFragStr}, ".")
}
if i < last {
parent = strings.Join(frags[0:last-i], ".")
} else {
parent = ""
}
if strings.HasSuffix(parent, currentFragStr) {
found = true
break
}
}
}
return found
}
// Validate validates the default values declared in the swagger spec
func (d *defaultValidator) Validate() (errs *Result) {
errs = new(Result)
if d == nil || d.SpecValidator == nil {
return errs
}
d.resetVisited()
errs.Merge(d.validateDefaultValueValidAgainstSchema()) // error -
return errs
}
func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result {
// every default value that is specified must validate against the schema for that property
// headers, items, parameters, schema
res := new(Result)
s := d.SpecValidator
for method, pathItem := range s.analyzer.Operations() {
if pathItem != nil { // Safeguard
for path, op := range pathItem {
// parameters
for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
if param.Default != nil && param.Required {
res.AddWarnings(requiredHasDefaultMsg(param.Name, param.In))
}
// reset explored schemas to get depth-first recursive-proof exploration
d.resetVisited()
// Check simple parameters first
// default values provided must validate against their inline definition (no explicit schema)
if param.Default != nil && param.Schema == nil {
// check param default value is valid
red := NewParamValidator(&param, s.KnownFormats).Validate(param.Default)
if red.HasErrorsOrWarnings() {
res.AddErrors(defaultValueDoesNotValidateMsg(param.Name, param.In))
res.Merge(red)
}
}
// Recursively follows Items and Schemas
if param.Items != nil {
red := d.validateDefaultValueItemsAgainstSchema(param.Name, param.In, &param, param.Items)
if red.HasErrorsOrWarnings() {
res.AddErrors(defaultValueItemsDoesNotValidateMsg(param.Name, param.In))
res.Merge(red)
}
}
if param.Schema != nil {
// Validate default value against schema
red := d.validateDefaultValueSchemaAgainstSchema(param.Name, param.In, param.Schema)
if red.HasErrorsOrWarnings() {
res.AddErrors(defaultValueDoesNotValidateMsg(param.Name, param.In))
res.Merge(red)
}
}
}
if op.Responses != nil {
if op.Responses.Default != nil {
// Same constraint on default Response
res.Merge(d.validateDefaultInResponse(op.Responses.Default, "default", path, 0, op.ID))
}
// Same constraint on regular Responses
if op.Responses.StatusCodeResponses != nil { // Safeguard
for code, r := range op.Responses.StatusCodeResponses {
res.Merge(d.validateDefaultInResponse(&r, "response", path, code, op.ID))
}
}
} else {
// Empty op.ID means there is no meaningful operation: no need to report a specific message
if op.ID != "" {
res.AddErrors(noValidResponseMsg(op.ID))
}
}
}
}
}
if s.spec.Spec().Definitions != nil { // Safeguard
// reset explored schemas to get depth-first recursive-proof exploration
d.resetVisited()
for nm, sch := range s.spec.Spec().Definitions {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("definitions.%s", nm), "body", &sch))
}
}
return res
}
func (d *defaultValidator) validateDefaultInResponse(resp *spec.Response, responseType, path string, responseCode int, operationID string) *Result {
s := d.SpecValidator
response, res := responseHelp.expandResponseRef(resp, path, s)
if !res.IsValid() {
return res
}
responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode)
if response.Headers != nil { // Safeguard
for nm, h := range response.Headers {
// reset explored schemas to get depth-first recursive-proof exploration
d.resetVisited()
if h.Default != nil {
red := NewHeaderValidator(nm, &h, s.KnownFormats).Validate(h.Default)
if red.HasErrorsOrWarnings() {
res.AddErrors(defaultValueHeaderDoesNotValidateMsg(operationID, nm, responseName))
res.Merge(red)
}
}
// Headers have inline definition, like params
if h.Items != nil {
red := d.validateDefaultValueItemsAgainstSchema(nm, "header", &h, h.Items)
if red.HasErrorsOrWarnings() {
res.AddErrors(defaultValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName))
res.Merge(red)
}
}
if _, err := compileRegexp(h.Pattern); err != nil {
res.AddErrors(invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err))
}
// Headers don't have schema
}
}
if response.Schema != nil {
// reset explored schemas to get depth-first recursive-proof exploration
d.resetVisited()
red := d.validateDefaultValueSchemaAgainstSchema(responseCodeAsStr, "response", response.Schema)
if red.HasErrorsOrWarnings() {
// Additional message to make sure the context of the error is not lost
res.AddErrors(defaultValueInDoesNotValidateMsg(operationID, responseName))
res.Merge(red)
}
}
return res
}
func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path, in string, schema *spec.Schema) *Result {
if schema == nil || d.isVisited(path) {
// Avoids recursing if we are already done with that check
return nil
}
d.beingVisited(path)
res := new(Result)
s := d.SpecValidator
if schema.Default != nil {
res.Merge(NewSchemaValidator(schema, s.spec.Spec(), path+".default", s.KnownFormats).Validate(schema.Default))
}
if schema.Items != nil {
if schema.Items.Schema != nil {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".items.default", in, schema.Items.Schema))
}
// Multiple schemas in items
if schema.Items.Schemas != nil { // Safeguard
for i, sch := range schema.Items.Schemas {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d].default", path, i), in, &sch))
}
}
}
if _, err := compileRegexp(schema.Pattern); err != nil {
res.AddErrors(invalidPatternInMsg(path, in, schema.Pattern))
}
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
// NOTE: we keep validating values, even though additionalItems is not supported by Swagger 2.0 (and 3.0 as well)
res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalItems", path), in, schema.AdditionalItems.Schema))
}
for propName, prop := range schema.Properties {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop))
}
for propName, prop := range schema.PatternProperties {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop))
}
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalProperties", path), in, schema.AdditionalProperties.Schema))
}
if schema.AllOf != nil {
for i, aoSch := range schema.AllOf {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.allOf[%d]", path, i), in, &aoSch))
}
}
return res
}
func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path, in string, root interface{}, items *spec.Items) *Result {
res := new(Result)
s := d.SpecValidator
if items != nil {
if items.Default != nil {
res.Merge(newItemsValidator(path, in, items, root, s.KnownFormats).Validate(0, items.Default))
}
if items.Items != nil {
res.Merge(d.validateDefaultValueItemsAgainstSchema(path+"[0].default", in, root, items.Items))
}
if _, err := compileRegexp(items.Pattern); err != nil {
res.AddErrors(invalidPatternInMsg(path, in, items.Pattern))
}
}
return res
}