restfulness 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,127 @@
1
+ module Restfulness
2
+ module Headers
3
+
4
+ # Generic media type handling according to the RFC2616 HTTP/1.1 header fields
5
+ # specification.
6
+ #
7
+ # If instantiated with a string, the MediaType object will attempt to parse and
8
+ # set the objects attributes.
9
+ #
10
+ # If an empty or no string is provided, the media-type can be prepared by setting
11
+ # the type, subtype and optional parameters values. Calling the #to_s method will
12
+ # provide the compiled version.
13
+ #
14
+ # Accessor names and parsing is based on details from https://en.wikipedia.org/wiki/Media_type.
15
+ #
16
+ class MediaType
17
+
18
+ # First part of the mime-type, typically "application", "text", or similar.
19
+ # Vendor types are not supported.
20
+ attr_accessor :type
21
+
22
+ # Always last part of definition. For example:
23
+ #
24
+ # * "json" from "application/json"
25
+ # * "user" from "application/vnd.example.user+json;version=1"
26
+ #
27
+ attr_accessor :subtype
28
+
29
+ # Refers to the vendor part of type string, for example:
30
+ #
31
+ # * "example" from "application/vnd.example.user+json"
32
+ #
33
+ attr_accessor :vendor
34
+
35
+ # When using vendor content types, a suffix may be provided:
36
+ #
37
+ # * "json" from "application/vnd.example.user+json"
38
+ #
39
+ attr_accessor :suffix
40
+
41
+ # Hash of parameters using symbols as keys
42
+ attr_accessor :parameters
43
+
44
+ def initialize(str = "")
45
+ # Defaults
46
+ self.type = "*"
47
+ self.subtype = "*"
48
+ self.vendor = ""
49
+ self.suffix = ""
50
+ self.parameters = {}
51
+
52
+ # Attempt to parse string if provided
53
+ parse(str) unless str.empty?
54
+ end
55
+
56
+ def parse(str)
57
+ # Split between base and parameters
58
+ parts = str.split(';').map{|p| p.strip}
59
+ t = parts.shift.split('/', 2)
60
+ self.type = t[0] if t[0]
61
+
62
+ # Handle subtype, and more complex vendor + suffix
63
+ if t[1]
64
+ (v, s) = t[1].split('+',2)
65
+ self.suffix = s if s
66
+ s = v.split('.')
67
+ s.shift if s[0] == 'vnd'
68
+ self.subtype = s.pop
69
+ self.vendor = s.join('.') unless s.empty?
70
+ end
71
+
72
+ # Finally, with remaining parts, handle parameters
73
+ self.parameters = Hash[parts.map{|p| (k,v) = p.split('=', 2); [k.to_sym, v]}]
74
+ end
75
+
76
+ def to_s
77
+ base = "#{type}/"
78
+ if !vendor.empty?
79
+ base << ["vnd", vendor, subtype].join('.')
80
+ else
81
+ base << subtype
82
+ end
83
+ base << "+#{suffix}" unless suffix.empty?
84
+ base << ";" + parameters.map{|k,v| "#{k}=#{v}"}.join(';') unless parameters.empty?
85
+ base
86
+ end
87
+
88
+ def ==(value)
89
+ if value.is_a?(String)
90
+ value = self.class.new(value)
91
+ end
92
+ raise "Invalid type comparison!" unless value.is_a?(MediaType)
93
+ type == value.type &&
94
+ subtype == value.subtype &&
95
+ vendor == value.vendor &&
96
+ suffix == value.suffix &&
97
+ parameters == value.parameters
98
+ end
99
+
100
+ def charset
101
+ parameters[:charset]
102
+ end
103
+
104
+ def version
105
+ parameters[:version]
106
+ end
107
+
108
+ def json?
109
+ type == "application" && (subtype == "json" || suffix == "json")
110
+ end
111
+
112
+ def xml?
113
+ type == "application" && (subtype == "xml" || suffix == "xml")
114
+ end
115
+
116
+ def text?
117
+ type == "text" && subtype == "plain"
118
+ end
119
+
120
+ def form?
121
+ type == "application" && subtype == "x-www-form-urlencoded"
122
+ end
123
+
124
+ end
125
+
126
+ end
127
+ end
@@ -1,12 +1,15 @@
1
1
  module Restfulness
2
2
 
3
3
  # Simple, indpendent, request interface for dealing with the incoming information
