webbed 0.1.1 → 0.2.0

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.
Files changed (46) hide show
  1. data/.gitignore +22 -0
  2. data/.travis.yml +7 -0
  3. data/.yardopts +6 -0
  4. data/Gemfile +5 -13
  5. data/Guardfile +5 -0
  6. data/README.md +36 -31
  7. data/Rakefile +7 -14
  8. data/lib/webbed.rb +28 -6
  9. data/lib/webbed/generic_message.rb +27 -15
  10. data/lib/webbed/headers.rb +2 -0
  11. data/lib/webbed/helpers/entity_headers_helper.rb +62 -0
  12. data/lib/webbed/helpers/method_helper.rb +83 -0
  13. data/lib/webbed/helpers/rack_request_helper.rb +86 -0
  14. data/lib/webbed/helpers/rack_response_helper.rb +56 -0
  15. data/lib/webbed/helpers/request_headers_helper.rb +62 -0
  16. data/lib/webbed/helpers/request_uri_helper.rb +34 -0
  17. data/lib/webbed/helpers/response_headers_helper.rb +48 -0
  18. data/lib/webbed/helpers/scheme_helper.rb +26 -0
  19. data/lib/webbed/http_version.rb +88 -33
  20. data/lib/webbed/media_type.rb +160 -0
  21. data/lib/webbed/method.rb +63 -33
  22. data/lib/webbed/request.rb +65 -21
  23. data/lib/webbed/response.rb +65 -24
  24. data/lib/webbed/status_code.rb +84 -17
  25. data/lib/webbed/version.rb +1 -1
  26. data/test/support/assertions.rb +17 -0
  27. data/test/support/runner.rb +326 -0
  28. data/test/test_helper.rb +13 -0
  29. data/test/webbed/generic_message_test.rb +44 -0
  30. data/test/webbed/headers_test.rb +31 -0
  31. data/test/webbed/helpers/entity_headers_helper_test.rb +68 -0
  32. data/test/webbed/helpers/method_helper_test.rb +151 -0
  33. data/test/webbed/helpers/rack_request_helper_test.rb +108 -0
  34. data/test/webbed/helpers/rack_response_helper_test.rb +33 -0
  35. data/test/webbed/helpers/request_headers_helper_test.rb +57 -0
  36. data/test/webbed/helpers/request_uri_helper_test.rb +32 -0
  37. data/test/webbed/helpers/response_headers_helper_test.rb +46 -0
  38. data/test/webbed/helpers/scheme_helper_test.rb +28 -0
  39. data/test/webbed/http_version_test.rb +52 -0
  40. data/test/webbed/media_type_test.rb +100 -0
  41. data/test/webbed/method_test.rb +160 -0
  42. data/test/webbed/request_test.rb +74 -0
  43. data/test/webbed/response_test.rb +86 -0
  44. data/test/webbed/status_code_test.rb +105 -0
  45. data/webbed.gemspec +31 -0
  46. metadata +128 -41
data/lib/webbed/method.rb CHANGED
@@ -1,64 +1,94 @@
1
1
  module Webbed
2
+ # Representation of an HTTP Method.
2
3
  class Method
3
-
4
- attr_reader :name
5
- alias :to_s :name
6
- alias :inspect :name
7
-
8
4
  DEFAULTS = {
9
5
  :safe => false,
10
6
  :idempotent => false,
11
- :entities => [:request, :response]
7
+ :allowable_entities => [:request, :response]
12
8
  }
13
9
 
14
- def self.new(name, options = {})
15
- if const_defined? name
16
- const_get(name)
10
+ # The allowable entities of a message.
11
+ #
12
+ # It can contain any combination of `:request` and `:response`.
13
+ #
14
+ # @return [Array<:request, :response>]
15
+ attr_reader :allowable_entities
16
+
17
+ # Checks for a cached Method or creates a new one.
18
+ #
19
+ # It caches the standard HTTP Methods that are in RFC 2616 as well as the
20
+ # new method `PATCH`. If the Method cannot be found, it calls `#initialize`.
21
+ #
22
+ # @example
23
+ # Webbed::Method.new('GET') # => Webbed::Method::GET
24
+ #
25
+ # @param (see #initialize)
26
+ # @return [Method] the new or cached Method
27
+ # @see #initialize
28
+ def self.new(value, options = {})
29
+ if const_defined?(value)
30
+ const_get(value)
17
31
  else
