my_api_client 0.13.0 → 0.16.1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +102 -36
  3. data/.dependabot/config.yml +34 -0
  4. data/.envrc.skeleton +1 -0
  5. data/.rubocop.yml +8 -10
  6. data/.rubocop_challenge.yml +3 -0
  7. data/.rubocop_todo.yml +5 -10
  8. data/CHANGELOG.md +229 -0
  9. data/Gemfile.lock +48 -43
  10. data/README.jp.md +98 -23
  11. data/bin/console +4 -0
  12. data/example/api_clients/application_api_client.rb +13 -0
  13. data/example/api_clients/my_error_api_client.rb +34 -0
  14. data/example/api_clients/my_errors.rb +27 -0
  15. data/example/api_clients/my_pagination_api_client.rb +18 -0
  16. data/example/api_clients/my_rest_api_client.rb +48 -0
  17. data/example/api_clients/my_status_api_client.rb +22 -0
  18. data/lib/generators/rails/templates/application_api_client.rb.erb +0 -11
  19. data/lib/my_api_client.rb +10 -2
  20. data/lib/my_api_client/base.rb +2 -18
  21. data/lib/my_api_client/config.rb +0 -29
  22. data/lib/my_api_client/default_error_handlers.rb +64 -0
  23. data/lib/my_api_client/error_handling.rb +13 -23
  24. data/lib/my_api_client/error_handling/generator.rb +30 -10
  25. data/lib/my_api_client/error_handling/{process_retry_option.rb → retry_option_processor.rb} +1 -1
  26. data/lib/my_api_client/errors.rb +0 -53
  27. data/lib/my_api_client/errors/api_limit_error.rb +6 -0
  28. data/lib/my_api_client/errors/client_error.rb +93 -0
  29. data/lib/my_api_client/errors/network_error.rb +43 -0
  30. data/lib/my_api_client/errors/server_error.rb +42 -0
  31. data/lib/my_api_client/params/request.rb +7 -10
  32. data/lib/my_api_client/request.rb +48 -70
  33. data/lib/my_api_client/request/basic.rb +32 -0
  34. data/lib/my_api_client/request/executor.rb +89 -0
  35. data/lib/my_api_client/request/logger.rb +37 -0
  36. data/lib/my_api_client/request/pagination.rb +39 -0
  37. data/lib/my_api_client/rspec/matcher_helper.rb +2 -2
  38. data/lib/my_api_client/rspec/matchers/be_handled_as_an_error.rb +2 -0
  39. data/lib/my_api_client/rspec/matchers/request_to.rb +3 -4
  40. data/lib/my_api_client/version.rb +1 -1
  41. data/my_api/.envrc.skeleton +3 -0
  42. data/my_api/.gitignore +14 -0
  43. data/my_api/.jetskeep +1 -0
  44. data/my_api/.rspec +3 -0
  45. data/my_api/.ruby-version +1 -0
  46. data/my_api/Gemfile +23 -0
  47. data/my_api/Gemfile.lock +243 -0
  48. data/my_api/Procfile +7 -0
  49. data/my_api/README.md +54 -0
  50. data/my_api/Rakefile +4 -0
  51. data/my_api/app/controllers/application_controller.rb +5 -0
  52. data/my_api/app/controllers/error_controller.rb +21 -0
  53. data/my_api/app/controllers/pagination_controller.rb +58 -0
  54. data/my_api/app/controllers/rest_controller.rb +60 -0
  55. data/my_api/app/controllers/status_controller.rb +11 -0
  56. data/my_api/app/helpers/application_helper.rb +5 -0
  57. data/my_api/app/jobs/application_job.rb +7 -0
  58. data/my_api/app/models/application_item.rb +5 -0
  59. data/my_api/config.ru +7 -0
  60. data/my_api/config/application.rb +73 -0
  61. data/my_api/config/dynamodb.yml +22 -0
  62. data/my_api/config/environments/development.rb +9 -0
  63. data/my_api/config/environments/production.rb +11 -0
  64. data/my_api/config/environments/test.rb +9 -0
  65. data/my_api/config/routes.rb +17 -0
  66. data/my_api/db/.gitkeep +0 -0
  67. data/my_api/public/404.html +67 -0
  68. data/my_api/public/422.html +67 -0
  69. data/my_api/public/500.html +66 -0
  70. data/my_api/public/favicon.ico +0 -0
  71. data/my_api/public/index.html +91 -0
  72. data/my_api/spec/controllers/error_controller_spec.rb +43 -0
  73. data/my_api/spec/controllers/pagination_controller_spec.rb +73 -0
  74. data/my_api/spec/controllers/rest_controller_spec.rb +99 -0
  75. data/my_api/spec/controllers/status_controller_spec.rb +47 -0
  76. data/my_api/spec/fixtures/payloads/posts-index.json +51 -0
  77. data/my_api/spec/fixtures/payloads/posts-show.json +53 -0
  78. data/my_api/spec/spec_helper.rb +31 -0
  79. data/my_api_client.gemspec +1 -1
  80. metadata +62 -9
  81. data/lib/my_api_client/logger.rb +0 -36
  82. data/renovate.json +0 -5
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'application_api_client'
4
+
5
+ # An usage example of the `my_api_client`.
6
+ # See also: my_api/app/controllers/rest_controller.rb
7
+ class MyRestApiClient < ApplicationApiClient
8
+ # GET rest
9
+ def get_posts(order: :asc)
10
+ order = :desc unless order == :asc
11
+ query = { order: order }
12
+ get 'rest', query: query, headers: headers
13
+ end
14
+
15
+ # GET rest/:id
16
+ def get_post(id:)
17
+ get "rest/#{id}", headers: headers
18
+ end
19
+
20
+ # POST rest
21
+ def post_post(title:)
22
+ body = { title: title }
23
+ post 'rest', body: body, headers: headers
24
+ end
25
+
26
+ # PUT rest/:id
27
+ def put_post(id:, title:)
28
+ body = { title: title }
29
+ put "rest/#{id}", body: body, headers: headers
30
+ end
31
+
32
+ # PATCH rest/:id
33
+ def patch_post(id:, title:)
34
+ body = { title: title }
35
+ patch "rest/#{id}", body: body, headers: headers
36
+ end
37
+
38
+ # DELETE rest/:id
39
+ def delete_post(id:)
40
+ delete "rest/#{id}", headers: headers
41
+ end
42
+
43
+ private
44
+
45
+ def headers
46
+ { 'Content-Type': 'application/json;charset=UTF-8' }
47
+ end
48
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'application_api_client'
4
+
5
+ # An usage example of the `my_api_client`.
6
+ # See also: my_api/app/controllers/status_controller.rb
7
+ class MyStatusApiClient < ApplicationApiClient
8
+ error_handling status_code: 400, raise: MyErrors::BadRequest
9
+ error_handling status_code: 401, raise: MyErrors::Unauthorized
10
+ error_handling status_code: 403, raise: MyErrors::Forbidden
11
+
12
+ # GET status/:status
13
+ def get_status(status:)
14
+ get "status/#{status}", headers: headers
15
+ end
16
+
17
+ private
18
+
19
+ def headers
20
+ { 'Content-Type': 'application/json;charset=UTF-8' }
21
+ end
22
+ end
@@ -20,15 +20,4 @@ class ApplicationApiClient < MyApiClient::Base
20
20
  # seconds.
