1package openapi3
2
3import (
4	"context"
5	"errors"
6	"fmt"
7	"strconv"
8
9	"github.com/getkin/kin-openapi/jsoninfo"
10	"github.com/go-openapi/jsonpointer"
11)
12
13type ParametersMap map[string]*ParameterRef
14
15var _ jsonpointer.JSONPointable = (*ParametersMap)(nil)
16
17func (p ParametersMap) JSONLookup(token string) (interface{}, error) {
18	ref, ok := p[token]
19	if ref == nil || ok == false {
20		return nil, fmt.Errorf("object has no field %q", token)
21	}
22
23	if ref.Ref != "" {
24		return &Ref{Ref: ref.Ref}, nil
25	}
26	return ref.Value, nil
27}
28
29// Parameters is specified by OpenAPI/Swagger 3.0 standard.
30type Parameters []*ParameterRef
31
32var _ jsonpointer.JSONPointable = (*Parameters)(nil)
33
34func (p Parameters) JSONLookup(token string) (interface{}, error) {
35	index, err := strconv.Atoi(token)
36	if err != nil {
37		return nil, err
38	}
39
40	if index < 0 || index >= len(p) {
41		return nil, fmt.Errorf("index %d out of bounds of array of length %d", index, len(p))
42	}
43
44	ref := p[index]
45
46	if ref != nil && ref.Ref != "" {
47		return &Ref{Ref: ref.Ref}, nil
48	}
49	return ref.Value, nil
50}
51
52func NewParameters() Parameters {
53	return make(Parameters, 0, 4)
54}
55
56func (parameters Parameters) GetByInAndName(in string, name string) *Parameter {
57	for _, item := range parameters {
58		if v := item.Value; v != nil {
59			if v.Name == name && v.In == in {
60				return v
61			}
62		}
63	}
64	return nil
65}
66
67func (value Parameters) Validate(ctx context.Context) error {
68	dupes := make(map[string]struct{})
69	for _, item := range value {
70		if v := item.Value; v != nil {
71			key := v.In + ":" + v.Name
72			if _, ok := dupes[key]; ok {
73				return fmt.Errorf("more than one %q parameter has name %q", v.In, v.Name)
74			}
75			dupes[key] = struct{}{}
76		}
77
78		if err := item.Validate(ctx); err != nil {
79			return err
80		}
81	}
82	return nil
83}
84
85// Parameter is specified by OpenAPI/Swagger 3.0 standard.
86// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#parameterObject
87type Parameter struct {
88	ExtensionProps
89	Name            string      `json:"name,omitempty" yaml:"name,omitempty"`
90	In              string      `json:"in,omitempty" yaml:"in,omitempty"`
91	Description     string      `json:"description,omitempty" yaml:"description,omitempty"`
92	Style           string      `json:"style,omitempty" yaml:"style,omitempty"`
93	Explode         *bool       `json:"explode,omitempty" yaml:"explode,omitempty"`
94	AllowEmptyValue bool        `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
95	AllowReserved   bool        `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
96	Deprecated      bool        `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
97	Required        bool        `json:"required,omitempty" yaml:"required,omitempty"`
98	Schema          *SchemaRef  `json:"schema,omitempty" yaml:"schema,omitempty"`
99	Example         interface{} `json:"example,omitempty" yaml:"example,omitempty"`
100	Examples        Examples    `json:"examples,omitempty" yaml:"examples,omitempty"`
101	Content         Content     `json:"content,omitempty" yaml:"content,omitempty"`
102}
103
104var _ jsonpointer.JSONPointable = (*Parameter)(nil)
105
106const (
107	ParameterInPath   = "path"
108	ParameterInQuery  = "query"
109	ParameterInHeader = "header"
110	ParameterInCookie = "cookie"
111)
112
113func NewPathParameter(name string) *Parameter {
114	return &Parameter{
115		Name:     name,
116		In:       ParameterInPath,
117		Required: true,
118	}
119}
120
121func NewQueryParameter(name string) *Parameter {
122	return &Parameter{
123		Name: name,
124		In:   ParameterInQuery,
125	}
126}
127
128func NewHeaderParameter(name string) *Parameter {
129	return &Parameter{
130		Name: name,
131		In:   ParameterInHeader,
132	}
133}
134
135func NewCookieParameter(name string) *Parameter {
136	return &Parameter{
137		Name: name,
138		In:   ParameterInCookie,
139	}
140}
141
142func (parameter *Parameter) WithDescription(value string) *Parameter {
143	parameter.Description = value
144	return parameter
145}
146
147func (parameter *Parameter) WithRequired(value bool) *Parameter {
148	parameter.Required = value
149	return parameter
150}
151
152func (parameter *Parameter) WithSchema(value *Schema) *Parameter {
153	if value == nil {
154		parameter.Schema = nil
155	} else {
156		parameter.Schema = &SchemaRef{
157			Value: value,
158		}
159	}
160	return parameter
161}
162
163func (parameter *Parameter) MarshalJSON() ([]byte, error) {
164	return jsoninfo.MarshalStrictStruct(parameter)
165}
166
167func (parameter *Parameter) UnmarshalJSON(data []byte) error {
168	return jsoninfo.UnmarshalStrictStruct(data, parameter)
169}
170
171func (value Parameter) JSONLookup(token string) (interface{}, error) {
172	switch token {
173	case "schema":
174		if value.Schema != nil {
175			if value.Schema.Ref != "" {
176				return &Ref{Ref: value.Schema.Ref}, nil
177			}
178			return value.Schema.Value, nil
179		}
180	case "name":
181		return value.Name, nil
182	case "in":
183		return value.In, nil
184	case "description":
185		return value.Description, nil
186	case "style":
187		return value.Style, nil
188	case "explode":
189		return value.Explode, nil
190	case "allowEmptyValue":
191		return value.AllowEmptyValue, nil
192	case "allowReserved":
193		return value.AllowReserved, nil
194	case "deprecated":
195		return value.Deprecated, nil
196	case "required":
197		return value.Required, nil
198	case "example":
199		return value.Example, nil
200	case "examples":
201		return value.Examples, nil
202	case "content":
203		return value.Content, nil
204	}
205
206	v, _, err := jsonpointer.GetForToken(value.ExtensionProps, token)
207	return v, err
208}
209
210// SerializationMethod returns a parameter's serialization method.
211// When a parameter's serialization method is not defined the method returns
212// the default serialization method corresponding to a parameter's location.
213func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error) {
214	switch parameter.In {
215	case ParameterInPath, ParameterInHeader:
216		style := parameter.Style
217		if style == "" {
218			style = SerializationSimple
219		}
220		explode := false
221		if parameter.Explode != nil {
222			explode = *parameter.Explode
223		}
224		return &SerializationMethod{Style: style, Explode: explode}, nil
225	case ParameterInQuery, ParameterInCookie:
226		style := parameter.Style
227		if style == "" {
228			style = SerializationForm
229		}
230		explode := true
231		if parameter.Explode != nil {
232			explode = *parameter.Explode
233		}
234		return &SerializationMethod{Style: style, Explode: explode}, nil
235	default:
236		return nil, fmt.Errorf("unexpected parameter's 'in': %q", parameter.In)
237	}
238}
239
240func (value *Parameter) Validate(ctx context.Context) error {
241	if value.Name == "" {
242		return errors.New("parameter name can't be blank")
243	}
244	in := value.In
245	switch in {
246	case
247		ParameterInPath,
248		ParameterInQuery,
249		ParameterInHeader,
250		ParameterInCookie:
251	default:
252		return fmt.Errorf("parameter can't have 'in' value %q", value.In)
253	}
254
255	// Validate a parameter's serialization method.
256	sm, err := value.SerializationMethod()
257	if err != nil {
258		return err
259	}
260	var smSupported bool
261	switch {
262	case value.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode,
263		value.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode,
264		value.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode,
265		value.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode,
266		value.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode,
267		value.In == ParameterInPath && sm.Style == SerializationMatrix && sm.Explode,
268
269		value.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode,
270		value.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode,
271		value.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode,
272		value.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode,
273		value.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode,
274		value.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode,
275		value.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode,
276
277		value.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode,
278		value.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode,
279
280		value.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode,
281		value.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode:
282		smSupported = true
283	}
284	if !smSupported {
285		e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a %s parameter", sm.Style, sm.Explode, in)
286		return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, e)
287	}
288
289	if (value.Schema == nil) == (value.Content == nil) {
290		e := errors.New("parameter must contain exactly one of content and schema")
291		return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, e)
292	}
293	if schema := value.Schema; schema != nil {
294		if err := schema.Validate(ctx); err != nil {
295			return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, err)
296		}
297	}
298
299	if content := value.Content; content != nil {
300		if err := content.Validate(ctx); err != nil {
301			return fmt.Errorf("parameter %q content is invalid: %v", value.Name, err)
302		}
303	}
304	return nil
305}
306