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