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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MyApiClient
4
+ # For API request limit error
5
+ class ApiLimitError < Error; end
6
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MyApiClient
4
+ # For 4xx client error
5
+ class ClientError < Error
6
+ # 400 Bad Request
7
+ class BadRequest < ClientError; end
8
+
9
+ # 401 Unauthorized
10
+ class Unauthorized < ClientError; end
11
+
12
+ # 402 Payment Required
13
+ class PaymentRequired < ClientError; end
14
+
15
+ # 403 Forbidden
16
+ class Forbidden < ClientError; end
17
+
18
+ # 404 Not Found
19
+ class NotFound < ClientError; end
20
+
21
+ # 405 Method Not Allowed
22
+ class MethodNotAllowed < ClientError; end
23
+
24
+ # 406 Not Acceptable
25
+ class NotAcceptable < ClientError; end
26
+
27
+ # 407 Proxy Authentication Required
28
+ class ProxyAuthenticationRequired < ClientError; end
29
+
30
+ # 408 Request Timeout
31
+ class RequestTimeout < ClientError; end
32
+
33
+ # 409 Conflict
34
+ class Conflict < ClientError; end
35
+
36
+ # 410 Gone
37
+ class Gone < ClientError; end
38
+
39
+ # 411 Length Required
40
+ class LengthRequired < ClientError; end
41
+
42
+ # 412 Precondition Failed
43
+ class PreconditionFailed < ClientError; end
44
+
45
+ # 413 Payload Too Large
46
+ class RequestEntityTooLarge < ClientError; end
47
+
48
+ # 414 URI Too Long
49
+ class RequestUriTooLong < ClientError; end
50
+
51
+ # 415 Unsupported Media Type
52
+ class UnsupportedMediaType < ClientError; end
53
+
54
+ # 416 Range Not Satisfiable
55
+ class RequestedRangeNotSatisfiable < ClientError; end
56
+
57
+ # 417 Expectation Failed
58
+ class ExpectationFailed < ClientError; end
59
+
60
+ # 418 I'm a teapot
61
+ class IamTeapot < ClientError; end
62
+
63
+ # 421 Misdirected Request
64
+ class MisdirectedRequest < ClientError; end
65
+
66
+ # 422 Unprocessable Entity
67
+ class UnprocessableEntity < ClientError; end
68
+
69
+ # 423 Locked
70
+ class Locked < ClientError; end
71
+
72
+ # 424 Failed Dependency
73
+ class FailedDependency < ClientError; end
74
+
75
+ # 425 Too Early
76
+ class TooEarly < ClientError; end
77
+
78
+ # 426 Upgrade Required
79
+ class UpgradeRequired < ClientError; end
80
+
81
+ # 428 Precondition Required
82
+ class PreconditionRequired < ClientError; end
83
+
84
+ # 429 Too Many Requests
85
+ class TooManyRequests < ClientError; end
86
+
87
+ # 431 Request Header Fields Too Large
88
+ class RequestHeaderFieldsTooLarge < ClientError; end
89
+
90
+ # 451 Unavailable for Legal Reasons
91
+ class UnavailableForLegalReasons < ClientError; end
92
+ end
93
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MyApiClient
4
+ NETWORK_ERRORS = [
5
+ Faraday::TimeoutError,
6
+ Faraday::ConnectionFailed,
7
+ Faraday::SSLError,
8
+ OpenSSL::SSL::SSLError,
9
+ Net::OpenTimeout,
10
+ Net::ReadTimeout,
11
+ SocketError,
12
+ ].freeze
13
+
14
+ # Raises it when occurred to some network error
15
+ class NetworkError < Error
16
+ attr_reader :original_error
17
+
18
+ # Initialize the error class
19
+ #
20
+ # @param params [MyApiClient::Params::Params]
21
+ # The request and response parameters
22
+ # @param original_error [StandardError]
23
+ # Some network error
24
+ def initialize(params, original_error)
25
+ @original_error = original_error
26
+ super params, original_error.message
27
+ end
28
+
29
+ # Returns contents as string for to be readable for human
30
+ #
31
+ # @return [String] Contents as string
32
+ def inspect
33
+ { error: original_error, params: params }.inspect
34
+ end
35
+
36
+ # Generate metadata for bugsnag.
37
+ #
38
+ # @return [Hash] Metadata for bugsnag
39
+ def metadata
40
+ super.merge(original_error: original_error.inspect)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MyApiClient
4
+ # For 5xx server error
5
+ class ServerError < Error
6
+ # 500 Internal Server Error
7
+ class InternalServerError < ServerError; end
8
+
9
+ # 501 Not Implemented
10
+ class NotImplemented < ServerError; end
11
+
12
+ # 502 Bad Gateway
13
+ class BadGateway < ServerError; end
14
+
15
+ # 503 Service Unavailable
16
+ class ServiceUnavailable < ServerError; end
17
+
18
+ # 504 Gateway Timeout
19
+ class GatewayTimeout < ServerError; end
20
+
21
+ # 505 HTTP Version Not Supported
22
+ class HttpVersionNotSupported < ServerError; end
23
+
24
+ # 506 Variant Also Negotiates
25
+ class VariantAlsoNegotiates < ServerError; end
26
+
27
+ # 507 Insufficient Storage
28
+ class InsufficientStorage < ServerError; end
29
+
30
+ # 508 Loop Detected
31
+ class LoopDetected < ServerError; end
32
+
33
+ # 509 Bandwidth Limit Exceeded
34
+ class BandwidthLimitExceeded < ServerError; end
35
+
36
+ # 510 Not Extended
37
+ class NotExtended < ServerError; end
38
+
39
+ # 511 Network Authentication Required
40
+ class NetworkAuthenticationRequired < ServerError; end
41
+ end
42
+ end
@@ -4,20 +4,18 @@ module MyApiClient
4
4
  module Params
