alf-rest 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +5 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +71 -0
- data/LICENCE.md +22 -0
- data/Manifest.txt +12 -0
- data/README.md +11 -0
- data/Rakefile +11 -0
- data/lib/alf-rest.rb +1 -0
- data/lib/alf/rest.rb +30 -0
- data/lib/alf/rest/alf-ext/renderer.rb +16 -0
- data/lib/alf/rest/alf-ext/unit_of_work.rb +3 -0
- data/lib/alf/rest/alf-ext/unit_of_work/delete.rb +21 -0
- data/lib/alf/rest/alf-ext/unit_of_work/insert.rb +22 -0
- data/lib/alf/rest/alf-ext/unit_of_work/update.rb +22 -0
- data/lib/alf/rest/config.rb +55 -0
- data/lib/alf/rest/errors.rb +5 -0
- data/lib/alf/rest/helpers.rb +68 -0
- data/lib/alf/rest/loader.rb +5 -0
- data/lib/alf/rest/middleware.rb +19 -0
- data/lib/alf/rest/payload.rb +12 -0
- data/lib/alf/rest/payload/client.rb +22 -0
- data/lib/alf/rest/request.rb +43 -0
- data/lib/alf/rest/response.rb +21 -0
- data/lib/alf/rest/test.rb +16 -0
- data/lib/alf/rest/test/client.rb +83 -0
- data/lib/alf/rest/test/ext.rb +7 -0
- data/lib/alf/rest/test/steps.rb +286 -0
- data/lib/alf/rest/version.rb +16 -0
- data/lib/sinatra/alf-rest.rb +73 -0
- data/spec/fixtures/sap.db +0 -0
- data/spec/integration/sinatra/rest_get/test_accept.rb +98 -0
- data/spec/integration/spec_helper.rb +27 -0
- data/spec/test_rest.rb +10 -0
- data/spec/unit/config/test_database.rb +28 -0
- data/spec/unit/config/test_viewpoint.rb +18 -0
- data/spec/unit/ext/renderer/test_from_http_accept.rb +50 -0
- data/spec/unit/ext/renderer/test_supported_media_types.rb +10 -0
- data/spec/unit/middleware/test_behavior.rb +55 -0
- data/spec/unit/request/test_to_relation.rb +56 -0
- data/spec/unit/spec_helper.rb +35 -0
- data/spec/unit/test_rest.rb +10 -0
- data/tasks/gem.rake +8 -0
- data/tasks/test.rake +17 -0
- metadata +251 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'alf-rest'
|
2
|
+
module Sinatra
|
3
|
+
module AlfRest
|
4
|
+
|
5
|
+
def rest_get(url, &bl)
|
6
|
+
get(url) do
|
7
|
+
Alf::Rest::Response.new(env){|r|
|
8
|
+
r.body = instance_exec(&bl)
|
9
|
+
}.finish
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def rest_post(url, heading, &bl)
|
14
|
+
post(url) do
|
15
|
+
input = Alf::Rest::Request.new(env, heading).to_tuple
|
16
|
+
result = instance_exec(input, &bl)
|
17
|
+
|
18
|
+
Alf::Rest::Response.new(env){|r|
|
19
|
+
r.status = result.rack_status
|
20
|
+
r.body = result.rack_body
|
21
|
+
if not(location_set?) and (loc = result.rack_location(request))
|
22
|
+
r["Location"] = loc
|
23
|
+
end
|
24
|
+
}.finish
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def rest_put(url, heading, &bl)
|
29
|
+
put(url) do
|
30
|
+
input = Alf::Rest::Request.new(env, heading).to_tuple
|
31
|
+
result = instance_exec(input, &bl)
|
32
|
+
|
33
|
+
Alf::Rest::Response.new(env){|r|
|
34
|
+
r.status = result.rack_status
|
35
|
+
r.body = result.rack_body
|
36
|
+
r["Location"] = request.path unless location_set?
|
37
|
+
}.finish
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def rest_delete(url, heading, &bl)
|
42
|
+
delete(url) do
|
43
|
+
input = Tuple[heading].coerce(params.select{|k| heading[k.to_sym]})
|
44
|
+
result = instance_exec(input, &bl)
|
45
|
+
|
46
|
+
Alf::Rest::Response.new(env){|r|
|
47
|
+
r.status = result.rack_status
|
48
|
+
r.body = result.rack_body
|
49
|
+
if not(location_set?) and (loc = result.rack_location(request))
|
50
|
+
r["Location"] = loc
|
51
|
+
end
|
52
|
+
}.finish
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def alf_rest
|
57
|
+
if block_given?
|
58
|
+
yield(settings.alf_configuration)
|
59
|
+
else
|
60
|
+
settings.alf_configuration
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.registered(app)
|
65
|
+
config = Alf::Rest::Config.new
|
66
|
+
app.set :alf_configuration, config
|
67
|
+
app.use Alf::Rest::Middleware, config
|
68
|
+
app.helpers Alf::Rest::Helpers
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
register AlfRest
|
73
|
+
end
|
Binary file
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Alf
|
3
|
+
module Rest
|
4
|
+
describe "rest_get", "Accept header" do
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
def app
|
8
|
+
mock_app do
|
9
|
+
rest_get '/suppliers' do
|
10
|
+
relvar{ suppliers }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def last_meta
|
16
|
+
[last_response.status, last_response.content_type]
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'defaults to application/json when unspecified' do
|
20
|
+
#
|
21
|
+
get '/suppliers'
|
22
|
+
#
|
23
|
+
last_meta.should eq([200, "application/json"])
|
24
|
+
#
|
25
|
+
result = JSON.load(last_response.body)
|
26
|
+
result.should be_a(Array)
|
27
|
+
result.size.should eq(5)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'supports */* and fallbacks to application/json' do
|
31
|
+
#
|
32
|
+
header "Accept", "*/*"
|
33
|
+
get '/suppliers'
|
34
|
+
#
|
35
|
+
last_meta.should eq([200, "application/json"])
|
36
|
+
#
|
37
|
+
result = JSON.load(last_response.body)
|
38
|
+
result.should be_a(Array)
|
39
|
+
result.size.should eq(5)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'supports application/json' do
|
43
|
+
#
|
44
|
+
header "Accept", "application/json"
|
45
|
+
get '/suppliers'
|
46
|
+
#
|
47
|
+
last_meta.should eq([200, "application/json"])
|
48
|
+
#
|
49
|
+
result = JSON.load(last_response.body)
|
50
|
+
result.should be_a(Array)
|
51
|
+
result.size.should eq(5)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'supports text/csv' do
|
55
|
+
#
|
56
|
+
header "Accept", "text/csv"
|
57
|
+
get '/suppliers'
|
58
|
+
#
|
59
|
+
last_meta.should eq([200, "text/csv"])
|
60
|
+
#
|
61
|
+
last_response.body.should =~ /1,Smith,20,London/
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'supports text/plain' do
|
65
|
+
#
|
66
|
+
header "Accept", "text/plain"
|
67
|
+
get '/suppliers'
|
68
|
+
#
|
69
|
+
last_meta.should eq([200, "text/plain"])
|
70
|
+
#
|
71
|
+
last_response.body.should =~ /\|\s+1\s+\|\s+Smith\s+\|/
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'supports text/x-yaml' do
|
75
|
+
#
|
76
|
+
header "Accept", "text/x-yaml"
|
77
|
+
get '/suppliers'
|
78
|
+
#
|
79
|
+
last_meta.should eq([200, "text/x-yaml"])
|
80
|
+
#
|
81
|
+
last_response.body.should =~ /- :sid: 1/
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'supports a complex specification' do
|
85
|
+
#
|
86
|
+
header "Accept", "application/*, text/*;q=0.8"
|
87
|
+
get '/suppliers'
|
88
|
+
#
|
89
|
+
last_meta.should eq([200, "application/json"])
|
90
|
+
#
|
91
|
+
result = JSON.load(last_response.body)
|
92
|
+
result.should be_a(Array)
|
93
|
+
result.size.should eq(5)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__)
|
2
|
+
require 'alf-sequel'
|
3
|
+
require 'sinatra/alf-rest'
|
4
|
+
require 'rspec'
|
5
|
+
require 'path'
|
6
|
+
require 'rack/test'
|
7
|
+
|
8
|
+
module Helpers
|
9
|
+
|
10
|
+
def mock_app(&bl)
|
11
|
+
Class.new(Sinatra::Base){
|
12
|
+
register Sinatra::AlfRest
|
13
|
+
alf_rest do |cfg|
|
14
|
+
cfg.database = Alf.database(Path.backfind('fixtures/sap.db'))
|
15
|
+
end
|
16
|
+
enable :raise_errors
|
17
|
+
disable :show_exceptions
|
18
|
+
instance_eval(&bl)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
RSpec.configure do |c|
|
25
|
+
c.include Helpers
|
26
|
+
c.extend Helpers
|
27
|
+
end
|
data/spec/test_rest.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Alf
|
3
|
+
module Rest
|
4
|
+
describe Config, "database=" do
|
5
|
+
|
6
|
+
let(:config){ Config.new }
|
7
|
+
|
8
|
+
subject{
|
9
|
+
config.database = db
|
10
|
+
config.database
|
11
|
+
}
|
12
|
+
|
13
|
+
context 'with a Alf::Database' do
|
14
|
+
let(:db){ Alf.database(Path.dir) }
|
15
|
+
|
16
|
+
it{ should be_a(Alf::Database) }
|
17
|
+
it{ should be(db) }
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'with an adapter' do
|
21
|
+
let(:db){ Path.dir }
|
22
|
+
|
23
|
+
it{ should be_a(Alf::Database) }
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Alf
|
3
|
+
module Rest
|
4
|
+
describe Config, "viewpoint=" do
|
5
|
+
|
6
|
+
let(:config){ Config.new }
|
7
|
+
|
8
|
+
subject{ config.viewpoint = Alf::Viewpoint::NATIVE }
|
9
|
+
|
10
|
+
it 'sets it on the connection options' do
|
11
|
+
subject
|
12
|
+
config.connection_options[:viewpoint].should be(Alf::Viewpoint::NATIVE)
|
13
|
+
config.viewpoint.should be(Alf::Viewpoint::NATIVE)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Alf
|
3
|
+
describe Renderer, "from_http_accept" do
|
4
|
+
|
5
|
+
subject{ Renderer.from_http_accept(accept) }
|
6
|
+
|
7
|
+
context 'application/json' do
|
8
|
+
let(:accept){ "application/json" }
|
9
|
+
|
10
|
+
it{ should be(Renderer::JSON) }
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'text/plain' do
|
14
|
+
let(:accept){ "text/plain" }
|
15
|
+
|
16
|
+
it{ should be(Renderer::Text) }
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'text/csv' do
|
20
|
+
let(:accept){ "text/csv" }
|
21
|
+
|
22
|
+
it{ should be(Renderer::CSV) }
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'text/x-yaml' do
|
26
|
+
let(:accept){ "text/x-yaml" }
|
27
|
+
|
28
|
+
it{ should be(Renderer::YAML) }
|
29
|
+
end
|
30
|
+
|
31
|
+
context '*/*' do
|
32
|
+
let(:accept){ "*/*" }
|
33
|
+
|
34
|
+
it{ should be(Renderer::JSON) }
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'text/unknown' do
|
38
|
+
let(:accept){ "text/unknown" }
|
39
|
+
|
40
|
+
it{ should be(nil) }
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'a complex one' do
|
44
|
+
let(:accept){ "text/unknown, text/*;q=0.8, */*;q=0.5" }
|
45
|
+
|
46
|
+
it{ should be(Renderer::CSV) }
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Alf
|
3
|
+
module Rest
|
4
|
+
describe Middleware, 'its Rack behavior' do
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
def mock_app(&bl)
|
8
|
+
Class.new(Sinatra::Base) do
|
9
|
+
set :environment, :test
|
10
|
+
|
11
|
+
disable :dump_errors
|
12
|
+
disable :logging
|
13
|
+
disable :dump_errors
|
14
|
+
enable :raise_errors
|
15
|
+
disable :show_exceptions
|
16
|
+
|
17
|
+
class_config = nil
|
18
|
+
use Alf::Rest do |cfg|
|
19
|
+
cfg.database = Alf::Database.new(Path.dir)
|
20
|
+
class_config = cfg
|
21
|
+
bl.call(cfg) if bl
|
22
|
+
end
|
23
|
+
|
24
|
+
get '/check-config' do
|
25
|
+
check = env[Rest::RACK_CONFIG_KEY].is_a?(Config)
|
26
|
+
check &= env[Rest::RACK_CONFIG_KEY] != class_config
|
27
|
+
check.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
get '/generate-error' do
|
31
|
+
raise "blah"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'on a default app' do
|
37
|
+
let(:app){ mock_app }
|
38
|
+
|
39
|
+
it 'sets a duplicata of the configuration' do
|
40
|
+
get '/check-config'
|
41
|
+
last_response.body.should eq("true")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when an error occurs' do
|
46
|
+
let(:app){ mock_app }
|
47
|
+
|
48
|
+
it 'raises the Error outside the app' do
|
49
|
+
lambda{ get '/generate-error' }.should raise_error(/blah/)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Alf
|
3
|
+
module Rest
|
4
|
+
describe Request, "to_relation" do
|
5
|
+
|
6
|
+
let(:request){ Alf::Rest::Request.new(env, heading) }
|
7
|
+
|
8
|
+
let(:heading){ {status: Integer, city: String} }
|
9
|
+
|
10
|
+
subject{ request.to_relation }
|
11
|
+
|
12
|
+
context 'with application/x-www-form-urlencoded' do
|
13
|
+
let(:env) { env_for("/foo", method: 'POST', params: tuple) }
|
14
|
+
let(:tuple){ {city: "London", status: "200"} }
|
15
|
+
|
16
|
+
it 'gets the iterated tuples from POST' do
|
17
|
+
subject.should eq(Relation(city: "London", status: 200))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with a body conforming to Rack spec strictly' do
|
22
|
+
let(:env) { json_post_env(tuple) }
|
23
|
+
let(:tuple){ {city: "London", status: "200"} }
|
24
|
+
|
25
|
+
before do
|
26
|
+
env['rack.input'] = Struct.new(:read, :rewind).new(env['rack.input'].read)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'gets the iterated tuples from POST' do
|
30
|
+
subject.should eq(Relation(city: "London", status: 200))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with a JSON encoded body' do
|
35
|
+
let(:env){ json_post_env(tuples) }
|
36
|
+
|
37
|
+
context 'when the input matches exactly' do
|
38
|
+
let(:tuples){ [{city: "London", status: "200"}] }
|
39
|
+
|
40
|
+
it 'gets the expected relation' do
|
41
|
+
subject.should eq(Relation(status: 200, city: "London"))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when a projection is needed' do
|
46
|
+
let(:tuples){ [{name: "Smith", status: "200"}] }
|
47
|
+
|
48
|
+
it 'gets the expected relation' do
|
49
|
+
subject.should eq(Relation(status: 200))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|