poncho 0.0.2

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/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in poncho.gemspec
4
+ gemspec
5
+
6
+ # For testing
7
+ gem 'sinatra'
8
+ gem 'thin'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Alex MacCaw
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,302 @@
1
+ # Poncho
2
+
3
+ Poncho is an API to build APIs or, in other words, a DSL to build REST interfaces.
4
+
5
+ It'll validate input and output, coerce values and is easily extendable with custom data types.
6
+
7
+ It's compatible with any rack-based framework, such as Rails or Sinatra.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'poncho'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install poncho
22
+
23
+ ## TLDR Usage
24
+
25
+ class ChargeResource < Poncho::Resource
26
+ param :amount, :type => :integer
27
+ param :currency
28
+
29
+ def currency
30
+ super || 'USD'
31
+ end
32
+ end
33
+
34
+ class ChargeCreateMethod < Poncho::JSONMethod
35
+ param :amount, :type => :integer, :required => true
36
+ param :currency, :in => ['USD', 'GBP']
37
+
38
+ def invoke
39
+ charge = Charge.new
40
+ charge.amount = param(:amount)
41
+ charge.currency = param(:currency)
42
+ charge.save
43
+
44
+ ChargeResource.new(charge)
45
+ end
46
+ end
47
+
48
+ post '/charges', &ChargeCreateMethod
49
+
50
+ ## Getting started with Methods
51
+
52
+ Methods inherit from `Poncho::Method` and override `invoke`, where they perform any necessary logic.
53
+
54
+ In a similar vein to Sinatra, anything returned from `invoke` is sent right back to the user. You can
55
+ return a http status code, a body string, or even a Rack response array.
56
+
57
+ class ChargeListMethod < Poncho::Method
58
+ def invoke
59
+ # Some DB shizzle
60
+
61
+ 200
62
+ end
63
+ end
64
+
65
+ To invoke the method just add it to your routes.
66
+
67
+ Using Rails:
68
+
69
+ match '/users' => UsersListMethod, :via => :get
70
+
71
+ Using Sinatra:
72
+
73
+ get '/users', &UsersListMethod
74
+
75
+ Or invoke manually:
76
+
77
+ UsersListMethod.call(rack_env)
78
+
79
+ If you're writing a JSON API, you'll probably want to inherit the Method from `Poncho::JSONMethod` instead of
80
+ `Poncho::Method`, but we'll cover that later.
81
+
82
+ ## Params
83
+
84
+ You can get access to the request params, via the `params` or `param(name)` methods.
85
+
86
+ Before you can use a param though, you need to define it:
87
+
88
+ param :param_name
89
+
90
+ By default, param are of type 'string'. you can choose a different type via the `:type` option:
91
+
92
+ param :amount, :type => :integer
93
+
94
+ There are a bunch of predefined types, such as `:integer`, `:array`, `:boolean_string` etc, but you can
95
+ also easily define your own custom ones (covered later).
96
+
97
+ Poncho will automatically validate that if a paramter is provided it is in a valid format.
98
+ Poncho will also handle type conversion for you.
99
+
100
+ So for example, in the case above, Poncho will automatically validate that the `amount` param is
101
+ indeed an Integer or an Integer string, and will coerce the parameter into an integer when you try to access it.
102
+
103
+ ## Validation
104
+
105
+ As well as the default type validation, Poncho lets you validate presence, format, length and much more!
106
+
107
+ For example, to validate that a `:currency` parameter is provided, pass in the `:presence' option:
108
+
109
+ param :currency, :presence => true
110
+
111
+ To validate that a currency is either 'USD' or 'GBP', use the `:in` option.
112
+
113
+ param :currency, :in => ['USD', 'GBP']
114
+
115
+ The other supported validations out of the box are `:format`, `:not_in`, and `:length`:
116
+
117
+ param :email, :format => /@/
118
+ param :password, :length => 5..20
119
+
120
+ ## Custom Validation
121
+
122
+ You can use a custom validator via the `validate` method, passing in a block:
123
+
124
+ validate do
125
+ unless param(:customer_id) ~= /\Acus_/
126
+ errors.add(:customer_id, :invalid_customer)
127
+ end
128
+ end
129
+
130
+ # Or
131
+
132
+ validates :customer_id, :customer_validate
133
+
134
+ Alternatively, if your validation is being used in multiple places, you can wrap it up in a class and
135
+ pass it to the `validate_with` method.
136
+
137
+ validates_with CustomValidator
138
+
139
+ For a good example of how to build validations, see the [
140
+ existing ones](https://github.com/stripe/poncho/tree/master/lib/poncho/validations).
141
+
142
+ ## Custom Params
143
+
144
+ As your API grows you'll probably start to need custom parameter types. These can be useful to ensure
145
+ parameters are both valid and converted into suitable values.
146
+
147
+ To define a custom parameter, simply inherit from `Poncho::Param`. For example, let's define a new param called
148
+ `CardHashParam`. It needs to validate input via overriding the `validate_each` method, and convert input via
149
+ overriding the `convert` method.
150
+
151
+ module Poncho
152
+ module Params
153
+ class CardHashParam < Param
154
+ def validate_each(method, attribute, value)
155
+ value = convert(value)
156
+
157
+ unless value.is_a?(Hash) && value.keys == [:number, :exp_month, :exp_year, :cvc]
158
+ method.errors.add(attribute, :invalid_card_hash, options.merge(:value => value))
159
+ end
160
+ end
161
+
162
+ def convert(value)
163
+ value && value.symbolize_keys
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ You can use custom parameters via the `:type` option.
170
+
171
+ param :card, :type => Poncho::Params::CardHashParam
172
+
173
+ # Or the shortcut
174
+ param :card, :type => :card_hash
175
+
176
+ ## Request & Response
177
+
178
+ You can gain access to the rack request via the `request` method, for example:
179
+
180
+ def invoke
181
+ accept = request.headers['Accept']
182
+ 200
183
+ end
184
+
185
+ The same goes for the response object:
186
+
187
+ def invoke
188
+ response.body = ['Fee-fi-fo-fum']
189
+ 200
190
+ end
191
+
192
+ There are some helper methods to set such things as the HTTP status response codes and body.
193
+
194
+ def invoke
195
+ status 201
196
+ body 'Created!'
197
+ end
198
+
199
+ ## Method filters
200
+
201
+ There are various filters you can apply to the request, for example:
202
+
203
+ class MyMethod < Poncho::Method
204
+ before_validation do
205
+ # Before validation
206
+ end
207
+
208
+ before do
209
+ # Before invoke
210
+ p params
211
+ end
212
+
213
+ after do
214
+ # After invocation
215
+ end
216
+ end
217
+
218
+ ## Error responses
219
+
220
+ You can provide custom responses to exceptions via the `error` class method.
221
+
222
+ Pass `error` a exception type or status code.
223
+
224
+ class MyMethod < Poncho::Method
225
+ error MyCustomClass do
226
+ 'Sorry, something went wrong.'
227
+ end
228
+
229
+ error 403 do
230
+ 'Not authorized.'
231
+ end
232
+ end
233
+
234
+ ## JSON APIs
235
+
236
+ If your API only returns JSON then Poncho has a convenient `JSONMethod` class which
237
+ will ensure that all response bodies are converted into JSON and that the correct content type
238
+ header is set.
239
+
240
+ class TokenCreateMethod < Poncho::JSONMethod
241
+ param :number, :required => true
242
+
243
+ def invoke
244
+ {:token => '123'}
245
+ end
246
+ end
247
+
248
+ `JSONMethod` also ensures that there's valid JSON error responses to 404s and 500s, as well
249
+ as returning a JSON error hash for validation errors.
250
+
251
+ $ curl http://localhost:4567/tokens -d number=
252
+ {"error":{"param":"number","type":"presence"}
253
+
254
+ ## Resources
255
+
256
+ Resources are wrappers around other classes, such as models, providing a view representation of them.
257
+
258
+ You can specify attributes to be returned to the client using the same `param` syntax as documented above.
259
+
260
+ class Card
261
+ attr_reader :number
262
+
263
+ def initialize(number)
264
+ @number = number
265
+ end
266
+ end
267
+
268
+ class CardResource < Poncho::Resource
269
+ param :number
270
+ param :description
271
+
272
+ def number
273
+ super[-4..-1]
274
+ end
275
+ end
276
+
277
+ As you can see in the example above, you can override params and return a custom response.
278
+
279
+ When the `Resource` instance is converted into JSON the appropriate params will be used and serialized.
280
+
281
+ class ChargeResource < Poncho::Resource
282
+ param :amount, :type => :integer
283
+ param :currency
284
+ param :card, :resource => CardResource
285
+
286
+ def currency
287
+ super || 'USD'
288
+ end
289
+ end
290
+
291
+ class ChargeListMethod < Poncho::JSONMethod
292
+ def invoke
293
+ [
294
+ ChargeResource.new(Charge.new(1000, 'USD')),
295
+ ChargeResource.new(Charge.new(50, 'USD'))
296
+ ]
297
+ end
298
+ end
299
+
300
+ If a particular param points to another resource, you can use the `:type => :resource` option as demonstrated above.
301
+
302
+
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.test_files = FileList['test/**/test*.rb']
7
+ t.verbose = true
8
+ end
data/examples/app.rb ADDED
@@ -0,0 +1,80 @@
1
+ require 'sinatra'
2
+ require 'poncho'
3
+
4
+ class Card
5
+ attr_reader :number
6
+
7
+ def initialize(number)
8
+ @number = number
9
+ end
10
+ end
11
+
12
+ class Charge
13
+ attr_reader :amount, :currency
14
+
15
+ def initialize(amount, currency)
16
+ @amount = amount
17
+ @currency = currency
18
+ end
19
+
20
+ def card
21
+ Card.new('4242 4242 4242 4242')
22
+ end
23
+ end
24
+
25
+ class CardResource < Poncho::Resource
26
+ param :number
27
+ param :expiry_month
28
+
29
+ def number
30
+ super[-4..-1]
31
+ end
32
+ end
33
+
34
+ class ChargeResource < Poncho::Resource
35
+ param :amount, :type => :integer
36
+ param :currency
37
+ param :card, :resource => CardResource
38
+
39
+ def currency
40
+ super || 'USD'
41
+ end
42
+ end
43
+
44
+ class ChargeListMethod < Poncho::JSONMethod
45
+ param :page, :type => :integer
46
+
47
+ def invoke
48
+ [
49
+ ChargeResource.new(Charge.new(1000, 'USD')),
50
+ ChargeResource.new(Charge.new(50, 'USD'))
51
+ ]
52
+ end
53
+ end
54
+
55
+ class ChargeCreateMethod < Poncho::JSONMethod
56
+ include Poncho::Returns
57
+
58
+ param :amount, :type => :integer, :required => true
59
+ param :currency, :in => ['GBP', 'USD']
60
+ param :card
61
+
62
+ returns ChargeResource
63
+
64
+ def invoke
65
+ charge = Charge.new(param(:amount), param(:currency))
66
+ ChargeResource.new(charge)
67
+ end
68
+ end
69
+
70
+ class ChargeRetrieveMethod < Poncho::JSONMethod
71
+ param :id, :type => :integer
72
+
73
+ def invoke
74
+ {:id => param(:id)}
75
+ end
76
+ end
77
+
78
+ post '/charges', &ChargeCreateMethod
79
+ get '/charges', &ChargeListMethod
80
+ get '/charges/:id', &ChargeRetrieveMethod
data/lib/poncho.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'poncho/version'
2
+
3
+ module Poncho
4
+ autoload :Error, 'poncho/error'
5
+ autoload :ResourceValidationError, 'poncho/error'
6
+ autoload :ClientError, 'poncho/error'
7
+ autoload :ServerError, 'poncho/error'
8
+ autoload :ValidationError, 'poncho/error'
9
+
10
+ autoload :Errors, 'poncho/errors'
11
+ autoload :Filters, 'poncho/filters'
12
+
13
+ autoload :Validator, 'poncho/validator'
14
+ autoload :Validations, 'poncho/validations'
15
+ autoload :EachValidator, 'poncho/validator'
16
+ autoload :BlockValidator, 'poncho/validator'
17
+
18
+ autoload :Method, 'poncho/method'
19
+ autoload :JSONMethod, 'poncho/json_method'
20
+
21
+ autoload :Resource, 'poncho/resource'
22
+ autoload :Request, 'poncho/request'
23
+ autoload :Response, 'poncho/response'
24
+ autoload :Returns, 'poncho/returns'
25
+ autoload :Param, 'poncho/param'
26
+ autoload :Params, 'poncho/params'
27
+ end