18
- super(name, options)
32
+ super(value, options)
19
33
  end
20
34
  end
21
35
 
36
+ # Creates a new Method.
37
+ #
38
+ # @param [String] name the name of the Method to create
39
+ # @param [Hash] options the options to create the Method with
40
+ # @option options [Boolean] :safe (false) whether or not the Method is safe
41
+ # @option options [Boolean] :idempotent (false) whether or not the Method is
42
+ # idempotent
43
+ # @option options [Array<:request, :response>] :allowable_entities
44
+ # ([:request, :response]) the allowable entites of a message
22
45
  def initialize(name, options = {})
23
- options = DEFAULTS.merge options
46
+ options = DEFAULTS.merge(options)
24
47
  @name = name
25
48
  @safe = options[:safe]
26
49
  @idempotent = options[:safe] || options[:idempotent]
27
- @has_request_entity = options[:entities].include? :request
28
- @has_response_entity = options[:entities].include? :response
50
+ @allowable_entities = options[:allowable_entities]
29
51
  end
30
52
 
53
+ # Whether or not the Method is safe.
54
+ #
55
+ # @return [Boolean]
31
56
  def safe?
32
57
  @safe
33
58
  end
34
59
 
60
+ # Whether or not the Method is idempotent.
61
+ #
62
+ # @return [Boolean]
35
63
  def idempotent?
36
64
  @idempotent
37
65
  end
38
66
 
39
- def has_request_entity?
40
- @has_request_entity
41
- end
42
-
43
- def has_response_entity?
44
- @has_response_entity
67
+ # Converts the Method to a string.
68
+ #
69
+ # @return [String]
70
+ def to_s
71
+ @name
45
72
  end
46
73
 
74
+ # Compares the Method to another Method.
75
+ #
76
+ # Two Methods are equal if they have the same name.
77
+ #
78
+ # @param [#to_s] other_method the other Method
79
+ # @return [Boolean]
47
80
  def ==(other_method)
48
- name == other_method.to_s
81
+ to_s == other_method.to_s
49
82
  end
50
83
 
51
- # Common methods used and their settings. Most are defined in RFC 2616 with
52
- # the exception of PATCH which is defined in RFC 5789. These are for caching
53
- # purposes, so that new objects don't need to be created on each request.
54
- OPTIONS = new 'OPTIONS', :safe => true, :idempotent => true, :entities => [:response]
55
- GET = new 'GET', :safe => true, :idempotent => true, :entities => [:response]
56
- HEAD = new 'HEAD', :safe => true, :idempotent => true, :entities => []
57
- POST = new 'POST', :safe => false, :idempotent => false, :entities => [:request, :response]
58
- PUT = new 'PUT', :safe => false, :idempotent => true, :entities => [:request, :response]
59
- DELETE = new 'DELETE', :safe => false, :idempotent => true, :entities => [:response]
60
- TRACE = new 'TRACE', :safe => true, :idempotent => true, :entities => [:response]
61
- CONNECT = new 'CONNECT', :safe => false, :idempotent => false, :entities => [:request, :response]
62
- PATCH = new 'PATCH', :safe => false, :idempotent => false, :entities => [:request, :response]
84
+ OPTIONS = new('OPTIONS', :safe => true, :idempotent => true, :allowable_entities => [:response])
85
+ GET = new('GET', :safe => true, :idempotent => true, :allowable_entities => [:response])
86
+ HEAD = new('HEAD', :safe => true, :idempotent => true, :allowable_entities => [])
87
+ POST = new('POST', :safe => false, :idempotent => false, :allowable_entities => [:request, :response])
88
+ PUT = new('PUT', :safe => false, :idempotent => true, :allowable_entities => [:request, :response])
89
+ DELETE = new('DELETE', :safe => false, :idempotent => true, :allowable_entities => [:response])
90
+ TRACE = new('TRACE', :safe => true, :idempotent => true, :allowable_entities => [:response])
91
+ CONNECT = new('CONNECT', :safe => false, :idempotent => false, :allowable_entities => [:request, :response])
92
+ PATCH = new('PATCH', :safe => false, :idempotent => false, :allowable_entities => [:request, :response])
63
93
  end
64
- end
94
+ end
@@ -1,44 +1,88 @@
1
1
  require 'addressable/uri'
2
2
 
3
3
  module Webbed
