grape 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- data/CHANGELOG.markdown +17 -3
- data/Gemfile +1 -1
- data/README.markdown +269 -171
- data/grape.gemspec +1 -0
- data/lib/grape.rb +43 -22
- data/lib/grape/api.rb +7 -4
- data/lib/grape/endpoint.rb +48 -9
- data/lib/grape/entity.rb +1 -1
- data/lib/grape/error_formatter/base.rb +32 -0
- data/lib/grape/error_formatter/json.rb +17 -0
- data/lib/grape/error_formatter/txt.rb +18 -0
- data/lib/grape/error_formatter/xml.rb +17 -0
- data/lib/grape/exceptions/validation_error.rb +9 -5
- data/lib/grape/formatter/base.rb +33 -0
- data/lib/grape/formatter/json.rb +15 -0
- data/lib/grape/formatter/serializable_hash.rb +35 -0
- data/lib/grape/formatter/txt.rb +13 -0
- data/lib/grape/formatter/xml.rb +13 -0
- data/lib/grape/middleware/base.rb +18 -103
- data/lib/grape/middleware/error.rb +16 -32
- data/lib/grape/middleware/formatter.rb +8 -9
- data/lib/grape/middleware/versioner.rb +3 -2
- data/lib/grape/middleware/versioner/header.rb +1 -1
- data/lib/grape/parser/base.rb +31 -0
- data/lib/grape/parser/json.rb +13 -0
- data/lib/grape/parser/xml.rb +13 -0
- data/lib/grape/validations.rb +1 -1
- data/lib/grape/validations/coerce.rb +1 -1
- data/lib/grape/validations/presence.rb +1 -1
- data/lib/grape/validations/regexp.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +183 -9
- data/spec/grape/endpoint_spec.rb +27 -1
- data/spec/grape/entity_spec.rb +21 -21
- data/spec/grape/middleware/base_spec.rb +15 -15
- data/spec/grape/middleware/exception_spec.rb +38 -16
- data/spec/grape/middleware/formatter_spec.rb +6 -40
- data/spec/grape/validations/presence_spec.rb +20 -20
- data/spec/spec_helper.rb +1 -1
- metadata +132 -58
data/grape.gemspec
CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
|
|
23
23
|
s.add_runtime_dependency 'multi_xml'
|
24
24
|
s.add_runtime_dependency 'hashie', '~> 1.2'
|
25
25
|
s.add_runtime_dependency 'virtus'
|
26
|
+
s.add_runtime_dependency 'builder'
|
26
27
|
|
27
28
|
s.add_development_dependency 'rake'
|
28
29
|
s.add_development_dependency 'maruku'
|
data/lib/grape.rb
CHANGED
@@ -2,42 +2,63 @@ require 'rack'
|
|
2
2
|
require 'rack/builder'
|
3
3
|
|
4
4
|
module Grape
|
5
|
-
autoload :API,
|
6
|
-
autoload :Endpoint,
|
7
|
-
autoload :MiddlewareStack,
|
8
|
-
autoload :Client,
|
9
|
-
autoload :Route,
|
10
|
-
autoload :Entity,
|
11
|
-
autoload :Cookies,
|
12
|
-
autoload :Validations,
|
5
|
+
autoload :API, 'grape/api'
|
6
|
+
autoload :Endpoint, 'grape/endpoint'
|
7
|
+
autoload :MiddlewareStack, 'grape/middleware_stack'
|
8
|
+
autoload :Client, 'grape/client'
|
9
|
+
autoload :Route, 'grape/route'
|
10
|
+
autoload :Entity, 'grape/entity'
|
11
|
+
autoload :Cookies, 'grape/cookies'
|
12
|
+
autoload :Validations, 'grape/validations'
|
13
13
|
|
14
14
|
module Exceptions
|
15
|
-
autoload :Base,
|
15
|
+
autoload :Base, 'grape/exceptions/base'
|
16
|
+
autoload :ValidationError, 'grape/exceptions/validation_error'
|
17
|
+
end
|
18
|
+
|
19
|
+
module ErrorFormatter
|
20
|
+
autoload :Base, 'grape/error_formatter/base'
|
21
|
+
autoload :Json, 'grape/error_formatter/json'
|
22
|
+
autoload :Txt, 'grape/error_formatter/txt'
|
23
|
+
autoload :Xml, 'grape/error_formatter/xml'
|
24
|
+
end
|
25
|
+
|
26
|
+
module Formatter
|
27
|
+
autoload :Base, 'grape/formatter/base'
|
28
|
+
autoload :Json, 'grape/formatter/json'
|
29
|
+
autoload :SerializableHash, 'grape/formatter/serializable_hash'
|
30
|
+
autoload :Txt, 'grape/formatter/txt'
|
31
|
+
autoload :Xml, 'grape/formatter/xml'
|
32
|
+
end
|
33
|
+
|
34
|
+
module Parser
|
35
|
+
autoload :Base, 'grape/parser/base'
|
36
|
+
autoload :Json, 'grape/parser/json'
|
37
|
+
autoload :Xml, 'grape/parser/xml'
|
16
38
|
end
|
17
|
-
autoload :ValidationError, 'grape/exceptions/validation_error'
|
18
39
|
|
19
40
|
module Middleware
|
20
|
-
autoload :Base,
|
21
|
-
autoload :Prefixer,
|
22
|
-
autoload :Versioner,
|
23
|
-
autoload :Formatter,
|
24
|
-
autoload :Error,
|
41
|
+
autoload :Base, 'grape/middleware/base'
|
42
|
+
autoload :Prefixer, 'grape/middleware/prefixer'
|
43
|
+
autoload :Versioner, 'grape/middleware/versioner'
|
44
|
+
autoload :Formatter, 'grape/middleware/formatter'
|
45
|
+
autoload :Error, 'grape/middleware/error'
|
25
46
|
|
26
47
|
module Auth
|
27
|
-
autoload :OAuth2,
|
28
|
-
autoload :Basic,
|
29
|
-
autoload :Digest,
|
48
|
+
autoload :OAuth2, 'grape/middleware/auth/oauth2'
|
49
|
+
autoload :Basic, 'grape/middleware/auth/basic'
|
50
|
+
autoload :Digest, 'grape/middleware/auth/digest'
|
30
51
|
end
|
31
52
|
|
32
53
|
module Versioner
|
33
|
-
autoload :Path,
|
34
|
-
autoload :Header,
|
35
|
-
autoload :Param,
|
54
|
+
autoload :Path, 'grape/middleware/versioner/path'
|
55
|
+
autoload :Header, 'grape/middleware/versioner/header'
|
56
|
+
autoload :Param, 'grape/middleware/versioner/param'
|
36
57
|
end
|
37
58
|
end
|
38
59
|
|
39
60
|
module Util
|
40
|
-
autoload :HashStack,
|
61
|
+
autoload :HashStack, 'grape/util/hash_stack'
|
41
62
|
end
|
42
63
|
end
|
43
64
|
|
data/lib/grape/api.rb
CHANGED
@@ -130,10 +130,13 @@ module Grape
|
|
130
130
|
new_format ? set(:format, new_format.to_sym) : settings[:format]
|
131
131
|
end
|
132
132
|
|
133
|
-
# Specify
|
134
|
-
|
135
|
-
|
136
|
-
|
133
|
+
# Specify a custom formatter for a content-type.
|
134
|
+
def formatter(content_type, new_formatter)
|
135
|
+
settings.imbue(:formatters, content_type.to_sym => new_formatter)
|
136
|
+
end
|
137
|
+
|
138
|
+
def error_formatter(format, new_formatter)
|
139
|
+
settings.imbue(:error_formatters, format.to_sym => new_formatter)
|
137
140
|
end
|
138
141
|
|
139
142
|
# Specify additional content-types, e.g.:
|
data/lib/grape/endpoint.rb
CHANGED
@@ -11,9 +11,37 @@ module Grape
|
|
11
11
|
attr_accessor :block, :options, :settings
|
12
12
|
attr_reader :env, :request
|
13
13
|
|
14
|
+
class << self
|
15
|
+
# @api private
|
16
|
+
#
|
17
|
+
# Create an UnboundMethod that is appropriate for executing an endpoint
|
18
|
+
# route.
|
19
|
+
#
|
20
|
+
# The unbound method allows explicit calls to +return+ without raising a
|
21
|
+
# +LocalJumpError+. The method will be removed, but a +Proc+ reference to
|
22
|
+
# it will be returned. The returned +Proc+ expects a single argument: the
|
23
|
+
# instance of +Endpoint+ to bind to the method during the call.
|
24
|
+
#
|
25
|
+
# @param [String, Symbol] method_name
|
26
|
+
# @return [Proc]
|
27
|
+
# @raise [NameError] an instance method with the same name already exists
|
28
|
+
def generate_api_method(method_name, &block)
|
29
|
+
if instance_methods.include?(method_name.to_sym) || instance_methods.include?(method_name.to_s)
|
30
|
+
raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name")
|
31
|
+
end
|
32
|
+
define_method(method_name, &block)
|
33
|
+
method = instance_method(method_name)
|
34
|
+
remove_method(method_name)
|
35
|
+
proc { |endpoint_instance| method.bind(endpoint_instance).call }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
14
39
|
def initialize(settings, options = {}, &block)
|
15
40
|
@settings = settings
|
16
|
-
|
41
|
+
if block_given?
|
42
|
+
method_name = "#{options[:method]} #{settings.gather(:namespace).join( "/")} #{Array(options[:path]).join("/")}"
|
43
|
+
@block = self.class.generate_api_method(method_name, &block)
|
44
|
+
end
|
17
45
|
@options = options
|
18
46
|
|
19
47
|
raise ArgumentError, "Must specify :path option." unless options.key?(:path)
|
@@ -82,10 +110,20 @@ module Grape
|
|
82
110
|
def prepare_path(path)
|
83
111
|
parts = []
|
84
112
|
parts << settings[:root_prefix] if settings[:root_prefix]
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
113
|
+
|
114
|
+
uses_path_versioning = settings[:version] && settings[:version_options][:using] == :path
|
115
|
+
namespace_is_empty = namespace && (namespace.to_s =~ /^\s*$/ || namespace.to_s == '/')
|
116
|
+
path_is_empty = path && (path.to_s =~ /^\s*$/ || path.to_s == '/')
|
117
|
+
|
118
|
+
parts << ':version' if uses_path_versioning
|
119
|
+
if !uses_path_versioning || (!namespace_is_empty || !path_is_empty)
|
120
|
+
parts << namespace.to_s if namespace
|
121
|
+
parts << path.to_s if path && '/' != path
|
122
|
+
format_suffix = '(.:format)'
|
123
|
+
else
|
124
|
+
format_suffix = '(/.:format)'
|
125
|
+
end
|
126
|
+
Rack::Mount::Utils.normalize_path(parts.join('/') + format_suffix)
|
89
127
|
end
|
90
128
|
|
91
129
|
def namespace
|
@@ -135,7 +173,7 @@ module Grape
|
|
135
173
|
unless settings[:declared_params]
|
136
174
|
raise ArgumentError, "Tried to filter for declared parameters but none exist."
|
137
175
|
end
|
138
|
-
|
176
|
+
|
139
177
|
settings[:declared_params].inject({}){|h,k|
|
140
178
|
output_key = options[:stringify] ? k.to_s : k.to_sym
|
141
179
|
if params.key?(output_key) || options[:include_missing]
|
@@ -324,7 +362,7 @@ module Grape
|
|
324
362
|
|
325
363
|
run_filters after_validations
|
326
364
|
|
327
|
-
response_text =
|
365
|
+
response_text = @block.call(self)
|
328
366
|
run_filters afters
|
329
367
|
cookies.write(header)
|
330
368
|
|
@@ -339,7 +377,7 @@ module Grape
|
|
339
377
|
:default_status => settings[:default_error_status] || 403,
|
340
378
|
:rescue_all => settings[:rescue_all],
|
341
379
|
:rescued_errors => aggregate_setting(:rescued_errors),
|
342
|
-
:
|
380
|
+
:error_formatters => settings[:error_formatters],
|
343
381
|
:rescue_options => settings[:rescue_options],
|
344
382
|
:rescue_handlers => merged_setting(:rescue_handlers)
|
345
383
|
|
@@ -357,7 +395,8 @@ module Grape
|
|
357
395
|
b.use Grape::Middleware::Formatter,
|
358
396
|
:format => settings[:format],
|
359
397
|
:default_format => settings[:default_format] || :txt,
|
360
|
-
:content_types => settings[:content_types]
|
398
|
+
:content_types => settings[:content_types],
|
399
|
+
:formatters => settings[:formatters]
|
361
400
|
|
362
401
|
aggregate_setting(:middleware).each do |m|
|
363
402
|
m = m.dup
|
data/lib/grape/entity.rb
CHANGED
@@ -306,7 +306,7 @@ module Grape
|
|
306
306
|
# hash for the given data model and is used as the basis for serialization to
|
307
307
|
# JSON and other formats.
|
308
308
|
#
|
309
|
-
# @param
|
309
|
+
# @param runtime_options [Hash] Any options you pass in here will be known to the entity
|
310
310
|
# representation, this is where you can trigger things from conditional options
|
311
311
|
# etc.
|
312
312
|
def serializable_hash(runtime_options = {})
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Grape
|
2
|
+
module ErrorFormatter
|
3
|
+
module Base
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
FORMATTERS = {
|
8
|
+
:json => Grape::ErrorFormatter::Json,
|
9
|
+
:txt => Grape::ErrorFormatter::Txt,
|
10
|
+
:xml => Grape::ErrorFormatter::Xml
|
11
|
+
}
|
12
|
+
|
13
|
+
def formatters(options)
|
14
|
+
FORMATTERS.merge(options[:error_formatters] || {})
|
15
|
+
end
|
16
|
+
|
17
|
+
def formatter_for(api_format, options = {})
|
18
|
+
spec = formatters(options)[api_format]
|
19
|
+
case spec
|
20
|
+
when nil
|
21
|
+
lambda { |message, backtrace, options| message }
|
22
|
+
when Symbol
|
23
|
+
method(spec)
|
24
|
+
else
|
25
|
+
spec
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Grape
|
2
|
+
module ErrorFormatter
|
3
|
+
module Json
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def call(message, backtrace, options = {}, env = nil)
|
7
|
+
result = message.is_a?(Hash) ? message : { :error => message }
|
8
|
+
if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
|
9
|
+
result = result.merge({ :backtrace => backtrace })
|
10
|
+
end
|
11
|
+
MultiJson.dump(result)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Grape
|
2
|
+
module ErrorFormatter
|
3
|
+
module Txt
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def call(message, backtrace, options = {}, env = nil)
|
7
|
+
result = message.is_a?(Hash) ? MultiJson.dump(message) : message
|
8
|
+
if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
|
9
|
+
result += "\r\n "
|
10
|
+
result += backtrace.join("\r\n ")
|
11
|
+
end
|
12
|
+
result
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Grape
|
2
|
+
module ErrorFormatter
|
3
|
+
module Xml
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def call(message, backtrace, options = {}, env = nil)
|
7
|
+
result = message.is_a?(Hash) ? message : { :message => message }
|
8
|
+
if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
|
9
|
+
result = result.merge({ :backtrace => backtrace })
|
10
|
+
end
|
11
|
+
result.respond_to?(:to_xml) ? result.to_xml(:root => :error) : result.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,10 +1,14 @@
|
|
1
1
|
require 'grape/exceptions/base'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module Grape
|
4
|
+
module Exceptions
|
5
|
+
class ValidationError < Grape::Exceptions::Base
|
6
|
+
attr_accessor :param
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
8
|
+
def initialize(args = {})
|
9
|
+
@param = args[:param].to_s if args.has_key? :param
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
9
13
|
end
|
10
14
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Grape
|
2
|
+
module Formatter
|
3
|
+
module Base
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
FORMATTERS = {
|
8
|
+
:json => Grape::Formatter::Json,
|
9
|
+
:serializable_hash => Grape::Formatter::SerializableHash,
|
10
|
+
:txt => Grape::Formatter::Txt,
|
11
|
+
:xml => Grape::Formatter::Xml
|
12
|
+
}
|
13
|
+
|
14
|
+
def formatters(options)
|
15
|
+
FORMATTERS.merge(options[:formatters] || {})
|
16
|
+
end
|
17
|
+
|
18
|
+
def formatter_for(api_format, options = {})
|
19
|
+
spec = formatters(options)[api_format]
|
20
|
+
case spec
|
21
|
+
when nil
|
22
|
+
lambda { |obj, env| obj }
|
23
|
+
when Symbol
|
24
|
+
method(spec)
|
25
|
+
else
|
26
|
+
spec
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Grape
|
2
|
+
module Formatter
|
3
|
+
module SerializableHash
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def call(object, env)
|
7
|
+
return object if object.is_a?(String)
|
8
|
+
return MultiJson.dump(serialize(object)) if serializable?(object)
|
9
|
+
return object.to_json if object.respond_to?(:to_json)
|
10
|
+
MultiJson.dump(object)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def serializable?(object)
|
16
|
+
object.respond_to?(:serializable_hash) ||
|
17
|
+
object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false) ||
|
18
|
+
object.kind_of?(Hash)
|
19
|
+
end
|
20
|
+
|
21
|
+
def serialize(object)
|
22
|
+
if object.respond_to? :serializable_hash
|
23
|
+
object.serializable_hash
|
24
|
+
elsif object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false)
|
25
|
+
object.map {|o| o.serializable_hash }
|
26
|
+
elsif object.kind_of?(Hash)
|
27
|
+
object.inject({}) { |h,(k,v)| h[k] = serialize(v); h }
|
28
|
+
else
|
29
|
+
object
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|