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 +18 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +302 -0
- data/Rakefile +8 -0
- data/examples/app.rb +80 -0
- data/lib/poncho.rb +27 -0
- data/lib/poncho/error.rb +75 -0
- data/lib/poncho/errors.rb +142 -0
- data/lib/poncho/filters.rb +52 -0
- data/lib/poncho/json_method.rb +47 -0
- data/lib/poncho/method.rb +271 -0
- data/lib/poncho/param.rb +29 -0
- data/lib/poncho/params.rb +92 -0
- data/lib/poncho/params/array.rb +15 -0
- data/lib/poncho/params/boolean.rb +20 -0
- data/lib/poncho/params/boolean_string.rb +19 -0
- data/lib/poncho/params/integer.rb +17 -0
- data/lib/poncho/params/object.rb +15 -0
- data/lib/poncho/params/resource.rb +27 -0
- data/lib/poncho/params/string.rb +15 -0
- data/lib/poncho/params/validations.rb +24 -0
- data/lib/poncho/request.rb +71 -0
- data/lib/poncho/resource.rb +80 -0
- data/lib/poncho/response.rb +28 -0
- data/lib/poncho/returns.rb +25 -0
- data/lib/poncho/validations.rb +198 -0
- data/lib/poncho/validations/exclusions.rb +77 -0
- data/lib/poncho/validations/format.rb +105 -0
- data/lib/poncho/validations/inclusions.rb +77 -0
- data/lib/poncho/validations/length.rb +123 -0
- data/lib/poncho/validations/presence.rb +49 -0
- data/lib/poncho/validator.rb +172 -0
- data/lib/poncho/version.rb +3 -0
- data/poncho.gemspec +19 -0
- data/test/poncho/test_method.rb +105 -0
- data/test/poncho/test_resource.rb +71 -0
- metadata +84 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
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
|