4
- # in a request.
4
+ # in a request.
5
5
  #
6
6
  # Currently wraps around the information provided in a Rack Request object.
7
7
  class Request
8
8
  include Requests::Authorization
9
9
 
10
+ # Expose rack env to interact with rack middleware
11
+ attr_accessor :env
12
+
10
13
  # Who does this request belong to?
11
14
  attr_reader :app
12
15
 
@@ -55,19 +58,32 @@ module Restfulness
55
58
  @sanitized_query ||= uri.query ? Sanitizer.sanitize_query_string(uri.query) : ''
56
59
  end
57
60
 
61
+ def accept
62
+ if headers[:accept]
63
+ @accept ||= Headers::Accept.new(headers[:accept])
64
+ end
65
+ end
66
+
67
+ def content_type
68
+ if headers[:content_type]
69
+ @content_type ||= Headers::MediaType.new(headers[:content_type])
70
+ end
71
+ end
72
+
58
73
  def params
59
74
  @params ||= begin
60
- if body.nil? || body.length == 0
61
- {}
62
- else
63
- case headers[:content_type]
64
- when /application\/json/
65
- @params = params_from_json(body)
66
- when /application\/x\-www\-form\-urlencoded/
67
- @params = params_from_form(body)
75
+ data = body_to_string || ""
76
+ if data.length > 0
77
+ if content_type && content_type.json?
78
+ params_from_json(data)
79
+ elsif content_type && content_type.form?
80
+ params_from_form(data)
68
81
  else
82
+ # Body provided with no or invalid content type
69
83
  raise HTTPException.new(406)
70
84
  end
85
+ else
86
+ {}
71
87
  end
72
88
  end
73
89
  end
@@ -90,15 +106,27 @@ module Restfulness
90
106
 
91
107
  protected
92
108
 
93
- def params_from_json(body)
94
- MultiJson.decode(body)
109
+ def body_to_string
110
+ unless body.nil?
111
+ # Sometimes the body can be a StringIO, Tempfile, or some other freakish IO.
112
+ if body.respond_to?(:read)
113
+ body.read
114
+ else
115
+ body
116
+ end
117
+ else
118
+ ""
119
+ end
120
+ end
121
+
122
+ def params_from_json(data)
123
+ MultiJson.decode(data)
95
124
  rescue MultiJson::LoadError
96
- raise HTTPException.new(400)
125
+ raise HTTPException.new(400, "Invalid JSON in request body")
97
126
  end
98
127
 
99
- def params_from_form(body)
100
- # Sometimes the body can be a StringIO
101
- Rack::Utils.parse_query(body.is_a?(StringIO) ? body.read : body)
128
+ def params_from_form(data)
129
+ Rack::Utils.parse_query(data)
102
130
  end
103
131
 
104
132
  end
@@ -1,3 +1,3 @@
1
1
  module Restfulness
2
- VERSION = "0.3.2"
2
+ VERSION = "0.3.3"
3
3
  end
@@ -25,5 +25,5 @@ Gem::Specification.new do |spec|
25
25
 
26
26
  spec.add_development_dependency "bundler", "~> 1.3"
27
27
  spec.add_development_dependency "rake"
28
- spec.add_development_dependency "rspec", "~> 2.14.1"
28
+ spec.add_development_dependency "rspec"
29
29
  end
@@ -5,7 +5,7 @@ require 'bundler/setup'
5
5
  require 'restfulness' # and any other gems you need
6
6
 
7
7
  RSpec.configure do |config|
8
- config.color_enabled = true
8
+ config.color = true
9
9
 
10
10
  # Avoid deprication messages with this:
11
11
  I18n.config.enforce_available_locales = false
@@ -14,7 +14,7 @@ describe Restfulness::Application do
14
14
  describe "#router" do
15
15
  it "should access class's router" do
16
16
  obj = klass.new
17
- obj.router.should eql(klass.router)
17
+ expect(obj.router).to eql(klass.router)
18
18
  end
19
19
  end
20
20
 
@@ -23,8 +23,8 @@ describe Restfulness::Application do
23
23
  env = {}
24
24
  obj = klass.new
25
25
  app = double(:app)
26
- app.should_receive(:call).with(env)
27
- obj.should_receive(:build_rack_app).and_return(app)
26
+ expect(app).to receive(:call).with(env)
27
+ expect(obj).to receive(:build_rack_app).and_return(app)
28
28
  obj.call(env)