5
5
  # Description of Params
6
6
  class Request
7
- attr_reader :method, :pathname, :headers, :query, :body
7
+ attr_reader :method, :uri, :headers, :body
8
8
 
9
9
  # Description of #initialize
10
10
  #
11
11
  # @param method [Symbol] describe_method_here
12
- # @param pathname [String] describe_pathname_here
12
+ # @param uri [URI] describe_uri_here
13
13
  # @param headers [Hash, nil] describe_headers_here
14
- # @param query [Hash, nil] describe_query_here
15
14
  # @param body [Hash, nil] describe_body_here
16
- def initialize(method, pathname, headers, query, body)
15
+ def initialize(method, uri, headers, body)
17
16
  @method = method
18
- @pathname = pathname
17
+ @uri = uri
19
18
  @headers = headers
20
- @query = query
21
19
  @body = body
22
20
  end
23
21
 
@@ -25,7 +23,7 @@ module MyApiClient
25
23
  #
26
24
  # @return [Array<Object>] Arguments for Sawyer::Agent#call
27
25
  def to_sawyer_args
28
- [method, pathname, body, { headers: headers, query: query }]
26
+ [method, uri.to_s, body, { headers: headers }]
29
27
  end
30
28
 
31
29
  # Generate metadata for bugsnag.
@@ -34,9 +32,8 @@ module MyApiClient
34
32
  # @return [Hash] Metadata for bugsnag
35
33
  def metadata
36
34
  {
37
- line: "#{method.upcase} #{pathname}",
35
+ line: "#{method.upcase} #{uri}",
38
36
  headers: headers,
39
- query: query,
40
37
  body: body,
41
38
  }.compact
42
39
  end
@@ -46,7 +43,7 @@ module MyApiClient
46
43
  #
47
44
  # @return [String] Contents as string
48
45
  def inspect
49
- { method: method, pathname: pathname, headers: headers, query: query, body: body }.inspect
46
+ { method: method, uri: uri.to_s, headers: headers, body: body }.inspect
50
47
  end
51
48
  end
52
49
  end
@@ -1,87 +1,65 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MyApiClient
4
- # Description of Request
4
+ # Provides HTTP request method.
5
5
  module Request
6
- # Description of #_request
7
- #
8
- # @param http_method [Symbol] describe_http_method_here
9
- # @param pathname [String] describe_pathname_here
10
- # @param headers [Hash, nil] describe_headers_here
11
- # @param query [Hash, nil] describe_query_here
12
- # @param body [Hash, nil] describe_body_here
13
- # @param logger [::Logger] describe_logger_here
14
- # @return [Sawyer::Resource] description_of_returned_object
15
- # rubocop:disable Metrics/ParameterLists
16
- def _request(http_method, pathname, headers, query, body, logger)
17
- processed_path = [common_path, pathname].join('/').gsub('//', '/')
18
- request_params = Params::Request.new(http_method, processed_path, headers, query, body)
19
- agent # Initializes for faraday
20
- request_logger = Logger.new(logger, faraday, http_method, processed_path)
21
- call(:_execute, request_params, request_logger)
22
- end
23
- # rubocop:enable Metrics/ParameterLists
6
+ include Basic
7
+ include Pagination
24
8
 
