k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/validation/validate/schema.go (about) 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 import ( 18 "encoding/json" 19 "fmt" 20 "reflect" 21 22 "github.com/go-openapi/swag" 23 "k8s.io/kube-openapi/pkg/validation/errors" 24 "k8s.io/kube-openapi/pkg/validation/spec" 25 "k8s.io/kube-openapi/pkg/validation/strfmt" 26 ) 27 28 var ( 29 specSchemaType = reflect.TypeOf(&spec.Schema{}) 30 //specItemsType = reflect.TypeOf(&spec.Items{}) 31 ) 32 33 // SchemaValidator validates data against a JSON schema 34 type SchemaValidator struct { 35 Path string 36 in string 37 Schema *spec.Schema 38 validators []ValueValidator 39 Root interface{} 40 KnownFormats strfmt.Registry 41 Options SchemaValidatorOptions 42 } 43 44 // AgainstSchema validates the specified data against the provided schema, using a registry of supported formats. 45 // 46 // When no pre-parsed *spec.Schema structure is provided, it uses a JSON schema as default. See example. 47 func AgainstSchema(schema *spec.Schema, data interface{}, formats strfmt.Registry, options ...Option) error { 48 res := NewSchemaValidator(schema, nil, "", formats, options...).Validate(data) 49 if res.HasErrors() { 50 return errors.CompositeValidationError(res.Errors...) 51 } 52 return nil 53 } 54 55 // NewSchemaValidator creates a new schema validator. 56 // 57 // Panics if the provided schema is invalid. 58 func NewSchemaValidator(schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, options ...Option) *SchemaValidator { 59 if schema == nil { 60 return nil 61 } 62 63 if rootSchema == nil { 64 rootSchema = schema 65 } 66 67 if ref := schema.Ref.String(); ref != "" { 68 panic(fmt.Sprintf("schema references not supported: %s", ref)) 69 } 70 71 s := SchemaValidator{ 72 Path: root, 73 in: "body", 74 Schema: schema, 75 Root: rootSchema, 76 KnownFormats: formats, 77 Options: SchemaValidatorOptions{}} 78 for _, o := range options { 79 o(&s.Options) 80 } 81 82 if s.Options.NewValidatorForIndex == nil { 83 s.Options.NewValidatorForIndex = s.NewValidatorForIndex 84 } 85 if s.Options.NewValidatorForField == nil { 86 s.Options.NewValidatorForField = s.NewValidatorForField 87 } 88 89 s.validators = []ValueValidator{ 90 s.typeValidator(), 91 s.schemaPropsValidator(), 92 s.stringValidator(), 93 s.formatValidator(), 94 s.numberValidator(), 95 s.sliceValidator(), 96 s.commonValidator(), 97 s.objectValidator(), 98 } 99 return &s 100 } 101 102 func (s *SchemaValidator) NewValidatorForField(field string, schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, opts ...Option) ValueValidator { 103 return NewSchemaValidator(schema, rootSchema, root, formats, opts...) 104 } 105 106 func (s *SchemaValidator) NewValidatorForIndex(index int, schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, opts ...Option) ValueValidator { 107 return NewSchemaValidator(schema, rootSchema, root, formats, opts...) 108 } 109 110 // SetPath sets the path for this schema validator 111 func (s *SchemaValidator) SetPath(path string) { 112 s.Path = path 113 for _, v := range s.validators { 114 v.SetPath(path) 115 } 116 } 117 118 // Applies returns true when this schema validator applies 119 func (s *SchemaValidator) Applies(source interface{}, kind reflect.Kind) bool { 120 _, ok := source.(*spec.Schema) 121 return ok 122 } 123 124 // Validate validates the data against the schema 125 func (s *SchemaValidator) Validate(data interface{}) *Result { 126 result := new(Result) 127 if s == nil { 128 return result 129 } 130 131 if data == nil { 132 result.Merge(s.validators[0].Validate(data)) // type validator 133 result.Merge(s.validators[6].Validate(data)) // common validator 134 return result 135 } 136 137 tpe := reflect.TypeOf(data) 138 kind := tpe.Kind() 139 for kind == reflect.Ptr { 140 tpe = tpe.Elem() 141 kind = tpe.Kind() 142 } 143 d := data 144 145 if kind == reflect.Struct { 146 // NOTE: since reflect retrieves the true nature of types 147 // this means that all strfmt types passed here (e.g. strfmt.Datetime, etc..) 148 // are converted here to strings, and structs are systematically converted 149 // to map[string]interface{}. 150 d = swag.ToDynamicJSON(data) 151 } 152 153 // TODO: this part should be handed over to type validator 154 // Handle special case of json.Number data (number marshalled as string) 155 isnumber := s.Schema.Type.Contains(numberType) || s.Schema.Type.Contains(integerType) 156 if num, ok := data.(json.Number); ok && isnumber { 157 if s.Schema.Type.Contains(integerType) { // avoid lossy conversion 158 in, erri := num.Int64() 159 if erri != nil { 160 result.AddErrors(invalidTypeConversionMsg(s.Path, erri)) 161 result.Inc() 162 return result 163 } 164 d = in 165 } else { 166 nf, errf := num.Float64() 167 if errf != nil { 168 result.AddErrors(invalidTypeConversionMsg(s.Path, errf)) 169 result.Inc() 170 return result 171 } 172 d = nf 173 } 174 175 tpe = reflect.TypeOf(d) 176 kind = tpe.Kind() 177 } 178 179 for _, v := range s.validators { 180 if !v.Applies(s.Schema, kind) { 181 debugLog("%T does not apply for %v", v, kind) 182 continue 183 } 184 185 err := v.Validate(d) 186 result.Merge(err) 187 result.Inc() 188 } 189 result.Inc() 190 return result 191 } 192 193 func (s *SchemaValidator) typeValidator() ValueValidator { 194 return &typeValidator{Type: s.Schema.Type, Nullable: s.Schema.Nullable, Format: s.Schema.Format, In: s.in, Path: s.Path} 195 } 196 197 func (s *SchemaValidator) commonValidator() ValueValidator { 198 return &basicCommonValidator{ 199 Path: s.Path, 200 In: s.in, 201 Enum: s.Schema.Enum, 202 } 203 } 204 205 func (s *SchemaValidator) sliceValidator() ValueValidator { 206 return &schemaSliceValidator{ 207 Path: s.Path, 208 In: s.in, 209 MaxItems: s.Schema.MaxItems, 210 MinItems: s.Schema.MinItems, 211 UniqueItems: s.Schema.UniqueItems, 212 AdditionalItems: s.Schema.AdditionalItems, 213 Items: s.Schema.Items, 214 Root: s.Root, 215 KnownFormats: s.KnownFormats, 216 Options: s.Options, 217 } 218 } 219 220 func (s *SchemaValidator) numberValidator() ValueValidator { 221 return &numberValidator{ 222 Path: s.Path, 223 In: s.in, 224 Default: s.Schema.Default, 225 MultipleOf: s.Schema.MultipleOf, 226 Maximum: s.Schema.Maximum, 227 ExclusiveMaximum: s.Schema.ExclusiveMaximum, 228 Minimum: s.Schema.Minimum, 229 ExclusiveMinimum: s.Schema.ExclusiveMinimum, 230 } 231 } 232 233 func (s *SchemaValidator) stringValidator() ValueValidator { 234 return &stringValidator{ 235 Path: s.Path, 236 In: s.in, 237 MaxLength: s.Schema.MaxLength, 238 MinLength: s.Schema.MinLength, 239 Pattern: s.Schema.Pattern, 240 } 241 } 242 243 func (s *SchemaValidator) formatValidator() ValueValidator { 244 return &formatValidator{ 245 Path: s.Path, 246 In: s.in, 247 Format: s.Schema.Format, 248 KnownFormats: s.KnownFormats, 249 } 250 } 251 252 func (s *SchemaValidator) schemaPropsValidator() ValueValidator { 253 sch := s.Schema 254 return newSchemaPropsValidator(s.Path, s.in, sch.AllOf, sch.OneOf, sch.AnyOf, sch.Not, sch.Dependencies, s.Root, s.KnownFormats, s.Options.Options()...) 255 } 256 257 func (s *SchemaValidator) objectValidator() ValueValidator { 258 return &objectValidator{ 259 Path: s.Path, 260 In: s.in, 261 MaxProperties: s.Schema.MaxProperties, 262 MinProperties: s.Schema.MinProperties, 263 Required: s.Schema.Required, 264 Properties: s.Schema.Properties, 265 AdditionalProperties: s.Schema.AdditionalProperties, 266 PatternProperties: s.Schema.PatternProperties, 267 Root: s.Root, 268 KnownFormats: s.KnownFormats, 269 Options: s.Options, 270 } 271 }