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 "bytes" 19 "encoding/gob" 20 "encoding/json" 21 "sort" 22 23 "github.com/go-openapi/jsonpointer" 24 "github.com/go-openapi/swag" 25) 26 27func init() { 28 gob.Register(map[string]interface{}{}) 29 gob.Register([]interface{}{}) 30} 31 32// OperationProps describes an operation 33// 34// NOTES: 35// - schemes, when present must be from [http, https, ws, wss]: see validate 36// - Security is handled as a special case: see MarshalJSON function 37type OperationProps struct { 38 Description string `json:"description,omitempty"` 39 Consumes []string `json:"consumes,omitempty"` 40 Produces []string `json:"produces,omitempty"` 41 Schemes []string `json:"schemes,omitempty"` 42 Tags []string `json:"tags,omitempty"` 43 Summary string `json:"summary,omitempty"` 44 ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"` 45 ID string `json:"operationId,omitempty"` 46 Deprecated bool `json:"deprecated,omitempty"` 47 Security []map[string][]string `json:"security,omitempty"` 48 Parameters []Parameter `json:"parameters,omitempty"` 49 Responses *Responses `json:"responses,omitempty"` 50} 51 52// MarshalJSON takes care of serializing operation properties to JSON 53// 54// We use a custom marhaller here to handle a special cases related to 55// the Security field. We need to preserve zero length slice 56// while omitting the field when the value is nil/unset. 57func (op OperationProps) MarshalJSON() ([]byte, error) { 58 type Alias OperationProps 59 if op.Security == nil { 60 return json.Marshal(&struct { 61 Security []map[string][]string `json:"security,omitempty"` 62 *Alias 63 }{ 64 Security: op.Security, 65 Alias: (*Alias)(&op), 66 }) 67 } 68 return json.Marshal(&struct { 69 Security []map[string][]string `json:"security"` 70 *Alias 71 }{ 72 Security: op.Security, 73 Alias: (*Alias)(&op), 74 }) 75} 76 77// Operation describes a single API operation on a path. 78// 79// For more information: http://goo.gl/8us55a#operationObject 80type Operation struct { 81 VendorExtensible 82 OperationProps 83} 84 85// SuccessResponse gets a success response model 86func (o *Operation) SuccessResponse() (*Response, int, bool) { 87 if o.Responses == nil { 88 return nil, 0, false 89 } 90 91 responseCodes := make([]int, 0, len(o.Responses.StatusCodeResponses)) 92 for k := range o.Responses.StatusCodeResponses { 93 if k >= 200 && k < 300 { 94 responseCodes = append(responseCodes, k) 95 } 96 } 97 if len(responseCodes) > 0 { 98 sort.Ints(responseCodes) 99 v := o.Responses.StatusCodeResponses[responseCodes[0]] 100 return &v, responseCodes[0], true 101 } 102 103 return o.Responses.Default, 0, false 104} 105 106// JSONLookup look up a value by the json property name 107func (o Operation) JSONLookup(token string) (interface{}, error) { 108 if ex, ok := o.Extensions[token]; ok { 109 return &ex, nil 110 } 111 r, _, err := jsonpointer.GetForToken(o.OperationProps, token) 112 return r, err 113} 114 115// UnmarshalJSON hydrates this items instance with the data from JSON 116func (o *Operation) UnmarshalJSON(data []byte) error { 117 if err := json.Unmarshal(data, &o.OperationProps); err != nil { 118 return err 119 } 120 return json.Unmarshal(data, &o.VendorExtensible) 121} 122 123// MarshalJSON converts this items object to JSON 124func (o Operation) MarshalJSON() ([]byte, error) { 125 b1, err := json.Marshal(o.OperationProps) 126 if err != nil { 127 return nil, err 128 } 129 b2, err := json.Marshal(o.VendorExtensible) 130 if err != nil { 131 return nil, err 132 } 133 concated := swag.ConcatJSON(b1, b2) 134 return concated, nil 135} 136 137// NewOperation creates a new operation instance. 138// It expects an ID as parameter but not passing an ID is also valid. 139func NewOperation(id string) *Operation { 140 op := new(Operation) 141 op.ID = id 142 return op 143} 144 145// WithID sets the ID property on this operation, allows for chaining. 146func (o *Operation) WithID(id string) *Operation { 147 o.ID = id 148 return o 149} 150 151// WithDescription sets the description on this operation, allows for chaining 152func (o *Operation) WithDescription(description string) *Operation { 153 o.Description = description 154 return o 155} 156 157// WithSummary sets the summary on this operation, allows for chaining 158func (o *Operation) WithSummary(summary string) *Operation { 159 o.Summary = summary 160 return o 161} 162 163// WithExternalDocs sets/removes the external docs for/from this operation. 164// When you pass empty strings as params the external documents will be removed. 165// When you pass non-empty string as one value then those values will be used on the external docs object. 166// So when you pass a non-empty description, you should also pass the url and vice versa. 167func (o *Operation) WithExternalDocs(description, url string) *Operation { 168 if description == "" && url == "" { 169 o.ExternalDocs = nil 170 return o 171 } 172 173 if o.ExternalDocs == nil { 174 o.ExternalDocs = &ExternalDocumentation{} 175 } 176 o.ExternalDocs.Description = description 177 o.ExternalDocs.URL = url 178 return o 179} 180 181// Deprecate marks the operation as deprecated 182func (o *Operation) Deprecate() *Operation { 183 o.Deprecated = true 184 return o 185} 186 187// Undeprecate marks the operation as not deprected 188func (o *Operation) Undeprecate() *Operation { 189 o.Deprecated = false 190 return o 191} 192 193// WithConsumes adds media types for incoming body values 194func (o *Operation) WithConsumes(mediaTypes ...string) *Operation { 195 o.Consumes = append(o.Consumes, mediaTypes...) 196 return o 197} 198 199// WithProduces adds media types for outgoing body values 200func (o *Operation) WithProduces(mediaTypes ...string) *Operation { 201 o.Produces = append(o.Produces, mediaTypes...) 202 return o 203} 204 205// WithTags adds tags for this operation 206func (o *Operation) WithTags(tags ...string) *Operation { 207 o.Tags = append(o.Tags, tags...) 208 return o 209} 210 211// AddParam adds a parameter to this operation, when a parameter for that location 212// and with that name already exists it will be replaced 213func (o *Operation) AddParam(param *Parameter) *Operation { 214 if param == nil { 215 return o 216 } 217 218 for i, p := range o.Parameters { 219 if p.Name == param.Name && p.In == param.In { 220 params := append(o.Parameters[:i], *param) 221 params = append(params, o.Parameters[i+1:]...) 222 o.Parameters = params 223 return o 224 } 225 } 226 227 o.Parameters = append(o.Parameters, *param) 228 return o 229} 230 231// RemoveParam removes a parameter from the operation 232func (o *Operation) RemoveParam(name, in string) *Operation { 233 for i, p := range o.Parameters { 234 if p.Name == name && p.In == in { 235 o.Parameters = append(o.Parameters[:i], o.Parameters[i+1:]...) 236 return o 237 } 238 } 239 return o 240} 241 242// SecuredWith adds a security scope to this operation. 243func (o *Operation) SecuredWith(name string, scopes ...string) *Operation { 244 o.Security = append(o.Security, map[string][]string{name: scopes}) 245 return o 246} 247 248// WithDefaultResponse adds a default response to the operation. 249// Passing a nil value will remove the response 250func (o *Operation) WithDefaultResponse(response *Response) *Operation { 251 return o.RespondsWith(0, response) 252} 253 254// RespondsWith adds a status code response to the operation. 255// When the code is 0 the value of the response will be used as default response value. 256// When the value of the response is nil it will be removed from the operation 257func (o *Operation) RespondsWith(code int, response *Response) *Operation { 258 if o.Responses == nil { 259 o.Responses = new(Responses) 260 } 261 if code == 0 { 262 o.Responses.Default = response 263 return o 264 } 265 if response == nil { 266 delete(o.Responses.StatusCodeResponses, code) 267 return o 268 } 269 if o.Responses.StatusCodeResponses == nil { 270 o.Responses.StatusCodeResponses = make(map[int]Response) 271 } 272 o.Responses.StatusCodeResponses[code] = *response 273 return o 274} 275 276type opsAlias OperationProps 277 278type gobAlias struct { 279 Security []map[string]struct { 280 List []string 281 Pad bool 282 } 283 Alias *opsAlias 284 SecurityIsEmpty bool 285} 286 287// GobEncode provides a safe gob encoder for Operation, including empty security requirements 288func (o Operation) GobEncode() ([]byte, error) { 289 raw := struct { 290 Ext VendorExtensible 291 Props OperationProps 292 }{ 293 Ext: o.VendorExtensible, 294 Props: o.OperationProps, 295 } 296 var b bytes.Buffer 297 err := gob.NewEncoder(&b).Encode(raw) 298 return b.Bytes(), err 299} 300 301// GobDecode provides a safe gob decoder for Operation, including empty security requirements 302func (o *Operation) GobDecode(b []byte) error { 303 var raw struct { 304 Ext VendorExtensible 305 Props OperationProps 306 } 307 308 buf := bytes.NewBuffer(b) 309 err := gob.NewDecoder(buf).Decode(&raw) 310 if err != nil { 311 return err 312 } 313 o.VendorExtensible = raw.Ext 314 o.OperationProps = raw.Props 315 return nil 316} 317 318// GobEncode provides a safe gob encoder for Operation, including empty security requirements 319func (op OperationProps) GobEncode() ([]byte, error) { 320 raw := gobAlias{ 321 Alias: (*opsAlias)(&op), 322 } 323 324 var b bytes.Buffer 325 if op.Security == nil { 326 // nil security requirement 327 err := gob.NewEncoder(&b).Encode(raw) 328 return b.Bytes(), err 329 } 330 331 if len(op.Security) == 0 { 332 // empty, but non-nil security requirement 333 raw.SecurityIsEmpty = true 334 raw.Alias.Security = nil 335 err := gob.NewEncoder(&b).Encode(raw) 336 return b.Bytes(), err 337 } 338 339 raw.Security = make([]map[string]struct { 340 List []string 341 Pad bool 342 }, 0, len(op.Security)) 343 for _, req := range op.Security { 344 v := make(map[string]struct { 345 List []string 346 Pad bool 347 }, len(req)) 348 for k, val := range req { 349 v[k] = struct { 350 List []string 351 Pad bool 352 }{ 353 List: val, 354 } 355 } 356 raw.Security = append(raw.Security, v) 357 } 358 359 err := gob.NewEncoder(&b).Encode(raw) 360 return b.Bytes(), err 361} 362 363// GobDecode provides a safe gob decoder for Operation, including empty security requirements 364func (op *OperationProps) GobDecode(b []byte) error { 365 var raw gobAlias 366 367 buf := bytes.NewBuffer(b) 368 err := gob.NewDecoder(buf).Decode(&raw) 369 if err != nil { 370 return err 371 } 372 if raw.Alias == nil { 373 return nil 374 } 375 376 switch { 377 case raw.SecurityIsEmpty: 378 // empty, but non-nil security requirement 379 raw.Alias.Security = []map[string][]string{} 380 case len(raw.Alias.Security) == 0: 381 // nil security requirement 382 raw.Alias.Security = nil 383 default: 384 raw.Alias.Security = make([]map[string][]string, 0, len(raw.Security)) 385 for _, req := range raw.Security { 386 v := make(map[string][]string, len(req)) 387 for k, val := range req { 388 v[k] = make([]string, 0, len(val.List)) 389 v[k] = append(v[k], val.List...) 390 } 391 raw.Alias.Security = append(raw.Alias.Security, v) 392 } 393 } 394 395 *op = *(*OperationProps)(raw.Alias) 396 return nil 397} 398