restful_mapper 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +121 -0
- data/Rakefile +1 -0
- data/examples/rubygems.rb +42 -0
- data/features/basic_authentication.feature +49 -0
- data/features/default_parameters.feature +55 -0
- data/features/delete_request.feature +25 -0
- data/features/get_request.feature +124 -0
- data/features/post_request.feature +31 -0
- data/features/put_request.feature +31 -0
- data/features/raising_exceptions.feature +30 -0
- data/features/step_definitions/basic_authentication_steps.rb +6 -0
- data/features/step_definitions/get_request_steps.rb +75 -0
- data/features/step_definitions/raising_exceptions_steps.rb +15 -0
- data/features/support/env.rb +6 -0
- data/lib/restful_mapper/version.rb +3 -0
- data/lib/restful_mapper.rb +156 -0
- data/restful_mapper.gemspec +33 -0
- metadata +245 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 77221199e858bff0473e341caeb476fab4a9f20f
|
4
|
+
data.tar.gz: 012a9f9893e7fed2f917715ee605f08e7e3f5840
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4c351450ae1850ba7a5c87aa7c0cc4b3af691dc6756aa62ae1ef0276fd14b9cf7b28de5486130f54b395d581c8aa28f8c1e7bf4b6ec9b50e0bcb83c05ce866d6
|
7
|
+
data.tar.gz: 4cb445bb1cd7f9c60b9cfb5131a8ae9f50d083a5e38290351721403c0bebefa852b6c3572d9e0cc225bcd1cb7e9a3de98313528fa46c31e0ba6ea81561216fd7
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Dragan Milic
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
# RestfulMapper
|
2
|
+
|
3
|
+
Provides DSL for describing RESTFul services using JSON transport.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'restful_mapper'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install restful_mapper
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
RestfulMapper provides a way to integrate calls to RESTful services into your code without much fuss. It gives you control over the format of the outgoing messages, request parameters and request paths. It also maps response codes to Ruby objects using structure_mapper gem.
|
22
|
+
|
23
|
+
The best way to learn about the DSL is to try combining features demonstrated by examples:
|
24
|
+
|
25
|
+
### Get request
|
26
|
+
In order to invoke a simple get request without any parameters, one can use following mapping:
|
27
|
+
|
28
|
+
class SimpleService < RestfulMapper::Service
|
29
|
+
base_url "http://localhost:8765"
|
30
|
+
|
31
|
+
get :simple_endpoint do
|
32
|
+
path "/simple"
|
33
|
+
responses 200 => true, 0 => false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Invoking SimpleService.simple_endpoint will return 'true' value if the response code of the service http://localhost:8765/simple is 200 and false for any other response code.
|
38
|
+
|
39
|
+
### Get request with request parameters
|
40
|
+
In order to add parameter to a simple service definition, one has to add 'query_parameters' array:
|
41
|
+
|
42
|
+
class SimpleService < RestfulMapper::Service
|
43
|
+
base_url "http://localhost:8765"
|
44
|
+
|
45
|
+
get :simple_endpoint do
|
46
|
+
path "/simple"
|
47
|
+
query_parameters [:name]
|
48
|
+
responses 200 => true, 0=>false
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
When invoked with 'SimpleService.simple_endpoint name: "test"' RestfulMapper will perform a GET request to http://localhost:8765/simple?name=test.
|
54
|
+
|
55
|
+
### Get request with path parameters
|
56
|
+
Path of the service can be parametrized using Mustache syntax:
|
57
|
+
|
58
|
+
class SimpleService < RestfulMapper::Service
|
59
|
+
base_url "http://localhost:8765"
|
60
|
+
|
61
|
+
get :simple_endpoint do
|
62
|
+
path "/simple/{{name}}"
|
63
|
+
responses 200 => true, 0=>false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
Calling 'SimpleService.simple_endpoint name: "test"' will perform GET request to http://localhost:8765/simple/test.
|
68
|
+
|
69
|
+
|
70
|
+
### Post request with JSON body
|
71
|
+
In order to use parameter as JSON body, one has to declare it using 'body_parameter' directive
|
72
|
+
|
73
|
+
class SimpleService < RestfulMapper::Service
|
74
|
+
base_url "http://localhost:8765"
|
75
|
+
|
76
|
+
post :simple_endpoint do
|
77
|
+
path "/simple/{{name}}"
|
78
|
+
body_parameter :body
|
79
|
+
responses 200 => true, 0=>false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
Calling 'SimpleService.simple_endpoint name: "test", body: {'a' => 'b'}' will perform POST request to http://localhost:8765/simple/test with body '{"a":"b"}'.
|
84
|
+
|
85
|
+
### Mapping JSON response to Ruby objects
|
86
|
+
Thanks to structure_mapper, one can easily transform JSON returned by the service into Ruby object. First we
|
87
|
+
need definition of the object:
|
88
|
+
|
89
|
+
class SimpleResponse
|
90
|
+
include StructureMapper::Hash
|
91
|
+
|
92
|
+
attribute a: String
|
93
|
+
end
|
94
|
+
|
95
|
+
And then we need to use the defined object as response mapping:
|
96
|
+
|
97
|
+
class SimpleService < RestfulMapper::Service
|
98
|
+
base_url "http://localhost:8765"
|
99
|
+
|
100
|
+
get :simple_endpoint do
|
101
|
+
path "/simple"
|
102
|
+
responses 200 => SimpleResponse, 0 => false
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
If http://localhost:8765/simple returns 200 and a JSON body '{"a":"b"}' then calling 'SimpleService.simple_endpoint'
|
107
|
+
will return instance of SimpleResponse with property 'a' set to 'b'.
|
108
|
+
|
109
|
+
### Basic Authentication
|
110
|
+
TBD
|
111
|
+
|
112
|
+
### Raising Exception
|
113
|
+
TBD
|
114
|
+
|
115
|
+
## Contributing
|
116
|
+
|
117
|
+
1. Fork it ( http://github.com/<my-github-username>/restful_mapper/fork )
|
118
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
119
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
120
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
121
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'restful_mapper'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
class Dependency
|
5
|
+
include StructureMapper::Hash
|
6
|
+
attribute name: String
|
7
|
+
attribute requirements: String
|
8
|
+
end
|
9
|
+
|
10
|
+
class RubyGemInfo
|
11
|
+
include StructureMapper::Hash
|
12
|
+
attribute name: String
|
13
|
+
attribute downloads: Fixnum
|
14
|
+
attribute version: String
|
15
|
+
attribute version_downloads: Fixnum
|
16
|
+
attribute platform: String
|
17
|
+
attribute authors: String
|
18
|
+
attribute info: String
|
19
|
+
attribute licenses: []
|
20
|
+
attribute project_uri: String
|
21
|
+
attribute gem_uri: String
|
22
|
+
attribute homepage_uri: String
|
23
|
+
attribute wiki_uri: String
|
24
|
+
attribute documentation_uri: String
|
25
|
+
attribute mailing_list_uri: String
|
26
|
+
attribute source_code_uri: String
|
27
|
+
attribute bug_tracker_uri: String
|
28
|
+
attribute dependencies: {String => [Dependency]}
|
29
|
+
end
|
30
|
+
|
31
|
+
class RubyGemsService < RestfulMapper::Service
|
32
|
+
|
33
|
+
base_url 'https://rubygems.org/api/v1'
|
34
|
+
|
35
|
+
get :gems do
|
36
|
+
path 'gems/rails.json', {}
|
37
|
+
responses 200 => RubyGemInfo
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
pp RubyGemsService.gems({})
|
@@ -0,0 +1,49 @@
|
|
1
|
+
Feature: basic authentication
|
2
|
+
|
3
|
+
Scenario: basic authentication of a GET request
|
4
|
+
Given following service definition
|
5
|
+
"""
|
6
|
+
class SimpleService < RestfulMapper::Service
|
7
|
+
base_url "http://localhost:8765"
|
8
|
+
|
9
|
+
default_parameters name: 'test'
|
10
|
+
|
11
|
+
basic_authentication 'username', 'password'
|
12
|
+
|
13
|
+
get :simple_endpoint do
|
14
|
+
path "/simple"
|
15
|
+
|
16
|
+
query_parameters [:name]
|
17
|
+
|
18
|
+
responses 302 => true
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
"""
|
23
|
+
And the service endpoint at port 8765 is running
|
24
|
+
When I call service "SimpleService.simple_endpoint"
|
25
|
+
And the endpoint should receive request header "Authorization" with value "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
|
26
|
+
|
27
|
+
Scenario: basic authentication using parameters for username and password
|
28
|
+
Given following service definition
|
29
|
+
"""
|
30
|
+
class SimpleService < RestfulMapper::Service
|
31
|
+
base_url "http://localhost:8765"
|
32
|
+
|
33
|
+
default_parameters name: 'test'
|
34
|
+
|
35
|
+
basic_authentication :user, :password
|
36
|
+
|
37
|
+
get :simple_endpoint do
|
38
|
+
path "/simple"
|
39
|
+
|
40
|
+
query_parameters [:name]
|
41
|
+
|
42
|
+
responses 302 => true
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
"""
|
47
|
+
And the service endpoint at port 8765 is running
|
48
|
+
When I call service "SimpleService.simple_endpoint user: 'username', password: 'password' "
|
49
|
+
And the endpoint should receive request header "Authorization" with value "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
|
@@ -0,0 +1,55 @@
|
|
1
|
+
Feature: default parameters
|
2
|
+
|
3
|
+
Scenario: simple get request with default request parameters
|
4
|
+
Given following service definition
|
5
|
+
"""
|
6
|
+
class SimpleService < RestfulMapper::Service
|
7
|
+
base_url "http://localhost:8765"
|
8
|
+
|
9
|
+
default_parameters name: 'test'
|
10
|
+
|
11
|
+
get :simple_endpoint do
|
12
|
+
path "/simple"
|
13
|
+
|
14
|
+
query_parameters [:name]
|
15
|
+
|
16
|
+
responses 302 => true
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
"""
|
21
|
+
And the service endpoint at port 8765 is running
|
22
|
+
When I call service "SimpleService.simple_endpoint"
|
23
|
+
Then the endpoint should receive request
|
24
|
+
"""
|
25
|
+
GET /simple?name=test HTTP/1.1
|
26
|
+
|
27
|
+
"""
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
Scenario: overriding default parameters with method parameters
|
32
|
+
Given following service definition
|
33
|
+
"""
|
34
|
+
class SimpleService < RestfulMapper::Service
|
35
|
+
base_url "http://localhost:8765"
|
36
|
+
|
37
|
+
default_parameters name: 'test'
|
38
|
+
|
39
|
+
get :simple_endpoint do
|
40
|
+
path "/simple"
|
41
|
+
|
42
|
+
query_parameters [:name]
|
43
|
+
|
44
|
+
responses 302 => true
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
"""
|
49
|
+
And the service endpoint at port 8765 is running
|
50
|
+
When I call service "SimpleService.simple_endpoint name: 'test2'"
|
51
|
+
Then the endpoint should receive request
|
52
|
+
"""
|
53
|
+
GET /simple?name=test2 HTTP/1.1
|
54
|
+
|
55
|
+
"""
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Feature: delete request
|
2
|
+
|
3
|
+
Scenario: simple get request with a body and no other parameters that returns 200
|
4
|
+
Given following service definition
|
5
|
+
"""
|
6
|
+
class SimpleService < RestfulMapper::Service
|
7
|
+
base_url "http://localhost:8765"
|
8
|
+
|
9
|
+
delete :simple_endpoint do
|
10
|
+
path "/simple"
|
11
|
+
|
12
|
+
responses 201 => true
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
"""
|
17
|
+
And the service endpoint at port 8765 is running
|
18
|
+
When I call service "SimpleService.simple_endpoint"
|
19
|
+
And the endpoint should receive request
|
20
|
+
"""
|
21
|
+
DELETE /simple HTTP/1.1
|
22
|
+
"""
|
23
|
+
|
24
|
+
|
25
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
Feature: get request
|
2
|
+
|
3
|
+
Scenario: simple get request without any parameters that returns 200
|
4
|
+
Given following service definition
|
5
|
+
"""
|
6
|
+
class SimpleService < RestfulMapper::Service
|
7
|
+
base_url "http://localhost:8765"
|
8
|
+
|
9
|
+
get :simple_endpoint do
|
10
|
+
path "/simple"
|
11
|
+
|
12
|
+
responses 200 => {String => String}
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
"""
|
17
|
+
And the service endpoint at port 8765 responds with following http response:
|
18
|
+
"""
|
19
|
+
HTTP/1.1 200 OK
|
20
|
+
Connection: close
|
21
|
+
Content-Type: application/json
|
22
|
+
|
23
|
+
{"a": "b"}
|
24
|
+
"""
|
25
|
+
When I call service "SimpleService.simple_endpoint"
|
26
|
+
Then the result should be equal to:
|
27
|
+
"""
|
28
|
+
{"a" => "b"}
|
29
|
+
"""
|
30
|
+
|
31
|
+
Scenario: simple get request without any parameters that returns 302
|
32
|
+
Given following service definition
|
33
|
+
"""
|
34
|
+
class SimpleService < RestfulMapper::Service
|
35
|
+
base_url "http://localhost:8765"
|
36
|
+
|
37
|
+
get :simple_endpoint do
|
38
|
+
path "/simple"
|
39
|
+
|
40
|
+
responses 302 => true
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
"""
|
45
|
+
And the service endpoint at port 8765 responds with following http response:
|
46
|
+
"""
|
47
|
+
HTTP/1.1 302 OK
|
48
|
+
Connection: close
|
49
|
+
Content-Type: application/json
|
50
|
+
|
51
|
+
"""
|
52
|
+
When I call service "SimpleService.simple_endpoint"
|
53
|
+
Then the result should be equal to:
|
54
|
+
"""
|
55
|
+
true
|
56
|
+
"""
|
57
|
+
|
58
|
+
Scenario: simple get request with request parameters
|
59
|
+
Given following service definition
|
60
|
+
"""
|
61
|
+
class SimpleService < RestfulMapper::Service
|
62
|
+
base_url "http://localhost:8765"
|
63
|
+
|
64
|
+
get :simple_endpoint do
|
65
|
+
path "/simple"
|
66
|
+
|
67
|
+
query_parameters [:name]
|
68
|
+
|
69
|
+
responses 302 => true
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
"""
|
74
|
+
And the service endpoint at port 8765 is running
|
75
|
+
When I call service "SimpleService.simple_endpoint name: 'test'"
|
76
|
+
Then the endpoint should receive request
|
77
|
+
"""
|
78
|
+
GET /simple?name=test HTTP/1.1
|
79
|
+
|
80
|
+
"""
|
81
|
+
|
82
|
+
Scenario: simple get request with path parameters
|
83
|
+
Given following service definition
|
84
|
+
"""
|
85
|
+
class SimpleService < RestfulMapper::Service
|
86
|
+
base_url "http://localhost:8765"
|
87
|
+
|
88
|
+
get :simple_endpoint do
|
89
|
+
path "/simple/{{name}}"
|
90
|
+
|
91
|
+
responses 302 => true
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
"""
|
96
|
+
And the service endpoint at port 8765 is running
|
97
|
+
When I call service "SimpleService.simple_endpoint name: 'test'"
|
98
|
+
Then the endpoint should receive request
|
99
|
+
"""
|
100
|
+
GET /simple/test HTTP/1.1
|
101
|
+
|
102
|
+
"""
|
103
|
+
|
104
|
+
Scenario: simple get request with path and query parameters
|
105
|
+
Given following service definition
|
106
|
+
"""
|
107
|
+
class SimpleService < RestfulMapper::Service
|
108
|
+
base_url "http://localhost:8765"
|
109
|
+
|
110
|
+
get :simple_endpoint do
|
111
|
+
path "/simple/{{name}}"
|
112
|
+
query_parameters [:name]
|
113
|
+
responses 302 => true
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
"""
|
118
|
+
And the service endpoint at port 8765 is running
|
119
|
+
When I call service "SimpleService.simple_endpoint name: 'test'"
|
120
|
+
Then the endpoint should receive request
|
121
|
+
"""
|
122
|
+
GET /simple/test?name=test HTTP/1.1
|
123
|
+
|
124
|
+
"""
|
@@ -0,0 +1,31 @@
|
|
1
|
+
Feature: post request
|
2
|
+
|
3
|
+
Scenario: simple get request with a body and no other parameters that returns 200
|
4
|
+
Given following service definition
|
5
|
+
"""
|
6
|
+
class SimpleService < RestfulMapper::Service
|
7
|
+
base_url "http://localhost:8765"
|
8
|
+
|
9
|
+
post :simple_endpoint do
|
10
|
+
path "/simple"
|
11
|
+
|
12
|
+
body_parameter :body
|
13
|
+
|
14
|
+
responses 201 => true
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
"""
|
19
|
+
And the service endpoint at port 8765 is running
|
20
|
+
When I call service "SimpleService.simple_endpoint body: {'p1' => 'v1'}"
|
21
|
+
And the endpoint should receive request
|
22
|
+
"""
|
23
|
+
POST /simple HTTP/1.1
|
24
|
+
"""
|
25
|
+
And request body should be
|
26
|
+
"""
|
27
|
+
{"p1":"v1"}
|
28
|
+
"""
|
29
|
+
|
30
|
+
|
31
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
Feature: put request
|
2
|
+
|
3
|
+
Scenario: simple get request with a body and no other parameters that returns 200
|
4
|
+
Given following service definition
|
5
|
+
"""
|
6
|
+
class SimpleService < RestfulMapper::Service
|
7
|
+
base_url "http://localhost:8765"
|
8
|
+
|
9
|
+
put :simple_endpoint do
|
10
|
+
path "/simple"
|
11
|
+
|
12
|
+
body_parameter :body
|
13
|
+
|
14
|
+
responses 201 => true
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
"""
|
19
|
+
And the service endpoint at port 8765 is running
|
20
|
+
When I call service "SimpleService.simple_endpoint body: {'p1' => 'v1'}"
|
21
|
+
And the endpoint should receive request
|
22
|
+
"""
|
23
|
+
PUT /simple HTTP/1.1
|
24
|
+
"""
|
25
|
+
And request body should be
|
26
|
+
"""
|
27
|
+
{"p1":"v1"}
|
28
|
+
"""
|
29
|
+
|
30
|
+
|
31
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
Feature: raising exception when return structure is an instance of exception
|
2
|
+
|
3
|
+
|
4
|
+
Scenario: raising exception when structure is exception
|
5
|
+
Given following service definition
|
6
|
+
"""
|
7
|
+
class ExceptionResponse < Exception
|
8
|
+
include StructureMapper::Hash
|
9
|
+
attribute a: String
|
10
|
+
end
|
11
|
+
class SimpleService < RestfulMapper::Service
|
12
|
+
base_url "http://localhost:8765"
|
13
|
+
|
14
|
+
put :simple_endpoint do
|
15
|
+
path "/simple"
|
16
|
+
|
17
|
+
body_parameter :body
|
18
|
+
|
19
|
+
responses 201 => ExceptionResponse
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
"""
|
24
|
+
And the service endpoint at port 8765 is running
|
25
|
+
When I call service "SimpleService.simple_endpoint body: {'p1' => 'v1'}" expecting exception
|
26
|
+
And exception should be raised
|
27
|
+
"""
|
28
|
+
ExceptionResponse.new
|
29
|
+
"""
|
30
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
Given(/^following service definition$/) do |code|
|
2
|
+
pretext="require 'restful_mapper'\n"
|
3
|
+
@module.module_eval "%s%s" % [pretext,code], "cucuber", 1
|
4
|
+
end
|
5
|
+
|
6
|
+
Given(/^the service endpoint at port (\d+) responds with following http response:$/) do |port, response|
|
7
|
+
server=TCPServer.new port.to_i
|
8
|
+
go! do
|
9
|
+
connection=server.accept
|
10
|
+
connection.readline
|
11
|
+
connection.write(response)
|
12
|
+
connection.close
|
13
|
+
server.close
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
When(/^I call service "(.*?)"$/) do |code|
|
19
|
+
@result=@module.module_eval code
|
20
|
+
end
|
21
|
+
|
22
|
+
Then(/^the result should be equal to:$/) do |expected|
|
23
|
+
@result.should == @module.module_eval(expected)
|
24
|
+
end
|
25
|
+
|
26
|
+
Given(/^the service endpoint at port (\d+) is running$/) do |port|
|
27
|
+
server=TCPServer.new port.to_i
|
28
|
+
response = "HTTP/1.1 201 OK\n\n"
|
29
|
+
channel=channel! String,1
|
30
|
+
response_channel=channel! String, 1
|
31
|
+
headers_channel=channel! Hash, 1
|
32
|
+
@server_channel=channel
|
33
|
+
@response_body_channel=response_channel
|
34
|
+
@response_headers_channel=headers_channel
|
35
|
+
|
36
|
+
go! do
|
37
|
+
connection=server.accept
|
38
|
+
channel << connection.readline()
|
39
|
+
content_length=nil
|
40
|
+
headers={}
|
41
|
+
while (line=connection.readline()) != "\r\n"
|
42
|
+
# puts "line: %s" % line
|
43
|
+
name, value=line.split(":",2)
|
44
|
+
if name == "Content-Length"
|
45
|
+
content_length=value.to_i
|
46
|
+
# puts "content length: %s" % content_length
|
47
|
+
end
|
48
|
+
headers[name]=value.gsub(/\n|\r/,'').strip
|
49
|
+
end
|
50
|
+
|
51
|
+
headers_channel << headers
|
52
|
+
|
53
|
+
# puts "content length: %d" % content_length
|
54
|
+
|
55
|
+
data=(content_length ? connection.read(content_length) : "")
|
56
|
+
|
57
|
+
# puts "data: %s" % data
|
58
|
+
|
59
|
+
response_channel << data
|
60
|
+
|
61
|
+
connection.write(response)
|
62
|
+
connection.close
|
63
|
+
server.close
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
Then(/^the endpoint should receive request$/) do |request|
|
69
|
+
@server_channel.receive.first.gsub(/\r|\n/,'').should == request.gsub(/\r|\n/,'')
|
70
|
+
end
|
71
|
+
|
72
|
+
When(/^request body should be$/) do |expected|
|
73
|
+
@response_body_channel.receive.first.should == expected
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
When(/^exception should be raised$/) do |code|
|
2
|
+
@exception.should == @module.module_eval(code)
|
3
|
+
end
|
4
|
+
|
5
|
+
When(/^I call service "(.*?)" expecting exception$/) do |code|
|
6
|
+
failed=true
|
7
|
+
begin
|
8
|
+
@result=@module.module_eval code
|
9
|
+
failed=false
|
10
|
+
rescue Exception => e
|
11
|
+
@exception=e
|
12
|
+
end
|
13
|
+
|
14
|
+
raise "expected to raise exception" unless failed
|
15
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'json'
|
3
|
+
require "restful_mapper/version"
|
4
|
+
require 'structure_mapper'
|
5
|
+
require 'multi_json'
|
6
|
+
require 'mustache'
|
7
|
+
|
8
|
+
|
9
|
+
class Object # http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
|
10
|
+
def meta_def name, &blk
|
11
|
+
(class << self; self; end).instance_eval { define_method name, &blk }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
module RestfulMapper
|
17
|
+
|
18
|
+
class EndpointDefinition
|
19
|
+
|
20
|
+
def initialize base_url, method, basic_authentication
|
21
|
+
@base_url=base_url
|
22
|
+
@query_parameters=[]
|
23
|
+
@method=method
|
24
|
+
@basic_authentication=basic_authentication
|
25
|
+
end
|
26
|
+
|
27
|
+
def path path, options={}
|
28
|
+
@path=path
|
29
|
+
end
|
30
|
+
|
31
|
+
def responses response_mapping={}
|
32
|
+
@response_mapping=response_mapping
|
33
|
+
end
|
34
|
+
|
35
|
+
def body_parameter body_parameter
|
36
|
+
@body_parameter=body_parameter
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def query_parameters parameters
|
41
|
+
@query_parameters=parameters.dup
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def filter_parameters hash
|
46
|
+
hash.dup.delete_if do |k,v|
|
47
|
+
not (@query_parameters.include?(k.to_sym) || @query_parameters.include?(k.to_s))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def call_service params
|
52
|
+
conn = Faraday.new(:url => @base_url) do |faraday|
|
53
|
+
# faraday.response :logger # log requests to STDOUT
|
54
|
+
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
|
55
|
+
end
|
56
|
+
|
57
|
+
if has_basic_authentication?
|
58
|
+
conn.basic_auth(*basic_authentication_data(params))
|
59
|
+
end
|
60
|
+
|
61
|
+
response=conn.run_request(@method, Mustache.render(@path, params), nil, {'Content-Type' => 'application/json'}) { |request|
|
62
|
+
request.params.update(filter_parameters(params)) if filter_parameters(params)
|
63
|
+
if @body_parameter
|
64
|
+
request.body=MultiJson.dump(params[@body_parameter].to_structure)
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
}
|
69
|
+
|
70
|
+
|
71
|
+
status=response.status
|
72
|
+
status_class=@response_mapping[status]
|
73
|
+
status_class||=@response_mapping[0]
|
74
|
+
body=response.body
|
75
|
+
result=deserialize body, status_class
|
76
|
+
if Exception === result
|
77
|
+
raise result
|
78
|
+
end
|
79
|
+
result
|
80
|
+
end
|
81
|
+
|
82
|
+
def has_basic_authentication?
|
83
|
+
@basic_authentication
|
84
|
+
end
|
85
|
+
|
86
|
+
def basic_authentication_data params
|
87
|
+
@basic_authentication.map{|name| Symbol === name ? params[name] : name}
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def deserialize json, mapping
|
92
|
+
if json && !json.empty?
|
93
|
+
mapping.from_structure(MultiJson.load(json))
|
94
|
+
else
|
95
|
+
if Hash == mapping || Array == mapping || mapping.is_a?(Class)
|
96
|
+
mapping.new
|
97
|
+
else
|
98
|
+
mapping
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
class Service
|
110
|
+
|
111
|
+
def self.base_url base_url
|
112
|
+
@base_url=base_url
|
113
|
+
end
|
114
|
+
|
115
|
+
attr_reader :base_url
|
116
|
+
|
117
|
+
|
118
|
+
def self.get name, &definition
|
119
|
+
service_method name, definition, :get
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.post name, &definition
|
123
|
+
service_method name, definition, :post
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.put name, &definition
|
127
|
+
service_method name, definition, :put
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.delete name, &definition
|
131
|
+
service_method name, definition, :delete
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.default_parameters parameters
|
135
|
+
@default_parameters=parameters
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.basic_authentication username, password
|
139
|
+
@basic_authentication=[username,password]
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def self.service_method name, definition, method
|
145
|
+
endpoint_definition=EndpointDefinition.new @base_url, method, @basic_authentication
|
146
|
+
endpoint_definition.instance_exec(&definition)
|
147
|
+
self.meta_def(name.to_sym) do |params={}|
|
148
|
+
copy=(@default_parameters || {}).merge(params)
|
149
|
+
endpoint_definition.call_service copy
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'restful_mapper/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "restful_mapper"
|
8
|
+
spec.version = RestfulMapper::VERSION
|
9
|
+
spec.authors = ["Dragan Milic"]
|
10
|
+
spec.email = ["dragan@netice9.com"]
|
11
|
+
spec.summary = %q{Mapper of RESTful services to ruby objects. Makes calling RESTful services and parsing responses a breeze.}
|
12
|
+
spec.description = %q{Mapper of RESTful services to ruby objects. Makes calling RESTful services and parsing responses a breeze.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "autotest"
|
25
|
+
spec.add_development_dependency "cucumber"
|
26
|
+
spec.add_development_dependency "puma"
|
27
|
+
spec.add_development_dependency "sinatra"
|
28
|
+
spec.add_development_dependency "agent"
|
29
|
+
spec.add_dependency "faraday"
|
30
|
+
spec.add_dependency "multi_json"
|
31
|
+
spec.add_dependency "structure_mapper"
|
32
|
+
spec.add_dependency "mustache"
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: restful_mapper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dragan Milic
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: autotest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: cucumber
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: puma
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sinatra
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: agent
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: faraday
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: multi_json
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: structure_mapper
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: mustache
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :runtime
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
description: Mapper of RESTful services to ruby objects. Makes calling RESTful services
|
182
|
+
and parsing responses a breeze.
|
183
|
+
email:
|
184
|
+
- dragan@netice9.com
|
185
|
+
executables: []
|
186
|
+
extensions: []
|
187
|
+
extra_rdoc_files: []
|
188
|
+
files:
|
189
|
+
- ".gitignore"
|
190
|
+
- Gemfile
|
191
|
+
- LICENSE.txt
|
192
|
+
- README.md
|
193
|
+
- Rakefile
|
194
|
+
- examples/rubygems.rb
|
195
|
+
- features/basic_authentication.feature
|
196
|
+
- features/default_parameters.feature
|
197
|
+
- features/delete_request.feature
|
198
|
+
- features/get_request.feature
|
199
|
+
- features/post_request.feature
|
200
|
+
- features/put_request.feature
|
201
|
+
- features/raising_exceptions.feature
|
202
|
+
- features/step_definitions/basic_authentication_steps.rb
|
203
|
+
- features/step_definitions/get_request_steps.rb
|
204
|
+
- features/step_definitions/raising_exceptions_steps.rb
|
205
|
+
- features/support/env.rb
|
206
|
+
- lib/restful_mapper.rb
|
207
|
+
- lib/restful_mapper/version.rb
|
208
|
+
- restful_mapper.gemspec
|
209
|
+
homepage: ''
|
210
|
+
licenses:
|
211
|
+
- MIT
|
212
|
+
metadata: {}
|
213
|
+
post_install_message:
|
214
|
+
rdoc_options: []
|
215
|
+
require_paths:
|
216
|
+
- lib
|
217
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - ">="
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '0'
|
222
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
223
|
+
requirements:
|
224
|
+
- - ">="
|
225
|
+
- !ruby/object:Gem::Version
|
226
|
+
version: '0'
|
227
|
+
requirements: []
|
228
|
+
rubyforge_project:
|
229
|
+
rubygems_version: 2.2.2
|
230
|
+
signing_key:
|
231
|
+
specification_version: 4
|
232
|
+
summary: Mapper of RESTful services to ruby objects. Makes calling RESTful services
|
233
|
+
and parsing responses a breeze.
|
234
|
+
test_files:
|
235
|
+
- features/basic_authentication.feature
|
236
|
+
- features/default_parameters.feature
|
237
|
+
- features/delete_request.feature
|
238
|
+
- features/get_request.feature
|
239
|
+
- features/post_request.feature
|
240
|
+
- features/put_request.feature
|
241
|
+
- features/raising_exceptions.feature
|
242
|
+
- features/step_definitions/basic_authentication_steps.rb
|
243
|
+
- features/step_definitions/get_request_steps.rb
|
244
|
+
- features/step_definitions/raising_exceptions_steps.rb
|
245
|
+
- features/support/env.rb
|