object_validator.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. "regexp"
  18. "strings"
  19. "github.com/go-openapi/errors"
  20. "github.com/go-openapi/spec"
  21. "github.com/go-openapi/strfmt"
  22. )
  23. type objectValidator struct {
  24. Path string
  25. In string
  26. MaxProperties *int64
  27. MinProperties *int64
  28. Required []string
  29. Properties map[string]spec.Schema
  30. AdditionalProperties *spec.SchemaOrBool
  31. PatternProperties map[string]spec.Schema
  32. Root interface{}
  33. KnownFormats strfmt.Registry
  34. Options SchemaValidatorOptions
  35. }
  36. func (o *objectValidator) SetPath(path string) {
  37. o.Path = path
  38. }
  39. func (o *objectValidator) Applies(source interface{}, kind reflect.Kind) bool {
  40. // TODO: this should also work for structs
  41. // there is a problem in the type validator where it will be unhappy about null values
  42. // so that requires more testing
  43. r := reflect.TypeOf(source) == specSchemaType && (kind == reflect.Map || kind == reflect.Struct)
  44. debugLog("object validator for %q applies %t for %T (kind: %v)\n", o.Path, r, source, kind)
  45. return r
  46. }
  47. func (o *objectValidator) isProperties() bool {
  48. p := strings.Split(o.Path, ".")
  49. return len(p) > 1 && p[len(p)-1] == jsonProperties && p[len(p)-2] != jsonProperties
  50. }
  51. func (o *objectValidator) isDefault() bool {
  52. p := strings.Split(o.Path, ".")
  53. return len(p) > 1 && p[len(p)-1] == jsonDefault && p[len(p)-2] != jsonDefault
  54. }
  55. func (o *objectValidator) isExample() bool {
  56. p := strings.Split(o.Path, ".")
  57. return len(p) > 1 && (p[len(p)-1] == swaggerExample || p[len(p)-1] == swaggerExamples) && p[len(p)-2] != swaggerExample
  58. }
  59. func (o *objectValidator) checkArrayMustHaveItems(res *Result, val map[string]interface{}) {
  60. // for swagger 2.0 schemas, there is an additional constraint to have array items defined explicitly.
  61. // with pure jsonschema draft 4, one may have arrays with undefined items (i.e. any type).
  62. if t, typeFound := val[jsonType]; typeFound {
  63. if tpe, ok := t.(string); ok && tpe == arrayType {
  64. if _, itemsKeyFound := val[jsonItems]; !itemsKeyFound {
  65. res.AddErrors(errors.Required(jsonItems, o.Path))
  66. }
  67. }
  68. }
  69. }
  70. func (o *objectValidator) checkItemsMustBeTypeArray(res *Result, val map[string]interface{}) {
  71. if !o.isProperties() && !o.isDefault() && !o.isExample() {
  72. if _, itemsKeyFound := val[jsonItems]; itemsKeyFound {
  73. t, typeFound := val[jsonType]
  74. if typeFound {
  75. if tpe, ok := t.(string); !ok || tpe != arrayType {
  76. res.AddErrors(errors.InvalidType(o.Path, o.In, arrayType, nil))
  77. }
  78. } else {
  79. // there is no type
  80. res.AddErrors(errors.Required(jsonType, o.Path))
  81. }
  82. }
  83. }
  84. }
  85. func (o *objectValidator) precheck(res *Result, val map[string]interface{}) {
  86. if o.Options.EnableArrayMustHaveItemsCheck {
  87. o.checkArrayMustHaveItems(res, val)
  88. }
  89. if o.Options.EnableObjectArrayTypeCheck {
  90. o.checkItemsMustBeTypeArray(res, val)
  91. }
  92. }
  93. func (o *objectValidator) Validate(data interface{}) *Result {
  94. val := data.(map[string]interface{})
  95. // TODO: guard against nil data
  96. numKeys := int64(len(val))
  97. if o.MinProperties != nil && numKeys < *o.MinProperties {
  98. return errorHelp.sErr(errors.TooFewProperties(o.Path, o.In, *o.MinProperties))
  99. }
  100. if o.MaxProperties != nil && numKeys > *o.MaxProperties {
  101. return errorHelp.sErr(errors.TooManyProperties(o.Path, o.In, *o.MaxProperties))
  102. }
  103. res := new(Result)
  104. o.precheck(res, val)
  105. // check validity of field names
  106. if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows {
  107. // Case: additionalProperties: false
  108. for k := range val {
  109. _, regularProperty := o.Properties[k]
  110. matched := false
  111. for pk := range o.PatternProperties {
  112. if matches, _ := regexp.MatchString(pk, k); matches {
  113. matched = true
  114. break
  115. }
  116. }
  117. if !regularProperty && k != "$schema" && k != "id" && !matched {
  118. // Special properties "$schema" and "id" are ignored
  119. res.AddErrors(errors.PropertyNotAllowed(o.Path, o.In, k))
  120. // BUG(fredbi): This section should move to a part dedicated to spec validation as
  121. // it will conflict with regular schemas where a property "headers" is defined.
  122. //
  123. // Croaks a more explicit message on top of the standard one
  124. // on some recognized cases.
  125. //
  126. // NOTE: edge cases with invalid type assertion are simply ignored here.
  127. // NOTE: prefix your messages here by "IMPORTANT!" so there are not filtered
  128. // by higher level callers (the IMPORTANT! tag will be eventually
  129. // removed).
  130. if k == "headers" && val[k] != nil {
  131. // $ref is forbidden in header
  132. if headers, mapOk := val[k].(map[string]interface{}); mapOk {
  133. for headerKey, headerBody := range headers {
  134. if headerBody != nil {
  135. if headerSchema, mapOfMapOk := headerBody.(map[string]interface{}); mapOfMapOk {
  136. if _, found := headerSchema["$ref"]; found {
  137. var msg string
  138. if refString, stringOk := headerSchema["$ref"].(string); stringOk {
  139. msg = strings.Join([]string{", one may not use $ref=\":", refString, "\""}, "")
  140. }
  141. res.AddErrors(refNotAllowedInHeaderMsg(o.Path, headerKey, msg))
  142. }
  143. }
  144. }
  145. }
  146. }
  147. /*
  148. case "$ref":
  149. if val[k] != nil {
  150. // TODO: check context of that ref: warn about siblings, check against invalid context
  151. }
  152. */
  153. }
  154. }
  155. }
  156. } else {
  157. // Cases: no additionalProperties (implying: true), or additionalProperties: true, or additionalProperties: { <<schema>> }
  158. for key, value := range val {
  159. _, regularProperty := o.Properties[key]
  160. // Validates property against "patternProperties" if applicable
  161. // BUG(fredbi): succeededOnce is always false
  162. // NOTE: how about regular properties which do not match patternProperties?
  163. matched, succeededOnce, _ := o.validatePatternProperty(key, value, res)
  164. if !(regularProperty || matched || succeededOnce) {
  165. // Cases: properties which are not regular properties and have not been matched by the PatternProperties validator
  166. if o.AdditionalProperties != nil && o.AdditionalProperties.Schema != nil {
  167. // AdditionalProperties as Schema
  168. r := NewSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...).Validate(value)
  169. res.mergeForField(data.(map[string]interface{}), key, r)
  170. } else if regularProperty && !(matched || succeededOnce) {
  171. // TODO: this is dead code since regularProperty=false here
  172. res.AddErrors(errors.FailedAllPatternProperties(o.Path, o.In, key))
  173. }
  174. }
  175. }
  176. // Valid cases: additionalProperties: true or undefined
  177. }
  178. createdFromDefaults := map[string]bool{}
  179. // Property types:
  180. // - regular Property
  181. for pName := range o.Properties {
  182. pSchema := o.Properties[pName] // one instance per iteration
  183. rName := pName
  184. if o.Path != "" {
  185. rName = o.Path + "." + pName
  186. }
  187. // Recursively validates each property against its schema
  188. if v, ok := val[pName]; ok {
  189. r := NewSchemaValidator(&pSchema, o.Root, rName, o.KnownFormats, o.Options.Options()...).Validate(v)
  190. res.mergeForField(data.(map[string]interface{}), pName, r)
  191. } else if pSchema.Default != nil {
  192. // If a default value is defined, creates the property from defaults
  193. // NOTE: JSON schema does not enforce default values to be valid against schema. Swagger does.
  194. createdFromDefaults[pName] = true
  195. res.addPropertySchemata(data.(map[string]interface{}), pName, &pSchema)
  196. }
  197. }
  198. // Check required properties
  199. if len(o.Required) > 0 {
  200. for _, k := range o.Required {
  201. if _, ok := val[k]; !ok && !createdFromDefaults[k] {
  202. res.AddErrors(errors.Required(o.Path+"."+k, o.In))
  203. continue
  204. }
  205. }
  206. }
  207. // Check patternProperties
  208. // TODO: it looks like we have done that twice in many cases
  209. for key, value := range val {
  210. _, regularProperty := o.Properties[key]
  211. matched, _ /*succeededOnce*/, patterns := o.validatePatternProperty(key, value, res)
  212. if !regularProperty && (matched /*|| succeededOnce*/) {
  213. for _, pName := range patterns {
  214. if v, ok := o.PatternProperties[pName]; ok {
  215. r := NewSchemaValidator(&v, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...).Validate(value)
  216. res.mergeForField(data.(map[string]interface{}), key, r)
  217. }
  218. }
  219. }
  220. }
  221. return res
  222. }
  223. // TODO: succeededOnce is not used anywhere
  224. func (o *objectValidator) validatePatternProperty(key string, value interface{}, result *Result) (bool, bool, []string) {
  225. matched := false
  226. succeededOnce := false
  227. var patterns []string
  228. for k, schema := range o.PatternProperties {
  229. sch := schema
  230. if match, _ := regexp.MatchString(k, key); match {
  231. patterns = append(patterns, k)
  232. matched = true
  233. validator := NewSchemaValidator(&sch, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...)
  234. res := validator.Validate(value)
  235. result.Merge(res)
  236. }
  237. }
  238. // BUG(fredbi): can't get to here. Should remove dead code (commented out).
  239. //if succeededOnce {
  240. // result.Inc()
  241. //}
  242. return matched, succeededOnce, patterns
  243. }