rack-parser 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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +21 -0
- data/README.md +82 -0
- data/Rakefile +9 -0
- data/lib/rack/parser.rb +59 -0
- data/rack-parser.gemspec +26 -0
- data/test/parser_test.rb +66 -0
- data/test/teststrap.rb +33 -0
- metadata +129 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2011 Arthur Chiu
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# Rack::Parser #
|
2
|
+
|
3
|
+
Rack::Parser is a Rack Middleware for parsing post body data for JSON/XML, and custom
|
4
|
+
content types using MultiJson, MultiXML, or any thing that you want to
|
5
|
+
use.
|
6
|
+
|
7
|
+
What this allows your rack application to do is decode/parse incoming post data
|
8
|
+
into param hashes for your applications to use.
|
9
|
+
|
10
|
+
## Installation ##
|
11
|
+
|
12
|
+
install it via rubygems:
|
13
|
+
|
14
|
+
```
|
15
|
+
gem install rack-parser
|
16
|
+
```
|
17
|
+
|
18
|
+
or put it in your Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
# Gemfile
|
22
|
+
|
23
|
+
gem 'rack-parser', :require => 'rack/parser'
|
24
|
+
```
|
25
|
+
|
26
|
+
|
27
|
+
## Usage ##
|
28
|
+
|
29
|
+
|
30
|
+
In a Sinatra or [Padrino](http://padrinorb.com) application, it would probably be something like:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
# app.rb
|
34
|
+
|
35
|
+
use Rack::Parser, :content_types => {
|
36
|
+
'application/json' => Proc.new { |body| MultiJson.decode body },
|
37
|
+
'application/xml' => Proc.new { |body| MultiXml.decode body },
|
38
|
+
'application/roll' => Proc.new { |body| 'never gonna give you up' }
|
39
|
+
}
|
40
|
+
```
|
41
|
+
|
42
|
+
|
43
|
+
By default, Rack::Parser uses MultiJson and MultiXml to decode/parse
|
44
|
+
your JSON/XML Data. these can be overwritten if you choose not to use
|
45
|
+
them. However, through using them you can just as easily leverage the
|
46
|
+
engine of your choice by setting the engine like so:
|
47
|
+
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# app.rb
|
51
|
+
|
52
|
+
MultiJson.engine = :yajl # Yajl-ruby for json decoding
|
53
|
+
MultiXml.parser = :libxml # libxml for XML parsing
|
54
|
+
|
55
|
+
use Rack::Parser, :content_types => {
|
56
|
+
'application/json' => Proc.new { |body| MultiJson.decode body },
|
57
|
+
'application/xml' => Proc.new { |body| MultiXml.decode body },
|
58
|
+
'application/roll' => Proc.new { |body| 'never gonna give you up' }
|
59
|
+
}
|
60
|
+
```
|
61
|
+
|
62
|
+
To set your own custom engine that perhaps neither MultiJson or MultiXml
|
63
|
+
support, just make it a Proc:
|
64
|
+
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
use Rack::Parser, :content_types => {
|
68
|
+
'application/json' => Proc.new { |body| MyCustomJsonEngine.do_it body }
|
69
|
+
}
|
70
|
+
```
|
71
|
+
|
72
|
+
## Inspirations ##
|
73
|
+
|
74
|
+
This project came to being because of:
|
75
|
+
|
76
|
+
* [Niko Dittmann's](https://www.github.com/niko) [rack-post-body-to-params](https://www.github.com/niko/rack-post-body-to-params) which some of its ideas are instilled in this middleware.
|
77
|
+
* Rack::PostBodyContentTypeParser from rack-contrib which proved to be an inspiration for both libraries.
|
78
|
+
|
79
|
+
## Copyright
|
80
|
+
|
81
|
+
Copyright © 2011 Arthur Chiu. See [MIT-LICENSE](https://github.com/achiu/rack-parser/blob/master/MIT-LICENSE) for details.
|
82
|
+
|
data/Rakefile
ADDED
data/lib/rack/parser.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'multi_xml'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
|
6
|
+
# Rack::Parser allows you to set custom parsers for incoming post body data. As a default,
|
7
|
+
# Rack::Parser uses MultiJson and MultiXml to do the decoding/parsing for you. This allows you to
|
8
|
+
# designate any engine you wish that is compatible with the MultiJson/MultiXml libraries.
|
9
|
+
# You can also conveniently use another library by as well by wrapping it as a Proc or add additional
|
10
|
+
# content types which are not default in this middleware.
|
11
|
+
#
|
12
|
+
class Parser
|
13
|
+
|
14
|
+
# Rack Constants
|
15
|
+
HEADER_CONTENT_TYPE = 'Content-Type'.freeze
|
16
|
+
POST_BODY = 'rack.input'.freeze
|
17
|
+
FORM_INPUT = 'rack.request.form_input'.freeze
|
18
|
+
FORM_HASH = 'rack.request.form_hash'.freeze
|
19
|
+
|
20
|
+
# Default Settings
|
21
|
+
DEFAULT_CONTENT_TYPE = {
|
22
|
+
'application/xml' => Proc.new { |body| MultiXml.parse(body) },
|
23
|
+
'application/json' => Proc.new { |body| MultiJson.decode(body) }
|
24
|
+
}
|
25
|
+
|
26
|
+
attr_reader :content_types
|
27
|
+
|
28
|
+
# Usage:
|
29
|
+
# use Rack::Parser, :content_types = {
|
30
|
+
# 'application/xml' => Proc.new { |body| XmlParser.parse body } # if you don't want the default
|
31
|
+
# 'application/json' => Proc.new { |body| JsonParser.decode body } # if you don't want the default
|
32
|
+
# 'application/foo' => Proc.new { |body| FooParser.parse body }
|
33
|
+
# }
|
34
|
+
def initialize(app, options = {})
|
35
|
+
@app = app
|
36
|
+
@content_types = DEFAULT_CONTENT_TYPE.merge(options.delete(:content_types) || {})
|
37
|
+
end
|
38
|
+
|
39
|
+
def call(env)
|
40
|
+
dup._call(env)
|
41
|
+
end
|
42
|
+
|
43
|
+
def _call(env)
|
44
|
+
body = env[POST_BODY].read
|
45
|
+
return @app.call(env) if (body.respond_to?(:empty?) ? body.empty? : !body) # Send it down the stack immediately
|
46
|
+
content_type = env[HEADER_CONTENT_TYPE]
|
47
|
+
format = content_type.split('/').last
|
48
|
+
begin
|
49
|
+
result = @content_types[content_type].call(body)
|
50
|
+
env.update FORM_HASH => result, FORM_INPUT => body
|
51
|
+
@app.call env
|
52
|
+
rescue Exception => e
|
53
|
+
logger.warn "#{self.class} #{content_type} parsing error: #{e.to_s}" if respond_to? :logger # Send to logger if its there.
|
54
|
+
[400, { 'Content-Type' => content_type }, [ {'errors' => e.to_s}.method("to_#{format}").call ] ] # Finally, return an error response.
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
data/rack-parser.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "rack-parser"
|
6
|
+
s.version = "0.0.1"
|
7
|
+
s.authors = ["Arthur Chiu"]
|
8
|
+
s.email = ["mr.arthur.chiu@gmail.com"]
|
9
|
+
s.homepage = ""
|
10
|
+
s.summary = %q{Rack Middleware for parsing post body data}
|
11
|
+
s.description = %q{Rack Middleware for parsing post body data for json/xml and various content types}
|
12
|
+
|
13
|
+
s.rubyforge_project = "rack-parser"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency 'rack'
|
21
|
+
s.add_dependency 'multi_json'
|
22
|
+
s.add_dependency 'multi_xml'
|
23
|
+
s.add_development_dependency 'riot'
|
24
|
+
s.add_development_dependency 'rack-test'
|
25
|
+
s.add_development_dependency 'json'
|
26
|
+
end
|
data/test/parser_test.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.expand_path('../teststrap', __FILE__)
|
2
|
+
|
3
|
+
class FooApp
|
4
|
+
def call(env); env; end
|
5
|
+
end
|
6
|
+
|
7
|
+
context "Rack::Parser" do
|
8
|
+
|
9
|
+
context "default configuration" do
|
10
|
+
setup do
|
11
|
+
Rack::Parser.new(FooApp.new).content_types
|
12
|
+
end
|
13
|
+
|
14
|
+
asserts(:[],'application/xml').kind_of Proc
|
15
|
+
asserts(:[],'application/json').kind_of Proc
|
16
|
+
end
|
17
|
+
|
18
|
+
context "with custom configuration" do
|
19
|
+
setup do
|
20
|
+
Rack::Parser.new(FooApp.new, :content_types => {
|
21
|
+
'application/xml' => :meh,
|
22
|
+
'application/foo' => :bar
|
23
|
+
}).content_types
|
24
|
+
end
|
25
|
+
|
26
|
+
asserts(:[], 'application/xml').equals :meh
|
27
|
+
asserts(:[], 'application/foo').equals :bar
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with json" do
|
31
|
+
setup do
|
32
|
+
post '/post', "{\"test\":1,\"foo\":2,\"bar\":\"3\"}", { 'Content-Type' => 'application/json' }
|
33
|
+
end
|
34
|
+
|
35
|
+
asserts(:status).equals 200
|
36
|
+
asserts(:body).equals "{\"test\"=>1, \"foo\"=>2, \"bar\"=>\"3\"}"
|
37
|
+
end
|
38
|
+
|
39
|
+
context "with xml" do
|
40
|
+
setup do
|
41
|
+
put '/post', "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<hash>\n <a type=\"integer\">1</a>\n</hash>\n", { 'Content-Type' => 'application/xml'}
|
42
|
+
end
|
43
|
+
|
44
|
+
asserts(:status).equals 200
|
45
|
+
asserts(:body).equals "{\"hash\"=>{\"a\"=>1}}"
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with custom 'foo'" do
|
49
|
+
setup do
|
50
|
+
post '/post', 'something that does not matter', { 'Content-Type' => 'application/foo' }
|
51
|
+
end
|
52
|
+
|
53
|
+
asserts(:status).equals 200
|
54
|
+
asserts(:body).equals 'foo'
|
55
|
+
end
|
56
|
+
|
57
|
+
context "with bad data" do
|
58
|
+
setup do
|
59
|
+
post '/post', "fuuuuuuuuuu", { 'Content-Type' => 'application/json' }
|
60
|
+
end
|
61
|
+
|
62
|
+
asserts(:status).equals 400
|
63
|
+
asserts(:body).equals "{\"errors\":\"706: unexpected token at 'fuuuuuuuuuu'\"}"
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/test/teststrap.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'riot'
|
3
|
+
require 'rack/test'
|
4
|
+
require 'rack/builder'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
require File.expand_path('../../lib/rack/parser', __FILE__)
|
8
|
+
|
9
|
+
class Riot::Situation
|
10
|
+
include Rack::Test::Methods
|
11
|
+
|
12
|
+
def app
|
13
|
+
main_app = lambda { |env|
|
14
|
+
request = Rack::Request.new(env)
|
15
|
+
return_code, body_text =
|
16
|
+
case request.path
|
17
|
+
when '/' then [200,'Hello world']
|
18
|
+
when '/post' then [200, env['rack.request.form_hash']]
|
19
|
+
else
|
20
|
+
[404,'Nothing here']
|
21
|
+
end
|
22
|
+
[return_code,{'Content-type' => 'text/plain'}, [body_text]]
|
23
|
+
}
|
24
|
+
|
25
|
+
builder = Rack::Builder.new
|
26
|
+
builder.use Rack::Parser, :content_types => { 'application/foo' => Proc.new { |b| 'foo' } }
|
27
|
+
builder.run main_app
|
28
|
+
builder.to_app
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Riot::Context
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Arthur Chiu
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-07-13 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: multi_json
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: multi_xml
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: riot
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
version_requirements: *id004
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: rack-test
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
type: :development
|
69
|
+
version_requirements: *id005
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: json
|
72
|
+
prerelease: false
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
type: :development
|
80
|
+
version_requirements: *id006
|
81
|
+
description: Rack Middleware for parsing post body data for json/xml and various content types
|
82
|
+
email:
|
83
|
+
- mr.arthur.chiu@gmail.com
|
84
|
+
executables: []
|
85
|
+
|
86
|
+
extensions: []
|
87
|
+
|
88
|
+
extra_rdoc_files: []
|
89
|
+
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- Gemfile
|
93
|
+
- MIT-LICENSE
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/rack/parser.rb
|
97
|
+
- rack-parser.gemspec
|
98
|
+
- test/parser_test.rb
|
99
|
+
- test/teststrap.rb
|
100
|
+
homepage: ""
|
101
|
+
licenses: []
|
102
|
+
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
|
106
|
+
require_paths:
|
107
|
+
- lib
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: "0"
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: "0"
|
120
|
+
requirements: []
|
121
|
+
|
122
|
+
rubyforge_project: rack-parser
|
123
|
+
rubygems_version: 1.8.4
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: Rack Middleware for parsing post body data
|
127
|
+
test_files:
|
128
|
+
- test/parser_test.rb
|
129
|
+
- test/teststrap.rb
|