21
21
  #
22
22
  # http_read_timeout 3.seconds
23
-
24
- # Catch the exception and re-execution after any seconds, like as ActiveJob.
25
- # Please note that it is executed as a synchronous process unlike ActiveJob.
26
- #
27
- # retry_on MyApiClient::NetworkError, wait: 5.seconds, attempts: 3
28
-
29
- # Set a HTTP response verifyment and behavior. If conflicting conditions are
30
- # set, the processing defined later takes precedence
31
- #
32
- # error_handling status_code: 400..499, raise: MyApiClient::ClientError
33
- # error_handling status_code: 500..599, raise: MyApiClient::ServerError
34
23
  end
@@ -11,11 +11,19 @@ require 'my_api_client/service_abstract'
11
11
  require 'my_api_client/version'
12
12
  require 'my_api_client/config'
13
13
  require 'my_api_client/error_handling/generator'
14
- require 'my_api_client/error_handling/process_retry_option'
14
+ require 'my_api_client/error_handling/retry_option_processor'
15
15
  require 'my_api_client/error_handling'
16
16
  require 'my_api_client/exceptions'
17
- require 'my_api_client/logger'
17
+ require 'my_api_client/request/logger'
18
+ require 'my_api_client/request/executor'
19
+ require 'my_api_client/request/basic'
20
+ require 'my_api_client/request/pagination'
18
21
  require 'my_api_client/errors'