29
29
  end
30
30
  end
@@ -34,10 +34,10 @@ describe Restfulness::Application do
34
34
  obj = klass.new
35
35
  obj.class.middlewares << Rack::ShowExceptions
36
36
  app = obj.send(:build_rack_app)
37
- app.should be_a(Rack::Builder)
37
+ expect(app).to be_a(Rack::Builder)
38
38
  # Note, this might brake if Rack changes!
39
- app.instance_variable_get(:@use).first.call.should be_a(klass.middlewares.first)
40
- app.instance_variable_get(:@run).should be_a(Restfulness::Dispatchers::Rack)
39
+ expect(app.instance_variable_get(:@use).first.call).to be_a(klass.middlewares.first)
40
+ expect(app.instance_variable_get(:@run)).to be_a(Restfulness::Dispatchers::Rack)
41
41
  end
42
42
  end
43
43
 
@@ -45,18 +45,18 @@ describe Restfulness::Application do
45
45
 
46
46
  context "basic usage" do
47
47
  it "should build a new router with block" do
48
- klass.router.should_not be_nil
49
- klass.router.should be_a(Restfulness::Router)
48
+ expect(klass.router).not_to be_nil
49
+ expect(klass.router).to be_a(Restfulness::Router)
50
50
  end
51
51
 
52
52
  it "should be accessable from instance" do
53
53
  obj = klass.new
54
- obj.router.should eql(klass.router)
54
+ expect(obj.router).to eql(klass.router)
55
55
  end
56
56
 
57
57
  it "should pass block to Router instance" do
58
58
  block = lambda { }
59
- Restfulness::Router.should_receive(:new).with(&block)
59
+ expect(Restfulness::Router).to receive(:new).with(no_args, &block)
60
60
  Class.new(Restfulness::Application) do
61
61
  routes &block
62
62
  end
@@ -67,14 +67,14 @@ describe Restfulness::Application do
67
67
 
68
68
  describe ".middlewares" do
69
69
  it "should provide empty array of middlewares" do
70
- klass.middlewares.should be_a(Array)
71
- klass.middlewares.should be_empty
70
+ expect(klass.middlewares).to be_a(Array)
71
+ expect(klass.middlewares).to be_empty
72
72
  end
73
73
  end
74
74
 
75
75
  describe ".logger" do
76
76
  it "should return main logger" do
77
- klass.logger.should eql(Restfulness.logger)
77
+ expect(klass.logger).to eql(Restfulness.logger)
78
78
  end
79
79
  end
80
80
 
@@ -83,7 +83,7 @@ describe Restfulness::Application do
83
83
  orig = Restfulness.logger
84
84
  logger = double(:Logger)
85
85
  klass.logger = logger
86
- Restfulness.logger.should eql(logger)
86
+ expect(Restfulness.logger).to eql(logger)
87
87
  Restfulness.logger = orig
88
88
  end
89
89
  end
@@ -6,7 +6,7 @@ describe Restfulness::Dispatcher do
6
6
  describe "#initialize" do
7
7
  it "should assign app variable" do
8
8
  obj = Restfulness::Dispatcher.new(:foo)
9
- obj.app.should eql(:foo)
9
+ expect(obj.app).to eql(:foo)
10
10
  end
11
11
  end
12
12
 
@@ -16,7 +16,7 @@ describe Restfulness::Dispatchers::Rack do
16
16
  let :app do
17
17
  Class.new(Restfulness::Application) {
18
18
  routes do
19
- add 'projects', RackExampleResource
19
+ add 'projects', RackExampleResource
20
20
  end
21
21
  }.new
22
22
  end
@@ -46,9 +46,9 @@ describe Restfulness::Dispatchers::Rack do
46
46
 
47
47
  it "should handle basic call and return response" do
48
48
  res = obj.call(env)
49
- res[0].should eql(200)
50
- res[1].should be_a(Hash)
51
- res[2].first.should eql('rack_example_result')
49
+ expect(res[0]).to eql(200)
50
+ expect(res[1]).to be_a(Hash)
51
+ expect(res[2].first).to eql('rack_example_result')
52
52
  end
53
53
 
54
54
 
@@ -60,7 +60,7 @@ describe Restfulness::Dispatchers::Rack do
60
60
  actions = ['DELETE', 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'OPTIONS']
61
61
  actions.each do |action|