4
+ # Representation of an HTTP Request.
5
+ #
6
+ # This class contains the absolute minimum for accessing the different parts
7
+ # of an HTTP Request. Helper modules provide far more functionality.
4
8
  class Request
9
+ include GenericMessage
5
10
 
6
- include Webbed::GenericMessage
11
+ # The Request-URI of the Request.
12
+ #
13
+ # The method automatically converts the new value to an instance of
14
+ # `Addressable::URI` if it is not already one.
15
+ #
16
+ # @return [Addressable::URI]
17
+ # @note {Helpers::RequestURIHelper} aliases this method to `#request_url`.
7
18
  attr_reader :request_uri
8
- DEFAULTS = {
9
- :method => 'GET',
10
- :request_uri => '*',
11
- :http_version => 'HTTP/1.1',
12
- :headers => {},
13
- :entity_body => ''
14
- }
15
-
16
- def initialize(request_hash = {})
17
- request_hash = DEFAULTS.merge(request_hash)
18
-
19
- self.method = request_hash[:method]
20
- self.request_uri = request_hash[:request_uri]
21
- self.http_version = request_hash[:http_version]
22
- self.headers.merge!(request_hash[:headers])
23
- self.entity_body = request_hash[:entity_body]
19
+
20
+ def request_uri=(request_uri)
21
+ @request_uri = Addressable::URI.parse(request_uri)
22
+ end
23
+
24
+ # The scheme of the Request.
25
+ #
26
+ # @return ['http', 'https']
27
+ attr_accessor :scheme
28
+
29
+ # Creates a new Request.
30
+ #
31
+ # The method converts the values passed in to their proper types.
32
+ #
33
+ # @example
34
+ # Webbed::Request.new('GET', 'http://example.com', {}, '')
35
+ #
36
+ # @param [Method, String] method
37
+ # @param [Addressable::URI, String] request_uri
38
+ # @param [Headers, Hash] headers
39
+ # @param [#to_s] entity_body
40
+ # @param [Array] options the options to create the Request with
41
+ # @option options [#to_s] :http_version (1.1) the HTTP-Version of the
42
+ # Request
43
+ # @option options ['http', 'https'] :scheme ('http') the scheme of the
44
+ # Request
45
+ def initialize(method, request_uri, headers, entity_body, options = {})
46
+ self.method = method
47
+ self.request_uri = request_uri
48
+ self.headers = headers
49
+ self.entity_body = entity_body
50
+ self.http_version = options[:http_version] || 1.1
51
+ self.scheme = options[:scheme] || 'http'
24
52
  end
25
53
 
54
+ # The Method of the Request.
55
+ #
56
+ # @return [Method]
26
57
  def method(*args)
27
58
  return super(*args) unless args.empty?
28
59
  @method
29
60
  end
30
61
 
62
+ # Sets the Method of the Request.
63
+ #
64
+ # @param [Method] method
31
65
  def method=(method)
32
66
  @method = Webbed::Method.new(method)
33
67
  end
34
68
 
35
- def request_uri=(uri)
36
- @request_uri = Addressable::URI.parse(uri)
37
- end
38
-
69
+ # The Request-Line of the Request as defined in RFC 2616.
70
+ #
71
+ # @example
72
+ # request = Webbed::Request.new(['GET', 'http://example.com', {}, ''])
73
+ # request.request_line # => "GET * HTTP/1.1\r\n"
74
+ #
75
+ # @return [String]
39
76
  def request_line
40
77
  "#{method} #{request_uri} #{http_version}\r\n"
41
78
  end
42
79
  alias :start_line :request_line
80
+
81
+ include Helpers::MethodHelper
82
+ include Helpers::RequestURIHelper
83
+ include Helpers::RackRequestHelper
84
+ include Helpers::SchemeHelper
85
+ include Helpers::RequestHeadersHelper
86
+ include Helpers::EntityHeadersHelper
43
87
  end
44
88
  end
@@ -1,42 +1,83 @@
1
1
  module Webbed
2
+ # Representation of an HTTP Response.
3
+ #
4
+ # This class contains the absolute minimum for accessing the different parts
5
+ # of an HTTP Response. Helper modules provide far more functionality.
2
6
  class Response
7
+ include GenericMessage
3
8
 
