sinatra-param 0.0.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/Gemfile +5 -0
- data/Gemfile.lock +28 -0
- data/LICENSE +19 -0
- data/README.md +80 -0
- data/Rakefile +11 -0
- data/example/Gemfile +4 -0
- data/example/Gemfile.lock +24 -0
- data/example/Procfile +1 -0
- data/example/app.rb +43 -0
- data/example/config.ru +3 -0
- data/lib/sinatra/param.rb +70 -0
- data/sinatra-param.gemspec +24 -0
- metadata +95 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
sinatra-param (0.0.1)
|
5
|
+
sinatra (~> 1.3)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
rack (1.4.1)
|
11
|
+
rack-protection (1.2.0)
|
12
|
+
rack
|
13
|
+
rake (0.9.2.2)
|
14
|
+
rspec (0.6.4)
|
15
|
+
sinatra (1.3.2)
|
16
|
+
rack (~> 1.3, >= 1.3.6)
|
17
|
+
rack-protection (~> 1.2)
|
18
|
+
tilt (~> 1.3, >= 1.3.3)
|
19
|
+
tilt (1.3.3)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
rake (~> 0.9.2)
|
26
|
+
rspec (~> 0.6.1)
|
27
|
+
sinatra (~> 1.3)
|
28
|
+
sinatra-param!
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2012 Mattt Thompson (http://mattt.me/)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# sinatra-param
|
2
|
+
_Parameter Validation and Typecasting for Sinatra_
|
3
|
+
|
4
|
+
REST conventions takes the guesswork out of designing and consuming web APIs. `GET` / `POST` / `PATCH` / `DELETE` resource endpoints and you get what you'd expect.
|
5
|
+
|
6
|
+
But figuring out what parameters are expected... well, all bets are off. This Sinatra extension takes a first step to solving this problem on the developer side
|
7
|
+
|
8
|
+
**`sinatra-param` allows you to declare, validate, and transform endpoint parameters as you would in frameworks like [DataMapper](http://datamapper.org/) or [ActiveModel](http://rubydoc.info/gems/activemodel/3.2.3/frames).**
|
9
|
+
|
10
|
+
## Example
|
11
|
+
|
12
|
+
``` ruby
|
13
|
+
class App < Sinatra::Base
|
14
|
+
helpers Sinatra::Param
|
15
|
+
|
16
|
+
before do
|
17
|
+
content_type :json
|
18
|
+
end
|
19
|
+
|
20
|
+
# GET /search?q=example
|
21
|
+
# GET /search?q=example&categories=news
|
22
|
+
# GET /search?q=example&sort=created_at&order=ASC
|
23
|
+
get '/search' do
|
24
|
+
param :q, String, required: true
|
25
|
+
param :categories, Array
|
26
|
+
param :sort, String, default: "title"
|
27
|
+
param :order, String, in: ["ASC", "DESC"], transform: :upcase, default: "ASC"
|
28
|
+
|
29
|
+
{...}.to_json
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
|
35
|
+
### Parameter Types
|
36
|
+
|
37
|
+
By declaring parameter types, incoming parameters will automatically be transformed into an object of that type. For instance, if a param is `Boolean`, values of `'1'`, `'true'`, `'t'`, `'yes'`, and `'y'` will be automatically transformed into `true`.
|
38
|
+
|
39
|
+
- String
|
40
|
+
- Integer
|
41
|
+
- Float
|
42
|
+
- Boolean _("1/0", "true/false", "t/f", "yes/no", "y/n")_
|
43
|
+
- Array _("1,2,3,4,5")_
|
44
|
+
- Hash _(key1:value1,key2:value2)_
|
45
|
+
|
46
|
+
### Validations
|
47
|
+
|
48
|
+
Encapsulate business logic in a consistent way with validations. If a parameter does not satisfy a particular condition, a `406` error is returned with a message explaining the failure.
|
49
|
+
|
50
|
+
- `required`
|
51
|
+
- `blank`
|
52
|
+
- `is`
|
53
|
+
- `in`, `within`, `range`
|
54
|
+
- `min` / `max`
|
55
|
+
|
56
|
+
### Defaults and Transformations
|
57
|
+
|
58
|
+
Passing a `default` option will provide a default value for a parameter if none is passed.
|
59
|
+
|
60
|
+
Use the `transform` option to take even more of the business logic of parameter I/O out of your code. Anything that responds to `to_proc` (including Procs and symbols) will do.
|
61
|
+
|
62
|
+
## Next Steps
|
63
|
+
|
64
|
+
- [Design by contract](http://en.wikipedia.org/wiki/Design_by_contract) like this is great for developers, and with a little meta-programming, it could probably be exposed to users as well. The self-documenting dream of [Hypermedia folks](http://twitter.com/#!/steveklabnik) could well be within reach.
|
65
|
+
|
66
|
+
- Another pain point is the awkward way parameters are passed as JSON in HTTP bodies. I'd love to see an elegant, unobtrusive way to do this automatically.
|
67
|
+
|
68
|
+
- Testing. This will happen soon.
|
69
|
+
|
70
|
+
## Contact
|
71
|
+
|
72
|
+
Mattt Thompson
|
73
|
+
|
74
|
+
- http://github.com/mattt
|
75
|
+
- http://twitter.com/mattt
|
76
|
+
- m@mattt.me
|
77
|
+
|
78
|
+
## License
|
79
|
+
|
80
|
+
sinatra-param is available under the MIT license. See the LICENSE file for more info.
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
gemspec = eval(File.read("sinatra-param.gemspec"))
|
5
|
+
|
6
|
+
task :build => "#{gemspec.full_name}.gem"
|
7
|
+
|
8
|
+
file "#{gemspec.full_name}.gem" => gemspec.files + ["sinatra-param.gemspec"] do
|
9
|
+
system "gem build sinatra-param.gemspec"
|
10
|
+
system "gem install sinatra-param-#{Sinatra::Param::VERSION}.gem"
|
11
|
+
end
|
data/example/Gemfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
daemons (1.1.8)
|
5
|
+
eventmachine (0.12.10)
|
6
|
+
rack (1.4.1)
|
7
|
+
rack-protection (1.2.0)
|
8
|
+
rack
|
9
|
+
sinatra (1.3.2)
|
10
|
+
rack (~> 1.3, >= 1.3.6)
|
11
|
+
rack-protection (~> 1.2)
|
12
|
+
tilt (~> 1.3, >= 1.3.3)
|
13
|
+
thin (1.3.1)
|
14
|
+
daemons (>= 1.0.9)
|
15
|
+
eventmachine (>= 0.12.6)
|
16
|
+
rack (>= 1.0.0)
|
17
|
+
tilt (1.3.3)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
sinatra
|
24
|
+
thin
|
data/example/Procfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
web: bundle exec thin start -p $PORT
|
data/example/app.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require '../lib/sinatra/param'
|
5
|
+
|
6
|
+
class App < Sinatra::Base
|
7
|
+
helpers Sinatra::Param
|
8
|
+
|
9
|
+
before do
|
10
|
+
content_type :json
|
11
|
+
end
|
12
|
+
|
13
|
+
# GET /messages
|
14
|
+
# GET /messages?sort=name&order=ASC
|
15
|
+
get '/messages' do
|
16
|
+
param :sort, String, default: "name"
|
17
|
+
param :order, String, in: ["ASC", "DESC"], transform: :upcase, default: "ASC"
|
18
|
+
|
19
|
+
{
|
20
|
+
sort: params[:sort],
|
21
|
+
order: params[:order]
|
22
|
+
}.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
# GET /messages/1,2,3,4,5
|
26
|
+
get '/messages/:ids' do
|
27
|
+
param :ids, Array, required: true
|
28
|
+
|
29
|
+
{
|
30
|
+
ids: params[:ids]
|
31
|
+
}.to_json
|
32
|
+
end
|
33
|
+
|
34
|
+
# POST /messages/1/response
|
35
|
+
post '/messages/:id/response' do
|
36
|
+
param :message, String, max: 1024, required: true
|
37
|
+
|
38
|
+
{
|
39
|
+
message: params[:message]
|
40
|
+
}.to_json
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
data/example/config.ru
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
|
3
|
+
module Sinatra
|
4
|
+
module Param
|
5
|
+
VERSION = "0.0.1"
|
6
|
+
|
7
|
+
class InvalidParameterError < StandardError; end
|
8
|
+
|
9
|
+
def param(name, type, options = {})
|
10
|
+
begin
|
11
|
+
params[name] = coerce(params[name], type, options) || options[:default]
|
12
|
+
params[name] = options[:transform].to_proc.call(params[name]) if options[:transform]
|
13
|
+
validate!(params[name], options)
|
14
|
+
rescue
|
15
|
+
error = "Invalid parameter, #{name}"
|
16
|
+
if content_type.match(mime_type(:json))
|
17
|
+
error = {message: error}.to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
halt 406, error
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def coerce(param, type, options = {})
|
27
|
+
return nil if param.nil?
|
28
|
+
return Integer(param) if type == Integer
|
29
|
+
return Float(param) if type == Float
|
30
|
+
return String(param) if type == String
|
31
|
+
return Array(param.split(options[:delimiter] || ",")) if type == Array
|
32
|
+
return Hash[param.split(options[:delimiter] || ",").map{|c| c.split(options[:separator] || ":")}] if type == Hash
|
33
|
+
return ((/(false|f|no|n|0)$/i === param) ? false : (/(true|t|yes|y|1)$/i === param) ? true : nil) if type == Boolean
|
34
|
+
return nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate!(param, options)
|
38
|
+
options.each do |key, value|
|
39
|
+
case key
|
40
|
+
when :required
|
41
|
+
raise InvalidParameterError if value && param.nil?
|
42
|
+
when :blank
|
43
|
+
raise InvalidParameterError if !value && case param
|
44
|
+
when String
|
45
|
+
!(/\S/ === param)
|
46
|
+
when Array, Hash
|
47
|
+
param.empty?
|
48
|
+
else
|
49
|
+
param.nil?
|
50
|
+
end
|
51
|
+
when :is
|
52
|
+
raise InvalidParameterError unless value === param
|
53
|
+
when :in, :within, :range
|
54
|
+
raise InvalidParameterError unless case value
|
55
|
+
when Range
|
56
|
+
value.include?(param)
|
57
|
+
else
|
58
|
+
Array(value).include?(param)
|
59
|
+
end
|
60
|
+
when :min
|
61
|
+
raise InvalidParameterError unless value <= param
|
62
|
+
when :max
|
63
|
+
raise InvalidParameterError unless value >= param
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
helpers Param
|
70
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "sinatra/param"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "sinatra-param"
|
7
|
+
s.authors = ["Mattt Thompson"]
|
8
|
+
s.email = "m@mattt.me"
|
9
|
+
s.homepage = "http://github.com/mattt/sinatra-param"
|
10
|
+
s.version = Sinatra::Param::VERSION
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.summary = "sinatra-param"
|
13
|
+
s.description = "Parameter Contracts for Sinatra"
|
14
|
+
|
15
|
+
s.add_dependency "sinatra", "~> 1.3"
|
16
|
+
|
17
|
+
s.add_development_dependency "rspec", "~> 0.6.1"
|
18
|
+
s.add_development_dependency "rake", "~> 0.9.2"
|
19
|
+
|
20
|
+
s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sinatra-param
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mattt Thompson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-30 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sinatra
|
16
|
+
requirement: &70189725114120 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70189725114120
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70189725113620 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.6.1
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70189725113620
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70189725113160 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.9.2
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70189725113160
|
47
|
+
description: Parameter Contracts for Sinatra
|
48
|
+
email: m@mattt.me
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- ./example/app.rb
|
54
|
+
- ./example/config.ru
|
55
|
+
- ./example/Gemfile
|
56
|
+
- ./example/Gemfile.lock
|
57
|
+
- ./example/Procfile
|
58
|
+
- ./Gemfile
|
59
|
+
- ./Gemfile.lock
|
60
|
+
- ./lib/sinatra/param.rb
|
61
|
+
- ./LICENSE
|
62
|
+
- ./Rakefile
|
63
|
+
- ./README.md
|
64
|
+
- ./sinatra-param.gemspec
|
65
|
+
homepage: http://github.com/mattt/sinatra-param
|
66
|
+
licenses: []
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
hash: 4481802987950945093
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
hash: 4481802987950945093
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.8.15
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: sinatra-param
|
95
|
+
test_files: []
|