sinatra-rabbit 0.9 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +137 -2
- data/lib/sinatra/rabbit.rb +6 -0
- data/lib/sinatra/rabbit/base.rb +192 -21
- data/lib/sinatra/rabbit/base_collection.rb +23 -8
- data/lib/sinatra/rabbit/dsl.rb +11 -1
- data/lib/sinatra/rabbit/features.rb +69 -0
- data/lib/sinatra/rabbit/param.rb +8 -6
- data/sinatra-rabbit.gemspec +1 -1
- metadata +6 -5
data/README.md
CHANGED
@@ -5,7 +5,7 @@ Sinatra::Rabbit is a [Sinatra](http://www.sinatrarb.com) extensions that
|
|
5
5
|
makes designing a [REST API](http://en.wikipedia.org/wiki/Representational_State_Transfer)
|
6
6
|
much easier.
|
7
7
|
|
8
|
-
Rabbit
|
8
|
+
Rabbit treats REST resource as 'collection'. Every collection then
|
9
9
|
could have multiple operations. This simple DSL is then transformed into
|
10
10
|
a proper modular [Sinatra::Base](http://www.sinatrarb.com/intro#Serving%20a%20Modular%20Application)
|
11
11
|
application that can be launched within any [Rack](http://rack.rubyforge.org/) container (including Rails).
|
@@ -15,6 +15,129 @@ project. However this is an complete rewrite of the original Deltacloud Rabbit.
|
|
15
15
|
The goal is to make Rabbit highly modular, so it can be used in other projects
|
16
16
|
as well as in Deltacloud.
|
17
17
|
|
18
|
+
Full documentation: [rubydoc.info/github/mifo/sinatra-rabbit](http://rubydoc.info/github/mifo/sinatra-rabbit/master)
|
19
|
+
|
20
|
+
Features
|
21
|
+
-------
|
22
|
+
|
23
|
+
* Automatically generate proper path and use right HTTP method for all CRUD operations:
|
24
|
+
* `operation :create` is being mapped as 'POST /:collection'
|
25
|
+
* `operation :index` is being mapped as 'GET /:collection'
|
26
|
+
* `operation :show` is being mapped as 'GET /:collection/:id'
|
27
|
+
* `operation :update` is being mapped as 'PUT /:collection/:id'
|
28
|
+
* `operation :destroy` is being mapped as 'DELETE /:collection/:id'
|
29
|
+
* Use of Sinatra route conditions
|
30
|
+
<pre>
|
31
|
+
<code>
|
32
|
+
operation :stop, :if => driver.support_stop_operation? do
|
33
|
+
description "Stop virtual machine"
|
34
|
+
param :id, :string, :required, "Virtual Machine ID"
|
35
|
+
control do
|
36
|
+
# ...
|
37
|
+
end
|
38
|
+
end
|
39
|
+
</code>
|
40
|
+
</pre>
|
41
|
+
* Support operation specific features
|
42
|
+
* Support REST subcollections
|
43
|
+
<pre>
|
44
|
+
<code>
|
45
|
+
collection :buckets do
|
46
|
+
collection :blobs, :with_id => :bucket_id do
|
47
|
+
description "Blobs are binary objects stored in buckets"
|
48
|
+
|
49
|
+
# GET /buckets/:bucket_id/blobs/:id
|
50
|
+
operation :show do
|
51
|
+
description "Show content of blob"
|
52
|
+
param :id, :string, :required
|
53
|
+
control { #... }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# GET /buckets/:id
|
58
|
+
operation :show do
|
59
|
+
description "Show content of bucket"
|
60
|
+
param :id, :string, :required
|
61
|
+
control { #... }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
</code>
|
65
|
+
</pre>
|
66
|
+
* Generate HEAD routes for all operations and collections
|
67
|
+
* Generate OPTIONS routes for all operations and collections
|
68
|
+
* `OPTIONS /:collection` will return list of all operations (using 'Allow' header)
|
69
|
+
* `OPTIONS /:collection/:operation` will return list of parameters defined for given operation (using 'Allow' header)
|
70
|
+
* Include bunch of tests to prove it work correctly
|
71
|
+
|
72
|
+
Configuration
|
73
|
+
-------
|
74
|
+
|
75
|
+
Rabbit behavior can be customized using `configure` method:
|
76
|
+
|
77
|
+
Sinatra::Rabbit.configure do
|
78
|
+
disable :head_routes # This will disable automatic generation of HEAD routes
|
79
|
+
disable :options_routes # This will disable automatic generation of OPTIONS routes
|
80
|
+
disable :documentation # This will disable automatic generation of documentation routes
|
81
|
+
end
|
82
|
+
|
83
|
+
Operation specific 'features'
|
84
|
+
--------
|
85
|
+
|
86
|
+
In some specific use-cases, you would like to have different set of parameters
|
87
|
+
for operation, based on some condition of your system.
|
88
|
+
To give you and example, in Deltacloud API project, we have support for
|
89
|
+
different drivers. The project aims to give user full cross-cloud abstraction
|
90
|
+
layer, however for some cloud providers the full abstraction is not possible and
|
91
|
+
in this case we need to handle this using 'features'. It mean that some
|
92
|
+
particular operations can have 'feature' assigned. If this 'feature' is
|
93
|
+
supported by underlaying driver, then we add new parameters to that operations.
|
94
|
+
|
95
|
+
In sinatra-rabbit, you can define features for every model in this way:
|
96
|
+
|
97
|
+
class Sample < Sinatra::Base
|
98
|
+
include Sinatra::Rabbit
|
99
|
+
include Sinatra::Rabbit::Features
|
100
|
+
|
101
|
+
features do
|
102
|
+
feature :user_name, :for => :instances do
|
103
|
+
operation :create do
|
104
|
+
param :user_name, :string, :optional, "Allow to define user-defined name"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
You can also wrap features into 'if' block or put some conditions on top
|
111
|
+
of this block to make it more flexible.
|
112
|
+
|
113
|
+
Tests
|
114
|
+
------
|
115
|
+
|
116
|
+
Project include set of minitest files located under 'tests/' directory. You can
|
117
|
+
launch them by typing:
|
118
|
+
|
119
|
+
$ rake test:all
|
120
|
+
|
121
|
+
If you want to run just DSL tests:
|
122
|
+
|
123
|
+
$ rake test:dsl
|
124
|
+
|
125
|
+
If you want to test if Sinatra::Base works on your system correctly with Rabbit:
|
126
|
+
|
127
|
+
$ rake test:app
|
128
|
+
|
129
|
+
If you want to see code coverage, you must install the *simplecov* gem:
|
130
|
+
|
131
|
+
$ gem install simplecov
|
132
|
+
$ rake coverage
|
133
|
+
|
134
|
+
NOTE: The coverage reports are supported only on Ruby 1.9
|
135
|
+
|
136
|
+
TODO
|
137
|
+
-------
|
138
|
+
|
139
|
+
* Add documentation generation and routes for Rabbit based applications
|
140
|
+
|
18
141
|
Installation
|
19
142
|
-------
|
20
143
|
|
@@ -32,7 +155,18 @@ Usage
|
|
32
155
|
collection :images do
|
33
156
|
description "Images description"
|
34
157
|
|
35
|
-
|
158
|
+
collection :subcollection do
|
159
|
+
description "Images subcollection"
|
160
|
+
|
161
|
+
operation :index do
|
162
|
+
description "Hello world"
|
163
|
+
control do
|
164
|
+
status 200
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
operation :index, :if => lambda { 1 == 1} do
|
36
170
|
description "Index operation description"
|
37
171
|
control do
|
38
172
|
status 200
|
@@ -50,6 +184,7 @@ Usage
|
|
50
184
|
"Hey #{params[:id]}"
|
51
185
|
end
|
52
186
|
end
|
187
|
+
end
|
53
188
|
end
|
54
189
|
|
55
190
|
MySinatraApp.run!
|
data/lib/sinatra/rabbit.rb
CHANGED
data/lib/sinatra/rabbit/base.rb
CHANGED
@@ -14,25 +14,74 @@
|
|
14
14
|
# License for the specific language governing permissions and limitations
|
15
15
|
# under the License.
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
require
|
17
|
+
unless Kernel.respond_to?(:require_relative)
|
18
|
+
module Kernel
|
19
|
+
def require_relative(path)
|
20
|
+
require File.join(File.dirname(caller[0]), path.to_str)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require_relative './dsl'
|
26
|
+
require_relative './param'
|
27
|
+
require_relative './base_collection'
|
28
|
+
require_relative './validator'
|
29
|
+
require_relative './features'
|
21
30
|
|
22
31
|
module Sinatra
|
23
32
|
module Rabbit
|
24
33
|
|
25
34
|
STANDARD_OPERATIONS = {
|
26
|
-
:create => {
|
27
|
-
|
28
|
-
|
29
|
-
|
35
|
+
:create => {
|
36
|
+
:member => false,
|
37
|
+
:method => :post,
|
38
|
+
:collection => true
|
39
|
+
},
|
40
|
+
:show => {
|
41
|
+
:member => false,
|
42
|
+
:method => :get,
|
43
|
+
:required_params => [ :id ]
|
44
|
+
},
|
45
|
+
:destroy => {
|
46
|
+
:member => false,
|
47
|
+
:method => :delete,
|
48
|
+
:required_params => [ :id ]
|
49
|
+
},
|
50
|
+
:index => {
|
51
|
+
:member => false,
|
52
|
+
:method => :get,
|
53
|
+
:collection => true
|
54
|
+
}
|
30
55
|
}
|
31
56
|
|
32
57
|
def self.configure(&block)
|
33
58
|
instance_eval(&block)
|
34
59
|
end
|
35
60
|
|
61
|
+
def self.configuration
|
62
|
+
@configuration || {}
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.enable(property)
|
66
|
+
@configuration[property] = true
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.enabled?(property)
|
70
|
+
!@configuration[property].nil? and @configuration[property] == true
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.disabled?(property)
|
74
|
+
!@configuration[property].nil? and @configuration[property] == false
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.disable(property)
|
78
|
+
@configuration[property] = false
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.set(property, value)
|
82
|
+
@configuration[property] = value
|
83
|
+
end
|
84
|
+
|
36
85
|
# Automatically register the DSL to Sinatra::Base if this
|
37
86
|
# module is included:
|
38
87
|
#
|
@@ -43,30 +92,126 @@ module Sinatra
|
|
43
92
|
# end
|
44
93
|
#
|
45
94
|
def self.included(base)
|
95
|
+
@configuration ||= {
|
96
|
+
:root_path => '/'
|
97
|
+
}
|
46
98
|
base.register(DSL)
|
47
99
|
end
|
48
100
|
|
49
101
|
class Collection < BaseCollection
|
50
102
|
|
51
|
-
|
52
|
-
|
103
|
+
include DSL
|
104
|
+
|
105
|
+
def self.generate(name, parent_collection=nil, &block)
|
106
|
+
@collection_name = name.to_sym
|
107
|
+
@collections ||= []
|
108
|
+
@parent_collection = parent_collection
|
53
109
|
class_eval(&block)
|
110
|
+
send(:head, full_path, {}) { status 200 } unless Rabbit.disabled? :head_routes
|
111
|
+
send(:options, full_path, {}) do
|
112
|
+
[200, { 'Allow' => operations.map { |o| o.operation_name }.join(',') }, '']
|
113
|
+
end unless Rabbit.disabled? :options_routes
|
54
114
|
self
|
55
115
|
end
|
56
116
|
|
57
|
-
def self.
|
117
|
+
def self.collection(name, opts={}, &block)
|
118
|
+
unless block_given?
|
119
|
+
return @collections.find { |c| c.collection_name == name }
|
120
|
+
end
|
121
|
+
current_collection = BaseCollection.collection_class(name, self)
|
122
|
+
current_collection.set_base_class(self.base_class)
|
123
|
+
current_collection.with_id!(opts.delete(:with_id)) if opts.has_key? :with_id
|
124
|
+
current_collection.generate(name, self, &block)
|
125
|
+
@collections << current_collection
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.parent_routes
|
129
|
+
return '' if @parent_collection.nil?
|
130
|
+
route = ["#{@parent_collection.collection_name}"]
|
131
|
+
current_parent = @parent_collection
|
132
|
+
while current_parent = current_parent.parent_collection
|
133
|
+
route << current_parent.collection_name
|
134
|
+
end
|
135
|
+
route.reverse.join('/')+'/'
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.set_base_class(klass)
|
139
|
+
@klass = klass
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.control(*args)
|
143
|
+
raise "The 'control' statement must be used only within context of Operation"
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.features
|
147
|
+
return [] unless base_class.respond_to? :features
|
148
|
+
base_class.features.select { |f| f.collection == collection_name }
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.with_id!(id)
|
152
|
+
@with_id = ":#{id}"
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.path
|
156
|
+
with_id_param = @with_id.nil? ? '' : ':id/'
|
157
|
+
parent_routes + with_id_param + collection_name.to_s
|
158
|
+
end
|
58
159
|
|
59
|
-
def self.
|
160
|
+
def self.base_class;@klass;end
|
161
|
+
def self.full_path;root_path + route_for(path);end
|
162
|
+
def self.collection_name; @collection_name; end
|
163
|
+
def self.parent_collection; @parent_collection; end
|
164
|
+
def self.collections; @collections; end
|
165
|
+
|
166
|
+
def self.description(text=nil)
|
167
|
+
return @description if text.nil?
|
60
168
|
@description = text
|
61
169
|
end
|
62
170
|
|
63
|
-
def self.
|
171
|
+
def self.documentation
|
172
|
+
Rabbit::Documentation.for_collection(self, operations)
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.operation(operation_name, opts={}, &block)
|
64
176
|
@operations ||= []
|
177
|
+
# Return operation when no block is given
|
178
|
+
return @operations.find { |o| o.operation_name == operation_name } unless block_given?
|
179
|
+
|
180
|
+
# Check if current operation is not already registred
|
65
181
|
if operation_registred?(operation_name)
|
66
|
-
raise "Operation #{operation_name} already
|
182
|
+
raise "Operation #{operation_name} already registered in #{self.name} collection"
|
183
|
+
end
|
184
|
+
|
185
|
+
# Create operation class
|
186
|
+
@operations << (operation = operation_class(self, operation_name).generate(self, operation_name, &block))
|
187
|
+
|
188
|
+
# Generate HEAD routes
|
189
|
+
unless Rabbit.disabled? :head_routes
|
190
|
+
send(:head, root_path + route_for(path, operation_name, {})) { status 200 }
|
191
|
+
end
|
192
|
+
|
193
|
+
# Add route conditions if defined
|
194
|
+
if opts.has_key? :if
|
195
|
+
base_class.send(:set, :if_true) do |value|
|
196
|
+
condition do
|
197
|
+
(value.kind_of?(Proc) ? value.call : value) == true
|
198
|
+
end
|
199
|
+
end
|
200
|
+
opts[:if_true] = opts.delete(:if)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Make the full_path method on operation return currect operation path
|
204
|
+
operation.set_route(root_path + route_for(path, operation_name, :id_name => @with_id || ':id'))
|
205
|
+
|
206
|
+
# Define Sinatra::Base route
|
207
|
+
base_class.send(http_method_for(operation_name), operation.full_path, opts, &operation.control)
|
208
|
+
|
209
|
+
# Generate OPTIONS routes
|
210
|
+
unless Rabbit.disabled? :options_routes
|
211
|
+
send(:options, root_path + route_for(path, operation_name, :member), {}) do
|
212
|
+
[200, { 'Allow' => operation.params.map { |p| p.to_s }.join(',') }, '']
|
213
|
+
end
|
67
214
|
end
|
68
|
-
operation = operation_class(self, operation_name).generate(self, operation_name, &block)
|
69
|
-
send(http_method_for(operation_name), route_for(path, operation_name), {}, &operation.control)
|
70
215
|
self
|
71
216
|
end
|
72
217
|
|
@@ -74,13 +219,33 @@ module Sinatra
|
|
74
219
|
|
75
220
|
class Operation
|
76
221
|
|
222
|
+
def self.set_route(path)
|
223
|
+
@operation_path = path
|
224
|
+
end
|
225
|
+
|
77
226
|
def self.generate(collection, name, &block)
|
78
|
-
@name, @params = name, []
|
227
|
+
@name, @params, @collection = name, [], collection
|
228
|
+
@collection.features.select { |f| f.operations.map { |o| o.name}.include?(@name) }.each do |feature|
|
229
|
+
feature.operations.each do |o|
|
230
|
+
instance_eval(&o.params)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
if Sinatra::Rabbit::STANDARD_OPERATIONS.has_key? name
|
234
|
+
required_params = Sinatra::Rabbit::STANDARD_OPERATIONS[name][:required_params]
|
235
|
+
required_params.each do |p|
|
236
|
+
param p, :required, :string, "The #{p} parameter"
|
237
|
+
end unless required_params.nil?
|
238
|
+
end
|
79
239
|
class_eval(&block)
|
240
|
+
description "#{name.to_s.capitalize} operation on #{@collection.name} collection" if description.nil?
|
80
241
|
self
|
81
242
|
end
|
82
243
|
|
83
|
-
def self.
|
244
|
+
def self.full_path; @operation_path; end
|
245
|
+
def self.operation_name; @name; end
|
246
|
+
|
247
|
+
def self.description(text=nil)
|
248
|
+
return @description if text.nil?
|
84
249
|
@description = text
|
85
250
|
end
|
86
251
|
|
@@ -91,8 +256,7 @@ module Sinatra
|
|
91
256
|
Rabbit::Validator.validate!(params, params_def)
|
92
257
|
rescue => e
|
93
258
|
if e.kind_of? Rabbit::Validator::ValidationError
|
94
|
-
|
95
|
-
halt
|
259
|
+
halt e.http_status_code, e.message
|
96
260
|
else
|
97
261
|
raise e
|
98
262
|
end
|
@@ -102,8 +266,11 @@ module Sinatra
|
|
102
266
|
end
|
103
267
|
|
104
268
|
def self.param(*args)
|
269
|
+
return @params.find { |p| p.name == args[0] } if args.size == 1
|
105
270
|
@params << Rabbit::Param.new(*args)
|
106
271
|
end
|
272
|
+
|
273
|
+
def self.params; @params; end
|
107
274
|
end
|
108
275
|
|
109
276
|
private
|
@@ -114,7 +281,11 @@ module Sinatra
|
|
114
281
|
|
115
282
|
# Create an unique class name for all operations within Collection class
|
116
283
|
def self.operation_class(collection_klass, operation_name)
|
117
|
-
|
284
|
+
begin
|
285
|
+
collection_klass.const_get(operation_name.to_s.camelize + 'Operation')
|
286
|
+
rescue NameError
|
287
|
+
collection_klass.const_set(operation_name.to_s.camelize + 'Operation', Class.new(Operation))
|
288
|
+
end
|
118
289
|
end
|
119
290
|
|
120
291
|
end
|
@@ -18,20 +18,26 @@ module Sinatra
|
|
18
18
|
module Rabbit
|
19
19
|
|
20
20
|
class BaseCollection < Sinatra::Base
|
21
|
-
attr_accessor :name, :description
|
22
|
-
attr_accessor :operations
|
23
|
-
|
24
21
|
set :views, Proc.new { File.join(File::dirname(__FILE__), "..", "..", "views") }
|
25
22
|
enable :method_overide
|
26
23
|
|
27
|
-
def self.route_for(collection, operation_name=nil)
|
24
|
+
def self.route_for(collection, operation_name=nil, member=:member)
|
28
25
|
if operation_name
|
29
26
|
o = Sinatra::Rabbit::STANDARD_OPERATIONS[operation_name]
|
27
|
+
if o
|
28
|
+
o[:member] = false if member == :no_member
|
29
|
+
o[:collection] = true if member == :no_id
|
30
|
+
if member == :no_id_and_member
|
31
|
+
o[:collection] = true
|
32
|
+
o[:member] = true
|
33
|
+
end
|
34
|
+
end
|
30
35
|
operation_path = (o && o[:member]) ? operation_name.to_s : nil
|
31
|
-
|
36
|
+
operation_path = operation_name.to_s unless o
|
37
|
+
id_param = (o && o[:collection]) ? nil : (member.kind_of?(Hash) ? member[:id_name] : ':id')
|
32
38
|
[route_for(collection), id_param, operation_path].compact.join('/')
|
33
39
|
else
|
34
|
-
|
40
|
+
collection.to_s
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
@@ -40,8 +46,17 @@ module Sinatra
|
|
40
46
|
(o && o[:method]) ? o[:method] : :get
|
41
47
|
end
|
42
48
|
|
43
|
-
def self.collection_class(name)
|
44
|
-
Sinatra::Rabbit
|
49
|
+
def self.collection_class(name, parent_class=nil)
|
50
|
+
klass = parent_class || Sinatra::Rabbit
|
51
|
+
begin
|
52
|
+
klass.const_get(name.to_s.camelize + 'Collection')
|
53
|
+
rescue NameError
|
54
|
+
klass.const_set(name.to_s.camelize + 'Collection', Class.new(Collection))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.root_path
|
59
|
+
Sinatra::Rabbit.configuration[:root_path]
|
45
60
|
end
|
46
61
|
|
47
62
|
end
|
data/lib/sinatra/rabbit/dsl.rb
CHANGED
@@ -30,8 +30,13 @@ module Sinatra
|
|
30
30
|
# end
|
31
31
|
# end
|
32
32
|
def collection(name, &block)
|
33
|
+
return @collections.find { |c| c.collection_name == name } unless block_given?
|
33
34
|
@collections ||= []
|
34
|
-
|
35
|
+
current_collection = BaseCollection.collection_class(name)
|
36
|
+
current_collection.set_base_class(self)
|
37
|
+
current_collection.generate(name, &block)
|
38
|
+
@collections << current_collection
|
39
|
+
Sinatra::Rabbit::DSL.register_collection(current_collection)
|
35
40
|
use current_collection
|
36
41
|
end
|
37
42
|
|
@@ -41,6 +46,11 @@ module Sinatra
|
|
41
46
|
@collections
|
42
47
|
end
|
43
48
|
|
49
|
+
def self.register_collection(c, &block)
|
50
|
+
@collections ||= []
|
51
|
+
@collections << c
|
52
|
+
end
|
53
|
+
|
44
54
|
end
|
45
55
|
end
|
46
56
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one or more
|
3
|
+
# contributor license agreements. See the NOTICE file distributed with
|
4
|
+
# this work for additional information regarding copyright ownership. The
|
5
|
+
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
6
|
+
# "License"); you may not use this file except in compliance with the
|
7
|
+
# License. You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations
|
15
|
+
# under the License.
|
16
|
+
|
17
|
+
module Sinatra
|
18
|
+
module Rabbit
|
19
|
+
class Feature
|
20
|
+
attr_reader :name
|
21
|
+
attr_reader :collection
|
22
|
+
attr_reader :operations
|
23
|
+
|
24
|
+
def initialize(name, opts={}, &block)
|
25
|
+
@name = name
|
26
|
+
@operations = []
|
27
|
+
@collection = opts[:for]
|
28
|
+
raise "Each feature must define collection for which it will be valid using :for parameter" unless @collection
|
29
|
+
instance_eval(&block) if block_given?
|
30
|
+
end
|
31
|
+
|
32
|
+
def operation(name, &block)
|
33
|
+
@operations << Operation.new(name, &block) if block_given?
|
34
|
+
@operations.find { |o| o.name == name }
|
35
|
+
end
|
36
|
+
|
37
|
+
class Operation
|
38
|
+
attr_reader :name
|
39
|
+
attr_reader :params
|
40
|
+
|
41
|
+
def initialize(name, &block)
|
42
|
+
@name = name
|
43
|
+
@params = block
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module Features
|
50
|
+
|
51
|
+
def features(&block)
|
52
|
+
@@features ||= []
|
53
|
+
instance_eval(&block) if block_given?
|
54
|
+
@@features
|
55
|
+
end
|
56
|
+
|
57
|
+
def feature(name, opts={}, &block)
|
58
|
+
@@features << Feature.new(name, opts, &block) if block_given?
|
59
|
+
@@features.find { |f| f.name == name }
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.included(base)
|
63
|
+
base.register(Features)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/lib/sinatra/rabbit/param.rb
CHANGED
@@ -24,22 +24,24 @@ module Sinatra
|
|
24
24
|
@name, @klass = args.pop, args.pop
|
25
25
|
raise "DSL: You need to specify the name and param type (#{@name})" unless @name or @klass
|
26
26
|
parse_params!(args)
|
27
|
+
@description ||= "Description not available"
|
27
28
|
end
|
28
29
|
|
29
30
|
def required?; @required == true; end
|
30
31
|
def optional?; !required?; end
|
31
32
|
def enum?; !@values.nil?; end
|
32
|
-
def number?; [:integer, :float].include?(@klass); end
|
33
|
+
def number?; [:integer, :float, :number].include?(@klass); end
|
34
|
+
def string?; @klass == :string; end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"#{name}:#{klass}:#{required? ? 'required' : 'optional'}"
|
38
|
+
end
|
33
39
|
|
34
40
|
private
|
35
41
|
|
36
42
|
def parse_params!(args)
|
37
|
-
if args.pop == :required
|
38
|
-
@required = true
|
39
|
-
else
|
40
|
-
@required = false
|
41
|
-
end
|
42
43
|
@values = args.pop if args.last.kind_of? Array
|
44
|
+
@required = args.pop == :required if [:required, :optional].include? args.last
|
43
45
|
@description = args.pop
|
44
46
|
end
|
45
47
|
|
data/sinatra-rabbit.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinatra-rabbit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.9.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-03-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sinatra
|
16
|
-
requirement: &
|
16
|
+
requirement: &70283050880100 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: 1.3.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70283050880100
|
25
25
|
description: ! " Rabbit is a Sinatra extension which can help you writing\n a
|
26
26
|
simple REST API using easy to undestand DSL.\n"
|
27
27
|
email: dev@deltacloud.apache.org
|
@@ -34,6 +34,7 @@ files:
|
|
34
34
|
- lib/sinatra/rabbit/base.rb
|
35
35
|
- lib/sinatra/rabbit/base_collection.rb
|
36
36
|
- lib/sinatra/rabbit/dsl.rb
|
37
|
+
- lib/sinatra/rabbit/features.rb
|
37
38
|
- lib/sinatra/rabbit/param.rb
|
38
39
|
- lib/sinatra/rabbit/validator.rb
|
39
40
|
- LICENSE
|
@@ -59,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
60
|
version: '0'
|
60
61
|
requirements: []
|
61
62
|
rubyforge_project:
|
62
|
-
rubygems_version: 1.8.
|
63
|
+
rubygems_version: 1.8.15
|
63
64
|
signing_key:
|
64
65
|
specification_version: 3
|
65
66
|
summary: Sinatra REST API DSL
|