4
- include Webbed::GenericMessage
9
+ STATUS_CODE_REGEX = /^(\d{3}) (.*)$/
10
+
11
+ # The Status Code of the Response.
12
+ #
13
+ # The method automatically converts the new value to an instance of
14
+ # {StatusCode} if it is not already one.
15
+ #
16
+ # @return [#to_i]
5
17
  attr_reader :status_code
6
- attr_writer :reason_phrase
7
- DEFAULTS = {
8
- :http_version => 'HTTP/1.1',
9
- :status_code => 200,
10
- :reason_phrase => nil,
11
- :headers => {},
12
- :entity_body => ''
13
- }
14
-
15
- def initialize(response_hash = {})
16
- response_hash = DEFAULTS.merge(response_hash)
17
-
18
- self.http_version = response_hash[:http_version]
19
- self.status_code = response_hash[:status_code]
20
- self.reason_phrase = response_hash[:reason_phrase]
21
- self.headers.merge!(response_hash[:headers])
22
- self.entity_body = response_hash[:entity_body]
18
+
19
+ def status_code=(status_code)
20
+ @status_code = Webbed::StatusCode.new(status_code)
23
21
  end
24
22
 
23
+ # The Reason Phrase of the Response.
24
+ #
25
+ # The method returns the Status Code's default reason phrase if the Reason
26
+ # Phrase has not already been set.
27
+ #
28
+ # @return [String]
25
29
  def reason_phrase
26
- @reason_phrase || default_reason_phrase
30
+ defined?(@reason_phrase) ? @reason_phrase : @status_code.default_reason_phrase
27
31
  end
28
32
 
29
- def default_reason_phrase
30
- @status_code.default_reason_phrase
31
- end
33
+ attr_writer :reason_phrase
32
34
 
33
- def status_code=(status_code)
34
- @status_code = Webbed::StatusCode.new(status_code)
35
+ # Creates a new Response.
36
+ #
37
+ # The Status Code may be passed in as a string with a Reason Phrase or just
38
+ # a number. If a number is provided, the default Reason Phrase for that
39
+ # Status Code is used.
40
+ #
41
+ # The method converts the values passed in to their proper types.
42
+ #
43
+ # @example
44
+ # Webbed::Response.new(200, {}, '')
45
+ # Webbed::Response.new('404 Missing File', {}, '')
46
+ #
47
+ # @param [Fixnum, String] status_code
48
+ # @param [Headers, Hash] headers
49
+ # @param [#to_s] entity_body
50
+ # @param [Array] options the options to create the Response with @option
51
+ # @options options [#to_s] :http_version (1.1) the HTTP-Version of the
52
+ # Response
53
+ def initialize(status_code, headers, entity_body, options = {})
54
+ if STATUS_CODE_REGEX =~ status_code.to_s
55
+ self.status_code = $1
56
+ self.reason_phrase = $2
57
+ else
58
+ self.status_code = status_code
59
+ end
60
+
61
+ self.headers = headers
62
+ self.entity_body = entity_body
63
+
64
+ self.http_version = options.delete(:http_version) || 1.1
35
65
  end
36
66
 
67
+ # The Status-Line of the Response.
68
+ #
69
+ # @example
70
+ # response = Webbed::Response.new([200, {}, ''])
71
+ # response.status_line # => "HTTP/1.1 200 OK\r\n"
72
+ #
73
+ # @return [String]
37
74
  def status_line
38
75
  "#{http_version} #{status_code} #{reason_phrase}\r\n"
39
76
  end
40
77
  alias :start_line :status_line
78
+
79
+ include Helpers::RackResponseHelper
80
+ include Helpers::ResponseHeadersHelper
81
+ include Helpers::EntityHeadersHelper
41
82
  end
42
83
  end
@@ -1,9 +1,7 @@
1
1
  module Webbed
2
+ # Representation of an HTTP Status Code.
2
3
  class StatusCode
3
-
4
4
  include Comparable
5
- attr_reader :status_code, :default_reason_phrase
6
- CACHED = {}
7
5
 
8
6
  UNKNOWN_REASON_PHRASE = 'Unknown Status Code'
9
7
  REASON_PHRASES = {
@@ -49,54 +47,123 @@ module Webbed
49
47
  505 => 'HTTP Version not supported'
50
48
  }
51
49
 
50
+ @@cached = {}
51
+
52
+ # The default Reason Phrase of the Status Code.
53
+ #
54
+ # @return [String]
55
+ attr_reader :default_reason_phrase
56
+
57
+ # Retrieves a Status Code from the cache or creates a new one.
58
+ #
59
+ # All created Status Codes are cached forever.
60
+ #
61
+ # @param (see #initialize)
62
+ # @return [StatusCode] the new or cached StatusCode
63
+ # @see #initialize
52
64
  def self.new(status_code)
