api_resource 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/Gemfile +37 -0
  2. data/Gemfile.lock +190 -0
  3. data/Guardfile +27 -0
  4. data/Rakefile +49 -0
  5. data/VERSION +1 -0
  6. data/api_resource.gemspec +111 -0
  7. data/coverage/assets/0.5.3/app.js +88 -0
  8. data/coverage/assets/0.5.3/fancybox/blank.gif +0 -0
  9. data/coverage/assets/0.5.3/fancybox/fancy_close.png +0 -0
  10. data/coverage/assets/0.5.3/fancybox/fancy_loading.png +0 -0
  11. data/coverage/assets/0.5.3/fancybox/fancy_nav_left.png +0 -0
  12. data/coverage/assets/0.5.3/fancybox/fancy_nav_right.png +0 -0
  13. data/coverage/assets/0.5.3/fancybox/fancy_shadow_e.png +0 -0
  14. data/coverage/assets/0.5.3/fancybox/fancy_shadow_n.png +0 -0
  15. data/coverage/assets/0.5.3/fancybox/fancy_shadow_ne.png +0 -0
  16. data/coverage/assets/0.5.3/fancybox/fancy_shadow_nw.png +0 -0
  17. data/coverage/assets/0.5.3/fancybox/fancy_shadow_s.png +0 -0
  18. data/coverage/assets/0.5.3/fancybox/fancy_shadow_se.png +0 -0
  19. data/coverage/assets/0.5.3/fancybox/fancy_shadow_sw.png +0 -0
  20. data/coverage/assets/0.5.3/fancybox/fancy_shadow_w.png +0 -0
  21. data/coverage/assets/0.5.3/fancybox/fancy_title_left.png +0 -0
  22. data/coverage/assets/0.5.3/fancybox/fancy_title_main.png +0 -0
  23. data/coverage/assets/0.5.3/fancybox/fancy_title_over.png +0 -0
  24. data/coverage/assets/0.5.3/fancybox/fancy_title_right.png +0 -0
  25. data/coverage/assets/0.5.3/fancybox/fancybox-x.png +0 -0
  26. data/coverage/assets/0.5.3/fancybox/fancybox-y.png +0 -0
  27. data/coverage/assets/0.5.3/fancybox/fancybox.png +0 -0
  28. data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.css +363 -0
  29. data/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.pack.js +44 -0
  30. data/coverage/assets/0.5.3/favicon_green.png +0 -0
  31. data/coverage/assets/0.5.3/favicon_red.png +0 -0
  32. data/coverage/assets/0.5.3/favicon_yellow.png +0 -0
  33. data/coverage/assets/0.5.3/highlight.css +129 -0
  34. data/coverage/assets/0.5.3/highlight.pack.js +1 -0
  35. data/coverage/assets/0.5.3/jquery-1.6.2.min.js +18 -0
  36. data/coverage/assets/0.5.3/jquery.dataTables.min.js +152 -0
  37. data/coverage/assets/0.5.3/jquery.timeago.js +141 -0
  38. data/coverage/assets/0.5.3/jquery.url.js +174 -0
  39. data/coverage/assets/0.5.3/loading.gif +0 -0
  40. data/coverage/assets/0.5.3/magnify.png +0 -0
  41. data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  42. data/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  43. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  44. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  45. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  46. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  47. data/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  48. data/coverage/assets/0.5.3/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  49. data/coverage/assets/0.5.3/smoothness/images/ui-icons_222222_256x240.png +0 -0
  50. data/coverage/assets/0.5.3/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  51. data/coverage/assets/0.5.3/smoothness/images/ui-icons_454545_256x240.png +0 -0
  52. data/coverage/assets/0.5.3/smoothness/images/ui-icons_888888_256x240.png +0 -0
  53. data/coverage/assets/0.5.3/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  54. data/coverage/assets/0.5.3/smoothness/jquery-ui-1.8.4.custom.css +295 -0
  55. data/coverage/assets/0.5.3/stylesheet.css +383 -0
  56. data/coverage/index.html +3573 -0
  57. data/lib/api_resource.rb +130 -0
  58. data/lib/api_resource/association_activation.rb +19 -0
  59. data/lib/api_resource/associations.rb +218 -0
  60. data/lib/api_resource/associations/association_proxy.rb +116 -0
  61. data/lib/api_resource/associations/belongs_to_remote_object_proxy.rb +16 -0
  62. data/lib/api_resource/associations/dynamic_resource_scope.rb +23 -0
  63. data/lib/api_resource/associations/generic_scope.rb +68 -0
  64. data/lib/api_resource/associations/has_many_remote_object_proxy.rb +16 -0
  65. data/lib/api_resource/associations/has_many_through_remote_object_proxy.rb +13 -0
  66. data/lib/api_resource/associations/has_one_remote_object_proxy.rb +24 -0
  67. data/lib/api_resource/associations/multi_argument_resource_scope.rb +15 -0
  68. data/lib/api_resource/associations/multi_object_proxy.rb +84 -0
  69. data/lib/api_resource/associations/related_object_hash.rb +12 -0
  70. data/lib/api_resource/associations/relation_scope.rb +25 -0
  71. data/lib/api_resource/associations/resource_scope.rb +32 -0
  72. data/lib/api_resource/associations/scope.rb +132 -0
  73. data/lib/api_resource/associations/single_object_proxy.rb +82 -0
  74. data/lib/api_resource/attributes.rb +243 -0
  75. data/lib/api_resource/base.rb +717 -0
  76. data/lib/api_resource/callbacks.rb +45 -0
  77. data/lib/api_resource/connection.rb +195 -0
  78. data/lib/api_resource/core_extensions.rb +7 -0
  79. data/lib/api_resource/custom_methods.rb +117 -0
  80. data/lib/api_resource/decorators.rb +6 -0
  81. data/lib/api_resource/decorators/caching_decorator.rb +20 -0
  82. data/lib/api_resource/exceptions.rb +99 -0
  83. data/lib/api_resource/formats.rb +22 -0
  84. data/lib/api_resource/formats/json_format.rb +25 -0
  85. data/lib/api_resource/formats/xml_format.rb +36 -0
  86. data/lib/api_resource/local.rb +12 -0
  87. data/lib/api_resource/log_subscriber.rb +15 -0
  88. data/lib/api_resource/mocks.rb +285 -0
  89. data/lib/api_resource/model_errors.rb +82 -0
  90. data/lib/api_resource/observing.rb +27 -0
  91. data/lib/api_resource/railtie.rb +24 -0
  92. data/lib/api_resource/scopes.rb +48 -0
  93. data/nohup.out +63 -0
  94. data/spec/lib/api_resource_spec.rb +43 -0
  95. data/spec/lib/associations_spec.rb +751 -0
  96. data/spec/lib/attributes_spec.rb +191 -0
  97. data/spec/lib/base_spec.rb +655 -0
  98. data/spec/lib/callbacks_spec.rb +68 -0
  99. data/spec/lib/connection_spec.rb +137 -0
  100. data/spec/lib/local_spec.rb +20 -0
  101. data/spec/lib/mocks_spec.rb +74 -0
  102. data/spec/lib/model_errors_spec.rb +29 -0
  103. data/spec/lib/prefixes_spec.rb +107 -0
  104. data/spec/spec_helper.rb +82 -0
  105. data/spec/support/mocks/association_mocks.rb +63 -0
  106. data/spec/support/mocks/error_resource_mocks.rb +21 -0
  107. data/spec/support/mocks/prefix_model_mocks.rb +5 -0
  108. data/spec/support/mocks/test_resource_mocks.rb +44 -0
  109. data/spec/support/requests/association_requests.rb +31 -0
  110. data/spec/support/requests/error_resource_requests.rb +25 -0
  111. data/spec/support/requests/prefix_model_requests.rb +7 -0
  112. data/spec/support/requests/test_resource_requests.rb +38 -0
  113. data/spec/support/test_resource.rb +72 -0
  114. data/spec/tmp/DIR +0 -0
  115. data/spec/tmp/api_resource_test_db.sqlite +0 -0
  116. metadata +119 -3