25
9
  private
26
10
 
27
- # Description of #agent
28
- #
29
- # @return [Sawyer::Agent] description_of_returned_object
30
- def agent
31
- @agent ||= Sawyer::Agent.new(schema_and_hostname, faraday: faraday)
32
- end
33
-
34
- # Description of #faraday
11
+ # Executes HTTP request with relative URI.
35
12
  #
36
- # @return [Faraday::Connection] description_of_returned_object
37
- def faraday
38
- @faraday ||=
39
- Faraday.new(
40
- nil,
41
- request: {
42
- timeout: (http_read_timeout if respond_to?(:http_read_timeout)),
43
- open_timeout: (http_open_timeout if respond_to?(:http_open_timeout)),
44
- }.compact
45
- )
13
+ # @param http_method [Symbol]
14
+ # HTTP method. e.g. `:get`, `:post`, `:put`, `:patch` and `:delete`.
15
+ # @param pathname [String]
16
+ # Pathname of the request target URL.
17
+ # It's joined with the defined by `endpoint`.
18
+ # @param headers [Hash, nil]
19
+ # Request headers.
20
+ # @param query [Hash, nil]
21
+ # Query string.
22
+ # @param body [Hash, nil]
23
+ # Request body.
24
+ # @return [Sawyer::Response]
25
+ # Response instance.
26
+ def _request_with_relative_uri(http_method, pathname, headers, query, body)
27
+ query_strings = query.present? ? '?' + query&.to_query : ''
28
+ uri = URI.join(File.join(endpoint, pathname), query_strings)
29
+ _request_with_absolute_uri(http_method, uri, headers, body)
46
30
  end
47
31
 
48
- # Description of #_execute
32
+ # Executes HTTP request with absolute URI.
49
33
  #
50
- # @param request_params [MyApiClient::Params::Request] describe_request_params_here
51
- # @param request_logger [MyApiClient::Logger] describe_request_logger_here
52
- # @return [Sawyer::Resource] description_of_returned_object
53
- # @raise [MyApiClient::Error]
54
- def _execute(request_params, request_logger)
55
- request_logger.info('Start')
56
- response = agent.call(*request_params.to_sawyer_args)
57
- request_logger.info("Duration #{response.timing} sec")
58
- params = Params::Params.new(request_params, response)
59
- _verify(params, request_logger)
60
- rescue *NETWORK_ERRORS => e
61
- params ||= Params::Params.new(request_params, nil)
62
- request_logger.error("Network Error (#{e.message})")
63
- raise MyApiClient::NetworkError.new(params, e)
64
- rescue MyApiClient::Error => e
65
- request_logger.warn("Failure (#{response.status})")
66
- raise e
67
- else
68
- request_logger.info("Success (#{response.status})")
69
- response.data
34
+ # @param http_method [Symbol]
35
+ # HTTP method. e.g. `:get`, `:post`, `:put`, `:patch` and `:delete`.
36
+ # @param uri [URI]
37
+ # Request target URI including query strings.
38
+ # @param headers [Hash, nil]
39
+ # Request headers.
40
+ # @param body [Hash, nil]
41
+ # Request body.
42
+ # @return [Sawyer::Response]
43
+ # Response instance.
44
+ def _request_with_absolute_uri(http_method, uri, headers, body)
45
+ Executor.call(
46
+ instance: self,
47
+ request_params: Params::Request.new(http_method, uri, headers, body),
48
+ request_logger: Logger.new(logger, http_method, uri),
49
+ faraday_options: faraday_options
50
+ )
70
51
  end
71
52
 
72
- # Description of #_verify
53
+ # Generates options for the faraday instance.
73
54
  #
74
- # @param params [MyApiClient::Params::Params] describe_params_here
75
- # @param request_logger [MyApiClient::Logger] describe_request_logger_here
76
- # @return [nil] description_of_returned_object
77
- # @raise [MyApiClient::Error]
78
- def _verify(params, request_logger)
79
- case error_handler = error_handling(params.response)
80
- when Proc
81
- error_handler.call(params, request_logger)
82
- when Symbol
83
- send(error_handler, params, request_logger)
84
- end
55
+ # @return [Hash] Generated options.
56
+ def faraday_options
57
+ {
58
+ request: {
59
+ timeout: (http_read_timeout if respond_to?(:http_read_timeout)),
60
+ open_timeout: (http_open_timeout if respond_to?(:http_open_timeout)),
61
+ }.compact,
62
+ }
85
63
  end
