json_api_client 1.5.2 → 1.22.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3b172a6b2cca062c34df683b1fcbea5a017420a5
4
- data.tar.gz: 129f0173840bafb6982f191f07fa2f815cb5dce1
2
+ SHA256:
3
+ metadata.gz: 50ee381b7fb9cdebf8538c162a5dab717e8e2e23fffdc293ef7738728f45f9c3
4
+ data.tar.gz: 1be6e62baf09fc3bc78c7c65a5a7b567b0c4a875d1e6bbe06411774ee34d059a
5
5
  SHA512:
6
- metadata.gz: c80f324ed56f8de4575ce6feada1576c253f034bf23c6ef1cb0bce3233a49efc4719cfa740bea078134057cc740fde6f9a7cdf883cd5bfc2b2536a44984da0f9
7
- data.tar.gz: 9b8a4f4842c6045772d77757781fb76c33e10704a82754749fb344ec9a4b77f155683d64c3646b6cd2a62457f2a19f2d118eb5028f4b2656188c35da8b2baee3
6
+ metadata.gz: 60b1b657a5b30630f03e461bc4d29f5c0127417c53d63da146059d7a4bfef97e294d48d3bea842811dc72366e18cca7b1ddb1f68a573d42f1571a30dc199a9f6
7
+ data.tar.gz: a99ff29dd85edb6e036538c61d9ae84359a62bd8464e4f9839c09e2d8c1859d30ee58182a962503b00134fcb20c473520157bbe52b0dba5819dfc70402054d4b
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # JsonApiClient [![Build Status](https://travis-ci.org/chingor13/json_api_client.png)](https://travis-ci.org/chingor13/json_api_client) [![Code Climate](https://codeclimate.com/github/chingor13/json_api_client.png)](https://codeclimate.com/github/chingor13/json_api_client) [![Code Coverage](https://codeclimate.com/github/chingor13/json_api_client/coverage.png)](https://codeclimate.com/github/chingor13/json_api_client)
1
+ # JsonApiClient [![Build Status](https://travis-ci.org/JsonApiClient/json_api_client.png?branch=master)](https://travis-ci.org/JsonApiClient/json_api_client) [![Code Climate](https://codeclimate.com/github/JsonApiClient/json_api_client.png)](https://codeclimate.com/github/JsonApiClient/json_api_client) [![Code Coverage](https://codeclimate.com/github/JsonApiClient/json_api_client/coverage.png)](https://codeclimate.com/github/JsonApiClient/json_api_client)
2
2
 
