...

Source file src/github.com/go-openapi/validate/helpers.go

Documentation: github.com/go-openapi/validate

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package validate
    16  
    17  // TODO: define this as package validate/internal
    18  // This must be done while keeping CI intact with all tests and test coverage
    19  
    20  import (
    21  	"reflect"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"github.com/go-openapi/errors"
    26  	"github.com/go-openapi/spec"
    27  )
    28  
    29  const (
    30  	swaggerBody     = "body"
    31  	swaggerExample  = "example"
    32  	swaggerExamples = "examples"
    33  )
    34  
    35  const (
    36  	objectType  = "object"
    37  	arrayType   = "array"
    38  	stringType  = "string"
    39  	integerType = "integer"
    40  	numberType  = "number"
    41  	booleanType = "boolean"
    42  	fileType    = "file"
    43  	nullType    = "null"
    44  )
    45  
    46  const (
    47  	jsonProperties = "properties"
    48  	jsonItems      = "items"
    49  	jsonType       = "type"
    50  	// jsonSchema     = "schema"
    51  	jsonDefault = "default"
    52  )
    53  
    54  const (
    55  	stringFormatDate     = "date"
    56  	stringFormatDateTime = "date-time"
    57  	stringFormatPassword = "password"
    58  	stringFormatByte     = "byte"
    59  	// stringFormatBinary       = "binary"
    60  	stringFormatCreditCard   = "creditcard"
    61  	stringFormatDuration     = "duration"
    62  	stringFormatEmail        = "email"
    63  	stringFormatHexColor     = "hexcolor"
    64  	stringFormatHostname     = "hostname"
    65  	stringFormatIPv4         = "ipv4"
    66  	stringFormatIPv6         = "ipv6"
    67  	stringFormatISBN         = "isbn"
    68  	stringFormatISBN10       = "isbn10"
    69  	stringFormatISBN13       = "isbn13"
    70  	stringFormatMAC          = "mac"
    71  	stringFormatBSONObjectID = "bsonobjectid"
    72  	stringFormatRGBColor     = "rgbcolor"
    73  	stringFormatSSN          = "ssn"
    74  	stringFormatURI          = "uri"
    75  	stringFormatUUID         = "uuid"
    76  	stringFormatUUID3        = "uuid3"
    77  	stringFormatUUID4        = "uuid4"
    78  	stringFormatUUID5        = "uuid5"
    79  
    80  	integerFormatInt32  = "int32"
    81  	integerFormatInt64  = "int64"
    82  	integerFormatUInt32 = "uint32"
    83  	integerFormatUInt64 = "uint64"
    84  
    85  	numberFormatFloat32 = "float32"
    86  	numberFormatFloat64 = "float64"
    87  	numberFormatFloat   = "float"
    88  	numberFormatDouble  = "double"
    89  )
    90  
    91  // Helpers available at the package level
    92  var (
    93  	pathHelp     *pathHelper
    94  	valueHelp    *valueHelper
    95  	errorHelp    *errorHelper
    96  	paramHelp    *paramHelper
    97  	responseHelp *responseHelper
    98  )
    99  
   100  type errorHelper struct {
   101  	// A collection of unexported helpers for error construction
   102  }
   103  
   104  func (h *errorHelper) sErr(err errors.Error, recycle bool) *Result {
   105  	// Builds a Result from standard errors.Error
   106  	var result *Result
   107  	if recycle {
   108  		result = pools.poolOfResults.BorrowResult()
   109  	} else {
   110  		result = new(Result)
   111  	}
   112  	result.Errors = []error{err}
   113  
   114  	return result
   115  }
   116  
   117  func (h *errorHelper) addPointerError(res *Result, err error, ref string, fromPath string) *Result {
   118  	// Provides more context on error messages
   119  	// reported by the jsoinpointer package by altering the passed Result
   120  	if err != nil {
   121  		res.AddErrors(cannotResolveRefMsg(fromPath, ref, err))
   122  	}
   123  	return res
   124  }
   125  
   126  type pathHelper struct {
   127  	// A collection of unexported helpers for path validation
   128  }
   129  
   130  func (h *pathHelper) stripParametersInPath(path string) string {
   131  	// Returns a path stripped from all path parameters, with multiple or trailing slashes removed.
   132  	//
   133  	// Stripping is performed on a slash-separated basis, e.g '/a{/b}' remains a{/b} and not /a.
   134  	//  - Trailing "/" make a difference, e.g. /a/ !~ /a (ex: canary/bitbucket.org/swagger.json)
   135  	//  - presence or absence of a parameter makes a difference, e.g. /a/{log} !~ /a/ (ex: canary/kubernetes/swagger.json)
   136  
   137  	// Regexp to extract parameters from path, with surrounding {}.
   138  	// NOTE: important non-greedy modifier
   139  	rexParsePathParam := mustCompileRegexp(`{[^{}]+?}`)
   140  	strippedSegments := []string{}
   141  
   142  	for _, segment := range strings.Split(path, "/") {
   143  		strippedSegments = append(strippedSegments, rexParsePathParam.ReplaceAllString(segment, "X"))
   144  	}
   145  	return strings.Join(strippedSegments, "/")
   146  }
   147  
   148  func (h *pathHelper) extractPathParams(path string) (params []string) {
   149  	// Extracts all params from a path, with surrounding "{}"
   150  	rexParsePathParam := mustCompileRegexp(`{[^{}]+?}`)
   151  
   152  	for _, segment := range strings.Split(path, "/") {
   153  		for _, v := range rexParsePathParam.FindAllStringSubmatch(segment, -1) {
   154  			params = append(params, v...)
   155  		}
   156  	}
   157  	return
   158  }
   159  
   160  type valueHelper struct {
   161  	// A collection of unexported helpers for value validation
   162  }
   163  
   164  func (h *valueHelper) asInt64(val interface{}) int64 {
   165  	// Number conversion function for int64, without error checking
   166  	// (implements an implicit type upgrade).
   167  	v := reflect.ValueOf(val)
   168  	switch v.Kind() { //nolint:exhaustive
   169  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   170  		return v.Int()
   171  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   172  		return int64(v.Uint())
   173  	case reflect.Float32, reflect.Float64:
   174  		return int64(v.Float())
   175  	default:
   176  		// panic("Non numeric value in asInt64()")
   177  		return 0
   178  	}
   179  }
   180  
   181  func (h *valueHelper) asUint64(val interface{}) uint64 {
   182  	// Number conversion function for uint64, without error checking
   183  	// (implements an implicit type upgrade).
   184  	v := reflect.ValueOf(val)
   185  	switch v.Kind() { //nolint:exhaustive
   186  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   187  		return uint64(v.Int())
   188  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   189  		return v.Uint()
   190  	case reflect.Float32, reflect.Float64:
   191  		return uint64(v.Float())
   192  	default:
   193  		// panic("Non numeric value in asUint64()")
   194  		return 0
   195  	}
   196  }
   197  
   198  // Same for unsigned floats
   199  func (h *valueHelper) asFloat64(val interface{}) float64 {
   200  	// Number conversion function for float64, without error checking
   201  	// (implements an implicit type upgrade).
   202  	v := reflect.ValueOf(val)
   203  	switch v.Kind() { //nolint:exhaustive
   204  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   205  		return float64(v.Int())
   206  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   207  		return float64(v.Uint())
   208  	case reflect.Float32, reflect.Float64:
   209  		return v.Float()
   210  	default:
   211  		// panic("Non numeric value in asFloat64()")
   212  		return 0
   213  	}
   214  }
   215  
   216  type paramHelper struct {
   217  	// A collection of unexported helpers for parameters resolution
   218  }
   219  
   220  func (h *paramHelper) safeExpandedParamsFor(path, method, operationID string, res *Result, s *SpecValidator) (params []spec.Parameter) {
   221  	operation, ok := s.expandedAnalyzer().OperationFor(method, path)
   222  	if ok {
   223  		// expand parameters first if necessary
   224  		resolvedParams := []spec.Parameter{}
   225  		for _, ppr := range operation.Parameters {
   226  			resolvedParam, red := h.resolveParam(path, method, operationID, &ppr, s) //#nosec
   227  			res.Merge(red)
   228  			if resolvedParam != nil {
   229  				resolvedParams = append(resolvedParams, *resolvedParam)
   230  			}
   231  		}
   232  		// remove params with invalid expansion from Slice
   233  		operation.Parameters = resolvedParams
   234  
   235  		for _, ppr := range s.expandedAnalyzer().SafeParamsFor(method, path,
   236  			func(_ spec.Parameter, err error) bool {
   237  				// since params have already been expanded, there are few causes for error
   238  				res.AddErrors(someParametersBrokenMsg(path, method, operationID))
   239  				// original error from analyzer
   240  				res.AddErrors(err)
   241  				return true
   242  			}) {
   243  			params = append(params, ppr)
   244  		}
   245  	}
   246  	return
   247  }
   248  
   249  func (h *paramHelper) resolveParam(path, method, operationID string, param *spec.Parameter, s *SpecValidator) (*spec.Parameter, *Result) {
   250  	// Ensure parameter is expanded
   251  	var err error
   252  	res := new(Result)
   253  	isRef := param.Ref.String() != ""
   254  	if s.spec.SpecFilePath() == "" {
   255  		err = spec.ExpandParameterWithRoot(param, s.spec.Spec(), nil)
   256  	} else {
   257  		err = spec.ExpandParameter(param, s.spec.SpecFilePath())
   258  
   259  	}
   260  	if err != nil { // Safeguard
   261  		// NOTE: we may enter here when the whole parameter is an unresolved $ref
   262  		refPath := strings.Join([]string{"\"" + path + "\"", method}, ".")
   263  		errorHelp.addPointerError(res, err, param.Ref.String(), refPath)
   264  		return nil, res
   265  	}
   266  	res.Merge(h.checkExpandedParam(param, param.Name, param.In, operationID, isRef))
   267  	return param, res
   268  }
   269  
   270  func (h *paramHelper) checkExpandedParam(pr *spec.Parameter, path, in, operation string, isRef bool) *Result {
   271  	// Secure parameter structure after $ref resolution
   272  	res := new(Result)
   273  	simpleZero := spec.SimpleSchema{}
   274  	// Try to explain why... best guess
   275  	switch {
   276  	case pr.In == swaggerBody && (pr.SimpleSchema != simpleZero && pr.SimpleSchema.Type != objectType):
   277  		if isRef {
   278  			// Most likely, a $ref with a sibling is an unwanted situation: in itself this is a warning...
   279  			// but we detect it because of the following error:
   280  			// schema took over Parameter for an unexplained reason
   281  			res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
   282  		}
   283  		res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
   284  	case pr.In != swaggerBody && pr.Schema != nil:
   285  		if isRef {
   286  			res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
   287  		}
   288  		res.AddErrors(invalidParameterDefinitionAsSchemaMsg(path, in, operation))
   289  	case (pr.In == swaggerBody && pr.Schema == nil) || (pr.In != swaggerBody && pr.SimpleSchema == simpleZero):
   290  		// Other unexpected mishaps
   291  		res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
   292  	}
   293  	return res
   294  }
   295  
   296  type responseHelper struct {
   297  	// A collection of unexported helpers for response resolution
   298  }
   299  
   300  func (r *responseHelper) expandResponseRef(
   301  	response *spec.Response,
   302  	path string, s *SpecValidator) (*spec.Response, *Result) {
   303  	// Ensure response is expanded
   304  	var err error
   305  	res := new(Result)
   306  	if s.spec.SpecFilePath() == "" {
   307  		// there is no physical document to resolve $ref in response
   308  		err = spec.ExpandResponseWithRoot(response, s.spec.Spec(), nil)
   309  	} else {
   310  		err = spec.ExpandResponse(response, s.spec.SpecFilePath())
   311  	}
   312  	if err != nil { // Safeguard
   313  		// NOTE: we may enter here when the whole response is an unresolved $ref.
   314  		errorHelp.addPointerError(res, err, response.Ref.String(), path)
   315  		return nil, res
   316  	}
   317  
   318  	return response, res
   319  }
   320  
   321  func (r *responseHelper) responseMsgVariants(
   322  	responseType string,
   323  	responseCode int) (responseName, responseCodeAsStr string) {
   324  	// Path variants for messages
   325  	if responseType == jsonDefault {
   326  		responseCodeAsStr = jsonDefault
   327  		responseName = "default response"
   328  	} else {
   329  		responseCodeAsStr = strconv.Itoa(responseCode)
   330  		responseName = "response " + responseCodeAsStr
   331  	}
   332  	return
   333  }
   334  

View as plain text