grape 0.0.0.alpha.1
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/.document +5 -0
- data/.gitignore +23 -0
- data/.rspec +3 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +53 -0
- data/LICENSE +20 -0
- data/README.rdoc +43 -0
- data/Rakefile +44 -0
- data/VERSION +1 -0
- data/autotest/discover.rb +1 -0
- data/grape.gemspec +92 -0
- data/lib/grape.rb +10 -0
- data/lib/grape/middleware/auth/oauth2.rb +55 -0
- data/lib/grape/middleware/base.rb +36 -0
- data/lib/grape/middleware/error.rb +20 -0
- data/lib/grape/middleware/formatter.rb +59 -0
- data/lib/grape/middleware/prefixer.rb +20 -0
- data/lib/grape/middleware/versioner.rb +23 -0
- data/spec/grape/middleware/auth/oauth2_spec.rb +88 -0
- data/spec/grape/middleware/base_spec.rb +63 -0
- data/spec/grape/middleware/error_spec.rb +49 -0
- data/spec/grape/middleware/formatter_spec.rb +35 -0
- data/spec/grape/middleware/prefixer_spec.rb +25 -0
- data/spec/grape/middleware/versioner_spec.rb +29 -0
- data/spec/grape_spec.rb +1 -0
- data/spec/spec_helper.rb +15 -0
- metadata +191 -0
data/.document
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
gem 'rack'
|
2
|
+
gem 'rack-mount'
|
3
|
+
gem 'rack-jsonp'
|
4
|
+
|
5
|
+
gem 'activesupport', '>= 3.0.0.rc2'
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem 'rake'
|
9
|
+
gem 'jeweler'
|
10
|
+
end
|
11
|
+
|
12
|
+
group :test do
|
13
|
+
gem 'rspec', '>= 2.0.0.beta.19'
|
14
|
+
gem 'rack-test'
|
15
|
+
gem 'cucumber', '>= 0.8.5'
|
16
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
GEM
|
2
|
+
specs:
|
3
|
+
activesupport (3.0.0.rc2)
|
4
|
+
builder (2.1.2)
|
5
|
+
cucumber (0.8.5)
|
6
|
+
builder (~> 2.1.2)
|
7
|
+
diff-lcs (~> 1.1.2)
|
8
|
+
gherkin (~> 2.1.4)
|
9
|
+
json_pure (~> 1.4.3)
|
10
|
+
term-ansicolor (~> 1.0.4)
|
11
|
+
diff-lcs (1.1.2)
|
12
|
+
gemcutter (0.6.1)
|
13
|
+
gherkin (2.1.5)
|
14
|
+
trollop (~> 1.16.2)
|
15
|
+
git (1.2.5)
|
16
|
+
jeweler (1.4.0)
|
17
|
+
gemcutter (>= 0.1.0)
|
18
|
+
git (>= 1.2.5)
|
19
|
+
rubyforge (>= 2.0.0)
|
20
|
+
json_pure (1.4.3)
|
21
|
+
rack (1.2.1)
|
22
|
+
rack-jsonp (1.0.0)
|
23
|
+
rack-mount (0.6.9)
|
24
|
+
rack (>= 1.0.0)
|
25
|
+
rack-test (0.5.4)
|
26
|
+
rack (>= 1.0)
|
27
|
+
rake (0.8.7)
|
28
|
+
rspec (2.0.0.beta.19)
|
29
|
+
rspec-core (= 2.0.0.beta.19)
|
30
|
+
rspec-expectations (= 2.0.0.beta.19)
|
31
|
+
rspec-mocks (= 2.0.0.beta.19)
|
32
|
+
rspec-core (2.0.0.beta.19)
|
33
|
+
rspec-expectations (2.0.0.beta.19)
|
34
|
+
diff-lcs (>= 1.1.2)
|
35
|
+
rspec-mocks (2.0.0.beta.19)
|
36
|
+
rubyforge (2.0.4)
|
37
|
+
json_pure (>= 1.1.7)
|
38
|
+
term-ansicolor (1.0.5)
|
39
|
+
trollop (1.16.2)
|
40
|
+
|
41
|
+
PLATFORMS
|
42
|
+
ruby
|
43
|
+
|
44
|
+
DEPENDENCIES
|
45
|
+
activesupport (>= 3.0.0.rc2)
|
46
|
+
cucumber (>= 0.8.5)
|
47
|
+
jeweler
|
48
|
+
rack
|
49
|
+
rack-jsonp
|
50
|
+
rack-mount
|
51
|
+
rack-test
|
52
|
+
rake
|
53
|
+
rspec (>= 2.0.0.beta.19)
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Michael Bleigh
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
= UNDER CONSTRUCTION. DO NOT USE
|
2
|
+
|
3
|
+
= Grape
|
4
|
+
|
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
|
+
|
7
|
+
class Twitter < Grape::Base
|
8
|
+
subdomain 'api'
|
9
|
+
version '1'
|
10
|
+
formats :xml, :json
|
11
|
+
authorization :oauth, User
|
12
|
+
|
13
|
+
resource :statuses do
|
14
|
+
group :timelines do
|
15
|
+
formats :rss, :atom
|
16
|
+
|
17
|
+
get :public_timeline do
|
18
|
+
optional :trim_user, Boolean
|
19
|
+
optional :include_entities, Boolean
|
20
|
+
|
21
|
+
Tweet.limit(20)
|
22
|
+
end
|
23
|
+
|
24
|
+
get :home_timeline do
|
25
|
+
authorized
|
26
|
+
|
27
|
+
user.home_timeline
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
== Note on Patches/Pull Requests
|
34
|
+
|
35
|
+
* Fork the project.
|
36
|
+
* Make your feature addition or bug fix.
|
37
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
38
|
+
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
39
|
+
* Send me a pull request. Bonus points for topic branches.
|
40
|
+
|
41
|
+
== Copyright
|
42
|
+
|
43
|
+
Copyright (c) 2010 Michael Bleigh and Intridea, Inc. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
|
4
|
+
Bundler.setup :default, :test, :development
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |gem|
|
9
|
+
gem.name = "grape"
|
10
|
+
gem.summary = %Q{A Ruby framework for rapid API development.}
|
11
|
+
gem.description = %Q{A Ruby framework for rapid API development with great conventions.}
|
12
|
+
gem.email = "michael@intridea.com"
|
13
|
+
gem.homepage = "http://github.com/intridea/grape"
|
14
|
+
gem.authors = ["Michael Bleigh"]
|
15
|
+
gem.add_bundler_dependencies
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'rspec/core/rake_task'
|
24
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
25
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
26
|
+
end
|
27
|
+
|
28
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
task :default => :spec
|
35
|
+
|
36
|
+
require 'rake/rdoctask'
|
37
|
+
Rake::RDocTask.new do |rdoc|
|
38
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
39
|
+
|
40
|
+
rdoc.rdoc_dir = 'rdoc'
|
41
|
+
rdoc.title = "grape #{version}"
|
42
|
+
rdoc.rdoc_files.include('README*')
|
43
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
44
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0.alpha.1
|
@@ -0,0 +1 @@
|
|
1
|
+
Autotest.add_discovery { "rspec2" }
|
data/grape.gemspec
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{grape}
|
8
|
+
s.version = "0.0.0.alpha.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Michael Bleigh"]
|
12
|
+
s.date = %q{2010-08-27}
|
13
|
+
s.description = %q{A Ruby framework for rapid API development with great conventions.}
|
14
|
+
s.email = %q{michael@intridea.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
".rspec",
|
23
|
+
".rvmrc",
|
24
|
+
"Gemfile",
|
25
|
+
"Gemfile.lock",
|
26
|
+
"LICENSE",
|
27
|
+
"README.rdoc",
|
28
|
+
"Rakefile",
|
29
|
+
"VERSION",
|
30
|
+
"autotest/discover.rb",
|
31
|
+
"grape.gemspec",
|
32
|
+
"lib/grape.rb",
|
33
|
+
"lib/grape/middleware/auth/oauth2.rb",
|
34
|
+
"lib/grape/middleware/base.rb",
|
35
|
+
"lib/grape/middleware/error.rb",
|
36
|
+
"lib/grape/middleware/formatter.rb",
|
37
|
+
"lib/grape/middleware/prefixer.rb",
|
38
|
+
"lib/grape/middleware/versioner.rb",
|
39
|
+
"spec/grape/middleware/auth/oauth2_spec.rb",
|
40
|
+
"spec/grape/middleware/base_spec.rb",
|
41
|
+
"spec/grape/middleware/error_spec.rb",
|
42
|
+
"spec/grape/middleware/formatter_spec.rb",
|
43
|
+
"spec/grape/middleware/prefixer_spec.rb",
|
44
|
+
"spec/grape/middleware/versioner_spec.rb",
|
45
|
+
"spec/grape_spec.rb",
|
46
|
+
"spec/spec_helper.rb"
|
47
|
+
]
|
48
|
+
s.homepage = %q{http://github.com/intridea/grape}
|
49
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
50
|
+
s.require_paths = ["lib"]
|
51
|
+
s.rubygems_version = %q{1.3.7}
|
52
|
+
s.summary = %q{A Ruby framework for rapid API development.}
|
53
|
+
s.test_files = [
|
54
|
+
"spec/grape/middleware/auth/oauth2_spec.rb",
|
55
|
+
"spec/grape/middleware/base_spec.rb",
|
56
|
+
"spec/grape/middleware/error_spec.rb",
|
57
|
+
"spec/grape/middleware/formatter_spec.rb",
|
58
|
+
"spec/grape/middleware/prefixer_spec.rb",
|
59
|
+
"spec/grape/middleware/versioner_spec.rb",
|
60
|
+
"spec/grape_spec.rb",
|
61
|
+
"spec/spec_helper.rb"
|
62
|
+
]
|
63
|
+
|
64
|
+
if s.respond_to? :specification_version then
|
65
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
66
|
+
s.specification_version = 3
|
67
|
+
|
68
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
69
|
+
s.add_runtime_dependency(%q<rack>, [">= 0"])
|
70
|
+
s.add_runtime_dependency(%q<rack-mount>, [">= 0"])
|
71
|
+
s.add_runtime_dependency(%q<rack-jsonp>, [">= 0"])
|
72
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 3.0.0.rc2"])
|
73
|
+
s.add_development_dependency(%q<rake>, [">= 0"])
|
74
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
75
|
+
else
|
76
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
77
|
+
s.add_dependency(%q<rack-mount>, [">= 0"])
|
78
|
+
s.add_dependency(%q<rack-jsonp>, [">= 0"])
|
79
|
+
s.add_dependency(%q<activesupport>, [">= 3.0.0.rc2"])
|
80
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
81
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
82
|
+
end
|
83
|
+
else
|
84
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
85
|
+
s.add_dependency(%q<rack-mount>, [">= 0"])
|
86
|
+
s.add_dependency(%q<rack-jsonp>, [">= 0"])
|
87
|
+
s.add_dependency(%q<activesupport>, [">= 3.0.0.rc2"])
|
88
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
89
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
data/lib/grape.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/builder'
|
3
|
+
|
4
|
+
require 'grape/middleware/base'
|
5
|
+
require 'grape/middleware/prefixer'
|
6
|
+
require 'grape/middleware/versioner'
|
7
|
+
require 'grape/middleware/formatter'
|
8
|
+
require 'grape/middleware/error'
|
9
|
+
|
10
|
+
require 'grape/middleware/auth/oauth2'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Grape::Middleware::Auth
|
2
|
+
class OAuth2 < Grape::Middleware::Base
|
3
|
+
def default_options
|
4
|
+
{
|
5
|
+
:token_class => 'AccessToken',
|
6
|
+
:realm => 'OAuth API'
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
def before
|
11
|
+
if request['oauth_token']
|
12
|
+
verify_token(request['oauth_token'])
|
13
|
+
elsif env['Authorization'] && t = parse_authorization_header
|
14
|
+
verify_token(t)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def token_class
|
19
|
+
@klass ||= eval(options[:token_class])
|
20
|
+
end
|
21
|
+
|
22
|
+
def verify_token(token)
|
23
|
+
if token = token_class.verify(token)
|
24
|
+
if token.expired?
|
25
|
+
error_out(401, 'expired_token')
|
26
|
+
else
|
27
|
+
if token.permission_for?(env)
|
28
|
+
env['api.token'] = token
|
29
|
+
else
|
30
|
+
error_out(403, 'insufficient_scope')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
else
|
34
|
+
error_out(401, 'invalid_token')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_authorization_header
|
39
|
+
if env['Authorization'] =~ /oauth (.*)/i
|
40
|
+
$1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def error_out(status, error)
|
45
|
+
throw :error, {
|
46
|
+
:message => 'The token provided has expired.',
|
47
|
+
:status => status,
|
48
|
+
:headers => {
|
49
|
+
'WWW-Authenticate' => "OAuth realm='#{options[:realm]}', error='#{error}'"
|
50
|
+
}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Grape
|
2
|
+
module Middleware
|
3
|
+
class Base
|
4
|
+
attr_reader :app, :env, :options
|
5
|
+
|
6
|
+
def initialize(app, options = {})
|
7
|
+
@app = app
|
8
|
+
@options = default_options.merge(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def default_options; {} end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
dup.call!(env)
|
15
|
+
end
|
16
|
+
|
17
|
+
def call!(env)
|
18
|
+
@env = env
|
19
|
+
before
|
20
|
+
@app_response = @app.call(@env)
|
21
|
+
after || @app_response
|
22
|
+
end
|
23
|
+
|
24
|
+
def before; end
|
25
|
+
def after; end
|
26
|
+
|
27
|
+
def request
|
28
|
+
Rack::Request.new(self.env)
|
29
|
+
end
|
30
|
+
|
31
|
+
def response
|
32
|
+
Rack::Response.new(@app_response)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
class Error < Base
|
6
|
+
def call!(env)
|
7
|
+
@env = env
|
8
|
+
err = catch :error do
|
9
|
+
@app.call(@env)
|
10
|
+
end
|
11
|
+
|
12
|
+
error_response(err)
|
13
|
+
end
|
14
|
+
|
15
|
+
def error_response(error = {})
|
16
|
+
Rack::Response.new([(error[:message] || options[:default_message])], error[:status] || 403, error[:headers] || {}).finish
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
require 'active_support/json'
|
3
|
+
|
4
|
+
module Grape
|
5
|
+
module Middleware
|
6
|
+
class Formatter < Base
|
7
|
+
CONTENT_TYPES = {
|
8
|
+
:xml => 'application/xml',
|
9
|
+
:json => 'application/json',
|
10
|
+
:atom => 'application/atom+xml',
|
11
|
+
:rss => 'application/rss+xml'
|
12
|
+
}
|
13
|
+
|
14
|
+
def default_options
|
15
|
+
{
|
16
|
+
:default_format => :json,
|
17
|
+
:content_types => {}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def content_types
|
22
|
+
CONTENT_TYPES.merge(options[:content_types])
|
23
|
+
end
|
24
|
+
|
25
|
+
def before
|
26
|
+
fmt = format_from_extension || format_from_header || options[:default_format]
|
27
|
+
|
28
|
+
if content_types.key?(fmt)
|
29
|
+
env['api.format'] = fmt
|
30
|
+
else
|
31
|
+
throw :error, :status => 406, :message => 'The requested format is not supported.'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def format_from_extension
|
36
|
+
parts = request.path.split('.')
|
37
|
+
hit = parts.last.to_sym
|
38
|
+
|
39
|
+
if parts.size <= 1
|
40
|
+
nil
|
41
|
+
else
|
42
|
+
hit
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def format_from_header
|
47
|
+
# TODO: Implement Accept header parsing.
|
48
|
+
end
|
49
|
+
|
50
|
+
def after
|
51
|
+
status, headers, bodies = *@app_response
|
52
|
+
bodies.map! do |body|
|
53
|
+
ActiveSupport::JSON.encode(body)
|
54
|
+
end
|
55
|
+
[status, headers, bodies]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
class Prefixer < Base
|
6
|
+
def prefix
|
7
|
+
prefix = options[:prefix] || ""
|
8
|
+
prefix.insert(0, '/') unless prefix.index('/') == 0
|
9
|
+
prefix
|
10
|
+
end
|
11
|
+
|
12
|
+
def before
|
13
|
+
if env['PATH_INFO'].index(prefix) == 0
|
14
|
+
env['PATH_INFO'].gsub!(prefix, '')
|
15
|
+
env['PATH_INFO'].insert(0, '/') unless env['PATH_INFO'].index('/') == 0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
class Versioner < Base
|
6
|
+
def default_options
|
7
|
+
{
|
8
|
+
:pattern => /.*/i
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def before
|
13
|
+
pieces = env['PATH_INFO'].split('/')
|
14
|
+
potential_version = pieces[1]
|
15
|
+
if potential_version =~ options[:pattern]
|
16
|
+
truncated_path = "/#{pieces[2..-1].join('/')}"
|
17
|
+
env['api.version'] = potential_version
|
18
|
+
env['PATH_INFO'] = truncated_path
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Auth::OAuth2 do
|
4
|
+
class FakeToken
|
5
|
+
def self.verify(token)
|
6
|
+
FakeToken.new(token) if %w(g e).include?(token[0..0])
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(token)
|
10
|
+
self.token = token
|
11
|
+
end
|
12
|
+
|
13
|
+
def expired?
|
14
|
+
self.token[0..0] == 'e'
|
15
|
+
end
|
16
|
+
|
17
|
+
def permission_for?(env)
|
18
|
+
env['PATH_INFO'] == '/forbidden' ? false : true
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :token
|
22
|
+
end
|
23
|
+
|
24
|
+
def app
|
25
|
+
Rack::Builder.app do
|
26
|
+
use Grape::Middleware::Auth::OAuth2, :token_class => 'FakeToken'
|
27
|
+
run lambda{|env| [200, {}, [ (env['api.token'].token rescue '') ]]}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'with the token in the query string' do
|
32
|
+
context 'and a valid token' do
|
33
|
+
before { get '/awesome?oauth_token=g123' }
|
34
|
+
|
35
|
+
it 'should set env["api.token"]' do
|
36
|
+
last_response.body.should == 'g123'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'and an invalid token' do
|
41
|
+
before do
|
42
|
+
@err = catch :error do
|
43
|
+
get '/awesome?oauth_token=b123'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should throw an error' do
|
48
|
+
@err[:status].should == 401
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should set the WWW-Authenticate header in the response' do
|
52
|
+
@err[:headers]['WWW-Authenticate'].should == "OAuth realm='OAuth API', error='invalid_token'"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with an expired token' do
|
58
|
+
before do
|
59
|
+
@err = catch :error do
|
60
|
+
get '/awesome?oauth_token=e123'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it { @err[:status].should == 401 }
|
65
|
+
it { @err[:headers]['WWW-Authenticate'].should == "OAuth realm='OAuth API', error='expired_token'" }
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'with the token in the header' do
|
69
|
+
before { get '/awesome', {}, 'Authorization' => 'OAuth g123' }
|
70
|
+
it { last_response.body.should == 'g123' }
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'with the token in the POST body' do
|
74
|
+
before { post '/awesome', {'oauth_token' => 'g123'} }
|
75
|
+
it { last_response.body.should == 'g123'}
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when accessing something outside its scope' do
|
79
|
+
before do
|
80
|
+
@err = catch :error do
|
81
|
+
get '/forbidden?oauth_token=g123'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it { @err[:headers]['WWW-Authenticate'].should == "OAuth realm='OAuth API', error='insufficient_scope'" }
|
86
|
+
it { @err[:status].should == 403 }
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Base do
|
4
|
+
subject { Grape::Middleware::Base.new(blank_app) }
|
5
|
+
let(:blank_app) { lambda{|env| [200, {}, 'Hi there.']} }
|
6
|
+
|
7
|
+
before do
|
8
|
+
# Keep it one object for testing.
|
9
|
+
subject.stub!(:dup).and_return(subject)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should have the app as an accessor' do
|
13
|
+
subject.app.should == blank_app
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should be able to access the request' do
|
17
|
+
subject.call({})
|
18
|
+
subject.request.should be_kind_of(Rack::Request)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should call through to the app' do
|
22
|
+
subject.call({}).should == [200, {}, 'Hi there.']
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'callbacks' do
|
26
|
+
it 'should call #before' do
|
27
|
+
subject.should_receive(:before)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should call #after' do
|
31
|
+
subject.should_receive(:after)
|
32
|
+
end
|
33
|
+
|
34
|
+
after{ subject.call!({}) }
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should be able to access the response' do
|
38
|
+
subject.call({})
|
39
|
+
subject.response.should be_kind_of(Rack::Response)
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'options' do
|
43
|
+
it 'should persist options passed at initialization' do
|
44
|
+
Grape::Middleware::Base.new(blank_app, {:abc => true}).options[:abc].should be_true
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'defaults' do
|
48
|
+
class ExampleWare < Grape::Middleware::Base
|
49
|
+
def default_options
|
50
|
+
{:monkey => true}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should persist the default options' do
|
55
|
+
ExampleWare.new(blank_app).options[:monkey].should be_true
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should override default options when provided' do
|
59
|
+
ExampleWare.new(blank_app, :monkey => false).options[:monkey].should be_false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Error do
|
4
|
+
class ErrApp
|
5
|
+
class << self
|
6
|
+
attr_accessor :error
|
7
|
+
attr_accessor :format
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
throw :error, self.error
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def app
|
16
|
+
Rack::Builder.app do
|
17
|
+
use Grape::Middleware::Error, :default_message => 'Aww, hamburgers.'
|
18
|
+
run ErrApp
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should set the status code appropriately' do
|
23
|
+
ErrApp.error = {:status => 410}
|
24
|
+
get '/'
|
25
|
+
last_response.status.should == 410
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should set the error message appropriately' do
|
29
|
+
ErrApp.error = {:message => 'Awesome stuff.'}
|
30
|
+
get '/'
|
31
|
+
last_response.body.should == 'Awesome stuff.'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should default to a 403 status' do
|
35
|
+
ErrApp.error = {}
|
36
|
+
get '/'
|
37
|
+
last_response.status.should == 403
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should have a default message' do
|
41
|
+
ErrApp.error = {}
|
42
|
+
get '/'
|
43
|
+
last_response.body.should == 'Aww, hamburgers.'
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with formatting' do
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Formatter do
|
4
|
+
subject{ Grape::Middleware::Formatter.new(app)}
|
5
|
+
before{ subject.stub!(:dup).and_return(subject) }
|
6
|
+
|
7
|
+
let(:app){ lambda{|env| [200, {}, [@body]]} }
|
8
|
+
|
9
|
+
context 'serialization' do
|
10
|
+
it 'should look at the bodies for possibly serializable data' do
|
11
|
+
@body = {"abc" => "def"}
|
12
|
+
status, headers, bodies = *subject.call({'PATH_INFO' => '/somewhere'})
|
13
|
+
bodies.first.should == ActiveSupport::JSON.encode(@body)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'detection' do
|
18
|
+
it 'should use the extension if one is provided' do
|
19
|
+
subject.call({'PATH_INFO' => '/info.xml'})
|
20
|
+
subject.env['api.format'].should == :xml
|
21
|
+
subject.call({'PATH_INFO' => '/info.json'})
|
22
|
+
subject.env['api.format'].should == :json
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should use the default format if none is provided' do
|
26
|
+
subject.call({'PATH_INFO' => '/info'})
|
27
|
+
subject.env['api.format'].should == :json
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should throw an error on an unrecognized format' do
|
31
|
+
err = catch(:error){ subject.call({'PATH_INFO' => '/info.barklar'}) }
|
32
|
+
err.should == {:status => 406, :message => "The requested format is not supported."}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Prefixer do
|
4
|
+
let(:app){ lambda{|env| [200, {}, env['PATH_INFO']]} }
|
5
|
+
subject{ Grape::Middleware::Prefixer.new(app, @options || {}) }
|
6
|
+
|
7
|
+
it 'should lop off a prefix (without a slash)' do
|
8
|
+
@options = {:prefix => 'monkey'}
|
9
|
+
subject.call('PATH_INFO' => '/monkey/beeswax').last.should == '/beeswax'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should lop off a prefix (with a slash)' do
|
13
|
+
@options = {:prefix => '/banana'}
|
14
|
+
subject.call('PATH_INFO' => '/banana/peel').last.should == '/peel'
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should not lop off non-prefixes' do
|
18
|
+
@options = {:prefix => '/monkey'}
|
19
|
+
subject.call('PATH_INFO' => '/banana/peel').last.should == '/banana/peel'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should pass through unaltered if there is no prefix' do
|
23
|
+
subject.call('PATH_INFO' => '/awesome').last.should == '/awesome'
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Versioner do
|
4
|
+
let(:app) { lambda{|env| [200, env, env['api.version']]} }
|
5
|
+
subject { Grape::Middleware::Versioner.new(app, @options || {}) }
|
6
|
+
|
7
|
+
it 'should set the API version based on the first path' do
|
8
|
+
subject.call('PATH_INFO' => '/v1/awesome').last.should == 'v1'
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should cut the version out of the path' do
|
12
|
+
subject.call('PATH_INFO' => '/v1/awesome')[1]['PATH_INFO'].should == '/awesome'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should provide a nil version if no path is given' do
|
16
|
+
subject.call('PATH_INFO' => '/').last.should be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'with a pattern' do
|
20
|
+
before{ @options = {:pattern => /v./i} }
|
21
|
+
it 'should set the version if it matches' do
|
22
|
+
subject.call('PATH_INFO' => '/v1/awesome').last.should == 'v1'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should ignore the version if it fails to match' do
|
26
|
+
subject.call('PATH_INFO' => '/awesome/radical').last.should be_nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/spec/grape_spec.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'spec_helper'
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
require 'grape'
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'bundler'
|
8
|
+
Bundler.setup :default, :test
|
9
|
+
|
10
|
+
require 'rspec'
|
11
|
+
require 'rack/test'
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.include Rack::Test::Methods
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: grape
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: -3702664354
|
5
|
+
prerelease: true
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
- alpha
|
11
|
+
- 1
|
12
|
+
version: 0.0.0.alpha.1
|
13
|
+
platform: ruby
|
14
|
+
authors:
|
15
|
+
- Michael Bleigh
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain: []
|
19
|
+
|
20
|
+
date: 2010-08-27 00:00:00 -05:00
|
21
|
+
default_executable:
|
22
|
+
dependencies:
|
23
|
+
- !ruby/object:Gem::Dependency
|
24
|
+
prerelease: false
|
25
|
+
type: :runtime
|
26
|
+
name: rack
|
27
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
hash: 3
|
33
|
+
segments:
|
34
|
+
- 0
|
35
|
+
version: "0"
|
36
|
+
requirement: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
prerelease: false
|
39
|
+
type: :runtime
|
40
|
+
name: rack-mount
|
41
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 3
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
version: "0"
|
50
|
+
requirement: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
prerelease: false
|
53
|
+
type: :runtime
|
54
|
+
name: rack-jsonp
|
55
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
requirement: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
prerelease: false
|
67
|
+
type: :runtime
|
68
|
+
name: activesupport
|
69
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
hash: 977940607
|
75
|
+
segments:
|
76
|
+
- 3
|
77
|
+
- 0
|
78
|
+
- 0
|
79
|
+
- rc2
|
80
|
+
version: 3.0.0.rc2
|
81
|
+
requirement: *id004
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
prerelease: false
|
84
|
+
type: :development
|
85
|
+
name: rake
|
86
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
requirement: *id005
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
prerelease: false
|
98
|
+
type: :development
|
99
|
+
name: jeweler
|
100
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
hash: 3
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
version: "0"
|
109
|
+
requirement: *id006
|
110
|
+
description: A Ruby framework for rapid API development with great conventions.
|
111
|
+
email: michael@intridea.com
|
112
|
+
executables: []
|
113
|
+
|
114
|
+
extensions: []
|
115
|
+
|
116
|
+
extra_rdoc_files:
|
117
|
+
- LICENSE
|
118
|
+
- README.rdoc
|
119
|
+
files:
|
120
|
+
- .document
|
121
|
+
- .gitignore
|
122
|
+
- .rspec
|
123
|
+
- .rvmrc
|
124
|
+
- Gemfile
|
125
|
+
- Gemfile.lock
|
126
|
+
- LICENSE
|
127
|
+
- README.rdoc
|
128
|
+
- Rakefile
|
129
|
+
- VERSION
|
130
|
+
- autotest/discover.rb
|
131
|
+
- grape.gemspec
|
132
|
+
- lib/grape.rb
|
133
|
+
- lib/grape/middleware/auth/oauth2.rb
|
134
|
+
- lib/grape/middleware/base.rb
|
135
|
+
- lib/grape/middleware/error.rb
|
136
|
+
- lib/grape/middleware/formatter.rb
|
137
|
+
- lib/grape/middleware/prefixer.rb
|
138
|
+
- lib/grape/middleware/versioner.rb
|
139
|
+
- spec/grape/middleware/auth/oauth2_spec.rb
|
140
|
+
- spec/grape/middleware/base_spec.rb
|
141
|
+
- spec/grape/middleware/error_spec.rb
|
142
|
+
- spec/grape/middleware/formatter_spec.rb
|
143
|
+
- spec/grape/middleware/prefixer_spec.rb
|
144
|
+
- spec/grape/middleware/versioner_spec.rb
|
145
|
+
- spec/grape_spec.rb
|
146
|
+
- spec/spec_helper.rb
|
147
|
+
has_rdoc: true
|
148
|
+
homepage: http://github.com/intridea/grape
|
149
|
+
licenses: []
|
150
|
+
|
151
|
+
post_install_message:
|
152
|
+
rdoc_options:
|
153
|
+
- --charset=UTF-8
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
none: false
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
hash: 3
|
162
|
+
segments:
|
163
|
+
- 0
|
164
|
+
version: "0"
|
165
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
166
|
+
none: false
|
167
|
+
requirements:
|
168
|
+
- - ">"
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
hash: 25
|
171
|
+
segments:
|
172
|
+
- 1
|
173
|
+
- 3
|
174
|
+
- 1
|
175
|
+
version: 1.3.1
|
176
|
+
requirements: []
|
177
|
+
|
178
|
+
rubyforge_project:
|
179
|
+
rubygems_version: 1.3.7
|
180
|
+
signing_key:
|
181
|
+
specification_version: 3
|
182
|
+
summary: A Ruby framework for rapid API development.
|
183
|
+
test_files:
|
184
|
+
- spec/grape/middleware/auth/oauth2_spec.rb
|
185
|
+
- spec/grape/middleware/base_spec.rb
|
186
|
+
- spec/grape/middleware/error_spec.rb
|
187
|
+
- spec/grape/middleware/formatter_spec.rb
|
188
|
+
- spec/grape/middleware/prefixer_spec.rb
|
189
|
+
- spec/grape/middleware/versioner_spec.rb
|
190
|
+
- spec/grape_spec.rb
|
191
|
+
- spec/spec_helper.rb
|