You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

178 lines
6.6 KiB

  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. package validate
  15. import (
  16. "reflect"
  17. "strings"
  18. "github.com/go-openapi/errors"
  19. "github.com/go-openapi/runtime"
  20. "github.com/go-openapi/spec"
  21. "github.com/go-openapi/strfmt"
  22. "github.com/go-openapi/swag"
  23. )
  24. type typeValidator struct {
  25. Type spec.StringOrArray
  26. Nullable bool
  27. Format string
  28. In string
  29. Path string
  30. }
  31. func (t *typeValidator) schemaInfoForType(data interface{}) (string, string) {
  32. // internal type to JSON type with swagger 2.0 format (with go-openapi/strfmt extensions),
  33. // see https://github.com/go-openapi/strfmt/blob/master/README.md
  34. // TODO: this switch really is some sort of reverse lookup for formats. It should be provided by strfmt.
  35. switch data.(type) {
  36. case []byte, strfmt.Base64, *strfmt.Base64:
  37. return stringType, stringFormatByte
  38. case strfmt.CreditCard, *strfmt.CreditCard:
  39. return stringType, stringFormatCreditCard
  40. case strfmt.Date, *strfmt.Date:
  41. return stringType, stringFormatDate
  42. case strfmt.DateTime, *strfmt.DateTime:
  43. return stringType, stringFormatDateTime
  44. case strfmt.Duration, *strfmt.Duration:
  45. return stringType, stringFormatDuration
  46. case runtime.File, *runtime.File:
  47. return fileType, ""
  48. case strfmt.Email, *strfmt.Email:
  49. return stringType, stringFormatEmail
  50. case strfmt.HexColor, *strfmt.HexColor:
  51. return stringType, stringFormatHexColor
  52. case strfmt.Hostname, *strfmt.Hostname:
  53. return stringType, stringFormatHostname
  54. case strfmt.IPv4, *strfmt.IPv4:
  55. return stringType, stringFormatIPv4
  56. case strfmt.IPv6, *strfmt.IPv6:
  57. return stringType, stringFormatIPv6
  58. case strfmt.ISBN, *strfmt.ISBN:
  59. return stringType, stringFormatISBN
  60. case strfmt.ISBN10, *strfmt.ISBN10:
  61. return stringType, stringFormatISBN10
  62. case strfmt.ISBN13, *strfmt.ISBN13:
  63. return stringType, stringFormatISBN13
  64. case strfmt.MAC, *strfmt.MAC:
  65. return stringType, stringFormatMAC
  66. case strfmt.ObjectId, *strfmt.ObjectId:
  67. return stringType, stringFormatBSONObjectID
  68. case strfmt.Password, *strfmt.Password:
  69. return stringType, stringFormatPassword
  70. case strfmt.RGBColor, *strfmt.RGBColor:
  71. return stringType, stringFormatRGBColor
  72. case strfmt.SSN, *strfmt.SSN:
  73. return stringType, stringFormatSSN
  74. case strfmt.URI, *strfmt.URI:
  75. return stringType, stringFormatURI
  76. case strfmt.UUID, *strfmt.UUID:
  77. return stringType, stringFormatUUID
  78. case strfmt.UUID3, *strfmt.UUID3:
  79. return stringType, stringFormatUUID3
  80. case strfmt.UUID4, *strfmt.UUID4:
  81. return stringType, stringFormatUUID4
  82. case strfmt.UUID5, *strfmt.UUID5:
  83. return stringType, stringFormatUUID5
  84. // TODO: missing binary (io.ReadCloser)
  85. // TODO: missing json.Number
  86. default:
  87. val := reflect.ValueOf(data)
  88. tpe := val.Type()
  89. switch tpe.Kind() {
  90. case reflect.Bool:
  91. return booleanType, ""
  92. case reflect.String:
  93. return stringType, ""
  94. case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32:
  95. // NOTE: that is the spec. With go-openapi, is that not uint32 for unsigned integers?
  96. return integerType, integerFormatInt32
  97. case reflect.Int, reflect.Int64, reflect.Uint, reflect.Uint64:
  98. return integerType, integerFormatInt64
  99. case reflect.Float32:
  100. // NOTE: is that not numberFormatFloat?
  101. return numberType, numberFormatFloat32
  102. case reflect.Float64:
  103. // NOTE: is that not "double"?
  104. return numberType, numberFormatFloat64
  105. // NOTE: go arrays (reflect.Array) are not supported (fixed length)
  106. case reflect.Slice:
  107. return arrayType, ""
  108. case reflect.Map, reflect.Struct:
  109. return objectType, ""
  110. case reflect.Interface:
  111. // What to do here?
  112. panic("dunno what to do here")
  113. case reflect.Ptr:
  114. return t.schemaInfoForType(reflect.Indirect(val).Interface())
  115. }
  116. }
  117. return "", ""
  118. }
  119. func (t *typeValidator) SetPath(path string) {
  120. t.Path = path
  121. }
  122. func (t *typeValidator) Applies(source interface{}, kind reflect.Kind) bool {
  123. // typeValidator applies to Schema, Parameter and Header objects
  124. stpe := reflect.TypeOf(source)
  125. r := (len(t.Type) > 0 || t.Format != "") && (stpe == specSchemaType || stpe == specParameterType || stpe == specHeaderType)
  126. debugLog("type validator for %q applies %t for %T (kind: %v)\n", t.Path, r, source, kind)
  127. return r
  128. }
  129. func (t *typeValidator) Validate(data interface{}) *Result {
  130. result := new(Result)
  131. result.Inc()
  132. if data == nil || reflect.DeepEqual(reflect.Zero(reflect.TypeOf(data)), reflect.ValueOf(data)) {
  133. // nil or zero value for the passed structure require Type: null
  134. if len(t.Type) > 0 && !t.Type.Contains(nullType) && !t.Nullable { // TODO: if a property is not required it also passes this
  135. return errorHelp.sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), nullType))
  136. }
  137. return result
  138. }
  139. // check if the type matches, should be used in every validator chain as first item
  140. val := reflect.Indirect(reflect.ValueOf(data))
  141. kind := val.Kind()
  142. // infer schema type (JSON) and format from passed data type
  143. schType, format := t.schemaInfoForType(data)
  144. debugLog("path: %s, schType: %s, format: %s, expType: %s, expFmt: %s, kind: %s", t.Path, schType, format, t.Type, t.Format, val.Kind().String())
  145. // check numerical types
  146. // TODO: check unsigned ints
  147. // TODO: check json.Number (see schema.go)
  148. isLowerInt := t.Format == integerFormatInt64 && format == integerFormatInt32
  149. isLowerFloat := t.Format == numberFormatFloat64 && format == numberFormatFloat32
  150. isFloatInt := schType == numberType && swag.IsFloat64AJSONInteger(val.Float()) && t.Type.Contains(integerType)
  151. isIntFloat := schType == integerType && t.Type.Contains(numberType)
  152. if kind != reflect.String && kind != reflect.Slice && t.Format != "" && !(t.Type.Contains(schType) || format == t.Format || isFloatInt || isIntFloat || isLowerInt || isLowerFloat) {
  153. // TODO: test case
  154. return errorHelp.sErr(errors.InvalidType(t.Path, t.In, t.Format, format))
  155. }
  156. if !(t.Type.Contains(numberType) || t.Type.Contains(integerType)) && t.Format != "" && (kind == reflect.String || kind == reflect.Slice) {
  157. return result
  158. }
  159. if !(t.Type.Contains(schType) || isFloatInt || isIntFloat) {
  160. return errorHelp.sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), schType))
  161. }
  162. return result
  163. }