sinatra-rabbit 0.9 → 0.9.1
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/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
|