grape 0.0.0.alpha.2 → 0.1.0
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/.rspec +1 -1
- data/Gemfile +5 -3
- data/Gemfile.lock +17 -13
- data/README.rdoc +10 -1
- data/VERSION +1 -1
- data/grape.gemspec +28 -10
- data/lib/grape.rb +19 -7
- data/lib/grape/api.rb +174 -0
- data/lib/grape/endpoint.rb +65 -0
- data/lib/grape/middleware/auth/basic.rb +30 -0
- data/lib/grape/middleware/base.rb +7 -0
- data/lib/grape/middleware/error.rb +3 -2
- data/lib/grape/middleware/formatter.rb +55 -8
- data/lib/grape/middleware/prefixer.rb +5 -4
- data/lib/grape/middleware/versioner.rb +4 -0
- data/lib/grape/middleware_stack.rb +35 -0
- data/spec/grape/api_spec.rb +283 -0
- data/spec/grape/endpoint_spec.rb +75 -0
- data/spec/grape/middleware/auth/basic_spec.rb +31 -0
- data/spec/grape/middleware/formatter_spec.rb +54 -2
- data/spec/grape/middleware/prefixer_spec.rb +5 -0
- data/spec/grape/middleware/versioner_spec.rb +11 -0
- data/spec/grape/middleware_stack_spec.rb +47 -0
- data/spec/spec_helper.rb +5 -0
- metadata +62 -26
data/.rspec
CHANGED
data/Gemfile
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
gem 'rack'
|
2
|
-
gem 'rack-mount'
|
2
|
+
gem 'rack-mount', '~> 0.6.13'
|
3
3
|
gem 'rack-jsonp'
|
4
4
|
|
5
|
-
gem '
|
5
|
+
gem 'json'
|
6
|
+
gem 'multi_json'
|
7
|
+
gem 'multi_xml'
|
6
8
|
|
7
9
|
group :development do
|
8
10
|
gem 'rake'
|
@@ -10,7 +12,7 @@ group :development do
|
|
10
12
|
end
|
11
13
|
|
12
14
|
group :test do
|
13
|
-
gem 'rspec', '>= 2.0.0.beta.19'
|
14
15
|
gem 'rack-test'
|
16
|
+
gem 'rspec', '>= 2.1.0'
|
15
17
|
gem 'cucumber', '>= 0.8.5'
|
16
18
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
GEM
|
2
2
|
specs:
|
3
|
-
activesupport (3.0.0)
|
4
3
|
builder (2.1.2)
|
5
4
|
cucumber (0.8.5)
|
6
5
|
builder (~> 2.1.2)
|
@@ -17,22 +16,25 @@ GEM
|
|
17
16
|
gemcutter (>= 0.1.0)
|
18
17
|
git (>= 1.2.5)
|
19
18
|
rubyforge (>= 2.0.0)
|
19
|
+
json (1.4.6)
|
20
20
|
json_pure (1.4.3)
|
21
|
+
multi_json (0.0.4)
|
22
|
+
multi_xml (0.0.1)
|
21
23
|
rack (1.2.1)
|
22
24
|
rack-jsonp (1.0.0)
|
23
|
-
rack-mount (0.6.
|
25
|
+
rack-mount (0.6.13)
|
24
26
|
rack (>= 1.0.0)
|
25
27
|
rack-test (0.5.4)
|
26
28
|
rack (>= 1.0)
|
27
29
|
rake (0.8.7)
|
28
|
-
rspec (2.
|
29
|
-
rspec-core (
|
30
|
-
rspec-expectations (
|
31
|
-
rspec-mocks (
|
32
|
-
rspec-core (2.
|
33
|
-
rspec-expectations (2.
|
34
|
-
diff-lcs (
|
35
|
-
rspec-mocks (2.
|
30
|
+
rspec (2.1.0)
|
31
|
+
rspec-core (~> 2.1.0)
|
32
|
+
rspec-expectations (~> 2.1.0)
|
33
|
+
rspec-mocks (~> 2.1.0)
|
34
|
+
rspec-core (2.1.0)
|
35
|
+
rspec-expectations (2.1.0)
|
36
|
+
diff-lcs (~> 1.1.2)
|
37
|
+
rspec-mocks (2.1.0)
|
36
38
|
rubyforge (2.0.4)
|
37
39
|
json_pure (>= 1.1.7)
|
38
40
|
term-ansicolor (1.0.5)
|
@@ -42,12 +44,14 @@ PLATFORMS
|
|
42
44
|
ruby
|
43
45
|
|
44
46
|
DEPENDENCIES
|
45
|
-
activesupport (~> 3.0.0)
|
46
47
|
cucumber (>= 0.8.5)
|
47
48
|
jeweler
|
49
|
+
json
|
50
|
+
multi_json
|
51
|
+
multi_xml
|
48
52
|
rack
|
49
53
|
rack-jsonp
|
50
|
-
rack-mount
|
54
|
+
rack-mount (~> 0.6.13)
|
51
55
|
rack-test
|
52
56
|
rake
|
53
|
-
rspec (>= 2.
|
57
|
+
rspec (>= 2.1.0)
|
data/README.rdoc
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
Grape is a REST-like API micro-framework for Ruby. It is built to complement existing web application frameworks such as Rails and Sinatra by providing a simple DSL to easily provide APIs. It has built-in support for common conventions such as multiple formats, subdomain/prefix restriction, and versioning.
|
6
6
|
|
7
|
-
class Twitter < Grape::Base
|
7
|
+
class Twitter::API < Grape::Base
|
8
8
|
subdomain 'api'
|
9
9
|
version '1'
|
10
10
|
formats :xml, :json
|
@@ -30,6 +30,15 @@ Grape is a REST-like API micro-framework for Ruby. It is built to complement exi
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
# Rack endpoint
|
34
|
+
Twitter::API.statuses.timelines.get(:public_timeline)
|
35
|
+
|
36
|
+
class Twitter::API::User < Grape::Resource::ActiveRecord
|
37
|
+
represents ::User
|
38
|
+
|
39
|
+
property :status, lambda{|u| u.latest_status}, Twitter::API::Status
|
40
|
+
end
|
41
|
+
|
33
42
|
== Note on Patches/Pull Requests
|
34
43
|
|
35
44
|
* Fork the project.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.1.0
|
data/grape.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{grape}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.1.0"
|
9
9
|
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new("
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Michael Bleigh"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-11-13}
|
13
13
|
s.description = %q{A Ruby framework for rapid API development with great conventions.}
|
14
14
|
s.email = %q{michael@intridea.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -30,18 +30,26 @@ Gem::Specification.new do |s|
|
|
30
30
|
"autotest/discover.rb",
|
31
31
|
"grape.gemspec",
|
32
32
|
"lib/grape.rb",
|
33
|
+
"lib/grape/api.rb",
|
34
|
+
"lib/grape/endpoint.rb",
|
35
|
+
"lib/grape/middleware/auth/basic.rb",
|
33
36
|
"lib/grape/middleware/auth/oauth2.rb",
|
34
37
|
"lib/grape/middleware/base.rb",
|
35
38
|
"lib/grape/middleware/error.rb",
|
36
39
|
"lib/grape/middleware/formatter.rb",
|
37
40
|
"lib/grape/middleware/prefixer.rb",
|
38
41
|
"lib/grape/middleware/versioner.rb",
|
42
|
+
"lib/grape/middleware_stack.rb",
|
43
|
+
"spec/grape/api_spec.rb",
|
44
|
+
"spec/grape/endpoint_spec.rb",
|
45
|
+
"spec/grape/middleware/auth/basic_spec.rb",
|
39
46
|
"spec/grape/middleware/auth/oauth2_spec.rb",
|
40
47
|
"spec/grape/middleware/base_spec.rb",
|
41
48
|
"spec/grape/middleware/error_spec.rb",
|
42
49
|
"spec/grape/middleware/formatter_spec.rb",
|
43
50
|
"spec/grape/middleware/prefixer_spec.rb",
|
44
51
|
"spec/grape/middleware/versioner_spec.rb",
|
52
|
+
"spec/grape/middleware_stack_spec.rb",
|
45
53
|
"spec/grape_spec.rb",
|
46
54
|
"spec/spec_helper.rb"
|
47
55
|
]
|
@@ -51,12 +59,16 @@ Gem::Specification.new do |s|
|
|
51
59
|
s.rubygems_version = %q{1.3.7}
|
52
60
|
s.summary = %q{A Ruby framework for rapid API development.}
|
53
61
|
s.test_files = [
|
54
|
-
"spec/grape/
|
62
|
+
"spec/grape/api_spec.rb",
|
63
|
+
"spec/grape/endpoint_spec.rb",
|
64
|
+
"spec/grape/middleware/auth/basic_spec.rb",
|
65
|
+
"spec/grape/middleware/auth/oauth2_spec.rb",
|
55
66
|
"spec/grape/middleware/base_spec.rb",
|
56
67
|
"spec/grape/middleware/error_spec.rb",
|
57
68
|
"spec/grape/middleware/formatter_spec.rb",
|
58
69
|
"spec/grape/middleware/prefixer_spec.rb",
|
59
70
|
"spec/grape/middleware/versioner_spec.rb",
|
71
|
+
"spec/grape/middleware_stack_spec.rb",
|
60
72
|
"spec/grape_spec.rb",
|
61
73
|
"spec/spec_helper.rb"
|
62
74
|
]
|
@@ -67,24 +79,30 @@ Gem::Specification.new do |s|
|
|
67
79
|
|
68
80
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
69
81
|
s.add_runtime_dependency(%q<rack>, [">= 0"])
|
70
|
-
s.add_runtime_dependency(%q<rack-mount>, ["
|
82
|
+
s.add_runtime_dependency(%q<rack-mount>, ["~> 0.6.13"])
|
71
83
|
s.add_runtime_dependency(%q<rack-jsonp>, [">= 0"])
|
72
|
-
s.add_runtime_dependency(%q<
|
84
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
85
|
+
s.add_runtime_dependency(%q<multi_json>, [">= 0"])
|
86
|
+
s.add_runtime_dependency(%q<multi_xml>, [">= 0"])
|
73
87
|
s.add_development_dependency(%q<rake>, [">= 0"])
|
74
88
|
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
75
89
|
else
|
76
90
|
s.add_dependency(%q<rack>, [">= 0"])
|
77
|
-
s.add_dependency(%q<rack-mount>, ["
|
91
|
+
s.add_dependency(%q<rack-mount>, ["~> 0.6.13"])
|
78
92
|
s.add_dependency(%q<rack-jsonp>, [">= 0"])
|
79
|
-
s.add_dependency(%q<
|
93
|
+
s.add_dependency(%q<json>, [">= 0"])
|
94
|
+
s.add_dependency(%q<multi_json>, [">= 0"])
|
95
|
+
s.add_dependency(%q<multi_xml>, [">= 0"])
|
80
96
|
s.add_dependency(%q<rake>, [">= 0"])
|
81
97
|
s.add_dependency(%q<jeweler>, [">= 0"])
|
82
98
|
end
|
83
99
|
else
|
84
100
|
s.add_dependency(%q<rack>, [">= 0"])
|
85
|
-
s.add_dependency(%q<rack-mount>, ["
|
101
|
+
s.add_dependency(%q<rack-mount>, ["~> 0.6.13"])
|
86
102
|
s.add_dependency(%q<rack-jsonp>, [">= 0"])
|
87
|
-
s.add_dependency(%q<
|
103
|
+
s.add_dependency(%q<json>, [">= 0"])
|
104
|
+
s.add_dependency(%q<multi_json>, [">= 0"])
|
105
|
+
s.add_dependency(%q<multi_xml>, [">= 0"])
|
88
106
|
s.add_dependency(%q<rake>, [">= 0"])
|
89
107
|
s.add_dependency(%q<jeweler>, [">= 0"])
|
90
108
|
end
|
data/lib/grape.rb
CHANGED
@@ -1,10 +1,22 @@
|
|
1
1
|
require 'rack'
|
2
2
|
require 'rack/builder'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
module Grape
|
5
|
+
autoload :API, 'grape/api'
|
6
|
+
autoload :Endpoint, 'grape/endpoint'
|
7
|
+
autoload :MiddlewareStack, 'grape/middleware_stack'
|
8
|
+
autoload :Client, 'grape/client'
|
9
|
+
|
10
|
+
module Middleware
|
11
|
+
autoload :Base, 'grape/middleware/base'
|
12
|
+
autoload :Prefixer, 'grape/middleware/prefixer'
|
13
|
+
autoload :Versioner, 'grape/middleware/versioner'
|
14
|
+
autoload :Formatter, 'grape/middleware/formatter'
|
15
|
+
autoload :Error, 'grape/middleware/error'
|
16
|
+
|
17
|
+
module Auth
|
18
|
+
autoload :OAuth2, 'grape/middleware/auth/oauth2'
|
19
|
+
autoload :Basic, 'grape/middleware/auth/basic'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/grape/api.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'rack/mount'
|
2
|
+
require 'rack/auth/basic'
|
3
|
+
|
4
|
+
module Grape
|
5
|
+
class API
|
6
|
+
module Helpers; end
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_reader :route_set
|
10
|
+
|
11
|
+
def reset!
|
12
|
+
@settings = [{}]
|
13
|
+
@route_set = Rack::Mount::RouteSet.new
|
14
|
+
@prototype = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
puts "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
|
19
|
+
route_set.freeze.call(env)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Settings are a stack, so when we
|
23
|
+
# want to access them they are merged
|
24
|
+
# in the order pushed.
|
25
|
+
def settings
|
26
|
+
@settings.inject({}){|f,h| f.merge!(h); f}
|
27
|
+
end
|
28
|
+
|
29
|
+
def settings_stack
|
30
|
+
@settings
|
31
|
+
end
|
32
|
+
|
33
|
+
def set(key, value)
|
34
|
+
@settings.last[key.to_sym] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
# Define a root prefix for your entire
|
38
|
+
# API. For instance, if you had an api
|
39
|
+
# that you wanted to be namespaced at
|
40
|
+
# `/api/` you would do this:
|
41
|
+
#
|
42
|
+
# prefix '/api'
|
43
|
+
def prefix(prefix = nil)
|
44
|
+
prefix ? set(:root_prefix, prefix) : settings[:root_prefix]
|
45
|
+
end
|
46
|
+
|
47
|
+
def version(*new_versions, &block)
|
48
|
+
new_versions.any? ? nest(block){ set(:version, new_versions) } : settings[:version]
|
49
|
+
end
|
50
|
+
|
51
|
+
def default_format(new_format = nil)
|
52
|
+
new_format ? set(:default_format, new_format.to_sym) : settings[:default_format]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Add helper methods that will be accessible from any
|
56
|
+
# endpoint within this namespace (and child namespaces).
|
57
|
+
#
|
58
|
+
# class ExampleAPI
|
59
|
+
# helpers do
|
60
|
+
# def current_user
|
61
|
+
# User.find_by_id(params[:token])
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
def helpers(&block)
|
66
|
+
if block_given?
|
67
|
+
m = settings_stack.last[:helpers] || Module.new
|
68
|
+
m.class_eval &block
|
69
|
+
set(:helpers, m)
|
70
|
+
else
|
71
|
+
m = Module.new
|
72
|
+
settings_stack.each do |s|
|
73
|
+
m.send :include, s[:helpers] if s[:helpers]
|
74
|
+
end
|
75
|
+
m
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def auth(type = nil, options = {}, &block)
|
80
|
+
if type
|
81
|
+
set(:auth, {:type => type.to_sym, :proc => block}.merge(options))
|
82
|
+
else
|
83
|
+
settings[:auth]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Add HTTP Basic authorization to the API.
|
88
|
+
#
|
89
|
+
# @param [Hash] options A hash of options.
|
90
|
+
# @option options [String] :realm "API Authorization" The HTTP Basic realm.
|
91
|
+
def http_basic(options = {}, &block)
|
92
|
+
options[:realm] ||= "API Authorization"
|
93
|
+
auth :http_basic, options, &block
|
94
|
+
end
|
95
|
+
|
96
|
+
def route_set
|
97
|
+
@route_set ||= Rack::Mount::RouteSet.new
|
98
|
+
end
|
99
|
+
|
100
|
+
def compile_path(path)
|
101
|
+
parts = []
|
102
|
+
parts << prefix if prefix
|
103
|
+
parts << ':version' if version
|
104
|
+
parts << namespace if namespace
|
105
|
+
parts << path
|
106
|
+
Rack::Mount::Utils.normalize_path(parts.join('/'))
|
107
|
+
end
|
108
|
+
|
109
|
+
def route(method, path_info, &block)
|
110
|
+
route_set.add_route(build_endpoint(&block),
|
111
|
+
:path_info => Rack::Mount::Strexp.compile(compile_path(path_info)),
|
112
|
+
:request_method => method
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
def build_endpoint(&block)
|
117
|
+
|
118
|
+
b = Rack::Builder.new
|
119
|
+
b.use Grape::Middleware::Error
|
120
|
+
b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
|
121
|
+
b.use Grape::Middleware::Prefixer, :prefix => prefix if prefix
|
122
|
+
b.use Grape::Middleware::Versioner, :versions => (version if version.is_a?(Array)) if version
|
123
|
+
b.use Grape::Middleware::Formatter, :default_format => default_format || :json
|
124
|
+
|
125
|
+
endpoint = Grape::Endpoint.new(&block)
|
126
|
+
endpoint.send :extend, helpers
|
127
|
+
b.run endpoint
|
128
|
+
|
129
|
+
b.to_app
|
130
|
+
end
|
131
|
+
|
132
|
+
def get(path_info = '', &block); route('GET', path_info, &block) end
|
133
|
+
def post(path_info = '', &block); route('POST', path_info, &block) end
|
134
|
+
def put(path_info = '', &block); route('PUT', path_info, &block) end
|
135
|
+
def head(path_info = '', &block); route('HEAD', path_info, &block) end
|
136
|
+
def delete(path_info = '', &block); route('DELETE', path_info, &block) end
|
137
|
+
|
138
|
+
def namespace(space = nil, &block)
|
139
|
+
if space || block_given?
|
140
|
+
nest(block) do
|
141
|
+
set(:namespace, space.to_s) if space
|
142
|
+
end
|
143
|
+
else
|
144
|
+
Rack::Mount::Utils.normalize_path(settings_stack.map{|s| s[:namespace]}.join('/'))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Execute first the provided block, then each of the
|
149
|
+
# block passed in. Allows for simple 'before' setups
|
150
|
+
# of settings stack pushes.
|
151
|
+
def nest(*blocks, &block)
|
152
|
+
blocks.reject!{|b| b.nil?}
|
153
|
+
if blocks.any?
|
154
|
+
settings_stack << {}
|
155
|
+
instance_eval &block
|
156
|
+
blocks.each{|b| instance_eval &b}
|
157
|
+
settings_stack.pop
|
158
|
+
else
|
159
|
+
instance_eval &block
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
alias_method :group, :namespace
|
164
|
+
alias_method :resource, :namespace
|
165
|
+
alias_method :resources, :namespace
|
166
|
+
|
167
|
+
def inherited(subclass)
|
168
|
+
subclass.reset!
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
reset!
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'grape'
|
3
|
+
|
4
|
+
module Grape
|
5
|
+
# An Endpoint is the proxy scope in which all routing
|
6
|
+
# blocks are executed. In other words, any methods
|
7
|
+
# on the instance level of this class may be called
|
8
|
+
# from inside a `get`, `post`, etc. block.
|
9
|
+
class Endpoint
|
10
|
+
def initialize(&block)
|
11
|
+
@block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :env, :request
|
15
|
+
|
16
|
+
def params
|
17
|
+
@params ||= request.params.merge(env['rack.routing_args'] || {}).inject({}) do |h,(k,v)|
|
18
|
+
h[k.to_s] = v
|
19
|
+
h[k.to_sym] = v
|
20
|
+
h
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def version; env['api.version'] end
|
25
|
+
|
26
|
+
def error!(message, status=403)
|
27
|
+
throw :error, :message => message, :status => status
|
28
|
+
end
|
29
|
+
|
30
|
+
# Set or retrieve the HTTP status code.
|
31
|
+
def status(status = nil)
|
32
|
+
if status
|
33
|
+
@status = status
|
34
|
+
else
|
35
|
+
return @status if @status
|
36
|
+
case request.request_method.to_s.upcase
|
37
|
+
when 'POST'
|
38
|
+
201
|
39
|
+
else
|
40
|
+
200
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Set an individual header or retrieve
|
46
|
+
# all headers that have been set.
|
47
|
+
def header(key = nil, val = nil)
|
48
|
+
if key
|
49
|
+
val ? @header[key.to_s] = val : @header.delete(key.to_s)
|
50
|
+
else
|
51
|
+
@header
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def call(env)
|
56
|
+
@env = env
|
57
|
+
@request = Rack::Request.new(@env)
|
58
|
+
@header = {}
|
59
|
+
|
60
|
+
response_text = instance_eval &@block
|
61
|
+
|
62
|
+
[status, header, [response_text]]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|