86
64
  end
87
65
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MyApiClient
4
+ module Request
5
+ # Provides basic HTTP request method.
6
+ module Basic
7
+ HTTP_METHODS = %i[get post put patch delete].freeze
8
+
9
+ HTTP_METHODS.each do |http_method|
10
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
11
+ # Executes HTTP request with #{http_method.upcase} method
12
+ #
13
+ # @param pathname [String]
14
+ # Pathname of the request target URL.
15
+ # It's joined with the defined by `endpoint`.
16
+ # @param headers [Hash, nil]
17
+ # Request headers.
18
+ # @param query [Hash, nil]
19
+ # Query string.
20
+ # @param body [Hash, nil]
21
+ # Request body. You should not specify it when use GET method.
22
+ # @return [Sawyer::Resource]
23
+ # Response body instance.
24
+ def #{http_method}(pathname, headers: nil, query: nil, body: nil)
25
+ response = call(:_request_with_relative_uri, :#{http_method}, pathname, headers, query, body)
26
+ response.data
27
+ end
28
+ METHOD
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MyApiClient
4
+ module Request
5
+ # Executes HTTP request with specified parameters.
6
+ class Executor < ServiceAbstract
7
+ # @param instance [MyApiClient::Base]
8
+ # The my_api_client instance.
9
+ # The instance method will be called on error handling.
10
+ # @param request_params [MyApiClient::Params::Request]
11
+ # Request parameter instance.
12
+ # @param request_logger [MyApiClient::Logger]
13
+ # Request logger instance.
14
+ # @param faraday_options [Hash]
15
+ # Options for the faraday instance. Mainly used for timeout settings.
16
+ # @return [Sawyer::Response]
17
+ # Response instance.
18
+ # @raise [MyApiClient::Error]
19
+ # Raises on invalid response or network errors.
20
+ def initialize(instance:, request_params:, request_logger:, faraday_options:)
21
+ @instance = instance
22
+ @request_params = request_params
23
+ @request_logger = request_logger
24
+ faraday = Faraday.new(nil, faraday_options)
25
+ @agent = Sawyer::Agent.new('', faraday: faraday)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :instance, :request_params, :request_logger, :agent
31
+
32
+ def call
33
+ request_logger.info('Start')
34
+ response = api_request
35
+ request_logger.info("Duration #{response.timing} sec")
36
+ verify(response)
37
+ rescue MyApiClient::Error => e
38
+ request_logger.warn("Failure (#{e.message})")
39
+ raise
40
+ else
41
+ request_logger.info("Success (#{response.status})")
42
+ response
43
+ end
44
+
45
+ # Executes HTTP request to the API.
46
+ #
47
+ # @return [Sawyer::Response]
48
+ # Response instance.
49
+ # @raise [MyApiClient::NetworkError]
50
+ # Raises on any network errors.
51
+ def api_request
52
+ agent.call(*request_params.to_sawyer_args)
53
+ rescue *NETWORK_ERRORS => e
54
+ params = Params::Params.new(request_params, nil)
55
+ raise MyApiClient::NetworkError.new(params, e)
56
+ end
57
+
58
+ # Verifies the response.
59
+ #
60
+ # @param response_params [Sawyer::Response]
61
+ # The target response.
62
+ # @return [nil]
63
+ # Returns nil when a valid response.
64
+ # @raise [MyApiClient::Error]
65
+ # Raises on any invalid response.
66
+ def verify(response_params)
67
+ params = Params::Params.new(request_params, response_params)
68
+ find_error_handler(response_params)&.call(params, request_logger)
69
+ end
70
+
71
+ # Executes response verifyment. If an invalid response is detected, return
72
+ # the error handler procedure. The error handlers defined later takes precedence.
73
+ #
74
+ # @param response_params [Sawyer::Response]
75
+ # The target response.
76
+ # @return [nil]
77
+ # Returns nil when a valid response.
78
+ # @return [Proc]
79
+ # Returns Proc when a invalid response.
80
+ def find_error_handler(response_params)
81
+ instance.error_handlers.reverse_each do |error_handler|
82
+ result = error_handler.call(instance, response_params)
83
+ return result unless result.nil?
84
+ end
85
+ nil
86
+ end
87
+ end
88
+ end
89
+ end