blob: c3dfa79f5640c76607771e1e49d45fe39c5be2bd [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// 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
15package validate
16
17import (
18 "fmt"
19 "reflect"
20
21 "github.com/go-openapi/spec"
22 "github.com/go-openapi/strfmt"
23)
24
25type schemaPropsValidator struct {
26 Path string
27 In string
28 AllOf []spec.Schema
29 OneOf []spec.Schema
30 AnyOf []spec.Schema
31 Not *spec.Schema
32 Dependencies spec.Dependencies
33 anyOfValidators []SchemaValidator
34 allOfValidators []SchemaValidator
35 oneOfValidators []SchemaValidator
36 notValidator *SchemaValidator
37 Root interface{}
38 KnownFormats strfmt.Registry
39}
40
41func (s *schemaPropsValidator) SetPath(path string) {
42 s.Path = path
43}
44
45func newSchemaPropsValidator(path string, in string, allOf, oneOf, anyOf []spec.Schema, not *spec.Schema, deps spec.Dependencies, root interface{}, formats strfmt.Registry) *schemaPropsValidator {
46 anyValidators := make([]SchemaValidator, 0, len(anyOf))
47 for _, v := range anyOf {
48 anyValidators = append(anyValidators, *NewSchemaValidator(&v, root, path, formats))
49 }
50 allValidators := make([]SchemaValidator, 0, len(allOf))
51 for _, v := range allOf {
52 allValidators = append(allValidators, *NewSchemaValidator(&v, root, path, formats))
53 }
54 oneValidators := make([]SchemaValidator, 0, len(oneOf))
55 for _, v := range oneOf {
56 oneValidators = append(oneValidators, *NewSchemaValidator(&v, root, path, formats))
57 }
58
59 var notValidator *SchemaValidator
60 if not != nil {
61 notValidator = NewSchemaValidator(not, root, path, formats)
62 }
63
64 return &schemaPropsValidator{
65 Path: path,
66 In: in,
67 AllOf: allOf,
68 OneOf: oneOf,
69 AnyOf: anyOf,
70 Not: not,
71 Dependencies: deps,
72 anyOfValidators: anyValidators,
73 allOfValidators: allValidators,
74 oneOfValidators: oneValidators,
75 notValidator: notValidator,
76 Root: root,
77 KnownFormats: formats,
78 }
79}
80
81func (s *schemaPropsValidator) Applies(source interface{}, kind reflect.Kind) bool {
82 r := reflect.TypeOf(source) == specSchemaType
83 debugLog("schema props validator for %q applies %t for %T (kind: %v)\n", s.Path, r, source, kind)
84 return r
85}
86
87func (s *schemaPropsValidator) Validate(data interface{}) *Result {
88 mainResult := new(Result)
89
90 // Intermediary error results
91
92 // IMPORTANT! messages from underlying validators
93 keepResultAnyOf := new(Result)
94 keepResultOneOf := new(Result)
95 keepResultAllOf := new(Result)
96
97 // Validates at least one in anyOf schemas
98 var firstSuccess *Result
99 if len(s.anyOfValidators) > 0 {
100 var bestFailures *Result
101 succeededOnce := false
102 for _, anyOfSchema := range s.anyOfValidators {
103 result := anyOfSchema.Validate(data)
104 // We keep inner IMPORTANT! errors no matter what MatchCount tells us
105 keepResultAnyOf.Merge(result.keepRelevantErrors())
106 if result.IsValid() {
107 bestFailures = nil
108 succeededOnce = true
109 if firstSuccess == nil {
110 firstSuccess = result
111 }
112 keepResultAnyOf = new(Result)
113 break
114 }
115 // MatchCount is used to select errors from the schema with most positive checks
116 if bestFailures == nil || result.MatchCount > bestFailures.MatchCount {
117 bestFailures = result
118 }
119 }
120
121 if !succeededOnce {
122 mainResult.AddErrors(mustValidateAtLeastOneSchemaMsg(s.Path))
123 }
124 if bestFailures != nil {
125 mainResult.Merge(bestFailures)
126 } else if firstSuccess != nil {
127 mainResult.Merge(firstSuccess)
128 }
129 }
130
131 // Validates exactly one in oneOf schemas
132 if len(s.oneOfValidators) > 0 {
133 var bestFailures *Result
134 var firstSuccess *Result
135 validated := 0
136
137 for _, oneOfSchema := range s.oneOfValidators {
138 result := oneOfSchema.Validate(data)
139 // We keep inner IMPORTANT! errors no matter what MatchCount tells us
140 keepResultOneOf.Merge(result.keepRelevantErrors())
141 if result.IsValid() {
142 validated++
143 bestFailures = nil
144 if firstSuccess == nil {
145 firstSuccess = result
146 }
147 keepResultOneOf = new(Result)
148 continue
149 }
150 // MatchCount is used to select errors from the schema with most positive checks
151 if validated == 0 && (bestFailures == nil || result.MatchCount > bestFailures.MatchCount) {
152 bestFailures = result
153 }
154 }
155
156 if validated != 1 {
157 additionalMsg := ""
158 if validated == 0 {
159 additionalMsg = "Found none valid"
160 } else {
161 additionalMsg = fmt.Sprintf("Found %d valid alternatives", validated)
162 }
163
164 mainResult.AddErrors(mustValidateOnlyOneSchemaMsg(s.Path, additionalMsg))
165 if bestFailures != nil {
166 mainResult.Merge(bestFailures)
167 }
168 } else if firstSuccess != nil {
169 mainResult.Merge(firstSuccess)
170 }
171 }
172
173 // Validates all of allOf schemas
174 if len(s.allOfValidators) > 0 {
175 validated := 0
176
177 for _, allOfSchema := range s.allOfValidators {
178 result := allOfSchema.Validate(data)
179 // We keep inner IMPORTANT! errors no matter what MatchCount tells us
180 keepResultAllOf.Merge(result.keepRelevantErrors())
181 //keepResultAllOf.Merge(result)
182 if result.IsValid() {
183 validated++
184 }
185 mainResult.Merge(result)
186 }
187
188 if validated != len(s.allOfValidators) {
189 additionalMsg := ""
190 if validated == 0 {
191 additionalMsg = ". None validated"
192 }
193
194 mainResult.AddErrors(mustValidateAllSchemasMsg(s.Path, additionalMsg))
195 }
196 }
197
198 if s.notValidator != nil {
199 result := s.notValidator.Validate(data)
200 // We keep inner IMPORTANT! errors no matter what MatchCount tells us
201 if result.IsValid() {
202 mainResult.AddErrors(mustNotValidatechemaMsg(s.Path))
203 }
204 }
205
206 if s.Dependencies != nil && len(s.Dependencies) > 0 && reflect.TypeOf(data).Kind() == reflect.Map {
207 val := data.(map[string]interface{})
208 for key := range val {
209 if dep, ok := s.Dependencies[key]; ok {
210
211 if dep.Schema != nil {
212 mainResult.Merge(NewSchemaValidator(dep.Schema, s.Root, s.Path+"."+key, s.KnownFormats).Validate(data))
213 continue
214 }
215
216 if len(dep.Property) > 0 {
217 for _, depKey := range dep.Property {
218 if _, ok := val[depKey]; !ok {
219 mainResult.AddErrors(hasADependencyMsg(s.Path, depKey))
220 }
221 }
222 }
223 }
224 }
225 }
226
227 mainResult.Inc()
228 // In the end we retain best failures for schema validation
229 // plus, if any, composite errors which may explain special cases (tagged as IMPORTANT!).
230 return mainResult.Merge(keepResultAllOf, keepResultOneOf, keepResultAnyOf)
231}