3
3
  This gem is meant to help you build an API client for interacting with REST APIs as laid out by [http://jsonapi.org](http://jsonapi.org). It attempts to give you a query building framework that is easy to understand (it is similar to ActiveRecord scopes).
4
4
 
5
- *Note: master is currently tracking the 1.0.0 specification. If you're looking for the older code, see [0.x branch](https://github.com/chingor13/json_api_client/tree/0.x)*
5
+ *Note: master is currently tracking the 1.0.0 specification. If you're looking for the older code, see [0.x branch](https://github.com/JsonApiClient/json_api_client/tree/0.x)*
6
6
 
7
7
  ## Usage
8
8
 
@@ -39,14 +39,29 @@ MyApi::Article.where(author_id: 1).all
39
39
  MyApi::Person.where(name: "foo").order(created_at: :desc).includes(:preferences, :cars).all
40
40
 
41
41
  u = MyApi::Person.new(first_name: "bar", last_name: "foo")
42
+ u.new_record?
43
+ # => true
42
44
  u.save
43
45
 
46
+ u.new_record?
47
+ # => false
48
+
44
49
  u = MyApi::Person.find(1).first
45
50
  u.update_attributes(
46
51
  a: "b",
47
52
  c: "d"
48
53
  )
49
54
 
55
+ u.persisted?
56
+ # => true
57
+
58
+ u.destroy
59
+
60
+ u.destroyed?
61
+ # => true
62
+ u.persisted?
63
+ # => false
64
+
50
65
  u = MyApi::Person.create(
51
66
  a: "b",
52
67
  c: "d"
@@ -142,13 +157,17 @@ articles.links.related
142
157
 
143
158
  You can force nested resource paths for your models by using a `belongs_to` association.
144
159
 
145
- **Note: Using belongs_to is only necessary for setting a nested path.**
160
+ **Note: Using belongs_to is only necessary for setting a nested path unless you provide `shallow_path: true` option.**
146
161
 
147
162
  ```ruby
148
163
  module MyApi
149
164
  class Account < JsonApiClient::Resource
150
165
  belongs_to :user
151
166
  end
167
+
168
+ class Customer < JsonApiClient::Resource
169
+ belongs_to :user, shallow_path: true
170
+ end
152
171
  end
153
172
 
154
173
  # try to find without the nested parameter
@@ -158,6 +177,28 @@ MyApi::Account.find(1)
158
177
  # makes request to /users/2/accounts/1
159
178
  MyApi::Account.where(user_id: 2).find(1)
160
179
  # => returns ResultSet
180
+
181
+ # makes request to /customers/1
182
+ MyApi::Customer.find(1)
183
+ # => returns ResultSet
184
+
185
+ # makes request to /users/2/customers/1
186
+ MyApi::Customer.where(user_id: 2).find(1)
187
+ # => returns ResultSet
188
+ ```
189
+
190
+ you can also override param name for `belongs_to` association
191
+
192
+ ```ruby
193
+ module MyApi
194
+ class Account < JsonApiClient::Resource
195
+ belongs_to :user, param: :customer_id
196
+ end
197
+ end
198
+
199
+ # makes request to /users/2/accounts/1
200
+ MyApi::Account.where(customer_id: 2).find(1)
201
+ # => returns ResultSet
161
202
  ```
162
203
 
163
204
  ## Custom Methods
@@ -196,6 +237,23 @@ results = Article.includes(:author, :comments => :author).find(1)
196
237
 
197
238
  # should not have to make additional requests to the server
198
239
  authors = results.map(&:author)
240
+
241
+ # makes POST request to /articles?include=author,comments.author
242
+ article = Article.new(title: 'New one').request_includes(:author, :comments => :author)
243
+ article.save
244
+
245
+ # makes PATCH request to /articles/1?include=author,comments.author
246
+ article = Article.find(1)
247
+ article.title = 'Changed'
248
+ article.request_includes(:author, :comments => :author)
249
+ article.save
250
+
251
+ # request includes will be cleared if response is successful
252
+ # to avoid this `keep_request_params` class attribute can be used
253
+ Article.keep_request_params = true
254
+
255
+ # to clear request_includes use
256
+ article.reset_request_includes!
199
257
  ```
200
258
 
201
259
  ## Sparse Fieldsets
@@ -217,6 +275,24 @@ article.created_at
217
275
  # or you can use fieldsets from multiple resources
218
276
  # makes request to /articles?fields[articles]=title,body&fields[comments]=tag
219
277
  article = Article.select("title", "body",{comments: 'tag'}).first
278
+
279
+ # makes POST request to /articles?fields[articles]=title,body&fields[comments]=tag
280
+ article = Article.new(title: 'New one').request_select(:title, :body, comments: 'tag')
281
+ article.save
282
+
283
+ # makes PATCH request to /articles/1?fields[articles]=title,body&fields[comments]=tag
284
+ article = Article.find(1)
285
+ article.title = 'Changed'
286
+ article.request_select(:title, :body, comments: 'tag')
287
+ article.save
288
+
289
+ # request fields will be cleared if response is successful
290
+ # to avoid this `keep_request_params` class attribute can be used
291
+ Article.keep_request_params = true
292
+
293
+ # to clear request fields use
294
+ article.reset_request_select!(:comments) # to clear for comments
295
+ article.reset_request_select! # to clear for all fields
220
296
  ```
221
297
 
222
298
  ## Sorting
@@ -246,6 +322,10 @@ articles = Article.page(2).per(30).to_a
246
322
 
247
323
  # also makes request to /articles?page=2&per_page=30
248
324
  articles = Article.paginate(page: 2, per_page: 30).to_a
325
+
326
+ # keep in mind that page number can be nil - in that case default number will be applied
327
+ # also makes request to /articles?page=1&per_page=30
328
+ articles = Article.paginate(page: nil, per_page: 30).to_a
249
329
  ```
250
330
 
251
331
  *Note: The mapping of pagination parameters is done by the `query_builder` which is [customizable](#custom-paginator).*
@@ -360,7 +440,7 @@ class NullConnection
360
440
  def initialize(*args)
361
441
  end
362
442
 
363
- def run(request_method, path, params = {}, headers = {})
443
+ def run(request_method, path, params: nil, headers: {}, body: nil)
364
444
  end
365
445
 
366
446
  def use(*args); end
@@ -394,6 +474,52 @@ module MyApi
394
474
  end
395
475
  ```
396
476
 
477
+ ##### Server errors handling
478
+
479
+ Non-success API response will cause the specific `JsonApiClient::Errors::SomeException` raised, depends on responded HTTP status.
480
+ Please refer to [JsonApiClient::Middleware::Status#handle_status](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/middleware/status.rb)
481
+ method for concrete status-to-exception mapping used out of the box.
482
+
483
+ JsonApiClient will try determine is failed API response JsonApi-compatible, if so - JsonApi error messages will be parsed from response body, and tracked as a part of particular exception message. In additional, `JsonApiClient::Errors::ServerError` exception will keep the actual HTTP status and message within its message.
484
+
485
+ ##### Custom status handler
486
+
487
+ You can change handling of response status using `connection_options`. For example you can override 400 status handling.
488
+ By default it raises `JsonApiClient::Errors::ClientError` but you can skip exception if you want to process errors from the server.
489
+ You need to provide a `proc` which should call `throw(:handled)` default handler for this status should be skipped.
490
+ ```ruby
491
+ class ApiBadRequestHandler
492
+ def self.call(_env)
493
+ # do not raise exception
494
+ end
495
+ end
496
+
497
+ class CustomUnauthorizedError < StandardError
498
+ attr_reader :env
499
+
500
+ def initialize(env)
501
+ @env = env
502
+ super('not authorized')
503
+ end
504
+ end
505
+
506
+ MyApi::Base.connection_options[:status_handlers] = {
507
+ 400 => ApiBadRequestHandler,
508
+ 401 => ->(env) { raise CustomUnauthorizedError, env }
509
+ }
510
+
511
+ module MyApi
512
+ class User < Base
513
+ # will use the customized status_handlers
514
+ end
515
+ end
516
+
517
+ user = MyApi::User.create(name: 'foo')
518
+ # server responds with { errors: [ { detail: 'bad request' } ] }
519
+ user.errors.messages # { base: ['bad request'] }
520
+ # on 401 it will raise CustomUnauthorizedError instead of JsonApiClient::Errors::NotAuthorized
521
+ ```
522
+
397
523
  ##### Specifying an HTTP Proxy
398
524
 
399
525
  All resources have a class method ```connection_options``` used to pass options to the JsonApiClient::Connection initializer.
@@ -451,16 +577,16 @@ end
451
577
 
452
578
  You can customize how your resources find pagination information from the response.
453
579
 
454
- If the [existing paginator](https://github.com/chingor13/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) fits your requirements but you don't use the default `page` and `per_page` params for pagination, you can customise the param keys as follows:
580
+ If the [existing paginator](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) fits your requirements but you don't use the default `page` and `per_page` params for pagination, you can customise the param keys as follows:
455
581
 
456
582
  ```ruby
457
- JsonApiClient::Paginating::Paginator.page_param = "page[number]"
458
- JsonApiClient::Paginating::Paginator.per_page_param = "page[size]"
583
+ JsonApiClient::Paginating::Paginator.page_param = "number"
584
+ JsonApiClient::Paginating::Paginator.per_page_param = "size"
459
585
  ```
460
586
 
461
587
  Please note that this is a global configuration, so library authors should create a custom paginator that inherits `JsonApiClient::Paginating::Paginator` and configure the custom paginator to avoid modifying global config.
462
588
 
463
- If the [existing paginator](https://github.com/chingor13/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) does not fit your needs, you can create a custom paginator:
589
+ If the [existing paginator](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) does not fit your needs, you can create a custom paginator:
464
590
 
465
591
  ```ruby
466
592
  class MyPaginator
@@ -473,6 +599,36 @@ class MyApi::Base < JsonApiClient::Resource
473
599
  end
474
600
  ```
475
601
 
602
+ ### NestedParamPaginator
603
+
604
+ The default `JsonApiClient::Paginating::Paginator` is not strict about how it handles the param keys ([#347](https://github.com/JsonApiClient/json_api_client/issues/347)). There is a second paginator that more rigorously adheres to the JSON:API pagination recommendation style of `page[page]=1&page[per_page]=10`.
605
+
606
+ If this second style suits your needs better, it is available as a class override:
607
+
608
+ ```ruby
609
+ class Order < JsonApiClient::Resource
610
+ self.paginator = JsonApiClient::Paginating::NestedParamPaginator
611
+ end
612
+ ```
613
+
614
+ You can also extend `NestedParamPaginator` in your custom paginators or assign the `page_param` or `per_page_param` as with the default version above.
615
+
616
+ ### Custom type
617
+
618
+ If your model must be named differently from classified type of resource you can easily customize it.
619
+ It will work both for defined and not defined relationships
620
+
621
+ ```ruby
622
+ class MyApi::Base < JsonApiClient::Resource
623
+ resolve_custom_type 'document--files', 'File'
624
+ end
625
+
626
+ class MyApi::File < MyApi::Base
627
+ def self.resource_name
628
+ 'document--files'
629
+ end
630
+ end
631
+ ```
476
632
 
477
633
  ### Type Casting
478
634
 
@@ -502,7 +658,48 @@ end
502
658
 
503
659
  ```
504
660
 
661
+ ### Safe singular resource fetching
662
+
663
+ That is a bit curios, but `json_api_client` returns an array from `.find` method, always.
664
+ The history of this fact was discussed [here](https://github.com/JsonApiClient/json_api_client/issues/75)
665
+
666
+ So, when we searching for a single resource by primary key, we typically write the things like
667
+
668
+ ```ruby
669
+ admin = User.find(id).first
670
+ ```
671
+
672
+ The next thing which we need to notice - `json_api_client` will just interpolate the incoming `.find` param to the end of API URL, just like that:
673
+
674
+ > http://somehost/api/v1/users/{id}
675
+
676
+ What will happen if we pass the blank id (nil or empty string) to the `.find` method then?.. Yeah, `json_api_client` will try to call the INDEX API endpoint instead of SHOW one:
677
+
678
+ > http://somehost/api/v1/users/
679
+
680
+ Lets sum all together - in case if `id` comes blank (from CGI for instance), we can silently receive the `admin` variable equal to some existing resource, with all the consequences.
681
+
682
+ Even worse, `admin` variable can equal to *random* resource, depends on ordering applied by INDEX endpoint.
683
+
684
+ If you prefer to get `JsonApiClient::Errors::NotFound` raised, please define in your base Resource class:
685
+
686
+ ```ruby
687
+ class Resource < JsonApiClient::Resource
688
+ self.raise_on_blank_find_param = true
689
+ end
690
+ ```
691
+
692
+ ## Contributing
693
+
694
+ Contributions are welcome! Please fork this repo and send a pull request. Your pull request should have:
695
+
696
+ * a description about what's broken or what the desired functionality is
697
+ * a test illustrating the bug or new feature
698
+ * the code to fix the bug
699
+
700
+ Ideally, the PR has 2 commits - the first showing the failed test and the second with the fix - although this is not
701
+ required. The commits will be squashed into master once accepted.
505
702
 
506
703
  ## Changelog
507
704
 
508
- See [changelog](https://github.com/chingor13/json_api_client/blob/master/CHANGELOG.md)
705
+ See [changelog](https://github.com/JsonApiClient/json_api_client/blob/master/CHANGELOG.md)
@@ -21,6 +21,13 @@ module JsonApiClient
21
21
  def from_result_set(result_set)
22
22
  result_set.to_a
23
23
  end
24
+
25
+ def load_records(data)
26
+ data.map do |d|
27
+ record_class = Utils.compute_type(klass, klass.key_formatter.unformat(d["type"]).classify)
28
+ record_class.load id: d["id"]
29
+ end
30
+ end
24
31
  end
25
32
  end
26
33
  end
@@ -1,19 +1,19 @@
1
1
  module JsonApiClient
2
2
  module Associations
3
3
  module BelongsTo
4
- extend ActiveSupport::Concern
4
+ class Association < BaseAssociation
5
+ include Helpers::URI
6
+
7
+ attr_reader :param
5
8
 
6
- module ClassMethods
7
- def belongs_to(attr_name, options = {})
8
- # self.associations = self.associations + [HasOne::Association.new(attr_name, self, options)]
9
- self.associations += [BelongsTo::Association.new(attr_name, self, options)]
9
+ def initialize(attr_name, klass, options = {})
10
+ super
11
+ @param = options.fetch(:param, :"#{attr_name}_id").to_sym
12
+ @shallow_path = options.fetch(:shallow_path, false)
10
13
  end
11
- end
12
14
 
13
- class Association < BaseAssociation
14
- include Helpers::URI
15
- def param
16
- :"#{attr_name}_id"
15
+ def shallow_path?
16
+ @shallow_path
17
17
  end
18
18
 
19
19
  def to_prefix_path(formatter)
@@ -21,6 +21,7 @@ module JsonApiClient
21
21
  end
22
22
 
23
23
  def set_prefix_path(attrs, formatter)
24
+ return if shallow_path? && !attrs[param]
24
25
  attrs[param] = encode_part(attrs[param]) if attrs.key?(param)
25
26
  to_prefix_path(formatter) % attrs
26
27
  end
@@ -1,14 +1,6 @@
1
1
  module JsonApiClient
2
2
  module Associations
3
3
  module HasMany
4
- extend ActiveSupport::Concern
5
-
6
- module ClassMethods
7
- def has_many(attr_name, options = {})
8
- self.associations = self.associations + [HasMany::Association.new(attr_name, self, options)]
9
- end
10
- end
11
-
12
4
  class Association < BaseAssociation
13
5
  end
14
6
  end
@@ -1,19 +1,16 @@
1
1
  module JsonApiClient
2
2
  module Associations
3
3
  module HasOne
4
- extend ActiveSupport::Concern
5
-
6
- module ClassMethods
7
- def has_one(attr_name, options = {})
8
- self.associations += [HasOne::Association.new(attr_name, self, options)]
9
- end
10
- end
11
-
12
4
  class Association < BaseAssociation
13
5
  def from_result_set(result_set)
14
6
  result_set.first
15
7
  end
8
+
9
+ def load_records(data)
10
+ record_class = Utils.compute_type(klass, klass.key_formatter.unformat(data["type"]).classify)
11
+ record_class.load id: data["id"]
12
+ end
16
13
  end
17
14
  end
18
15
  end
19
- end
16
+ end
@@ -7,11 +7,14 @@ module JsonApiClient
7
7
  site = options.fetch(:site)
8
8
  connection_options = options.slice(:proxy, :ssl, :request, :headers, :params)
9
9
  adapter_options = Array(options.fetch(:adapter, Faraday.default_adapter))
10
+ status_middleware_options = {}
11
+ status_middleware_options[:custom_handlers] = options[:status_handlers] if options[:status_handlers].present?
10
12
  @faraday = Faraday.new(site, connection_options) do |builder|
11
13
  builder.request :json
12
14
  builder.use Middleware::JsonRequest
13
- builder.use Middleware::Status
15
+ builder.use Middleware::Status, status_middleware_options
14
16
  builder.use Middleware::ParseJson
17
+ builder.use ::FaradayMiddleware::Gzip
15
18
  builder.adapter(*adapter_options)
16
19
  end
17
20
  yield(self) if block_given?
@@ -28,8 +31,10 @@ module JsonApiClient
28
31
  faraday.builder.delete(middleware)
29
32
  end
30
33
 
31
- def run(request_method, path, params = {}, headers = {})
32
- faraday.send(request_method, path, params, headers)
34
+ def run(request_method, path, params: nil, headers: {}, body: nil)
35
+ faraday.run_request(request_method, path, body, headers) do |request|
36
+ request.params.update(params) if params
37
+ end
33
38
  end
34
39
 
35
40
  end
@@ -33,18 +33,27 @@ module JsonApiClient
33
33
  end
34
34
 
35
35
  def source_parameter
36
- source.fetch(:parameter) do
37
- source[:pointer] ?
38
- source[:pointer].split("/").last :
39
- nil
40
- end
36
+ source[:parameter]
41
37
  end
42
38
 
43
39
  def source_pointer
44
- source.fetch(:pointer) do
45
- source[:parameter] ?
46
- "/data/attributes/#{source[:parameter]}" :
47
- nil
40
+ source[:pointer]
41
+ end
42
+
43
+ def error_key
44
+ if source_pointer && source_pointer != "/data"
45
+ source_pointer.split("/").last
46
+ else
47
+ "base"
48
+ end
49
+ end
50
+
51
+ def error_msg
52
+ msg = title || detail || "invalid"
53
+ if source_parameter
54
+ "#{source_parameter} #{msg}"
55
+ else
56
+ msg
48
57
  end
49
58
  end
50
59
 
@@ -74,7 +83,7 @@ module JsonApiClient
74
83
 
75
84
  def [](source)
76
85
  map do |error|
77
- error.source_parameter == source
86
+ error.error_key == source
78
87
  end
79
88
  end
80
89
 
@@ -1,44 +1,103 @@
1
+ require 'rack'
2
+
1
3
  module JsonApiClient
2
4
  module Errors
3
5
  class ApiError < StandardError
4
6
  attr_reader :env
5
- def initialize(env)
7
+
8
+ def initialize(env, msg = nil)
6
9
  @env = env
10
+ # Try to fetch json_api errors from response
11
+ msg = track_json_api_errors(msg)
12
+
13
+ super msg
14
+ end
15
+
16
+ private
17
+
18
+ # Try to fetch json_api errors from response
19
+ def track_json_api_errors(msg)
20
+ return msg unless env.try(:body).kind_of?(Hash) || env.body.key?('errors')
21
+
22
+ errors_msg = env.body['errors'].map { |e| e['title'] }.compact.join('; ').presence
23
+ return msg unless errors_msg
24
+
25
+ msg.nil? ? errors_msg : "#{msg} (#{errors_msg})"
26
+ # Just to be sure that it is back compatible
27
+ rescue StandardError
28
+ msg
7
29
  end
8
30
  end
9
31
 
10
32
  class ClientError < ApiError
11
33
  end
12
34
 
35
+ class ResourceImmutableError < StandardError
36
+ def initialize(msg = 'Resource immutable')
37
+ super msg
38
+ end
39
+ end
40
+
13
41
  class AccessDenied < ClientError
14
42
  end
15
43
 
16
44
  class NotAuthorized < ClientError
17
45
  end
18
46
 
47
+ class NotFound < ClientError
48
+ attr_reader :uri
49
+ def initialize(env_or_uri, msg = nil)
50
+ env = nil
51
+
52
+ if env_or_uri.kind_of?(Faraday::Env)
53
+ env = env_or_uri
54
+ @uri = env[:url]
55
+ else
56
+ @uri = env_or_uri
57
+ end
58
+
59
+ msg ||= "Resource not found: #{uri.to_s}"
60
+ super env, msg
61
+ end
62
+ end
63
+
64
+ class RequestTimeout < ClientError
65
+ end
66
+
67
+ class Conflict < ClientError
68
+ def initialize(env, msg = 'Resource already exists')
69
+ super env, msg
70
+ end
71
+ end
72
+
73
+ class TooManyRequests < ClientError
74
+ end
75
+
19
76
  class ConnectionError < ApiError
20
77
  end
21
78
 
22
79
  class ServerError < ApiError
23
- def message
24
- "Internal server error"
80
+ def initialize(env, msg = nil)
81
+ msg ||= begin
82
+ status = env.status
83
+ message = ::Rack::Utils::HTTP_STATUS_CODES[status]
84
+ "#{status} #{message}"
85
+ end
86
+
87
+ super env, msg
25
88
  end
26
89
  end
27
90
 
28
- class Conflict < ServerError
29
- def message
30
- "Resource already exists"
31
- end
91
+ class InternalServerError < ServerError
32
92
  end
33
93
 
34
- class NotFound < ServerError
35
- attr_reader :uri
36
- def initialize(uri)
37
- @uri = uri
38
- end
39
- def message
40
- "Couldn't find resource at: #{uri.to_s}"
41
- end
94
+ class BadGateway < ServerError
95
+ end
96
+
97
+ class ServiceUnavailable < ServerError
98
+ end
99
+
100
+ class GatewayTimeout < ServerError
42
101
  end
43
102
 
44
103
  class UnexpectedStatus < ServerError
@@ -46,11 +105,21 @@ module JsonApiClient
46
105
  def initialize(code, uri)
47
106
  @code = code
48
107
  @uri = uri
108
+
109
+ msg = "Unexpected response status: #{code} from: #{uri.to_s}"
110
+ super nil, msg
111
+ end
112
+ end
113
+
114
+ class RecordNotSaved < ServerError
115
+ attr_reader :record
116
+
117
+ def initialize(message = nil, record = nil)
118
+ @record = record
49
119
  end
50
120
  def message
51
- "Unexpected response status: #{code} from: #{uri.to_s}"
121
+ "Record not saved"
52
122
  end
53
123
  end
54
-
55
124
  end
56
125
  end