Skip to content

Add response header OpenAPI spec generation #684

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Metrics/BlockLength:
Exclude:
- spec/**/*

Metrics/ClassLength:
Max: 300

Metrics/LineLength:
Max: 120
Exclude:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#### Features

* [#684](https://github.com/ruby-grape/grape-swagger/pull/684): Add response headers - [@jdmurphy](https://github.com/jdmurphy).
* Your contribution here.

#### Fixes
Expand Down
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ A hash merged into the `info` key of the JSON documentation.
* [File response](#file-response)
* [Extensions](#extensions)
* [Response examples documentation](#response-examples)
* [Response headers documentation](#response-headers)

#### Swagger Header Parameters <a name="headers" />

Expand Down Expand Up @@ -1037,6 +1038,60 @@ The result will look like following:

Failure information can be passed as an array of arrays or an array of hashes.

#### Response headers documentation <a name="response-headers" />

You can also add header information to your responses by using the `desc` DSL with block syntax.

By specifying headers to `success` and `failure`.

```ruby
desc 'This returns headers' do
success model: Thing, headers: { 'Location' => { description: 'Location of resource', type: 'string' } }
failure [[404, 'NotFound', ApiError, { 'application/json' => { code: 404, message: 'Not found' } }, { 'Date' => { description: 'Date of failure', type: 'string' } }]]
end
get '/thing' do
...
end
```

The result will look like following:

```json
"responses": {
"200": {
"description": "This returns examples",
"schema": {
"$ref": "#/definitions/Thing"
},
"headers": {
"Location": {
"description": "Location of resource",
"type": "string"
}
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/ApiError"
},
"examples": {
"application/json": {
"code": 404,
"message": "Not found"
}
},
"headers": {
"Date": {
"description": "Date of failure",
"type": "string"
}
}
}
}
```

Failure information can be passed as an array of arrays or an array of hashes.

## Using Grape Entities <a name="grape-entity" />

Expand Down
4 changes: 3 additions & 1 deletion lib/grape-swagger/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def params_object(route, path)

def response_object(route)
codes = http_codes_from_route(route)
codes.map! { |x| x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3] } : x }
codes.map! { |x| x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x }

codes.each_with_object({}) do |value, memo|
value[:message] ||= ''
Expand All @@ -217,6 +217,7 @@ def response_object(route)

memo[value[:code]][:schema] = build_reference(route, value, response_model)
memo[value[:code]][:examples] = value[:examples] if value[:examples]
memo[value[:code]][:headers] = value[:headers] if value[:headers]
end
end

Expand All @@ -240,6 +241,7 @@ def success_codes_from_route(route)
default_code[:model] = @entity[:model] if @entity[:model].present?
default_code[:message] = @entity[:message] || route.description || default_code[:message].sub('{item}', @item)
default_code[:examples] = @entity[:examples] if @entity[:examples]
default_code[:headers] = @entity[:headers] if @entity[:headers]
else
default_code = GrapeSwagger::DocMethods::StatusCodes.get[route.request_method.downcase.to_sym]
default_code[:model] = @entity if @entity
Expand Down
138 changes: 138 additions & 0 deletions spec/swagger_v2/api_swagger_v2_response_with_headers_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'response with headers' do
# include_context "#{MODEL_PARSER} swagger header"

before :all do
module TheApi
class ResponseApiHeaders < Grape::API
format :json

desc 'This returns headers' do
success model: Entities::UseResponse, headers: { 'Location' => { description: 'Location of resource', type: 'string' } }
failure [[404, 'NotFound', Entities::ApiError, { 'application/json' => { code: 404, message: 'Not found' } }, { 'Date' => { description: 'Date of failure', type: 'string' } }]]
end
get '/response_headers' do
{ 'declared_params' => declared(params) }
end

desc 'This syntax also returns headers' do
success model: Entities::UseResponse, headers: { 'Location' => { description: 'Location of resource', type: 'string' } }
failure [
{
code: 404,
message: 'NotFound',
model: Entities::ApiError,
headers: { 'Date' => { description: 'Date of failure', type: 'string' } }
},
{
code: 400,
message: 'BadRequest',
model: Entities::ApiError,
headers: { 'Date' => { description: 'Date of failure', type: 'string' } }
}
]
end
get '/response_failure_headers' do
{ 'declared_params' => declared(params) }
end

desc 'This does not return headers' do
success model: Entities::UseResponse
failure [[404, 'NotFound', Entities::ApiError]]
end
get '/response_no_headers' do
{ 'declared_params' => declared(params) }
end
add_swagger_documentation
end
end
end

def app
TheApi::ResponseApiHeaders
end

describe 'response headers' do
let(:header_200) do
{ 'Location' => { 'description' => 'Location of resource', 'type' => 'string' } }
end
let(:header_404) do
{ 'Date' => { 'description' => 'Date of failure', 'type' => 'string' } }
end
let(:examples_404) do
{ 'application/json' => { 'code' => 404, 'message' => 'Not found' } }
end

subject do
get '/swagger_doc/response_headers'
JSON.parse(last_response.body)
end

specify do
expect(subject['paths']['/response_headers']['get']).to eql(
'description' => 'This returns headers',
'produces' => ['application/json'],
'responses' => {
'200' => { 'description' => 'This returns headers', 'schema' => { '$ref' => '#/definitions/UseResponse' }, 'headers' => header_200 },
'404' => { 'description' => 'NotFound', 'schema' => { '$ref' => '#/definitions/ApiError' }, 'examples' => examples_404, 'headers' => header_404 }
},
'tags' => ['response_headers'],
'operationId' => 'getResponseHeaders'
)
end
end

describe 'response failure headers' do
let(:header_200) do
{ 'Location' => { 'description' => 'Location of resource', 'type' => 'string' } }
end
let(:header_404) do
{ 'Date' => { 'description' => 'Date of failure', 'type' => 'string' } }
end
let(:header_400) do
{ 'Date' => { 'description' => 'Date of failure', 'type' => 'string' } }
end

subject do
get '/swagger_doc/response_failure_headers'
JSON.parse(last_response.body)
end

specify do
expect(subject['paths']['/response_failure_headers']['get']).to eql(
'description' => 'This syntax also returns headers',
'produces' => ['application/json'],
'responses' => {
'200' => { 'description' => 'This syntax also returns headers', 'schema' => { '$ref' => '#/definitions/UseResponse' }, 'headers' => header_200 },
'404' => { 'description' => 'NotFound', 'schema' => { '$ref' => '#/definitions/ApiError' }, 'headers' => header_404 },
'400' => { 'description' => 'BadRequest', 'schema' => { '$ref' => '#/definitions/ApiError' }, 'headers' => header_400 }
},
'tags' => ['response_failure_headers'],
'operationId' => 'getResponseFailureHeaders'
)
end
end

describe 'response no headers' do
subject do
get '/swagger_doc/response_no_headers'
JSON.parse(last_response.body)
end

specify do
expect(subject['paths']['/response_no_headers']['get']).to eql(
'description' => 'This does not return headers',
'produces' => ['application/json'],
'responses' => {
'200' => { 'description' => 'This does not return headers', 'schema' => { '$ref' => '#/definitions/UseResponse' } },
'404' => { 'description' => 'NotFound', 'schema' => { '$ref' => '#/definitions/ApiError' } }
},
'tags' => ['response_no_headers'],
'operationId' => 'getResponseNoHeaders'
)
end
end
end