restfulness 0.3.2 → 0.3.3

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.
@@ -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