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