Skip to content

Commit 11576c8

Browse files
committed
Fix #309: Added XML support for entities. An XML API will return an error instead of returning a string representation of a response. Failing formatters will get reported with a 500.
1 parent 30bb773 commit 11576c8

16 files changed

+132
-35
lines changed

CHANGELOG.markdown

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
Next Release
22
============
3+
* [#309](https://github.com/intridea/grape/pull/309): An XML format API will return an error instead of returning a string representation of the response if the latter cannot be converted to XML - [@dblock](http://github.com/dblock).
4+
* [#309](https://github.com/intridea/grape/pull/309): Added XML support to entities - [@johnnyiller](https://github.com/johnnyiller), [@dblock](http://github.com/dblock).
5+
* A formatter that raises an exception will cause the API to return a 500 error - [@dblock](http://github.com/dblock).
36
* Your contribution here.
47

58
0.2.6 (01/11/2013)

lib/grape.rb

+15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
1+
require 'logger'
12
require 'rack'
3+
require 'rack/mount'
24
require 'rack/builder'
5+
require 'rack/accept'
6+
require 'rack/auth/basic'
7+
require 'rack/auth/digest/md5'
8+
require 'hashie'
9+
require 'active_support/all'
10+
require 'grape/util/deep_merge'
11+
require 'grape/util/content_types'
12+
require 'multi_json'
13+
require 'multi_xml'
14+
require 'virtus'
15+
require 'i18n'
16+
17+
I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__)
318

419
module Grape
520
autoload :API, 'grape/api'

lib/grape/api.rb

-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
require 'rack/mount'
2-
require 'rack/auth/basic'
3-
require 'rack/auth/digest/md5'
4-
require 'logger'
5-
require 'grape/util/deep_merge'
6-
require 'grape/util/content_types'
7-
81
module Grape
92
# The API class is the primary entry point for
103
# creating Grape APIs.Users should subclass this

lib/grape/endpoint.rb

-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
require 'rack'
2-
require 'grape'
3-
require 'hashie'
4-
51
module Grape
62
# An Endpoint is the proxy scope in which all routing
73
# blocks are executed. In other words, any methods

lib/grape/entity.rb

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
require 'hashie'
2-
31
module Grape
42
# An Entity is a lightweight structure that allows you to easily
53
# represent data from your application in a consistent and abstracted
@@ -335,6 +333,11 @@ def to_json(options = {})
335333
MultiJson.dump(serializable_hash(options))
336334
end
337335

336+
def to_xml(options = {})
337+
options = options.to_h if options && options.respond_to?(:to_h)
338+
serializable_hash(options).to_xml(options)
339+
end
340+
338341
protected
339342

340343
def key_for(attribute)

lib/grape/formatter/xml.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ module Xml
44
class << self
55

66
def call(object, env)
7-
object.respond_to?(:to_xml) ? object.to_xml : object.to_s
7+
return object.to_xml if object.respond_to?(:to_xml)
8+
raise "cannot convert #{object.class} to xml"
89
end
910

1011
end

lib/grape/middleware/base.rb

-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
require 'active_support/core_ext/hash/indifferent_access'
2-
require 'grape/util/content_types'
3-
require 'multi_json'
4-
require 'multi_xml'
5-
61
module Grape
72
module Middleware
83
class Base

lib/grape/middleware/error.rb

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
require 'grape/middleware/base'
2-
require 'multi_json'
32

43
module Grape
54
module Middleware

lib/grape/middleware/formatter.rb

+6-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ def before
2424
def after
2525
status, headers, bodies = *@app_response
2626
formatter = Grape::Formatter::Base.formatter_for env['api.format'], options
27-
bodymap = bodies.collect do |body|
28-
formatter.call body, env
27+
begin
28+
bodymap = bodies.collect do |body|
29+
formatter.call body, env
30+
end
31+
rescue Exception => e
32+
throw :error, :status => 500, :message => e.message
2933
end
3034
headers['Content-Type'] = content_type_for(env['api.format']) unless headers['Content-Type']
3135
Rack::Response.new(bodymap, status, headers).to_a

lib/grape/middleware/versioner/header.rb

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
require 'grape/middleware/base'
2-
require 'rack/accept'
32

43
module Grape
54
module Middleware

lib/grape/util/content_types.rb

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
require 'active_support/ordered_hash'
2-
31
module Grape
42
module ContentTypes
53
# Content types are listed in order of preference.

lib/grape/validations.rb

-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
require 'virtus'
2-
require 'i18n'
3-
4-
I18n.load_path << File.expand_path('../locale/en.yml', __FILE__)
51
module Grape
62

73
module Validations

spec/grape/api_spec.rb

+46
Original file line numberDiff line numberDiff line change
@@ -1690,6 +1690,52 @@ def serializable_hash
16901690
last_response.body.should == '[{"abc":"def"},{"abc":"def"}]'
16911691
end
16921692
end
1693+
context ":xml" do
1694+
before(:each) do
1695+
subject.format :xml
1696+
end
1697+
it 'string' do
1698+
subject.get "/example" do
1699+
"example"
1700+
end
1701+
get '/example'
1702+
last_response.status.should == 500
1703+
last_response.body.should == <<-XML
1704+
<?xml version="1.0" encoding="UTF-8"?>
1705+
<error>
1706+
<message>cannot convert String to xml</message>
1707+
</error>
1708+
XML
1709+
end
1710+
it 'hash' do
1711+
subject.get "/example" do
1712+
{ :example1 => "example1", :example2 => "example2" }
1713+
end
1714+
get '/example'
1715+
last_response.status.should == 200
1716+
last_response.body.should == <<-XML
1717+
<?xml version="1.0" encoding="UTF-8"?>
1718+
<hash>
1719+
<example1>example1</example1>
1720+
<example2>example2</example2>
1721+
</hash>
1722+
XML
1723+
end
1724+
it 'array' do
1725+
subject.get "/example" do
1726+
[ "example1", "example2" ]
1727+
end
1728+
get '/example'
1729+
last_response.status.should == 200
1730+
last_response.body.should == <<-XML
1731+
<?xml version="1.0" encoding="UTF-8"?>
1732+
<strings type="array">
1733+
<string>example1</string>
1734+
<string>example2</string>
1735+
</strings>
1736+
XML
1737+
end
1738+
end
16931739
end
16941740

16951741
context "catch-all" do

spec/grape/endpoint_spec.rb

+51
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,57 @@ def initialize(id)
552552
end
553553

554554
end
555+
556+
it 'presents with xml' do
557+
entity = Class.new(Grape::Entity)
558+
entity.root "examples", "example"
559+
entity.expose :name
560+
561+
subject.format :xml
562+
563+
subject.get '/example' do
564+
c = Class.new do
565+
attr_reader :name
566+
def initialize(args)
567+
@name = args[:name] || "no name set"
568+
end
569+
end
570+
present c.new({:name => "johnnyiller"}), :with => entity
571+
end
572+
get '/example'
573+
last_response.status.should == 200
574+
last_response.headers['Content-type'].should == "application/xml"
575+
last_response.body.should == <<-XML
576+
<?xml version="1.0" encoding="UTF-8"?>
577+
<hash>
578+
<example>
579+
<name>johnnyiller</name>
580+
</example>
581+
</hash>
582+
XML
583+
end
584+
585+
it 'presents with json' do
586+
entity = Class.new(Grape::Entity)
587+
entity.root "examples", "example"
588+
entity.expose :name
589+
590+
subject.format :json
591+
592+
subject.get '/example' do
593+
c = Class.new do
594+
attr_reader :name
595+
def initialize(args)
596+
@name = args[:name] || "no name set"
597+
end
598+
end
599+
present c.new({:name => "johnnyiller"}), :with => entity
600+
end
601+
get '/example'
602+
last_response.status.should == 200
603+
last_response.headers['Content-type'].should == "application/json"
604+
last_response.body.should == '{"example":{"name":"johnnyiller"}}'
605+
end
555606
end
556607

557608
context 'filters' do

spec/grape/middleware/formatter_spec.rb

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
subject{ Grape::Middleware::Formatter.new(app) }
55
before{ subject.stub!(:dup).and_return(subject) }
66

7-
let(:app){ lambda{|env| [200, {}, [@body]]} }
7+
let(:app){ lambda{|env| [200, {}, [@body || { "foo" => "bar" }]]} }
88

99
context 'serialization' do
1010
it 'looks at the bodies for possibly serializable data' do
@@ -37,6 +37,7 @@ def to_xml
3737
end
3838

3939
context 'detection' do
40+
4041
it 'uses the extension if one is provided' do
4142
subject.call({'PATH_INFO' => '/info.xml'})
4243
subject.env['api.format'].should == :xml
@@ -45,9 +46,9 @@ def to_xml
4546
end
4647

4748
it 'uses the format parameter if one is provided' do
48-
subject.call({'PATH_INFO' => '/somewhere','QUERY_STRING' => 'format=json'})
49+
subject.call({'PATH_INFO' => '/info','QUERY_STRING' => 'format=json'})
4950
subject.env['api.format'].should == :json
50-
subject.call({'PATH_INFO' => '/somewhere','QUERY_STRING' => 'format=xml'})
51+
subject.call({'PATH_INFO' => '/info','QUERY_STRING' => 'format=xml'})
5152
subject.env['api.format'].should == :xml
5253
end
5354

spec/spec_helper.rb

-3
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@
1212

1313
require 'rack/test'
1414
require 'pry'
15-
1615
require 'base64'
1716

18-
require 'hashie/hash'
19-
2017
Dir["#{File.dirname(__FILE__)}/support/*.rb"].each do |file|
2118
require file
2219
end

0 commit comments

Comments
 (0)