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"}, 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	if v == nil {
139		return nil
140	}
141	if vv, ok := v["$schema"]; ok {
142		if str, ok := vv.(string); ok {
143			u, err := url.Parse(str)
144			if err != nil {
145				return err
146			}
147
148			*r = SchemaURL(u.String())
149		}
150	}
151	return nil
152}
153
154// type ExtraSchemaProps map[string]interface{}
155
156// // JSONSchema represents a structure that is a json schema draft 04
157// type JSONSchema struct {
158// 	SchemaProps
159// 	ExtraSchemaProps
160// }
161
162// // MarshalJSON marshal this to JSON
163// func (s JSONSchema) MarshalJSON() ([]byte, error) {
164// 	b1, err := json.Marshal(s.SchemaProps)
165// 	if err != nil {
166// 		return nil, err
167// 	}
168// 	b2, err := s.Ref.MarshalJSON()
169// 	if err != nil {
170// 		return nil, err
171// 	}
172// 	b3, err := s.Schema.MarshalJSON()
173// 	if err != nil {
174// 		return nil, err
175// 	}
176// 	b4, err := json.Marshal(s.ExtraSchemaProps)
177// 	if err != nil {
178// 		return nil, err
179// 	}
180// 	return swag.ConcatJSON(b1, b2, b3, b4), nil
181// }
182
183// // UnmarshalJSON marshal this from JSON
184// func (s *JSONSchema) UnmarshalJSON(data []byte) error {
185// 	var sch JSONSchema
186// 	if err := json.Unmarshal(data, &sch.SchemaProps); err != nil {
187// 		return err
188// 	}
189// 	if err := json.Unmarshal(data, &sch.Ref); err != nil {
190// 		return err
191// 	}
192// 	if err := json.Unmarshal(data, &sch.Schema); err != nil {
193// 		return err
194// 	}
195// 	if err := json.Unmarshal(data, &sch.ExtraSchemaProps); err != nil {
196// 		return err
197// 	}
198// 	*s = sch
199// 	return nil
200// }
201
202type SchemaProps struct {
203	ID                   string            `json:"id,omitempty"`
204	Ref                  Ref               `json:"-,omitempty"`
205	Schema               SchemaURL         `json:"-,omitempty"`
206	Description          string            `json:"description,omitempty"`
207	Type                 StringOrArray     `json:"type,omitempty"`
208	Format               string            `json:"format,omitempty"`
209	Title                string            `json:"title,omitempty"`
210	Default              interface{}       `json:"default,omitempty"`
211	Maximum              *float64          `json:"maximum,omitempty"`
212	ExclusiveMaximum     bool              `json:"exclusiveMaximum,omitempty"`
213	Minimum              *float64          `json:"minimum,omitempty"`
214	ExclusiveMinimum     bool              `json:"exclusiveMinimum,omitempty"`
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,omitempty"`
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,omitempty"`
227	AllOf                []Schema          `json:"allOf,omitempty"`
228	OneOf                []Schema          `json:"oneOf,omitempty"`
229	AnyOf                []Schema          `json:"anyOf,omitempty"`
230	Not                  *Schema           `json:"not,omitempty"`
231	Properties           map[string]Schema `json:"properties,omitempty"`
232	AdditionalProperties *SchemaOrBool     `json:"additionalProperties,omitempty"`
233	PatternProperties    map[string]Schema `json:"patternProperties,omitempty"`
234	Dependencies         Dependencies      `json:"dependencies,omitempty"`
235	AdditionalItems      *SchemaOrBool     `json:"additionalItems,omitempty"`
236	Definitions          Definitions       `json:"definitions,omitempty"`
237}
238
239type SwaggerSchemaProps struct {
240	Discriminator string                 `json:"discriminator,omitempty"`
241	ReadOnly      bool                   `json:"readOnly,omitempty"`
242	XML           *XMLObject             `json:"xml,omitempty"`
243	ExternalDocs  *ExternalDocumentation `json:"externalDocs,omitempty"`
244	Example       interface{}            `json:"example,omitempty"`
245}
246
247// Schema the schema object allows the definition of input and output data types.
248// These types can be objects, but also primitives and arrays.
249// This object is based on the [JSON Schema Specification Draft 4](http://json-schema.org/)
250// and uses a predefined subset of it.
251// On top of this subset, there are extensions provided by this specification to allow for more complete documentation.
252//
253// For more information: http://goo.gl/8us55a#schemaObject
254type Schema struct {
255	VendorExtensible
256	SchemaProps
257	SwaggerSchemaProps
258	ExtraProps map[string]interface{} `json:"-"`
259}
260
261// JSONLookup implements an interface to customize json pointer lookup
262func (s Schema) JSONLookup(token string) (interface{}, error) {
263	if ex, ok := s.Extensions[token]; ok {
264		return &ex, nil
265	}
266
267	if ex, ok := s.ExtraProps[token]; ok {
268		return &ex, nil
269	}
270
271	r, _, err := jsonpointer.GetForToken(s.SchemaProps, token)
272	if r != nil || (err != nil && !strings.HasPrefix(err.Error(), "object has no field")) {
273		return r, err
274	}
275	r, _, err = jsonpointer.GetForToken(s.SwaggerSchemaProps, token)
276	return r, err
277}
278
279// WithID sets the id for this schema, allows for chaining
280func (s *Schema) WithID(id string) *Schema {
281	s.ID = id
282	return s
283}
284
285// WithTitle sets the title for this schema, allows for chaining
286func (s *Schema) WithTitle(title string) *Schema {
287	s.Title = title
288	return s
289}
290
291// WithDescription sets the description for this schema, allows for chaining
292func (s *Schema) WithDescription(description string) *Schema {
293	s.Description = description
294	return s
295}
296
297// WithProperties sets the properties for this schema
298func (s *Schema) WithProperties(schemas map[string]Schema) *Schema {
299	s.Properties = schemas
300	return s
301}
302
303// SetProperty sets a property on this schema
304func (s *Schema) SetProperty(name string, schema Schema) *Schema {
305	if s.Properties == nil {
306		s.Properties = make(map[string]Schema)
307	}
308	s.Properties[name] = schema
309	return s
310}
311
312// WithAllOf sets the all of property
313func (s *Schema) WithAllOf(schemas ...Schema) *Schema {
314	s.AllOf = schemas
315	return s
316}
317
318// WithMaxProperties sets the max number of properties an object can have
319func (s *Schema) WithMaxProperties(max int64) *Schema {
320	s.MaxProperties = &max
321	return s
322}
323
324// WithMinProperties sets the min number of properties an object must have
325func (s *Schema) WithMinProperties(min int64) *Schema {
326	s.MinProperties = &min
327	return s
328}
329
330// Typed sets the type of this schema for a single value item
331func (s *Schema) Typed(tpe, format string) *Schema {
332	s.Type = []string{tpe}
333	s.Format = format
334	return s
335}
336
337// AddType adds a type with potential format to the types for this schema
338func (s *Schema) AddType(tpe, format string) *Schema {
339	s.Type = append(s.Type, tpe)
340	if format != "" {
341		s.Format = format
342	}
343	return s
344}
345
346// CollectionOf a fluent builder method for an array parameter
347func (s *Schema) CollectionOf(items Schema) *Schema {
348	s.Type = []string{"array"}
349	s.Items = &SchemaOrArray{Schema: &items}
350	return s
351}
352
353// WithDefault sets the default value on this parameter
354func (s *Schema) WithDefault(defaultValue interface{}) *Schema {
355	s.Default = defaultValue
356	return s
357}
358
359// WithRequired flags this parameter as required
360func (s *Schema) WithRequired(items ...string) *Schema {
361	s.Required = items
362	return s
363}
364
365// AddRequired  adds field names to the required properties array
366func (s *Schema) AddRequired(items ...string) *Schema {
367	s.Required = append(s.Required, items...)
368	return s
369}
370
371// WithMaxLength sets a max length value
372func (s *Schema) WithMaxLength(max int64) *Schema {
373	s.MaxLength = &max
374	return s
375}
376
377// WithMinLength sets a min length value
378func (s *Schema) WithMinLength(min int64) *Schema {
379	s.MinLength = &min
380	return s
381}
382
383// WithPattern sets a pattern value
384func (s *Schema) WithPattern(pattern string) *Schema {
385	s.Pattern = pattern
386	return s
387}
388
389// WithMultipleOf sets a multiple of value
390func (s *Schema) WithMultipleOf(number float64) *Schema {
391	s.MultipleOf = &number
392	return s
393}
394
395// WithMaximum sets a maximum number value
396func (s *Schema) WithMaximum(max float64, exclusive bool) *Schema {
397	s.Maximum = &max
398	s.ExclusiveMaximum = exclusive
399	return s
400}
401
402// WithMinimum sets a minimum number value
403func (s *Schema) WithMinimum(min float64, exclusive bool) *Schema {
404	s.Minimum = &min
405	s.ExclusiveMinimum = exclusive
406	return s
407}
408
409// WithEnum sets a the enum values (replace)
410func (s *Schema) WithEnum(values ...interface{}) *Schema {
411	s.Enum = append([]interface{}{}, values...)
412	return s
413}
414
415// WithMaxItems sets the max items
416func (s *Schema) WithMaxItems(size int64) *Schema {
417	s.MaxItems = &size
418	return s
419}
420
421// WithMinItems sets the min items
422func (s *Schema) WithMinItems(size int64) *Schema {
423	s.MinItems = &size
424	return s
425}
426
427// UniqueValues dictates that this array can only have unique items
428func (s *Schema) UniqueValues() *Schema {
429	s.UniqueItems = true
430	return s
431}
432
433// AllowDuplicates this array can have duplicates
434func (s *Schema) AllowDuplicates() *Schema {
435	s.UniqueItems = false
436	return s
437}
438
439// AddToAllOf adds a schema to the allOf property
440func (s *Schema) AddToAllOf(schemas ...Schema) *Schema {
441	s.AllOf = append(s.AllOf, schemas...)
442	return s
443}
444
445// WithDiscriminator sets the name of the discriminator field
446func (s *Schema) WithDiscriminator(discriminator string) *Schema {
447	s.Discriminator = discriminator
448	return s
449}
450
451// AsReadOnly flags this schema as readonly
452func (s *Schema) AsReadOnly() *Schema {
453	s.ReadOnly = true
454	return s
455}
456
457// AsWritable flags this schema as writeable (not read-only)
458func (s *Schema) AsWritable() *Schema {
459	s.ReadOnly = false
460	return s
461}
462
463// WithExample sets the example for this schema
464func (s *Schema) WithExample(example interface{}) *Schema {
465	s.Example = example
466	return s
467}
468
469// WithExternalDocs sets/removes the external docs for/from this schema.
470// When you pass empty strings as params the external documents will be removed.
471// When you pass non-empty string as one value then those values will be used on the external docs object.
472// So when you pass a non-empty description, you should also pass the url and vice versa.
473func (s *Schema) WithExternalDocs(description, url string) *Schema {
474	if description == "" && url == "" {
475		s.ExternalDocs = nil
476		return s
477	}
478
479	if s.ExternalDocs == nil {
480		s.ExternalDocs = &ExternalDocumentation{}
481	}
482	s.ExternalDocs.Description = description
483	s.ExternalDocs.URL = url
484	return s
485}
486
487// WithXMLName sets the xml name for the object
488func (s *Schema) WithXMLName(name string) *Schema {
489	if s.XML == nil {
490		s.XML = new(XMLObject)
491	}
492	s.XML.Name = name
493	return s
494}
495
496// WithXMLNamespace sets the xml namespace for the object
497func (s *Schema) WithXMLNamespace(namespace string) *Schema {
498	if s.XML == nil {
499		s.XML = new(XMLObject)
500	}
501	s.XML.Namespace = namespace
502	return s
503}
504
505// WithXMLPrefix sets the xml prefix for the object
506func (s *Schema) WithXMLPrefix(prefix string) *Schema {
507	if s.XML == nil {
508		s.XML = new(XMLObject)
509	}
510	s.XML.Prefix = prefix
511	return s
512}
513
514// AsXMLAttribute flags this object as xml attribute
515func (s *Schema) AsXMLAttribute() *Schema {
516	if s.XML == nil {
517		s.XML = new(XMLObject)
518	}
519	s.XML.Attribute = true
520	return s
521}
522
523// AsXMLElement flags this object as an xml node
524func (s *Schema) AsXMLElement() *Schema {
525	if s.XML == nil {
526		s.XML = new(XMLObject)
527	}
528	s.XML.Attribute = false
529	return s
530}
531
532// AsWrappedXML flags this object as wrapped, this is mostly useful for array types
533func (s *Schema) AsWrappedXML() *Schema {
534	if s.XML == nil {
535		s.XML = new(XMLObject)
536	}
537	s.XML.Wrapped = true
538	return s
539}
540
541// AsUnwrappedXML flags this object as an xml node
542func (s *Schema) AsUnwrappedXML() *Schema {
543	if s.XML == nil {
544		s.XML = new(XMLObject)
545	}
546	s.XML.Wrapped = false
547	return s
548}
549
550// MarshalJSON marshal this to JSON
551func (s Schema) MarshalJSON() ([]byte, error) {
552	b1, err := json.Marshal(s.SchemaProps)
553	if err != nil {
554		return nil, fmt.Errorf("schema props %v", err)
555	}
556	b2, err := json.Marshal(s.VendorExtensible)
557	if err != nil {
558		return nil, fmt.Errorf("vendor props %v", err)
559	}
560	b3, err := s.Ref.MarshalJSON()
561	if err != nil {
562		return nil, fmt.Errorf("ref prop %v", err)
563	}
564	b4, err := s.Schema.MarshalJSON()
565	if err != nil {
566		return nil, fmt.Errorf("schema prop %v", err)
567	}
568	b5, err := json.Marshal(s.SwaggerSchemaProps)
569	if err != nil {
570		return nil, fmt.Errorf("common validations %v", err)
571	}
572	var b6 []byte
573	if s.ExtraProps != nil {
574		jj, err := json.Marshal(s.ExtraProps)
575		if err != nil {
576			return nil, fmt.Errorf("extra props %v", err)
577		}
578		b6 = jj
579	}
580	return swag.ConcatJSON(b1, b2, b3, b4, b5, b6), nil
581}
582
583// UnmarshalJSON marshal this from JSON
584func (s *Schema) UnmarshalJSON(data []byte) error {
585	var sch Schema
586	if err := json.Unmarshal(data, &sch.SchemaProps); err != nil {
587		return err
588	}
589	if err := json.Unmarshal(data, &sch.Ref); err != nil {
590		return err
591	}
592	if err := json.Unmarshal(data, &sch.Schema); err != nil {
593		return err
594	}
595	if err := json.Unmarshal(data, &sch.SwaggerSchemaProps); err != nil {
596		return err
597	}
598
599	var d map[string]interface{}
600	if err := json.Unmarshal(data, &d); err != nil {
601		return err
602	}
603
604	delete(d, "$ref")
605	delete(d, "$schema")
606	for _, pn := range swag.DefaultJSONNameProvider.GetJSONNames(s) {
607		delete(d, pn)
608	}
609
610	for k, vv := range d {
611		lk := strings.ToLower(k)
612		if strings.HasPrefix(lk, "x-") {
613			if sch.Extensions == nil {
614				sch.Extensions = map[string]interface{}{}
615			}
616			sch.Extensions[k] = vv
617			continue
618		}
619		if sch.ExtraProps == nil {
620			sch.ExtraProps = map[string]interface{}{}
621		}
622		sch.ExtraProps[k] = vv
623	}
624
625	*s = sch
626
627	return nil
628}
629