22
+ require 'my_api_client/errors/api_limit_error'
23
+ require 'my_api_client/errors/client_error'
24
+ require 'my_api_client/errors/network_error'
25
+ require 'my_api_client/errors/server_error'
26
+ require 'my_api_client/default_error_handlers'
19
27
  require 'my_api_client/params/params'
20
28
  require 'my_api_client/params/request'
21
29
  require 'my_api_client/request'
@@ -18,26 +18,10 @@ module MyApiClient
18
18
  self.error_handlers = []
19
19
  end
20
20
 
21
+ include MyApiClient::DefaultErrorHandlers
22
+
21
23
  # NOTE: This class **MUST NOT** implement #initialize method. Because it
22
24
  # will become constraint that need call #super in the #initialize at
23
25
  # definition of the child classes.
24
-
25
- HTTP_METHODS = %i[get post patch delete].freeze
26
-
27
- HTTP_METHODS.each do |http_method|
28
- class_eval <<~METHOD, __FILE__, __LINE__ + 1
29
- # Description of ##{http_method}
30
- #
31
- # @param pathname [String]
32
- # @param headers [Hash, nil]
33
- # @param query [Hash, nil]
34
- # @param body [Hash, nil]
35
- # @return [Sawyer::Resouce] description_of_returned_object
36
- def #{http_method}(pathname, headers: nil, query: nil, body: nil)
37
- _request :#{http_method}, pathname, headers, query, body, logger
38
- end
39
- METHOD
40
- end
41
- alias put patch
42
26
  end
43
27
  end
@@ -16,34 +16,5 @@ module MyApiClient
16
16
  METHOD
17
17
  end
18
18
  end
19
-
20
- # Extracts schema and hostname from endpoint
21
- #
22
- # @example Extracts schema and hostname from 'https://example.com/path/to/api'
23
- # schema_and_hostname # => 'https://example.com'
24
- # @return [String] description_of_returned_object
25
- def schema_and_hostname
26
- if _uri.default_port == _uri.port
27
- "#{_uri.scheme}://#{_uri.host}"
28
- else
29
- "#{_uri.scheme}://#{_uri.host}:#{_uri.port}"
30
- end
31
- end
32
-
33
- # Extracts pathname from endpoint
34
- #
35
- # @example Extracts pathname from 'https://example.com/path/to/api'
36
- # common_path # => 'path/to/api'
37
- # @return [String] The pathanem
38
- def common_path
39
- _uri.path
40
- end
41
-
42
- private
43
-
44
- # @return [URI] Returns a memoized URI instance
45
- def _uri
46
- @_uri ||= URI.parse(endpoint)
47
- end
48
19
  end
