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