53
- CACHED[status_code] ||= super(status_code)
65
+ status_code = status_code.to_i
66
+ @@cached[status_code] ||= super(status_code)
54
67
  end
55
68
 
69
+ # Creates a new Status Code.
70
+ #
71
+ # @param [Fixnum] status_code
56
72
  def initialize(status_code)
57
- status_code = status_code.to_i
58
73
  @status_code = status_code
59
- @default_reason_phrase = REASON_PHRASES[status_code] || UNKNOWN_REASON_PHRASE
74
+ @default_reason_phrase = REASON_PHRASES[@status_code] || UNKNOWN_REASON_PHRASE
60
75
  end
61
76
 
77
+ # Comparse the Status Code to another Status Code.
78
+ #
79
+ # @param [#to_i] other_status_code the other Status Code
80
+ # @return [Fixnum] the sign of the comparison (either `1`, `0`, or `-1`)
62
81
  def <=>(other_status_code)
63
- status_code <=> other_status_code.to_i
82
+ @status_code <=> other_status_code.to_i
64
83
  end
65
84
 
85
+ # Converts the Status Code to an integer.
86
+ #
87
+ # @return [Fixnum]
66
88
  def to_i
67
- status_code
89
+ @status_code
68
90
  end
69
91
 
92
+ # Converts the Status Code to a string.
93
+ #
94
+ # @return [String]
70
95
  def to_s
71
- '%03d' % status_code
96
+ @status_code.to_s
72
97
  end
73
98
 
99
+ # Whether or not the Status Code is informational.
100
+ #
101
+ # According to RFC 2616, informational status codes are in the range of 100
102
+ # to 199, inclusive.
103
+ #
104
+ # @return [Boolean]
74
105
  def informational?
75
- (100...200).include? status_code
106
+ (100...200).include?(@status_code)
76
107
  end
77
108
 
78
- def success?
79
- (200...300).include? status_code
109
+ # Whether or not the Status Code is successful.
110
+ #
111
+ # According to RFC 2616, successful status codes are in the range of 200 to
112
+ # 299, inclusive.
113
+ #
114
+ # @return [Boolean]
115
+ def successful?
116
+ (200...300).include?(@status_code)
80
117
  end
81
118
 
119
+ # Whether or not the Status Code is a redirection.
120
+ #
121
+ # According to RFC 2616, redirection status codes are in the range of 300 to
122
+ # 399, inclusive.
123
+ #
124
+ # @return [Boolean]
82
125
  def redirection?
83
- (300...400).include? status_code
126
+ (300...400).include?(@status_code)
84
127
  end
85
128
 
129
+ # Whether or not the Status Code is a client error.
130
+ #
131
+ # According to RFC 2616, client error status codes are in the range of 400
132
+ # to 499, inclusive.
133
+ #
134
+ # @return [Boolean]
86
135
  def client_error?
87
- (400...500).include? status_code
136
+ (400...500).include?(@status_code)
88
137
  end
89
138
 
139
+ # Whether or not the Status Code is a server error.
140
+ #
141
+ # According to RFC 2616, server error status codes are in the range of 500
142
+ # to 599, inclusive.
143
+ #
144
+ # @return [Boolean]
90
145
  def server_error?
91
- (500...600).include? status_code
146
+ (500...600).include?(@status_code)
92
147
  end
93
148
 
149
+ # Whether or not the Status Code is unknown.
150
+ #
151
+ # According to RFC 2616, the only defined Status Code ranges are from 100 to
152
+ # 599, inclusive. Anything outside that range is an unknown Status Code.
153
+ #
154
+ # @return [Boolean]
94
155
  def unknown?
95
- !(100...600).include? status_code
156
+ !(100...600).include?(@status_code)
96
157
  end
97
158
 
159
+ # Whether or not the Status Code is an error.
160
+ #
161
+ # According to RFC 2616, Status Codes that signify errors are in the range
162
+ # of 400 to 599, inclusive.
163
+ #
164
+ # @return [Boolean]
98
165
  def error?
99
- (400...600).include? status_code
166
+ (400...600).include?(@status_code)
100
167
  end
101
168
  end
102
169
  end