@@ -0,0 +1,45 @@
1
+ module ApiResource
2
+
3
+ module Callbacks
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+
9
+ extend ActiveModel::Callbacks
10
+
11
+ define_model_callbacks :save, :create, :update, :destroy
12
+
13
+ [:save, :create, :update, :destroy].each do |action|
14
+ alias_method_chain action, :callbacks
15
+ end
16
+
17
+ end
18
+
19
+ def save_with_callbacks(*args)
20
+ run_callbacks :save do
21
+ save_without_callbacks(*args)
22
+ end
23
+ end
24
+
25
+ def create_with_callbacks(*args)
26
+ run_callbacks :create do
27
+ create_without_callbacks(*args)
28
+ end
29
+ end
30
+
31
+ def update_with_callbacks(*args)
32
+ run_callbacks :update do
33
+ update_without_callbacks(*args)
34
+ end
35
+ end
36
+
37
+ def destroy_with_callbacks(*args)
38
+ run_callbacks :destroy do
39
+ destroy_without_callbacks(*args)
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,195 @@
1
+ require 'active_support/core_ext/benchmark'
2
+ require 'rest_client'
3
+ require 'net/https'
4
+ require 'date'
5
+ require 'time'
6
+ require 'uri'
7
+
8
+ module ApiResource
9
+ # Class to handle connections to remote web services.
10
+ # This class is used by ActiveResource::Base to interface with REST
11
+ # services.
12
+ class Connection
13
+
14
+ HTTP_FORMAT_HEADER_NAMES = {
15
+ :get => 'Accept',
16
+ :put => 'Content-Type',
17
+ :post => 'Content-Type',
18
+ :delete => 'Accept',
19
+ :head => 'Accept'
20
+ }
21
+
22
+ attr_reader :site, :user, :password, :auth_type, :timeout, :proxy, :ssl_options
23
+ attr_accessor :format, :headers
24
+
25
+ class << self
26
+ def requests
27
+ @@requests ||= []
28
+ end
29
+ end
30
+
31
+ # The +site+ parameter is required and will set the +site+
32
+ # attribute to the URI for the remote resource service.
33
+ def initialize(site, format = ApiResource::Formats::JsonFormat, headers)
34
+ raise ArgumentError, 'Missing site URI' unless site
35
+ @user = @password = nil
36
+ @uri_parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
37
+ self.site = site
38
+ self.format = format
39
+ self.headers = headers
40
+ end
41
+
42
+ # Set URI for remote service.
43
+ def site=(site)
44
+ @site = site.is_a?(URI) ? site : @uri_parser.parse(site)
45
+ @user = @uri_parser.unescape(@site.user) if @site.user
46
+ @password = @uri_parser.unescape(@site.password) if @site.password
47
+ end
48
+
49
+ # Sets the number of seconds after which HTTP requests to the remote service should time out.
50
+ def timeout=(timeout)
51
+ @timeout = timeout
52
+ end
53
+
54
+ def get(path, headers = self.headers)
55
+ # our site and headers for this request
56
+ site = self.site.merge(path)
57
+ headers = build_request_headers(headers, :get, site)
58
+
59
+ self.with_caching(path, headers) do
60
+ format.decode(request(:get, path, headers))
61
+ end
62
+ end
63
+
64
+ def delete(path, headers = self.headers)
65
+ request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path)))
66
+ return true
67
+ end
68
+
69
+ def head(path, headers = self.headers)
70
+ request(:head, path, build_request_headers(headers, :head, self.site.merge(path)))
71
+ end
72
+
73
+
74
+ def put(path, body = {}, headers = self.headers)
75
+ # If there's a file to send then we can't use JSON or XML
76
+ if !body.is_a?(String) && RestClient::Payload.has_file?(body)
77
+ format.decode(request(:put, path, body, build_request_headers(headers, :put, self.site.merge(path))))
78
+ else
79
+ format.decode(request(:put, path, body, build_request_headers(headers, :put, self.site.merge(path))))
80
+ end
81
+ end
82
+
83
+ def post(path, body = {}, headers = self.headers)
84
+ if !body.is_a?(String) && RestClient::Payload.has_file?(body)
85
+ format.decode(request(:post, path, body, build_request_headers(headers, :post, self.site.merge(path))))
86
+ else
87
+ format.decode(request(:post, path, body, build_request_headers(headers, :post, self.site.merge(path))))
88
+ end
89
+ end
90
+
91
+ protected
92
+
93
+ def cache_key(path, headers)
94
+ key = Digest::MD5.hexdigest([path, headers].to_s)
95
+ return "a-#{key}-#{ApiResource::Base.ttl}"
96
+ end
97
+
98
+ def with_caching(path, data = {}, &block)
99
+ if ApiResource::Base.ttl.to_f > 0.0
100
+ key = self.cache_key(path, data)
101
+ ApiResource.cache.fetch(key, :expires_in => ApiResource::Base.ttl) do
102
+ yield
103
+ end
104
+ else
105
+ yield
106
+ end
107
+ end
108
+
109
+ private
110
+ # Makes a request to the remote service.
111
+ def request(method, path, *arguments)
112
+ handle_response(path) do
113
+ ActiveSupport::Notifications.instrument("request.api_resource") do |payload|
114
+
115
+ # debug logging
116
+ ApiResource.logger.debug("#{method.to_s.upcase} #{path}")
117
+
118
+ payload[:method] = method
119
+ payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}"
120
+ payload[:result] = http(path).send(method, *arguments)
121
+ end
122
+ end
123
+ end
124
+
125
+ # Handles response and error codes from the remote service.
126
+ def handle_response(path, &block)
127
+ begin
128
+ result = yield
129
+ rescue RestClient::RequestTimeout
130
+ raise ApiResource::RequestTimeout.new("Request Time Out - Accessing #{path}}")
131
+ rescue Exception => error
132
+ if error.respond_to?(:http_code)
133
+ ApiResource.logger.error("#{self} accessing #{path}")
134
+ ApiResource.logger.error(error.message)
135
+ result = error.response
136
+ else
137
+ raise ApiResource::ConnectionError.new(nil, :message => "Unknown error #{error}")
138
+ end
139
+ end
140
+ return propogate_response_or_error(result, result.code)
141
+ end
142
+
143
+ def propogate_response_or_error(response, code)
144
+ case code.to_i
145
+ when 301,302
146
+ raise ApiResource::Redirection.new(response)
147
+ when 200..400
148
+ response.body
149
+ when 400
150
+ raise ApiResource::BadRequest.new(response)
151
+ when 401
152
+ raise ApiResource::UnauthorizedAccess.new(response)
153
+ when 403
154
+ raise ApiResource::ForbiddenAccess.new(response)
155
+ when 404
156
+ raise ApiResource::ResourceNotFound.new(response)
157
+ when 405
158
+ raise ApiResource::MethodNotAllowed.new(response)
159
+ when 406
160
+ raise ApiResource::NotAccepatable.new(response)
161
+ when 409
162
+ raise ApiResource::ResourceNotFound.new(response)
163
+ when 410
164
+ raise ApiResource::ResourceGone.new(response)
165
+ when 422
166
+ raise ApiResource::UnprocessableEntity.new(response)
167
+ when 401..499
168
+ raise ApiResource::ClientError.new(response)
169
+ when 500..600
170
+ raise ApiResource::ServerError.new(response)
171
+ else
172
+ raise ApiResource::ConnectionError.new(response, :message => "Unknown response code: #{code}")
173
+ end
174
+ end
175
+
176
+ # Creates new Net::HTTP instance for communication with the
177
+ # remote service and resources.
178
+ def http(path)
179
+ unless path =~ /\./
180
+ path += ".#{self.format.extension}"
181
+ end
182
+ RestClient::Resource.new("#{site.scheme}://#{site.host}:#{site.port}#{path}", {:timeout => ApiResource::Base.timeout, :open_timeout => ApiResource::Base.open_timeout})
183
+ end
184
+
185
+ def build_request_headers(headers, verb, uri)
186
+ http_format_header(verb).update(headers)
187
+ end
188
+
189
+ def http_format_header(verb)
190
+ {}.tap do |ret|
191
+ ret[HTTP_FORMAT_HEADER_NAMES[verb]] = format.mime_type
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,7 @@
1
+ class Array
2
+
3
+ def symbolize_array
4
+ self.collect{|item| item.to_s.to_sym}
5
+ end
6
+
7
+ end
@@ -0,0 +1,117 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module ApiResource
4
+ # A module to support custom REST methods and sub-resources, allowing you to break out
5
+ # of the "default" REST methods with your own custom resource requests. For example,
6
+ # say you use Rails to expose a REST service and configure your routes with:
7
+ #
8
+ # map.resources :people, :new => { :register => :post },
9
+ # :member => { :promote => :put, :deactivate => :delete }
10
+ # :collection => { :active => :get }
11
+ #
12
+ # This route set creates routes for the following HTTP requests:
13
+ #
14
+ # POST /people/new/register.xml # PeopleController.register
15
+ # PUT /people/1/promote.xml # PeopleController.promote with :id => 1
16
+ # DELETE /people/1/deactivate.xml # PeopleController.deactivate with :id => 1
17
+ # GET /people/active.xml # PeopleController.active
18
+ #
19
+ # Using this module, Active Resource can use these custom REST methods just like the
20
+ # standard methods.
21
+ #
22
+ # class Person < ActiveResource::Base
23
+ # self.site = "http://37s.sunrise.i:3000"
24
+ # end
25
+ #
26
+ # Person.new(:name => 'Ryan).post(:register) # POST /people/new/register.xml
27
+ # # => { :id => 1, :name => 'Ryan' }
28
+ #
29
+ # Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.xml
30
+ # Person.find(1).delete(:deactivate) # DELETE /people/1/deactivate.xml
31
+ #
32
+ # Person.get(:active) # GET /people/active.xml
33
+ # # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
34
+ #
35
+ module CustomMethods
36
+ extend ActiveSupport::Concern
37
+
38
+ included do
39
+ class << self
40
+ alias :orig_delete :delete
41
+
42
+ # Invokes a GET to a given custom REST method. For example:
43
+ #
44
+ # Person.get(:active) # GET /people/active.xml
45
+ # # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
46
+ #
47
+ # Person.get(:active, :awesome => true) # GET /people/active.xml?awesome=true
48
+ # # => [{:id => 1, :name => 'Ryan'}]
49
+ #
50
+ # Note: the objects returned from this method are not automatically converted
51
+ # into ActiveResource::Base instances - they are ordinary Hashes. If you are expecting
52
+ # ActiveResource::Base instances, use the <tt>find</tt> class method with the
53
+ # <tt>:from</tt> option. For example:
54
+ #
55
+ # Person.find(:all, :from => :active)
56
+ def get(custom_method_name, options = {})
57
+ connection.get(custom_method_collection_url(custom_method_name, options), headers)
58
+ end
59
+
60
+ def post(custom_method_name, options = {}, body = '')
61
+ connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
62
+ end
63
+
64
+ def put(custom_method_name, options = {}, body = '')
65
+ connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
66
+ end
67
+
68
+ def delete(custom_method_name, options = {})
69
+ # Need to jump through some hoops to retain the original class 'delete' method
70
+ if custom_method_name.is_a?(Symbol)
71
+ connection.delete(custom_method_collection_url(custom_method_name, options), headers)
72
+ else
73
+ orig_delete(custom_method_name, options)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ module ClassMethods
80
+ def custom_method_collection_url(method_name, options = {})
81
+ prefix_options, query_options = split_options(options)
82
+ "#{prefix(prefix_options)}#{collection_name}/#{method_name}.#{format.extension}#{query_string(query_options)}"
83
+ end
84
+ end
85
+
86
+ def get(method_name, options = {})
87
+ connection.get(custom_method_element_url(method_name, options), self.class.headers)
88
+ end
89
+
90
+ def post(method_name, options = {}, body = nil)
91
+ request_body = body.blank? ? encode : body
92
+ if new?
93
+ connection.post(custom_method_new_element_url(method_name, options), request_body, self.class.headers)
94
+ else
95
+ connection.post(custom_method_element_url(method_name, options), request_body, self.class.headers)
96
+ end
97
+ end
98
+
99
+ def put(method_name, options = {}, body = '')
100
+ connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
101
+ end
102
+
103
+ def delete(method_name, options = {})
104
+ connection.delete(custom_method_element_url(method_name, options), self.class.headers)
105
+ end
106
+
107
+
108
+ private
109
+ def custom_method_element_url(method_name, options = {})
110
+ "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}"
111
+ end
112
+
113
+ def custom_method_new_element_url(method_name, options = {})
114
+ "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}"
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,6 @@
1
+ module ApiResource
2
+ module Decorators
3
+ extend ActiveSupport::Autoload
4
+ autoload :CachingDecorator
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ module ApiResource
2
+ module Decorators
3
+ class CachingDecorator
4
+ attr_reader :owner
5
+ attr_reader :ttl
6
+
7
+ def initialize(owner, ttl)
8
+ @owner = owner
9
+ @ttl = ttl
10
+ end
11
+
12
+
13
+ def method_missing(method_name, *args, &block)
14
+ ApiResource.with_ttl(self.ttl) do
15
+ self.owner.send(method_name, *args, &block)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,99 @@
1
+ module ApiResource
2
+ class ConnectionError < StandardError # :nodoc:
3
+
4
+ cattr_accessor :http_code
5
+
6
+ attr_reader :response
7
+
8
+ def initialize(response, options = {})
9
+ @response = response
10
+ @message = options[:message]
11
+ @path = options[:path]
12
+ end
13
+
14
+ def to_s
15
+ message = "Failed."
16
+
17
+ if response.respond_to?(:code)
18
+ message << " Response code = #{response.code}."
19
+ end
20
+
21
+ if response.respond_to?(:body)
22
+ begin
23
+ body = JSON.parse(response.body).pretty_inspect
24
+ rescue
25
+ body = "INVALID JSON: #{response.body}"
26
+ end
27
+ message << "\nResponse message = #{body}."
28
+ end
29
+
30
+ message << "\n#{@message}"
31
+ message << "\n#{@path}"
32
+ end
33
+
34
+ def http_code
35
+ self.class.http_code
36
+ end
37
+
38
+ end
39
+
40
+ # Raised when a Timeout::Error occurs.
41
+ class RequestTimeout < ConnectionError
42
+ def initialize(message)
43
+ @message = message
44
+ end
45
+ def to_s; @message ;end
46
+ end
47
+
48
+ # Raised when a OpenSSL::SSL::SSLError occurs.
49
+ class SSLError < ConnectionError
50
+ def initialize(message)
51
+ @message = message
52
+ end
53
+ def to_s; @message ;end
54
+ end
55
+
56
+ # 3xx Redirection
57
+ class Redirection < ConnectionError # :nodoc:
58
+ def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
59
+ end
60
+
61
+ # 4xx Client Error
62
+ class ClientError < ConnectionError; end # :nodoc:
63
+
64
+ # 400 Bad Request
65
+ class BadRequest < ClientError; self.http_code = 400; end # :nodoc
66
+
67
+ # 401 Unauthorized
68
+ class UnauthorizedAccess < ClientError; self.http_code = 401; end # :nodoc
69
+
70
+ # 403 Forbidden
71
+ class ForbiddenAccess < ClientError; self.http_code = 403; end # :nodoc
72
+
73
+ # 404 Not Found
74
+ class ResourceNotFound < ClientError; self.http_code = 404; end # :nodoc:
75
+
76
+ # 406 Not Acceptable
77
+ class NotAcceptable < ClientError; self.http_code = 406; end
78
+
79
+ # 409 Conflict
80
+ class ResourceConflict < ClientError; self.http_code = 409; end # :nodoc:
81
+
82
+ # 410 Gone
83
+ class ResourceGone < ClientError; self.http_code = 410; end # :nodoc:
84
+
85
+ class UnprocessableEntity < ClientError; self.http_code = 422; end
86
+
87
+ # 5xx Server Error
88
+ class ServerError < ConnectionError; self.http_code = 400; end # :nodoc:
89
+
90
+ # 405 Method Not Allowed
91
+ class MethodNotAllowed < ClientError # :nodoc:
92
+
93
+ self.http_code = 405
94
+
95
+ def allowed_methods
96
+ @response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
97
+ end
98
+ end
99
+ end