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	XML           *XMLObject             `json:"xml,omitempty"`
203	ExternalDocs  *ExternalDocumentation `json:"externalDocs,omitempty"`
204	Example       interface{}            `json:"example,omitempty"`
205}
206
207// Schema the schema object allows the definition of input and output data types.
208// These types can be objects, but also primitives and arrays.
209// This object is based on the [JSON Schema Specification Draft 4](http://json-schema.org/)
210// and uses a predefined subset of it.
211// On top of this subset, there are extensions provided by this specification to allow for more complete documentation.
212//
213// For more information: http://goo.gl/8us55a#schemaObject
214type Schema struct {
215	VendorExtensible
216	SchemaProps
217	SwaggerSchemaProps
218	ExtraProps map[string]interface{} `json:"-"`
219}
220
221// JSONLookup implements an interface to customize json pointer lookup
222func (s Schema) JSONLookup(token string) (interface{}, error) {
223	if ex, ok := s.Extensions[token]; ok {
224		return &ex, nil
225	}
226
227	if ex, ok := s.ExtraProps[token]; ok {
228		return &ex, nil
229	}
230
231	r, _, err := jsonpointer.GetForToken(s.SchemaProps, token)
232	if r != nil || (err != nil && !strings.HasPrefix(err.Error(), "object has no field")) {
233		return r, err
234	}
235	r, _, err = jsonpointer.GetForToken(s.SwaggerSchemaProps, token)
236	return r, err
237}
238
239// WithID sets the id for this schema, allows for chaining
240func (s *Schema) WithID(id string) *Schema {
241	s.ID = id
242	return s
243}
244
245// WithTitle sets the title for this schema, allows for chaining
246func (s *Schema) WithTitle(title string) *Schema {
247	s.Title = title
248	return s
249}
250
251// WithDescription sets the description for this schema, allows for chaining
252func (s *Schema) WithDescription(description string) *Schema {
253	s.Description = description
254	return s
255}
256
257// WithProperties sets the properties for this schema
258func (s *Schema) WithProperties(schemas map[string]Schema) *Schema {
259	s.Properties = schemas
260	return s
261}
262
263// SetProperty sets a property on this schema
264func (s *Schema) SetProperty(name string, schema Schema) *Schema {
265	if s.Properties == nil {
266		s.Properties = make(map[string]Schema)
267	}
268	s.Properties[name] = schema
269	return s
270}
271
272// WithAllOf sets the all of property
273func (s *Schema) WithAllOf(schemas ...Schema) *Schema {
274	s.AllOf = schemas
275	return s
276}
277
278// WithMaxProperties sets the max number of properties an object can have
279func (s *Schema) WithMaxProperties(max int64) *Schema {
280	s.MaxProperties = &max
281	return s
282}
283
284// WithMinProperties sets the min number of properties an object must have
285func (s *Schema) WithMinProperties(min int64) *Schema {
286	s.MinProperties = &min
287	return s
288}
289
290// Typed sets the type of this schema for a single value item
291func (s *Schema) Typed(tpe, format string) *Schema {
292	s.Type = []string{tpe}
293	s.Format = format
294	return s
295}
296
297// AddType adds a type with potential format to the types for this schema
298func (s *Schema) AddType(tpe, format string) *Schema {
299	s.Type = append(s.Type, tpe)
300	if format != "" {
301		s.Format = format
302	}
303	return s
304}
305
306// AsNullable flags this schema as nullable.
307func (s *Schema) AsNullable() *Schema {
308	s.Nullable = true
309	return s
310}
311
312// CollectionOf a fluent builder method for an array parameter
313func (s *Schema) CollectionOf(items Schema) *Schema {
314	s.Type = []string{jsonArray}
315	s.Items = &SchemaOrArray{Schema: &items}
316	return s
317}
318
319// WithDefault sets the default value on this parameter
320func (s *Schema) WithDefault(defaultValue interface{}) *Schema {
321	s.Default = defaultValue
322	return s
323}
324
325// WithRequired flags this parameter as required
326func (s *Schema) WithRequired(items ...string) *Schema {
327	s.Required = items
328	return s
329}
330
331// AddRequired  adds field names to the required properties array
332func (s *Schema) AddRequired(items ...string) *Schema {
333	s.Required = append(s.Required, items...)
334	return s
335}
336
337// WithMaxLength sets a max length value
338func (s *Schema) WithMaxLength(max int64) *Schema {
339	s.MaxLength = &max
340	return s
341}
342
343// WithMinLength sets a min length value
344func (s *Schema) WithMinLength(min int64) *Schema {
345	s.MinLength = &min
346	return s
347}
348
349// WithPattern sets a pattern value
350func (s *Schema) WithPattern(pattern string) *Schema {
351	s.Pattern = pattern
352	return s
353}
354
355// WithMultipleOf sets a multiple of value
356func (s *Schema) WithMultipleOf(number float64) *Schema {
357	s.MultipleOf = &number
358	return s
359}
360
361// WithMaximum sets a maximum number value
362func (s *Schema) WithMaximum(max float64, exclusive bool) *Schema {
363	s.Maximum = &max
364	s.ExclusiveMaximum = exclusive
365	return s
366}
367
368// WithMinimum sets a minimum number value
369func (s *Schema) WithMinimum(min float64, exclusive bool) *Schema {
370	s.Minimum = &min
371	s.ExclusiveMinimum = exclusive
372	return s
373}
374
375// WithEnum sets a the enum values (replace)
376func (s *Schema) WithEnum(values ...interface{}) *Schema {
377	s.Enum = append([]interface{}{}, values...)
378	return s
379}
380
381// WithMaxItems sets the max items
382func (s *Schema) WithMaxItems(size int64) *Schema {
383	s.MaxItems = &size
384	return s
385}
386
387// WithMinItems sets the min items
388func (s *Schema) WithMinItems(size int64) *Schema {
389	s.MinItems = &size
390	return s
391}
392
393// UniqueValues dictates that this array can only have unique items
394func (s *Schema) UniqueValues() *Schema {
395	s.UniqueItems = true
396	return s
397}
398
399// AllowDuplicates this array can have duplicates
400func (s *Schema) AllowDuplicates() *Schema {
401	s.UniqueItems = false
402	return s
403}
404
405// AddToAllOf adds a schema to the allOf property
406func (s *Schema) AddToAllOf(schemas ...Schema) *Schema {
407	s.AllOf = append(s.AllOf, schemas...)
408	return s
409}
410
411// WithDiscriminator sets the name of the discriminator field
412func (s *Schema) WithDiscriminator(discriminator string) *Schema {
413	s.Discriminator = discriminator
414	return s
415}
416
417// AsReadOnly flags this schema as readonly
418func (s *Schema) AsReadOnly() *Schema {
419	s.ReadOnly = true
420	return s
421}
422
423// AsWritable flags this schema as writeable (not read-only)
424func (s *Schema) AsWritable() *Schema {
425	s.ReadOnly = false
426	return s
427}
428
429// WithExample sets the example for this schema
430func (s *Schema) WithExample(example interface{}) *Schema {
431	s.Example = example
432	return s
433}
434
435// WithExternalDocs sets/removes the external docs for/from this schema.
436// When you pass empty strings as params the external documents will be removed.
437// When you pass non-empty string as one value then those values will be used on the external docs object.
438// So when you pass a non-empty description, you should also pass the url and vice versa.
439func (s *Schema) WithExternalDocs(description, url string) *Schema {
440	if description == "" && url == "" {
441		s.ExternalDocs = nil
442		return s
443	}
444
445	if s.ExternalDocs == nil {
446		s.ExternalDocs = &ExternalDocumentation{}
447	}
448	s.ExternalDocs.Description = description
449	s.ExternalDocs.URL = url
450	return s
451}
452
453// WithXMLName sets the xml name for the object
454func (s *Schema) WithXMLName(name string) *Schema {
455	if s.XML == nil {
456		s.XML = new(XMLObject)
457	}
458	s.XML.Name = name
459	return s
460}
461
462// WithXMLNamespace sets the xml namespace for the object
463func (s *Schema) WithXMLNamespace(namespace string) *Schema {
464	if s.XML == nil {
465		s.XML = new(XMLObject)
466	}
467	s.XML.Namespace = namespace
468	return s
469}
470
471// WithXMLPrefix sets the xml prefix for the object
472func (s *Schema) WithXMLPrefix(prefix string) *Schema {
473	if s.XML == nil {
474		s.XML = new(XMLObject)
475	}
476	s.XML.Prefix = prefix
477	return s
478}
479
480// AsXMLAttribute flags this object as xml attribute
481func (s *Schema) AsXMLAttribute() *Schema {
482	if s.XML == nil {
483		s.XML = new(XMLObject)
484	}
485	s.XML.Attribute = true
486	return s
487}
488
489// AsXMLElement flags this object as an xml node
490func (s *Schema) AsXMLElement() *Schema {
491	if s.XML == nil {
492		s.XML = new(XMLObject)
493	}
494	s.XML.Attribute = false
495	return s
496}
497
498// AsWrappedXML flags this object as wrapped, this is mostly useful for array types
499func (s *Schema) AsWrappedXML() *Schema {
500	if s.XML == nil {
501		s.XML = new(XMLObject)
502	}
503	s.XML.Wrapped = true
504	return s
505}
506
507// AsUnwrappedXML flags this object as an xml node
508func (s *Schema) AsUnwrappedXML() *Schema {
509	if s.XML == nil {
510		s.XML = new(XMLObject)
511	}
512	s.XML.Wrapped = false
513	return s
514}
515
516// MarshalJSON marshal this to JSON
517func (s Schema) MarshalJSON() ([]byte, error) {
518	b1, err := json.Marshal(s.SchemaProps)
519	if err != nil {
520		return nil, fmt.Errorf("schema props %v", err)
521	}
522	b2, err := json.Marshal(s.VendorExtensible)
523	if err != nil {
524		return nil, fmt.Errorf("vendor props %v", err)
525	}
526	b3, err := s.Ref.MarshalJSON()
527	if err != nil {
528		return nil, fmt.Errorf("ref prop %v", err)
529	}
530	b4, err := s.Schema.MarshalJSON()
531	if err != nil {
532		return nil, fmt.Errorf("schema prop %v", err)
533	}
534	b5, err := json.Marshal(s.SwaggerSchemaProps)
535	if err != nil {
536		return nil, fmt.Errorf("common validations %v", err)
537	}
538	var b6 []byte
539	if s.ExtraProps != nil {
540		jj, err := json.Marshal(s.ExtraProps)
541		if err != nil {
542			return nil, fmt.Errorf("extra props %v", err)
543		}
544		b6 = jj
545	}
546	return swag.ConcatJSON(b1, b2, b3, b4, b5, b6), nil
547}
548
549// UnmarshalJSON marshal this from JSON
550func (s *Schema) UnmarshalJSON(data []byte) error {
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