49
20
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MyApiClient
4
+ # Provides default error handlers.
5
+ module DefaultErrorHandlers
6
+ extend ActiveSupport::Concern
7
+
8
+ # rubocop:disable Metrics/BlockLength
9
+ included do
10
+ # NOTE: The built-in error handlers are following. Although they are prepared
11
+ # to save the trouble of defining, but you can override any error handlers
12
+ # in your API client class.
13
+ error_handling status_code: 400..499, raise: ClientError
14
+ error_handling status_code: 400, raise: ClientError::BadRequest
15
+ error_handling status_code: 401, raise: ClientError::Unauthorized
16
+ error_handling status_code: 402, raise: ClientError::PaymentRequired
17
+ error_handling status_code: 403, raise: ClientError::Forbidden
18
+ error_handling status_code: 404, raise: ClientError::NotFound
19
+ error_handling status_code: 405, raise: ClientError::MethodNotAllowed
20
+ error_handling status_code: 406, raise: ClientError::NotAcceptable
21
+ error_handling status_code: 407, raise: ClientError::ProxyAuthenticationRequired
22
+ error_handling status_code: 408, raise: ClientError::RequestTimeout
23
+ error_handling status_code: 409, raise: ClientError::Conflict
24
+ error_handling status_code: 410, raise: ClientError::Gone
25
+ error_handling status_code: 411, raise: ClientError::LengthRequired
26
+ error_handling status_code: 412, raise: ClientError::PreconditionFailed
27
+ error_handling status_code: 413, raise: ClientError::RequestEntityTooLarge
28
+ error_handling status_code: 414, raise: ClientError::RequestUriTooLong
29
+ error_handling status_code: 415, raise: ClientError::UnsupportedMediaType
30
+ error_handling status_code: 416, raise: ClientError::RequestedRangeNotSatisfiable
31
+ error_handling status_code: 417, raise: ClientError::ExpectationFailed
32
+ error_handling status_code: 418, raise: ClientError::IamTeapot
33
+ error_handling status_code: 421, raise: ClientError::MisdirectedRequest
34
+ error_handling status_code: 422, raise: ClientError::UnprocessableEntity
35
+ error_handling status_code: 423, raise: ClientError::Locked
36
+ error_handling status_code: 424, raise: ClientError::FailedDependency
37
+ error_handling status_code: 425, raise: ClientError::TooEarly
38
+ error_handling status_code: 426, raise: ClientError::UpgradeRequired
39
+ error_handling status_code: 428, raise: ClientError::PreconditionRequired
40
+ error_handling status_code: 429, raise: ClientError::TooManyRequests
41
+ error_handling status_code: 431, raise: ClientError::RequestHeaderFieldsTooLarge
42
+ error_handling status_code: 451, raise: ClientError::UnavailableForLegalReasons
43
+
44
+ error_handling status_code: 500..599, raise: ServerError
45
+ error_handling status_code: 500, raise: ServerError::InternalServerError
46
+ error_handling status_code: 501, raise: ServerError::NotImplemented
47
+ error_handling status_code: 502, raise: ServerError::BadGateway
48
+ error_handling status_code: 503, raise: ServerError::ServiceUnavailable
49
+ error_handling status_code: 504, raise: ServerError::GatewayTimeout
50
+ error_handling status_code: 505, raise: ServerError::HttpVersionNotSupported
51
+ error_handling status_code: 506, raise: ServerError::VariantAlsoNegotiates
52
+ error_handling status_code: 507, raise: ServerError::InsufficientStorage
53
+ error_handling status_code: 508, raise: ServerError::LoopDetected
54
+ error_handling status_code: 509, raise: ServerError::BandwidthLimitExceeded
55
+ error_handling status_code: 510, raise: ServerError::NotExtended
56
+ error_handling status_code: 511, raise: ServerError::NetworkAuthenticationRequired
57
+
58
+ # Catch the exception and re-execution after any seconds, like as ActiveJob.
59
+ # Please note that it is executed as a synchronous process unlike ActiveJob.
60
+ retry_on NetworkError, wait: 0.3.seconds, attempts: 3
61
+ end
62
+ # rubocop:enable Metrics/BlockLength
63
+ end
64
+ end
@@ -9,9 +9,8 @@ module MyApiClient
9
9
  # @example
10
10
  # error_handling status_code: 200, json: :forbid_nil
11
11
  # error_handling status_code: 400..499, raise: MyApiClient::ClientError
12
- # error_handling status_code: 500..599 do |params, logger|
12
+ # error_handling status_code: 500..599, raise: MyApiClient::ServerError do |params, logger|
13
13
  # logger.warn 'Server error occurred.'
