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