62
62
  val = obj.send(:parse_action, env, action)
63
- val.should eql(action.downcase.to_sym)
63
+ expect(val).to eql(action.downcase.to_sym)
64
64
  end
65
65
  end
66
66
 
@@ -72,12 +72,12 @@ describe Restfulness::Dispatchers::Rack do
72
72
 
73
73
  it "should override the action if the override header is present" do
74
74
  env['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'PATCH'
75
- obj.send(:parse_action, env, 'POST').should eql(:patch)
75
+ expect(obj.send(:parse_action, env, 'POST')).to eql(:patch)
76
76
  end
77
77
 
78
78
  it "should handle junk in action override header" do
79
79
  env['HTTP_X_HTTP_METHOD_OVERRIDE'] = ' PatCH '
80
- obj.send(:parse_action, env, 'POST').should eql(:patch)
80
+ expect(obj.send(:parse_action, env, 'POST')).to eql(:patch)
81
81
  end
82
82
 
83
83
  end
@@ -86,8 +86,8 @@ describe Restfulness::Dispatchers::Rack do
86
86
 
87
87
  it "should parse headers from environment" do
88
88
  res = obj.send(:prepare_headers, env)
89
- res[:content_type].should eql('application/json')
90
- res[:x_auth_token].should eql('foobartoken')
89
+ expect(res[:content_type]).to eql('application/json')
90
+ expect(res[:x_auth_token]).to eql('foobartoken')
91
91
  end
92
92
  end
93
93
 
@@ -96,24 +96,25 @@ describe Restfulness::Dispatchers::Rack do
96
96
  it "should prepare request object with main fields" do
97
97
  req = obj.send(:prepare_request, env)
98
98
 
99
- req.uri.should be_a(URI)
100
- req.action.should eql(:get)
101
- req.body.should be_nil
102
- req.headers.keys.should include(:x_auth_token)
103
- req.remote_ip.should eql('192.168.1.23')
104
- req.user_agent.should eql('Some Navigator')
99
+ expect(req.uri).to be_a(URI)
100
+ expect(req.action).to eql(:get)
101
+ expect(req.body).to be_nil
102
+ expect(req.headers.keys).to include(:x_auth_token)
103
+ expect(req.remote_ip).to eql('192.168.1.23')
104
+ expect(req.user_agent).to eql('Some Navigator')
105
+ expect(req.env).to be env
105
106
 
106
- req.query.should_not be_empty
107
- req.query[:query].should eql('test')
107
+ expect(req.query).not_to be_empty
108
+ expect(req.query[:query]).to eql('test')
108
109
 
109
- req.headers[:content_type].should eql('application/json')
110
+ expect(req.headers[:content_type]).to eql('application/json')
110
111
  end
111
112
 
112
113
  it "should handle the body stringio" do
113
114
  env['rack.input'] = StringIO.new("Some String")
114
115
 
115
116
  req = obj.send(:prepare_request, env)
116
- req.body.read.should eql('Some String')
117
+ expect(req.body.read).to eql('Some String')
117
118
  end
118
119
 
119
120
  it "should rewind the body stringio" do
@@ -121,7 +122,7 @@ describe Restfulness::Dispatchers::Rack do
121
122
  env['rack.input'].read
122
123
 
123
124
  req = obj.send(:prepare_request, env)
124
- req.body.read.should eql('Some String')
125
+ expect(req.body.read).to eql('Some String')
125
126
  end
126
127
 
127
128
 
@@ -5,15 +5,15 @@ describe Restfulness::HTTPException do
5
5
  describe "#initialize" do
6
6
  it "should assign variables" do
7
7
  obj = Restfulness::HTTPException.new(200, "payload", :message => 'foo', :headers => {})
8
- obj.status.should eql(200)
9
- obj.payload.should eql("payload")
10
- obj.message.should eql('foo')
11
- obj.headers.should eql({})
8
+ expect(obj.status).to eql(200)
9
+ expect(obj.payload).to eql("payload")
10
+ expect(obj.message).to eql('foo')
11
+ expect(obj.headers).to eql({})
12
12
  end
13
13
 
14
14
  it "should use status status for message if none provided" do
15
15
  obj = Restfulness::HTTPException.new(200, "payload")
16
- obj.message.should eql('OK')
16
+ expect(obj.message).to eql('OK')
17
17
  end
18
18
  end
19
19