14
- # raise MyApiClient::ServerError, params
15
14
  # end
16
15
  #
17
16
  # error_handling json: { '$.errors.code': 10..19 }, with: :my_error_handling
@@ -29,10 +28,11 @@ module MyApiClient
29
28
  # @option status_code [String, Range, Integer, Regexp]
30
29
  # Verifies response HTTP status code and raises error if matched
31
30
  # @option json [Hash, Symbol]
32
- # Verifies response body as JSON and raises error if matched.
33
- # If specified `:forbid_nil`, it forbid `nil` on response_body.
31
+ # Specify the validation target value path included in the response body
32
+ # as JsonPath expression.
33
+ # If specified `:forbid_nil`, it forbid `nil` at the response body.
34
34
  # @option with [Symbol]
35
- # Calls specified method when error detected
35
+ # Calls specified method before raising exception when error detected.
36
36
  # @option raise [MyApiClient::Error]
37
37
  # Raises specified error when an invalid response detected.
38
38
  # Should be inherited `MyApiClient::Error` class.
@@ -40,30 +40,20 @@ module MyApiClient
40
40
  # @option retry [TrueClass, Hash]
41
41
  # If the error detected, retries the API request. Requires `raise` option.
42
42
  # You can set `true` or `retry_on` options (`wait` and `attempts`).
43
- # @yield [MyApiClient::Params::Params, MyApiClient::Logger]
44
- # Executes the block when error detected.
43
+ # @yield [MyApiClient::Params::Params, MyApiClient::Request::Logger]
44
+ # Executes the block before raising exception when error detected.
45
45
  # Forbid to be used with the` retry` option.
46
46
  def error_handling(**options, &block)
47
47
  options[:block] = block
48
- retry_options = ProcessRetryOption.call(error_handling_options: options)
48
+ retry_options = RetryOptionProcessor.call(error_handling_options: options)
49
49
  retry_on(options[:raise], **retry_options) if retry_options
50
50
 
51
- temp = error_handlers.dup
52
- temp << ->(response) { Generator.call(**options.merge(response: response)) }
53
- self.error_handlers = temp
51
+ new_error_handlers = error_handlers.dup
52
+ new_error_handlers << lambda { |instance, response|
53
+ Generator.call(**options.merge(instance: instance, response: response))
54
+ }
55
+ self.error_handlers = new_error_handlers
54
56
  end
55
57
  end
56
-
57
- # The error handlers defined later takes precedence
58
- #
59
- # @param response [Sawyer::Response] describe_params_here
60
- # @return [Proc, Symbol, nil] description_of_returned_object
61
- def error_handling(response)
62
- error_handlers.reverse_each do |error_handler|
63
- result = error_handler.call(response)
64
- return result unless result.nil?
65
- end
66
- nil
67
- end
68
58
  end
69
59
  end
@@ -4,10 +4,12 @@ module MyApiClient
4
4
  module ErrorHandling
5
5
  # Generates an error handler proc (or symbol)
6
6
  class Generator < ServiceAbstract
7
- ARGUMENTS = %i[response status_code json with raise block].freeze
7
+ ARGUMENTS = %i[instance response status_code json with raise block].freeze
8
8
 
9
9
  # @param options [Hash]
10
10
  # Options for this generator
11
+ # @option instance [MyApiClient::Base]
12
+ # The API client class.
11
13
  # @option response [Sawyer::Response]
12
14
  # The target of verifying
13
15
  # @option status_code [String, Range, Integer, Regexp]
@@ -21,10 +23,8 @@ module MyApiClient
21
23
  # Raises specified error when error detected. default: MyApiClient::Error
22
24
  # @option block [Proc]
23
25
  # Executes the block when error detected
24
- # @return [Proc]
25
- # Returns value as `Proc` if given `raise` or `block` option
26
- # @return [Symbol]
27
- # Returns value as `Symbol` if given `with` option
26
+ # @return [Proc, nil]
27
+ # Returns the error handler as "Proc". If no error occurs, return `nil`.
28
28
  def initialize(**options)
29
29
  options[:raise] ||= MyApiClient::Error
