Skip to content

Commit

Permalink
feat: Add plain text responses (#25)
Browse files Browse the repository at this point in the history
Adds the option to return plain text from an Apia API, by calling response.plain_text_body, and passing in the body content
  • Loading branch information
mattbearman authored Apr 9, 2024
1 parent b1d9d42 commit dd556e6
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 43 deletions.
29 changes: 26 additions & 3 deletions lib/apia/rack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,41 @@ def triplet_for_exception(exception)

class << self

# Return a plain text triplet for the given body.
#
# @param body [String]
# @param status [Integer]
# @param headers [Hash]
# @return [Array]
def plain_triplet(body, status: 200, headers: {})
response_triplet(body, content_type: 'text/plain', status: status, headers: headers)
end

# Return a JSON-ready triplet for the given body.
#
# @param body [Hash, Array]
# @param status [Integer]
# @param headers [Hash]
# @return [Array]
def json_triplet(body, status: 200, headers: {})
body_as_json = body.to_json
response_triplet(body.to_json, content_type: 'application/json', status: status, headers: headers)
end

# Return a triplet for the given body.
#
# @param body [Hash, Array]
# @param content_type [String]
# @param status [Integer]
# @param headers [Hash]
# @return [Array]
def response_triplet(body, content_type:, status: 200, headers: {})
content_length = body.bytesize.to_s
body = [body] unless body.respond_to?(:each)

[
status,
headers.merge('content-type' => 'application/json', 'content-length' => body_as_json.bytesize.to_s),
[body_as_json]
headers.merge('content-type' => content_type, 'content-length' => content_length),
body
]
end

Expand Down
21 changes: 20 additions & 1 deletion lib/apia/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
module Apia
class Response

TYPES = [
JSON = :json,
PLAIN = :plain
].freeze

attr_accessor :status
attr_reader :fields
attr_reader :headers
Expand All @@ -20,6 +25,11 @@ def initialize(request, endpoint)
@headers = {}
end

def plain_text_body(body)
@type = PLAIN
@body = body
end

# Add a field value for this endpoint
#
# @param name [Symbol]
Expand Down Expand Up @@ -53,11 +63,20 @@ def body
@body || hash
end

def type
@type || JSON
end

# Return the rack triplet for this response
#
# @return [Array]
def rack_triplet
Rack.json_triplet(body, headers: @headers, status: @status)
case type
when JSON
Rack.json_triplet(body, headers: headers, status: status)
when PLAIN
Rack.plain_triplet(body, headers: headers, status: status)
end
end

end
Expand Down
43 changes: 43 additions & 0 deletions spec/specs/apia/rack_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,49 @@ def call(_env)
end
end

context '.plain_triplet' do
it 'should return json encoded data' do
data = 'hello world'
triplet = Apia::Rack.plain_triplet(data)
expect(triplet).to be_a Array
expect(triplet[0]).to eq 200
expect(triplet[1]).to be_a Hash
expect(triplet[2]).to be_a Array
expect(triplet[2][0]).to eq 'hello world'
end

it 'should set the content type' do
data = 'hello world'
triplet = Apia::Rack.plain_triplet(data)
expect(triplet).to be_a Array
expect(triplet[1]['content-type']).to eq 'text/plain'
end

it 'should set the content length' do
data = 'hello world'
triplet = Apia::Rack.plain_triplet(data)
expect(triplet).to be_a Array
expect(triplet[1]['content-length']).to eq '11'
end

it 'should set the status' do
data = 'hello world'
triplet = Apia::Rack.plain_triplet(data, status: 400)
expect(triplet).to be_a Array
expect(triplet[0]).to eq 400
end

it 'should merge additional headers' do
data = 'hello world'
triplet = Apia::Rack.plain_triplet(data, headers: { 'x-something' => 'hello' })
expect(triplet).to be_a Array
expect(triplet[1]).to be_a Hash
expect(triplet[1]['x-something']).to eq 'hello'
expect(triplet[1]['content-length']).to eq '11'
expect(triplet[1]['content-type']).to eq 'text/plain'
end
end

context '.error_triplet' do
it 'should format the JSON appropriately' do
triplet = Apia::Rack.error_triplet('example_error', description: 'Some example', detail: { hello: 'world' })
Expand Down
136 changes: 97 additions & 39 deletions spec/specs/apia/response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,52 +34,110 @@
end

context '#rack_triplet' do
it 'should return 200 by default' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
expect(response.rack_triplet[0]).to eq 200
end
context 'with a JSON response' do
it 'should return 200 by default' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
expect(response.rack_triplet[0]).to eq 200
end

it 'should return whatever the status is set to' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
response.status = 403
expect(response.rack_triplet[0]).to eq 403
end
it 'should return whatever the status is set to' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
response.status = 403
expect(response.rack_triplet[0]).to eq 403
end

it 'should return the status from the endpoint' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
endpoint.http_status :created
response = Apia::Response.new(request, endpoint)
expect(response.rack_triplet[0]).to eq 201
end
it 'should return the status from the endpoint' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
endpoint.http_status :created
response = Apia::Response.new(request, endpoint)
expect(response.rack_triplet[0]).to eq 201
end

it 'should return the headers' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
response.add_header 'x-example', 'hello world'
expect(response.rack_triplet[1]['x-example']).to eq 'hello world'
end
it 'should return the headers' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
response.add_header 'x-example', 'hello world'
expect(response.rack_triplet[1]['x-example']).to eq 'hello world'
end

it 'should always provide the content-type as json' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
expect(response.rack_triplet[1]['content-type']).to eq 'application/json'
end
it 'should always provide the content-type as json' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
expect(response.rack_triplet[1]['content-type']).to eq 'application/json'
end

it 'should always set a content-length' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
expect(response.rack_triplet[2][0]).to eq '{}'
expect(response.rack_triplet[1]['content-length']).to eq '2'
it 'should always set a content-length' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
expect(response.rack_triplet[2][0]).to eq '{}'
expect(response.rack_triplet[1]['content-length']).to eq '2'
end

it 'should return the body if one has been set' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
response.body = { hello: 'world' }
expect(response.rack_triplet[2][0]).to eq '{"hello":"world"}'
expect(response.rack_triplet[1]['content-length']).to eq '17'
end
end

it 'should return the body if one has been set' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
response.body = { hello: 'world' }
expect(response.rack_triplet[2][0]).to eq '{"hello":"world"}'
expect(response.rack_triplet[1]['content-length']).to eq '17'
context 'with a plain text response' do
it 'should return 200 by default' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
response.plain_text_body('hello world')
expect(response.rack_triplet[0]).to eq 200
end

it 'should return whatever the status is set to' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
response.plain_text_body('hello world')
response.status = 403
expect(response.rack_triplet[0]).to eq 403
end

it 'should return the status from the endpoint' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
endpoint.http_status :created
response = Apia::Response.new(request, endpoint)
response.plain_text_body('hello world')
expect(response.rack_triplet[0]).to eq 201
end

it 'should return the headers' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
response.plain_text_body('hello world')
response.add_header 'x-example', 'hi!'
expect(response.rack_triplet[1]['x-example']).to eq 'hi!'
end

it 'should always provide the content-type as plain' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
response.plain_text_body('hello world')
expect(response.rack_triplet[1]['content-type']).to eq 'text/plain'
end

it 'should always set a content-length' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
response.plain_text_body('')
expect(response.rack_triplet[2][0]).to eq ''
expect(response.rack_triplet[1]['content-length']).to eq '0'
end

it 'should return the body if one has been set' do
endpoint = Apia::Endpoint.create('ExampleEndpoint')
response = Apia::Response.new(request, endpoint)
response.plain_text_body('hello world')
expect(response.rack_triplet[2][0]).to eq 'hello world'
expect(response.rack_triplet[1]['content-length']).to eq '11'
end
end
end
end

0 comments on commit dd556e6

Please sign in to comment.