sinatra-rabbit 0.0.2 → 0.9

Sign up to get free protection for your applications and to get access to all the features.
File without changes
data/README.md ADDED
@@ -0,0 +1,236 @@
1
+ Sinatra::Rabbit
2
+ =============
3
+
4
+ Sinatra::Rabbit is a [Sinatra](http://www.sinatrarb.com) extensions that
5
+ makes designing a [REST API](http://en.wikipedia.org/wiki/Representational_State_Transfer)
6
+ much easier.
7
+
8
+ Rabbit threat REST resource as 'collection'. Every collection then
9
+ could have multiple operations. This simple DSL is then transformed into
10
+ a proper modular [Sinatra::Base](http://www.sinatrarb.com/intro#Serving%20a%20Modular%20Application)
11
+ application that can be launched within any [Rack](http://rack.rubyforge.org/) container (including Rails).
12
+
13
+ Original Rabbit idea has been taken from the [Deltacloud API](http://deltacloud.org)
14
+ project. However this is an complete rewrite of the original Deltacloud Rabbit.
15
+ The goal is to make Rabbit highly modular, so it can be used in other projects
16
+ as well as in Deltacloud.
17
+
18
+ Installation
19
+ -------
20
+
21
+ * `gem install sinatra-rabbit`
22
+
23
+ Usage
24
+ -------
25
+
26
+ require 'sinatra/base'
27
+ require 'sinatra/rabbit'
28
+
29
+ class MySinatraApp < Sinatra::Base
30
+ include Sinatra::Rabbit
31
+
32
+ collection :images do
33
+ description "Images description"
34
+
35
+ operation :index do
36
+ description "Index operation description"
37
+ control do
38
+ status 200
39
+ "Hello from index operation"
40
+ end
41
+ end
42
+
43
+ operation :show do
44
+ description "Index operation description"
45
+ param :id, :string, :required
46
+ param :r1, :string, :optional, "Optional parameter"
47
+ param :v1, :string, :optional, [ 'test1', 'test2', 'test3' ], "Optional parameter"
48
+ param :v2, :string, :optional, "Optional parameter"
49
+ control do
50
+ "Hey #{params[:id]}"
51
+ end
52
+ end
53
+ end
54
+
55
+ MySinatraApp.run!
56
+
57
+ License
58
+ ---------
59
+
60
+
61
+ Apache License
62
+ Version 2.0, January 2004
63
+ http://www.apache.org/licenses/
64
+
65
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
66
+
67
+ 1. Definitions.
68
+
69
+ "License" shall mean the terms and conditions for use, reproduction,
70
+ and distribution as defined by Sections 1 through 9 of this document.
71
+
72
+ "Licensor" shall mean the copyright owner or entity authorized by
73
+ the copyright owner that is granting the License.
74
+
75
+ "Legal Entity" shall mean the union of the acting entity and all
76
+ other entities that control, are controlled by, or are under common
77
+ control with that entity. For the purposes of this definition,
78
+ "control" means (i) the power, direct or indirect, to cause the
79
+ direction or management of such entity, whether by contract or
80
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
81
+ outstanding shares, or (iii) beneficial ownership of such entity.
82
+
83
+ "You" (or "Your") shall mean an individual or Legal Entity
84
+ exercising permissions granted by this License.
85
+
86
+ "Source" form shall mean the preferred form for making modifications,
87
+ including but not limited to software source code, documentation
88
+ source, and configuration files.
89
+
90
+ "Object" form shall mean any form resulting from mechanical
91
+ transformation or translation of a Source form, including but
92
+ not limited to compiled object code, generated documentation,
93
+ and conversions to other media types.
94
+
95
+ "Work" shall mean the work of authorship, whether in Source or
96
+ Object form, made available under the License, as indicated by a
97
+ copyright notice that is included in or attached to the work
98
+ (an example is provided in the Appendix below).
99
+
100
+ "Derivative Works" shall mean any work, whether in Source or Object
101
+ form, that is based on (or derived from) the Work and for which the
102
+ editorial revisions, annotations, elaborations, or other modifications
103
+ represent, as a whole, an original work of authorship. For the purposes
104
+ of this License, Derivative Works shall not include works that remain
105
+ separable from, or merely link (or bind by name) to the interfaces of,
106
+ the Work and Derivative Works thereof.
107
+
108
+ "Contribution" shall mean any work of authorship, including
109
+ the original version of the Work and any modifications or additions
110
+ to that Work or Derivative Works thereof, that is intentionally
111
+ submitted to Licensor for inclusion in the Work by the copyright owner
112
+ or by an individual or Legal Entity authorized to submit on behalf of
113
+ the copyright owner. For the purposes of this definition, "submitted"
114
+ means any form of electronic, verbal, or written communication sent
115
+ to the Licensor or its representatives, including but not limited to
116
+ communication on electronic mailing lists, source code control systems,
117
+ and issue tracking systems that are managed by, or on behalf of, the
118
+ Licensor for the purpose of discussing and improving the Work, but
119
+ excluding communication that is conspicuously marked or otherwise
120
+ designated in writing by the copyright owner as "Not a Contribution."
121
+
122
+ "Contributor" shall mean Licensor and any individual or Legal Entity
123
+ on behalf of whom a Contribution has been received by Licensor and
124
+ subsequently incorporated within the Work.
125
+
126
+ 2. Grant of Copyright License. Subject to the terms and conditions of
127
+ this License, each Contributor hereby grants to You a perpetual,
128
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
129
+ copyright license to reproduce, prepare Derivative Works of,
130
+ publicly display, publicly perform, sublicense, and distribute the
131
+ Work and such Derivative Works in Source or Object form.
132
+
133
+ 3. Grant of Patent License. Subject to the terms and conditions of
134
+ this License, each Contributor hereby grants to You a perpetual,
135
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
136
+ (except as stated in this section) patent license to make, have made,
137
+ use, offer to sell, sell, import, and otherwise transfer the Work,
138
+ where such license applies only to those patent claims licensable
139
+ by such Contributor that are necessarily infringed by their
140
+ Contribution(s) alone or by combination of their Contribution(s)
141
+ with the Work to which such Contribution(s) was submitted. If You
142
+ institute patent litigation against any entity (including a
143
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
144
+ or a Contribution incorporated within the Work constitutes direct
145
+ or contributory patent infringement, then any patent licenses
146
+ granted to You under this License for that Work shall terminate
147
+ as of the date such litigation is filed.
148
+
149
+ 4. Redistribution. You may reproduce and distribute copies of the
150
+ Work or Derivative Works thereof in any medium, with or without
151
+ modifications, and in Source or Object form, provided that You
152
+ meet the following conditions:
153
+
154
+ (a) You must give any other recipients of the Work or
155
+ Derivative Works a copy of this License; and
156
+
157
+ (b) You must cause any modified files to carry prominent notices
158
+ stating that You changed the files; and
159
+
160
+ (c) You must retain, in the Source form of any Derivative Works
161
+ that You distribute, all copyright, patent, trademark, and
162
+ attribution notices from the Source form of the Work,
163
+ excluding those notices that do not pertain to any part of
164
+ the Derivative Works; and
165
+
166
+ (d) If the Work includes a "NOTICE" text file as part of its
167
+ distribution, then any Derivative Works that You distribute must
168
+ include a readable copy of the attribution notices contained
169
+ within such NOTICE file, excluding those notices that do not
170
+ pertain to any part of the Derivative Works, in at least one
171
+ of the following places: within a NOTICE text file distributed
172
+ as part of the Derivative Works; within the Source form or
173
+ documentation, if provided along with the Derivative Works; or,
174
+ within a display generated by the Derivative Works, if and
175
+ wherever such third-party notices normally appear. The contents
176
+ of the NOTICE file are for informational purposes only and
177
+ do not modify the License. You may add Your own attribution
178
+ notices within Derivative Works that You distribute, alongside
179
+ or as an addendum to the NOTICE text from the Work, provided
180
+ that such additional attribution notices cannot be construed
181
+ as modifying the License.
182
+
183
+ You may add Your own copyright statement to Your modifications and
184
+ may provide additional or different license terms and conditions
185
+ for use, reproduction, or distribution of Your modifications, or
186
+ for any such Derivative Works as a whole, provided Your use,
187
+ reproduction, and distribution of the Work otherwise complies with
188
+ the conditions stated in this License.
189
+
190
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
191
+ any Contribution intentionally submitted for inclusion in the Work
192
+ by You to the Licensor shall be under the terms and conditions of
193
+ this License, without any additional terms or conditions.
194
+ Notwithstanding the above, nothing herein shall supersede or modify
195
+ the terms of any separate license agreement you may have executed
196
+ with Licensor regarding such Contributions.
197
+
198
+ 6. Trademarks. This License does not grant permission to use the trade
199
+ names, trademarks, service marks, or product names of the Licensor,
200
+ except as required for reasonable and customary use in describing the
201
+ origin of the Work and reproducing the content of the NOTICE file.
202
+
203
+ 7. Disclaimer of Warranty. Unless required by applicable law or
204
+ agreed to in writing, Licensor provides the Work (and each
205
+ Contributor provides its Contributions) on an "AS IS" BASIS,
206
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
207
+ implied, including, without limitation, any warranties or conditions
208
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
209
+ PARTICULAR PURPOSE. You are solely responsible for determining the
210
+ appropriateness of using or redistributing the Work and assume any
211
+ risks associated with Your exercise of permissions under this License.
212
+
213
+ 8. Limitation of Liability. In no event and under no legal theory,
214
+ whether in tort (including negligence), contract, or otherwise,
215
+ unless required by applicable law (such as deliberate and grossly
216
+ negligent acts) or agreed to in writing, shall any Contributor be
217
+ liable to You for damages, including any direct, indirect, special,
218
+ incidental, or consequential damages of any character arising as a
219
+ result of this License or out of the use or inability to use the
220
+ Work (including but not limited to damages for loss of goodwill,
221
+ work stoppage, computer failure or malfunction, or any and all
222
+ other commercial damages or losses), even if such Contributor
223
+ has been advised of the possibility of such damages.
224
+
225
+ 9. Accepting Warranty or Additional Liability. While redistributing
226
+ the Work or Derivative Works thereof, You may choose to offer,
227
+ and charge a fee for, acceptance of support, warranty, indemnity,
228
+ or other liability obligations and/or rights consistent with this
229
+ License. However, in accepting such obligations, You may act only
230
+ on Your own behalf and on Your sole responsibility, not on behalf
231
+ of any other Contributor, and only if You agree to indemnify,
232
+ defend, and hold each Contributor harmless for any liability
233
+ incurred by, or claims asserted against, such Contributor by reason
234
+ of your accepting any such warranty or additional liability.
235
+
236
+ END OF TERMS AND CONDITIONS
@@ -1,282 +1,26 @@
1
- require 'sinatra/base'
2
- require 'sinatra/rabbit/url_for'
3
- require 'sinatra/rabbit/respond_to'
4
- require 'sinatra/rabbit/validation'
5
-
6
- module Sinatra
7
-
8
- module Rabbit
9
-
10
- class RabbitDuplicateParamException < Exception; end
11
- class RabbitDuplicateOperationException < Exception; end
12
- class RabbitDuplicateCollectionException < Exception; end
13
-
14
- def self.registered(app)
15
- app.helpers Rabbit::Helpers
16
- end
17
-
18
- class Operation
19
- attr_reader :name, :method
20
-
21
- include Rabbit::Validation
22
-
23
- STANDARD = {
24
- :index => { :method => :get, :member => false },
25
- :show => { :method => :get, :member => true },
26
- :create => { :method => :post, :member => false },
27
- :update => { :method => :put, :member => true },
28
- :destroy => { :method => :delete, :member => true }
29
- }
30
-
31
- def initialize(coll, name, opts, &block)
32
- @name = name.to_sym
33
- opts = STANDARD[@name].merge(opts) if standard?
34
- @collection = coll
35
- raise "No method for operation #{name}" unless opts[:method]
36
- @method = opts[:method].to_sym
37
- @member = opts[:member]
38
- @description = ""
39
- instance_eval(&block) if block_given?
40
- generate_documentation
41
- end
42
-
43
- def standard?
44
- STANDARD.keys.include?(name)
45
- end
46
-
47
- def description(text="")
48
- return @description if text.blank?
49
- @description = text
50
- end
51
-
52
- def generate_documentation
53
- coll, oper = @collection, self
54
- ::Sinatra::Application.get("/api/docs/#{@collection.name}/#{@name}") do
55
- @collection, @operation = coll, oper
56
- respond_to do |format|
57
- format.html { haml :'docs/operation' }
58
- format.xml { haml :'docs/operation' }
59
- end
60
- end
61
- end
62
-
63
- def control(&block)
64
- op = self
65
- @control = Proc.new do
66
- op.validate(params)
67
- instance_eval(&block)
68
- end
69
- end
70
-
71
- def prefix
72
- # FIXME: Make the /api prefix configurable
73
- "/api"
74
- end
75
-
76
- def path(args = {})
77
- l_prefix = args[:prefix] ? args[:prefix] : prefix
78
- if @member
79
- if standard?
80
- "#{l_prefix}/#{@collection.name}/:id"
81
- else
82
- "#{l_prefix}/#{@collection.name}/:id/#{name}"
83
- end
84
- else
85
- "#{l_prefix}/#{@collection.name}"
86
- end
87
- end
88
-
89
- def generate
90
- ::Sinatra::Application.send(@method, path, {}, &@control)
91
- # Set up some Rails-like URL helpers
92
- if name == :index
93
- gen_route "#{@collection.name}_url"
94
- elsif name == :show
95
- gen_route "#{@collection.name.to_s.singularize}_url"
96
- else
97
- gen_route "#{name}_#{@collection.name.to_s.singularize}_url"
98
- end
99
- end
100
-
101
- private
102
- def gen_route(name)
103
- route_url = path
104
- if @member
105
- ::Sinatra::Application.send(:define_method, name) do |id, *args|
106
- url = query_url(route_url, args[0])
107
- url_for url.gsub(/:id/, id.to_s), :full
108
- end
109
- else
110
- ::Sinatra::Application.send(:define_method, name) do |*args|
111
- url = query_url(route_url, args[0])
112
- url_for url, :full
113
- end
114
- end
115
- end
116
- end
117
-
118
- class Collection
119
- attr_reader :name, :operations
120
-
121
- def initialize(name, &block)
122
- @name = name
123
- @description = ""
124
- @operations = {}
125
- instance_eval(&block) if block_given?
126
- generate_documentation
127
- end
128
-
129
- # Set/Return description for collection
130
- # If first parameter is not present, full description will be
131
- # returned.
132
- def description(text='')
133
- return @description if text.blank?
134
- @description = text
135
- end
136
-
137
- def generate_documentation
138
- coll, oper, features = self, @operations, features(name)
139
- ::Sinatra::Application.get("/api/docs/#{@name}") do
140
- @collection, @operations, @features = coll, oper, features
141
- respond_to do |format|
142
- format.html { haml :'docs/collection' }
143
- format.xml { haml :'docs/collection' }
144
- end
145
- end
146
- end
147
-
148
- # Add a new operation for this collection. For the standard REST
149
- # operations :index, :show, :update, and :destroy, we already know
150
- # what method to use and whether this is an operation on the URL for
151
- # individual elements or for the whole collection.
152
- #
153
- # For non-standard operations, options must be passed:
154
- # :method : one of the HTTP methods
155
- # :member : whether this is an operation on the collection or an
156
- # individual element (FIXME: custom operations on the
157
- # collection will use a nonsensical URL) The URL for the
158
- # operation is the element URL with the name of the operation
159
- # appended
160
- #
161
- # This also defines a helper method like show_instance_url that returns
162
- # the URL to this operation (in request context)
163
- def operation(name, opts = {}, &block)
164
- raise DuplicateOperationException if @operations[name]
165
- @operations[name] = Operation.new(self, name, opts, &block)
166
- end
167
-
168
- def generate
169
- operations.values.each { |op| op.generate }
170
- app = ::Sinatra::Application
171
- collname = name # Work around Ruby's weird scoping/capture
172
- app.send(:define_method, "#{name.to_s.singularize}_url") do |id|
173
- url_for "/api/#{collname}/#{id}", :full
174
- end
175
-
176
- if index_op = operations[:index]
177
- app.send(:define_method, "#{name}_url") do
178
- url_for index_op.path.gsub(/\/\?$/,''), :full
179
- end
180
- end
181
- end
182
-
183
- def add_feature_params(features)
184
- features.each do |f|
185
- f.operations.each do |fop|
186
- if cop = operations[fop.name]
187
- fop.params.each_key do |k|
188
- if cop.params.has_key?(k)
189
- raise DuplicateParamException, "Parameter '#{k}' for operation #{fop.name} defined by collection #{@name} and by feature #{f.name}"
190
- else
191
- cop.params[k] = fop.params[k]
192
- end
193
- end
194
- end
195
- end
196
- end
197
- end
198
- end
199
-
200
- def collections
201
- @collections ||= {}
202
- end
203
-
204
- # Create a new collection. NAME should be the pluralized name of the
205
- # collection.
206
- #
207
- # Adds a helper method #{name}_url which returns the URL to the :index
208
- # operation on this collection.
209
- def collection(name, &block)
210
- raise DuplicateCollectionException if collections[name]
211
- return if collection_excluded?(name.to_sym)
212
- collections[name] = Collection.new(name, &block)
213
- collections[name].add_feature_params(features(name))
214
- collections[name].generate
215
- end
216
-
217
- def collection_excluded?(name)
218
- false
219
- end
220
-
221
- def features(collection_name)
222
- []
223
- end
224
-
225
- # Generate a root route for API docs
226
- get '/api/docs\/?' do
227
- respond_to do |format|
228
- format.html { haml :'docs/index' }
229
- format.xml { haml :'docs/index' }
230
- end
231
- end
232
-
233
- module Helpers
234
- def query_url(url, params)
235
- return url if params.nil? || params.empty?
236
- url + "?#{URI.escape(params.collect{|k,v| "#{k}=#{v}"}.join('&'))}"
237
- end
238
-
239
- def entry_points
240
- collections.values.inject([]) do |m, coll|
241
- url = url_for coll.operations[:index].path, :full
242
- m << [ coll.name, url ]
243
- end
244
- end
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
+ unless "".respond_to? :camelize
18
+ class String
19
+ def camelize
20
+ split('_').map { |w| w.capitalize }.join
245
21
  end
246
22
  end
247
23
  end
248
24
 
249
- class String
250
- # Rails defines this for a number of other classes, including Object
251
- # see activesupport/lib/active_support/core_ext/object/blank.rb
252
- def blank?
253
- self !~ /\S/
254
- end
255
-
256
- # Title case.
257
- #
258
- # "this is a string".titlecase
259
- # => "This Is A String"
260
- #
261
- # CREDIT: Eliazar Parra
262
- # Copied from facets
263
- def titlecase
264
- gsub(/\b\w/){ $`[-1,1] == "'" ? $& : $&.upcase }
265
- end
266
-
267
- def pluralize
268
- self + "s"
269
- end
270
-
271
- def singularize
272
- self.gsub(/s$/, '')
273
- end
274
-
275
- def underscore
276
- gsub(/::/, '/').
277
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
278
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
279
- tr("-", "_").
280
- downcase
281
- end
282
- end
25
+ $:.unshift File.join(File::dirname(__FILE__), '.')
26
+ require 'rabbit/base'