30
30
  verify_and_set_arguments(**options)
@@ -38,15 +38,37 @@ module MyApiClient
38
38
  return unless match?(_status_code, _response.status)
39
39
  return unless match_all?(_json, _response.body)
40
40
 
41
+ generate_error_handler
42
+ end
43
+
44
+ def generate_error_handler
41
45
  if _block
42
- ->(params, logger) { _block.call(params, logger) }
46
+ block_caller
43
47
  elsif _with
44
- _with
48
+ method_caller
45
49
  else
46
- ->(params, _) { raise _raise, params }
50
+ error_raiser
47
51
  end
48
52
  end
49
53
 
54
+ def block_caller
55
+ lambda { |params, logger|
56
+ _block.call(params, logger)
57
+ error_raiser.call(params, logger)
58
+ }
59
+ end
60
+
61
+ def method_caller
62
+ lambda { |params, logger|
63
+ _instance.send(_with, params, logger)
64
+ error_raiser.call(params, logger)
65
+ }
66
+ end
67
+
68
+ def error_raiser
69
+ ->(params, _) { raise _raise, params }
70
+ end
71
+
50
72
  # Verify given options and raise error if they are incorrect.
51
73
  # If not, set them to instance variables.
52
74
  #
@@ -63,7 +85,6 @@ module MyApiClient
63
85
  end
64
86
  end
65
87
 
66
- # rubocop:disable Metrics/CyclomaticComplexity
67
88
  def match?(operator, target)
68
89
  case operator
69
90
  when nil
@@ -80,7 +101,6 @@ module MyApiClient
80
101
  raise "Unexpected operator type was given: #{operator.inspect}"
81
102
  end
82
103
  end
83
- # rubocop:enable Metrics/CyclomaticComplexity
84
104
 
85
105
  def match_all?(json, response_body)
86
106
  return true if json.nil?
@@ -3,7 +3,7 @@
3
3
  module MyApiClient
4
4
  module ErrorHandling
5
5
  # Processes the `retry` option.
6
- class ProcessRetryOption < ServiceAbstract
6
+ class RetryOptionProcessor < ServiceAbstract
7
7
  # @param error_handling_options [Hash]
8
8
  # Options for the retry.
9
9
  # @option raise [MyApiClient::Error]
@@ -25,57 +25,4 @@ module MyApiClient
25
25
  { error: super, params: params }.inspect
26
26
  end
27
27
  end
28
-
29
- NETWORK_ERRORS = [
30
- Faraday::TimeoutError,
31
- Faraday::ConnectionFailed,
32
- Faraday::SSLError,
33
- OpenSSL::SSL::SSLError,
34
- Net::OpenTimeout,
35
- Net::ReadTimeout,
36
- SocketError,
37
- ].freeze
38
-
39
- # Raises it when occurred to some network error
40
- class NetworkError < Error
41
- attr_reader :original_error
42
-
43
- # Initialize the error class
44
- #
45
- # @param params [MyApiClient::Params::Params]
46
- # The request and response parameters
47
- # @param original_error [StandardError]
48
- # Some network error
49
- def initialize(params, original_error)
50
- @original_error = original_error
51
- super params, original_error.message
52
- end
53
-
54
- # Returns contents as string for to be readable for human
55
- #
56
- # @return [String] Contents as string
57
- def inspect
58
- { error: original_error, params: params }.inspect
59
- end
60
-
61
- # Generate metadata for bugsnag.
62
- #
63
- # @return [Hash] Metadata for bugsnag
64
- def metadata
65
- super.merge(original_error: original_error.inspect)
66
- end
67
- end
68
-
69
- # NOTE: The built-in error classes are following. Although they are prepared
70
- # to save the trouble of defining, but you can create any error classes
71
- # which inherit the ancestor error class.
72
-
73
- # For 4xx client error
74
- class ClientError < Error; end
75
-
76
- # For 5xx server error
77
- class ServerError < Error; end
78
-
79
- # For API request limit error
80
- class ApiLimitError < Error; end
81
28
  end