k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/validation/spec/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 spec 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "net/url" 21 "strings" 22 23 "github.com/go-openapi/swag" 24 "k8s.io/kube-openapi/pkg/internal" 25 jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json" 26 ) 27 28 // BooleanProperty creates a boolean property 29 func BooleanProperty() *Schema { 30 return &Schema{SchemaProps: SchemaProps{Type: []string{"boolean"}}} 31 } 32 33 // BoolProperty creates a boolean property 34 func BoolProperty() *Schema { return BooleanProperty() } 35 36 // StringProperty creates a string property 37 func StringProperty() *Schema { 38 return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}}} 39 } 40 41 // CharProperty creates a string property 42 func CharProperty() *Schema { 43 return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}}} 44 } 45 46 // Float64Property creates a float64/double property 47 func Float64Property() *Schema { 48 return &Schema{SchemaProps: SchemaProps{Type: []string{"number"}, Format: "double"}} 49 } 50 51 // Float32Property creates a float32/float property 52 func Float32Property() *Schema { 53 return &Schema{SchemaProps: SchemaProps{Type: []string{"number"}, Format: "float"}} 54 } 55 56 // Int8Property creates an int8 property 57 func Int8Property() *Schema { 58 return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int8"}} 59 } 60 61 // Int16Property creates an int16 property 62 func Int16Property() *Schema { 63 return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int16"}} 64 } 65 66 // Int32Property creates an int32 property 67 func Int32Property() *Schema { 68 return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int32"}} 69 } 70 71 // Int64Property creates an int64 property 72 func Int64Property() *Schema { 73 return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int64"}} 74 } 75 76 // StrFmtProperty creates a property for the named string format 77 func StrFmtProperty(format string) *Schema { 78 return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: format}} 79 } 80 81 // DateProperty creates a date property 82 func DateProperty() *Schema { 83 return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: "date"}} 84 } 85 86 // DateTimeProperty creates a date time property 87 func DateTimeProperty() *Schema { 88 return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: "date-time"}} 89 } 90 91 // MapProperty creates a map property 92 func MapProperty(property *Schema) *Schema { 93 return &Schema{SchemaProps: SchemaProps{Type: []string{"object"}, 94 AdditionalProperties: &SchemaOrBool{Allows: true, Schema: property}}} 95 } 96 97 // RefProperty creates a ref property 98 func RefProperty(name string) *Schema { 99 return &Schema{SchemaProps: SchemaProps{Ref: MustCreateRef(name)}} 100 } 101 102 // RefSchema creates a ref property 103 func RefSchema(name string) *Schema { 104 return &Schema{SchemaProps: SchemaProps{Ref: MustCreateRef(name)}} 105 } 106 107 // ArrayProperty creates an array property 108 func ArrayProperty(items *Schema) *Schema { 109 if items == nil { 110 return &Schema{SchemaProps: SchemaProps{Type: []string{"array"}}} 111 } 112 return &Schema{SchemaProps: SchemaProps{Items: &SchemaOrArray{Schema: items}, Type: []string{"array"}}} 113 } 114 115 // ComposedSchema creates a schema with allOf 116 func ComposedSchema(schemas ...Schema) *Schema { 117 s := new(Schema) 118 s.AllOf = schemas 119 return s 120 } 121 122 // SchemaURL represents a schema url 123 type SchemaURL string 124 125 // MarshalJSON marshal this to JSON 126 func (r SchemaURL) MarshalJSON() ([]byte, error) { 127 if r == "" { 128 return []byte("{}"), nil 129 } 130 v := map[string]interface{}{"$schema": string(r)} 131 return json.Marshal(v) 132 } 133 134 // UnmarshalJSON unmarshal this from JSON 135 func (r *SchemaURL) UnmarshalJSON(data []byte) error { 136 var v map[string]interface{} 137 if err := json.Unmarshal(data, &v); err != nil { 138 return err 139 } 140 return r.fromMap(v) 141 } 142 143 func (r *SchemaURL) fromMap(v map[string]interface{}) error { 144 if v == nil { 145 return nil 146 } 147 if vv, ok := v["$schema"]; ok { 148 if str, ok := vv.(string); ok { 149 u, err := url.Parse(str) 150 if err != nil { 151 return err 152 } 153 154 *r = SchemaURL(u.String()) 155 } 156 } 157 return nil 158 } 159 160 // SchemaProps describes a JSON schema (draft 4) 161 type SchemaProps struct { 162 ID string `json:"id,omitempty"` 163 Ref Ref `json:"-"` 164 Schema SchemaURL `json:"-"` 165 Description string `json:"description,omitempty"` 166 Type StringOrArray `json:"type,omitempty"` 167 Nullable bool `json:"nullable,omitempty"` 168 Format string `json:"format,omitempty"` 169 Title string `json:"title,omitempty"` 170 Default interface{} `json:"default,omitempty"` 171 Maximum *float64 `json:"maximum,omitempty"` 172 ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` 173 Minimum *float64 `json:"minimum,omitempty"` 174 ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` 175 MaxLength *int64 `json:"maxLength,omitempty"` 176 MinLength *int64 `json:"minLength,omitempty"` 177 Pattern string `json:"pattern,omitempty"` 178 MaxItems *int64 `json:"maxItems,omitempty"` 179 MinItems *int64 `json:"minItems,omitempty"` 180 UniqueItems bool `json:"uniqueItems,omitempty"` 181 MultipleOf *float64 `json:"multipleOf,omitempty"` 182 Enum []interface{} `json:"enum,omitempty"` 183 MaxProperties *int64 `json:"maxProperties,omitempty"` 184 MinProperties *int64 `json:"minProperties,omitempty"` 185 Required []string `json:"required,omitempty"` 186 Items *SchemaOrArray `json:"items,omitempty"` 187 AllOf []Schema `json:"allOf,omitempty"` 188 OneOf []Schema `json:"oneOf,omitempty"` 189 AnyOf []Schema `json:"anyOf,omitempty"` 190 Not *Schema `json:"not,omitempty"` 191 Properties map[string]Schema `json:"properties,omitempty"` 192 AdditionalProperties *SchemaOrBool `json:"additionalProperties,omitempty"` 193 PatternProperties map[string]Schema `json:"patternProperties,omitempty"` 194 Dependencies Dependencies `json:"dependencies,omitempty"` 195 AdditionalItems *SchemaOrBool `json:"additionalItems,omitempty"` 196 Definitions Definitions `json:"definitions,omitempty"` 197 } 198 199 // Marshaling structure only, always edit along with corresponding 200 // struct (or compilation will fail). 201 type schemaPropsOmitZero struct { 202 ID string `json:"id,omitempty"` 203 Ref Ref `json:"-"` 204 Schema SchemaURL `json:"-"` 205 Description string `json:"description,omitempty"` 206 Type StringOrArray `json:"type,omitzero"` 207 Nullable bool `json:"nullable,omitzero"` 208 Format string `json:"format,omitempty"` 209 Title string `json:"title,omitempty"` 210 Default interface{} `json:"default,omitzero"` 211 Maximum *float64 `json:"maximum,omitempty"` 212 ExclusiveMaximum bool `json:"exclusiveMaximum,omitzero"` 213 Minimum *float64 `json:"minimum,omitempty"` 214 ExclusiveMinimum bool `json:"exclusiveMinimum,omitzero"` 215 MaxLength *int64 `json:"maxLength,omitempty"` 216 MinLength *int64 `json:"minLength,omitempty"` 217 Pattern string `json:"pattern,omitempty"` 218 MaxItems *int64 `json:"maxItems,omitempty"` 219 MinItems *int64 `json:"minItems,omitempty"` 220 UniqueItems bool `json:"uniqueItems,omitzero"` 221 MultipleOf *float64 `json:"multipleOf,omitempty"` 222 Enum []interface{} `json:"enum,omitempty"` 223 MaxProperties *int64 `json:"maxProperties,omitempty"` 224 MinProperties *int64 `json:"minProperties,omitempty"` 225 Required []string `json:"required,omitempty"` 226 Items *SchemaOrArray `json:"items,omitzero"` 227 AllOf []Schema `json:"allOf,omitempty"` 228 OneOf []Schema `json:"oneOf,omitempty"` 229 AnyOf []Schema `json:"anyOf,omitempty"` 230 Not *Schema `json:"not,omitzero"` 231 Properties map[string]Schema `json:"properties,omitempty"` 232 AdditionalProperties *SchemaOrBool `json:"additionalProperties,omitzero"` 233 PatternProperties map[string]Schema `json:"patternProperties,omitempty"` 234 Dependencies Dependencies `json:"dependencies,omitempty"` 235 AdditionalItems *SchemaOrBool `json:"additionalItems,omitzero"` 236 Definitions Definitions `json:"definitions,omitempty"` 237 } 238 239 // SwaggerSchemaProps are additional properties supported by swagger schemas, but not JSON-schema (draft 4) 240 type SwaggerSchemaProps struct { 241 Discriminator string `json:"discriminator,omitempty"` 242 ReadOnly bool `json:"readOnly,omitempty"` 243 ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"` 244 Example interface{} `json:"example,omitempty"` 245 } 246 247 // Marshaling structure only, always edit along with corresponding 248 // struct (or compilation will fail). 249 type swaggerSchemaPropsOmitZero struct { 250 Discriminator string `json:"discriminator,omitempty"` 251 ReadOnly bool `json:"readOnly,omitzero"` 252 ExternalDocs *ExternalDocumentation `json:"externalDocs,omitzero"` 253 Example interface{} `json:"example,omitempty"` 254 } 255 256 // Schema the schema object allows the definition of input and output data types. 257 // These types can be objects, but also primitives and arrays. 258 // This object is based on the [JSON Schema Specification Draft 4](http://json-schema.org/) 259 // and uses a predefined subset of it. 260 // On top of this subset, there are extensions provided by this specification to allow for more complete documentation. 261 // 262 // For more information: http://goo.gl/8us55a#schemaObject 263 type Schema struct { 264 VendorExtensible 265 SchemaProps 266 SwaggerSchemaProps 267 ExtraProps map[string]interface{} `json:"-"` 268 } 269 270 // WithID sets the id for this schema, allows for chaining 271 func (s *Schema) WithID(id string) *Schema { 272 s.ID = id 273 return s 274 } 275 276 // WithTitle sets the title for this schema, allows for chaining 277 func (s *Schema) WithTitle(title string) *Schema { 278 s.Title = title 279 return s 280 } 281 282 // WithDescription sets the description for this schema, allows for chaining 283 func (s *Schema) WithDescription(description string) *Schema { 284 s.Description = description 285 return s 286 } 287 288 // WithProperties sets the properties for this schema 289 func (s *Schema) WithProperties(schemas map[string]Schema) *Schema { 290 s.Properties = schemas 291 return s 292 } 293 294 // SetProperty sets a property on this schema 295 func (s *Schema) SetProperty(name string, schema Schema) *Schema { 296 if s.Properties == nil { 297 s.Properties = make(map[string]Schema) 298 } 299 s.Properties[name] = schema 300 return s 301 } 302 303 // WithAllOf sets the all of property 304 func (s *Schema) WithAllOf(schemas ...Schema) *Schema { 305 s.AllOf = schemas 306 return s 307 } 308 309 // WithMaxProperties sets the max number of properties an object can have 310 func (s *Schema) WithMaxProperties(max int64) *Schema { 311 s.MaxProperties = &max 312 return s 313 } 314 315 // WithMinProperties sets the min number of properties an object must have 316 func (s *Schema) WithMinProperties(min int64) *Schema { 317 s.MinProperties = &min 318 return s 319 } 320 321 // Typed sets the type of this schema for a single value item 322 func (s *Schema) Typed(tpe, format string) *Schema { 323 s.Type = []string{tpe} 324 s.Format = format 325 return s 326 } 327 328 // AddType adds a type with potential format to the types for this schema 329 func (s *Schema) AddType(tpe, format string) *Schema { 330 s.Type = append(s.Type, tpe) 331 if format != "" { 332 s.Format = format 333 } 334 return s 335 } 336 337 // AsNullable flags this schema as nullable. 338 func (s *Schema) AsNullable() *Schema { 339 s.Nullable = true 340 return s 341 } 342 343 // CollectionOf a fluent builder method for an array parameter 344 func (s *Schema) CollectionOf(items Schema) *Schema { 345 s.Type = []string{jsonArray} 346 s.Items = &SchemaOrArray{Schema: &items} 347 return s 348 } 349 350 // WithDefault sets the default value on this parameter 351 func (s *Schema) WithDefault(defaultValue interface{}) *Schema { 352 s.Default = defaultValue 353 return s 354 } 355 356 // WithRequired flags this parameter as required 357 func (s *Schema) WithRequired(items ...string) *Schema { 358 s.Required = items 359 return s 360 } 361 362 // AddRequired adds field names to the required properties array 363 func (s *Schema) AddRequired(items ...string) *Schema { 364 s.Required = append(s.Required, items...) 365 return s 366 } 367 368 // WithMaxLength sets a max length value 369 func (s *Schema) WithMaxLength(max int64) *Schema { 370 s.MaxLength = &max 371 return s 372 } 373 374 // WithMinLength sets a min length value 375 func (s *Schema) WithMinLength(min int64) *Schema { 376 s.MinLength = &min 377 return s 378 } 379 380 // WithPattern sets a pattern value 381 func (s *Schema) WithPattern(pattern string) *Schema { 382 s.Pattern = pattern 383 return s 384 } 385 386 // WithMultipleOf sets a multiple of value 387 func (s *Schema) WithMultipleOf(number float64) *Schema { 388 s.MultipleOf = &number 389 return s 390 } 391 392 // WithMaximum sets a maximum number value 393 func (s *Schema) WithMaximum(max float64, exclusive bool) *Schema { 394 s.Maximum = &max 395 s.ExclusiveMaximum = exclusive 396 return s 397 } 398 399 // WithMinimum sets a minimum number value 400 func (s *Schema) WithMinimum(min float64, exclusive bool) *Schema { 401 s.Minimum = &min 402 s.ExclusiveMinimum = exclusive 403 return s 404 } 405 406 // WithEnum sets a the enum values (replace) 407 func (s *Schema) WithEnum(values ...interface{}) *Schema { 408 s.Enum = append([]interface{}{}, values...) 409 return s 410 } 411 412 // WithMaxItems sets the max items 413 func (s *Schema) WithMaxItems(size int64) *Schema { 414 s.MaxItems = &size 415 return s 416 } 417 418 // WithMinItems sets the min items 419 func (s *Schema) WithMinItems(size int64) *Schema { 420 s.MinItems = &size 421 return s 422 } 423 424 // UniqueValues dictates that this array can only have unique items 425 func (s *Schema) UniqueValues() *Schema { 426 s.UniqueItems = true 427 return s 428 } 429 430 // AllowDuplicates this array can have duplicates 431 func (s *Schema) AllowDuplicates() *Schema { 432 s.UniqueItems = false 433 return s 434 } 435 436 // AddToAllOf adds a schema to the allOf property 437 func (s *Schema) AddToAllOf(schemas ...Schema) *Schema { 438 s.AllOf = append(s.AllOf, schemas...) 439 return s 440 } 441 442 // WithDiscriminator sets the name of the discriminator field 443 func (s *Schema) WithDiscriminator(discriminator string) *Schema { 444 s.Discriminator = discriminator 445 return s 446 } 447 448 // AsReadOnly flags this schema as readonly 449 func (s *Schema) AsReadOnly() *Schema { 450 s.ReadOnly = true 451 return s 452 } 453 454 // AsWritable flags this schema as writeable (not read-only) 455 func (s *Schema) AsWritable() *Schema { 456 s.ReadOnly = false 457 return s 458 } 459 460 // WithExample sets the example for this schema 461 func (s *Schema) WithExample(example interface{}) *Schema { 462 s.Example = example 463 return s 464 } 465 466 // WithExternalDocs sets/removes the external docs for/from this schema. 467 // When you pass empty strings as params the external documents will be removed. 468 // When you pass non-empty string as one value then those values will be used on the external docs object. 469 // So when you pass a non-empty description, you should also pass the url and vice versa. 470 func (s *Schema) WithExternalDocs(description, url string) *Schema { 471 if description == "" && url == "" { 472 s.ExternalDocs = nil 473 return s 474 } 475 476 if s.ExternalDocs == nil { 477 s.ExternalDocs = &ExternalDocumentation{} 478 } 479 s.ExternalDocs.Description = description 480 s.ExternalDocs.URL = url 481 return s 482 } 483 484 // MarshalJSON marshal this to JSON 485 func (s Schema) MarshalJSON() ([]byte, error) { 486 if internal.UseOptimizedJSONMarshaling { 487 return internal.DeterministicMarshal(s) 488 } 489 b1, err := json.Marshal(s.SchemaProps) 490 if err != nil { 491 return nil, fmt.Errorf("schema props %v", err) 492 } 493 b2, err := json.Marshal(s.VendorExtensible) 494 if err != nil { 495 return nil, fmt.Errorf("vendor props %v", err) 496 } 497 b3, err := s.Ref.MarshalJSON() 498 if err != nil { 499 return nil, fmt.Errorf("ref prop %v", err) 500 } 501 b4, err := s.Schema.MarshalJSON() 502 if err != nil { 503 return nil, fmt.Errorf("schema prop %v", err) 504 } 505 b5, err := json.Marshal(s.SwaggerSchemaProps) 506 if err != nil { 507 return nil, fmt.Errorf("common validations %v", err) 508 } 509 var b6 []byte 510 if s.ExtraProps != nil { 511 jj, err := json.Marshal(s.ExtraProps) 512 if err != nil { 513 return nil, fmt.Errorf("extra props %v", err) 514 } 515 b6 = jj 516 } 517 return swag.ConcatJSON(b1, b2, b3, b4, b5, b6), nil 518 } 519 520 func (s Schema) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error { 521 type ArbitraryKeys map[string]interface{} 522 var x struct { 523 ArbitraryKeys 524 SchemaProps schemaPropsOmitZero `json:",inline"` 525 SwaggerSchemaProps swaggerSchemaPropsOmitZero `json:",inline"` 526 Schema string `json:"$schema,omitempty"` 527 Ref string `json:"$ref,omitempty"` 528 } 529 x.ArbitraryKeys = make(map[string]any, len(s.Extensions)+len(s.ExtraProps)) 530 for k, v := range s.Extensions { 531 if internal.IsExtensionKey(k) { 532 x.ArbitraryKeys[k] = v 533 } 534 } 535 for k, v := range s.ExtraProps { 536 x.ArbitraryKeys[k] = v 537 } 538 x.SchemaProps = schemaPropsOmitZero(s.SchemaProps) 539 x.SwaggerSchemaProps = swaggerSchemaPropsOmitZero(s.SwaggerSchemaProps) 540 x.Ref = s.Ref.String() 541 x.Schema = string(s.Schema) 542 return opts.MarshalNext(enc, x) 543 } 544 545 // UnmarshalJSON marshal this from JSON 546 func (s *Schema) UnmarshalJSON(data []byte) error { 547 if internal.UseOptimizedJSONUnmarshaling { 548 return jsonv2.Unmarshal(data, s) 549 } 550 551 props := struct { 552 SchemaProps 553 SwaggerSchemaProps 554 }{} 555 if err := json.Unmarshal(data, &props); err != nil { 556 return err 557 } 558 559 sch := Schema{ 560 SchemaProps: props.SchemaProps, 561 SwaggerSchemaProps: props.SwaggerSchemaProps, 562 } 563 564 var d map[string]interface{} 565 if err := json.Unmarshal(data, &d); err != nil { 566 return err 567 } 568 569 _ = sch.Ref.fromMap(d) 570 _ = sch.Schema.fromMap(d) 571 572 delete(d, "$ref") 573 delete(d, "$schema") 574 for _, pn := range swag.DefaultJSONNameProvider.GetJSONNames(s) { 575 delete(d, pn) 576 } 577 578 for k, vv := range d { 579 lk := strings.ToLower(k) 580 if strings.HasPrefix(lk, "x-") { 581 if sch.Extensions == nil { 582 sch.Extensions = map[string]interface{}{} 583 } 584 sch.Extensions[k] = vv 585 continue 586 } 587 if sch.ExtraProps == nil { 588 sch.ExtraProps = map[string]interface{}{} 589 } 590 sch.ExtraProps[k] = vv 591 } 592 593 *s = sch 594 595 return nil 596 } 597 598 func (s *Schema) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error { 599 var x struct { 600 Extensions 601 SchemaProps 602 SwaggerSchemaProps 603 } 604 if err := opts.UnmarshalNext(dec, &x); err != nil { 605 return err 606 } 607 608 if err := x.Ref.fromMap(x.Extensions); err != nil { 609 return err 610 } 611 612 if err := x.Schema.fromMap(x.Extensions); err != nil { 613 return err 614 } 615 616 delete(x.Extensions, "$ref") 617 delete(x.Extensions, "$schema") 618 619 for _, pn := range swag.DefaultJSONNameProvider.GetJSONNames(s) { 620 delete(x.Extensions, pn) 621 } 622 if len(x.Extensions) == 0 { 623 x.Extensions = nil 624 } 625 626 s.ExtraProps = x.Extensions.sanitizeWithExtra() 627 s.Extensions = internal.SanitizeExtensions(x.Extensions) 628 s.SchemaProps = x.SchemaProps 629 s.SwaggerSchemaProps = x.SwaggerSchemaProps 630 return nil 631 }