alf-rest 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|