api-response-presenter 0.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 34e509264188009fe9c15dd6c38c58286b338a688e33372f1e15ab690e4f7416
4
+ data.tar.gz: a3365ff34ea26895b3393dccc4c450ccb6645dd7d7410931c2ba4c47e1c16690
5
+ SHA512:
6
+ metadata.gz: f205f9e01e0853e9afed337b6460b3672984bb632eb2e6d7ed6ea9a41d31d9bf3e3ca2cd265f9929a856b304b4f8b2b909a470ab7e5d9ee319d6ac0725e02b68
7
+ data.tar.gz: 3fb35b7a9b8def679a29868f4b9cab6fd7cd9a48a653e3d5a5329b8fcbf9a72ec9bf132cee5bcc67ee3c64e54debc3c045abdcd6ce3d4c6e991e2c2c08cacc6f
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ - version: 0.0.1
2
+ - date: 2024-03-10
3
+ - summary: Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 David Rybolovlev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,324 @@
1
+ # api-response-presenter
2
+ [![Gem Version](https://badge.fury.io/rb/api-response-presenter.svg)](https://badge.fury.io/rb/api-response-presenter) [![Build Status](https://app.travis-ci.com/golifox/api-response-presenter.svg?branch=main)](https://app.travis-ci.com/golifox/api-response-presenter)
3
+ [![Coverage Status](https://coveralls.io/repos/github/golifox/api-response-presenter/badge.svg)](https://coveralls.io/github/golifox/api-response-presenter)
4
+ [![Inline docs](https://inch-ci.org/github/golifox/api-response-presenter.svg?branch=main)](https://inch-ci.org/github/golifox/api-response-presenter)
5
+
6
+ The `api-response-presenter` gem provides a flexible and easy-to-use interface for processing API responses using Faraday or
7
+ RestClient with the possibility to configure global settings or per-instance settings. It leverages
8
+ the `Dry::Configurable` for configurations, ensuring high performance and full test coverage.
9
+
10
+ ## Supported Ruby Versions
11
+ This library oficially supports the following Ruby versions:
12
+ - MRI `>=2.7.4`
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'api-response-presenter'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ ```bash
25
+ bundle install
26
+ ```
27
+
28
+ Or install it yourself as:
29
+
30
+ ```bash
31
+ gem install api-response-presenter
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ### Configuration
37
+
38
+ You can configure api_response globally in an initializer or setup block:
39
+
40
+ ```ruby
41
+ # config/initializers/api_response.rb
42
+
43
+ ApiResponse.config.some_option = 'some_value'
44
+
45
+ # or
46
+
47
+ ApiResponse.configure do |config|
48
+ config.adapter = :faraday # or :rest_client, :excon, :http
49
+ config.monad = false
50
+ config.extract_from_body = ->(body) { body }
51
+ config.struct = nil
52
+ config.raw_response = false
53
+ config.error_json = false
54
+ config.default_return_value = nil
55
+ config.default_status = :conflict
56
+ config.default_error_key = :external_api_error
57
+ config.default_error = 'External Api error'
58
+
59
+ # dependency injection
60
+ config.success_processor = ApiResponse::Processor::Success
61
+ config.failure_processor = ApiResponse::Processor::Failure
62
+ config.parser = ApiResponse::Parser
63
+ config.options = {}
64
+ end
65
+ ```
66
+
67
+ or on instance config, provide block (see: BasicUsage).
68
+
69
+ ### Basic Example
70
+
71
+ Here is a basic example of using api_response to process an API response:
72
+
73
+ ```ruby
74
+ response = Faraday.get('https://api.example.com/data')
75
+ result = ApiResponse::Presenter.call(response) do |config|
76
+ config.monad = true
77
+ end
78
+
79
+ # or
80
+ # Usefull for using in another libraries
81
+ result ||= ApiResponse::Presenter.call(response, monad: true)
82
+
83
+ if result.success?
84
+ puts "Success: #{result.success}"
85
+ else
86
+ puts "Error: #{result.failure}"
87
+ end
88
+
89
+ ```
90
+
91
+ ### Config options
92
+
93
+ - `ApiResponse.config.adapter`: response adapter that you are using.
94
+ - Default: `:faraday`.
95
+ - Available values: `:rest_client`, `:excon`, `:http` and others. Checks that response respond to `#status` (only Faraday and Excon)
96
+ or `#code` (others)
97
+ - `ApiResponse.config.monad` wrap result into [dry-monads](https://github.com/dry-rb/dry-monads)
98
+ - Default: `false`
99
+ - Example: `ApiResponse::Presenter.call(response, monad: true) # => Success({})` or `Failure({error:, status:, error_key:})`
100
+ - Note: if you use `ApiResponse::Presenter.call` with monad: true, you should use `#success?` and `#failure?` methods to check result
101
+ - Options only for `ApiResponse.config.monad = true`:
102
+ - `ApiResponse.config.default_status` default status for `ApiResponse::Presenter.call` if response is not success. You can provide symbol or integer.
103
+ - Default: `:conflict`
104
+ - `ApiResponse.config.symbol_status` option for symbolize status from response (or default status if it an Integer).
105
+ - Default: `true`
106
+ - Example: `ApiResponse::Presenter.call(response, monad: true, default_status: 500, symbol_status: false) # => Failure({error:, status: 500, error_key:})`
107
+ - `ApiResponse.config.default_error_key` default error key for `ApiResponse::Presenter.call` if response is not success
108
+ - Default: `:external_api_error`
109
+ - `ApiResponse.config.default_error` default error message for `ApiResponse::Presenter.call` if response is not success
110
+ - Default: `'External Api error'`
111
+ - `ApiResponse.config.extract_from_body` procedure that is applied to the `response.body` after it has been parsed from JSON string to Ruby hash with symbolize keys.
112
+ - Default: `->(body) { body }`.
113
+ - Example lambdas: `->(b) { b.first }`, `->(b) { b.slice(:id, :name) }`, `-> (b) { b.deep_stringify_keys )}`
114
+ - `ApiResponse.config.struct` struct for pack your extracted value from body.
115
+ - Default: `nil`
116
+ - Note: packing only into classes with key value constructors (e.g. `MyAwesomeStruct.new(**attrs)`, not `Struct.new(attrs)`)
117
+ - Recommend to use [dry-struct](https://github.com/dry-rb/dry-struct) or [Ruby#OpenStruct](https://ruby-doc.org/stdlib-3.0.0/libdoc/ostruct/rdoc/OpenStruct.html)
118
+ - `ApiResponse.config.raw_response` returns raw response, that you passes into class.
119
+ - Default: `false`
120
+ - Example: `ApiResponse::Presenter.call(Faraday::Response<...>, raw_response: true) # => Faraday::Response<...>`
121
+ - `ApiResponse.config.error_json` returns error message from response body if it is JSON (parsed with symbolize keys)
122
+ - Default: `false`
123
+ - Example: `ApiResponse::Presenter.call(Response<body: "{\"error\": \"some_error\"}">, error_json: true) # => {error: "some_error"}`
124
+ - `ApiResponse.config.default_return_value` default value for `ApiResponse::Presenter.call` if response is not success
125
+ - Default: `nil`
126
+ - Example: `ApiResponse::Presenter.call(response, default_return_value: []) # => []`
127
+
128
+ NOTE: You can override global settings on instance config, provide block (see: BasicUsage). Params options has higher priority than global settings and block settings.
129
+
130
+ ### Examples:
131
+
132
+ #### Interactors:
133
+ ```ruby
134
+ class ExternalApiCaller < ApplicationInteractor
135
+ class Response < Dry::Struct
136
+ attribute :data, Types::Array
137
+ end
138
+
139
+ def call
140
+ response = RestClient.get('https://api.example.com/data') # => body: "{\"data\": [{\"id\": 1, \"name\": \"John\"}]}"
141
+ ApiResponse::Presenter.call(response) do |config|
142
+ config.adapter = :rest_client
143
+ config.monad = true
144
+ config.struct = Response
145
+ config.default_status = 400 # no matter what status came in fact
146
+ config.symbol_status = true # return :bad_request instead of 400
147
+ config.default_error = 'ExternalApiCaller api error' # instead of response error field (e.g. body[:error])
148
+ end
149
+ end
150
+ end
151
+
152
+ def MyController
153
+ def index
154
+ result = ExternalApiCaller.call
155
+ if result.success?
156
+ render json: result.success # => ExternalApiCaller::Response<data: [{id: 1, name: "John"}]> => {data: [{id: 1, name: "John"}]}
157
+ else
158
+ render json: {error: result.failure[:error]}, status: result.failure[:status] # => {error: "ExternalApiCaller api error"}, status: 400
159
+ end
160
+ end
161
+ end
162
+ ```
163
+
164
+ #### ExternalApi services
165
+
166
+ ```ruby
167
+
168
+ class EmployeeApiService
169
+ class Employee < Dry::Struct
170
+ attribute :id, Types::Integer
171
+ attribute :name, Types::String
172
+ end
173
+
174
+ def self.get_employees(monad: false, adapter: :faraday, **options)
175
+ # or (params, presenter_options = {})
176
+ response = Faraday.get('https://api.example.com/data', params) # => body: "{\"data\": [{\"id\": 1, \"name\": \"John\"}]}"
177
+ ApiResponse::Presenter.call(response, monad: monad, adapter: adapter) do |c|
178
+ c.extract_from_body = ->(body) { Kaminari.paginate_array(body[:data]).page(1).per(5) }
179
+ c.struct = Employee
180
+ c.default_return_value = []
181
+ end
182
+ end
183
+ end
184
+
185
+ class MyController
186
+ def index
187
+ employees = EmployeeApiService.get_employees(page: 1, per: 5)
188
+ if employees.any?
189
+ render json: employees # => [Employee<id: 1, name: "John">] => [{id: 1, name: "John"}]
190
+ else
191
+ render json: {error: 'No employees found'}, status: 404
192
+ end
193
+ end
194
+ end
195
+ ```
196
+
197
+
198
+ ### Customization
199
+
200
+ #### Processors
201
+ You can customize the response processing by providing a block to `ApiResponse::Presenter.call` or redefine global processors and parser:
202
+ All of them must implement `.new(response, config: ApiResponse.config).call` method.
203
+ You can use not default config in your processor, just pass it as a second named argument.
204
+
205
+ 1. Redefine `ApiResponse::Processor::Success` # contains logic for success response (status/code 100-399)
206
+ 2. Redefine `ApiResponse::Processor::Failure` # contains logic for failure response (status/code 400-599)
207
+ 3. Redefine `ApiResponse::Parser` # contains logic for parsing response body (e.g. `Oj.load(response.body)`)
208
+
209
+ ```ruby
210
+ class MyClass
211
+ def initialize(response, config: ApiResponse.config)
212
+ @response = response
213
+ @config = config
214
+ end
215
+
216
+ def call
217
+ # your custom logic
218
+ end
219
+ end
220
+ ```
221
+
222
+ or with `Dry::Initializer`
223
+
224
+ ```ruby
225
+ require 'dry/initializer'
226
+
227
+ class MyClass
228
+ extend Dry::Initializer
229
+ option :response
230
+ option :config, default: -> { ApiResponse.config }
231
+
232
+ def call
233
+ # your custom logic
234
+ end
235
+ end
236
+ ```
237
+
238
+ You can use your custom processor or parser in `ApiResponse::Presenter.call` or redefine in global settings:
239
+
240
+ ```ruby
241
+ ApiResponse.config.success_processor = MyClass
242
+ ApiResponse.config.failure_processor = MyClass
243
+ ApiResponse.config.parser = MyClass
244
+ ```
245
+
246
+ or
247
+
248
+ ```ruby
249
+ ApiResponse::Presenter.call(response, success_processor: MyClass, failure_processor: MyClass, parser: MyClass)
250
+ ```
251
+
252
+ #### Options
253
+
254
+ Also you can add custom options to `ApiResponse.config.options = {}` and use it in your processor or parser:
255
+
256
+ ```ruby
257
+ ApiResponse.config do |config|
258
+ config.options[:my_option] = 'my_value'
259
+ config.options[:my_another_option] = 'my_another_value'
260
+ end
261
+ ```
262
+
263
+ or
264
+
265
+ ```ruby
266
+ ApiResponse::Presenter.call(response, success_processor: MyClass, options: {my_option: 'my_value', my_another_option: 'my_another_value'})
267
+ ```
268
+
269
+ Example:
270
+
271
+ ```ruby
272
+
273
+ class MyCustomParser
274
+ attr_reader :response, :config
275
+
276
+ def initialize(response, config: ApiResponse.config)
277
+ @response = response
278
+ @config = config
279
+ end
280
+
281
+ def call
282
+ JSON.parse(response.body, symbolize_names: true) # or Oj.load(response.body, symbol_keys: true)
283
+ rescue JSON::ParserError => e
284
+ raise ::ParseError.new(e) if config.options[:raise_on_failure]
285
+ response.body
286
+ end
287
+ end
288
+
289
+ class MyCustomFailureProcessor
290
+ class BadRequestError < StandardError; end
291
+
292
+ attr_reader :response, :config
293
+
294
+ def initialize(response, config: ApiResponse.config)
295
+ @response = response
296
+ @config = config
297
+ end
298
+
299
+ def call
300
+ parsed_body = config.parser.new(response).call
301
+ raise BadRequestError.new(parsed_body) if config.options[:raise_on_failure]
302
+
303
+ {error: parsed_body, status: response.status || config.default_status, error_key: :external_api_error}
304
+ end
305
+ end
306
+
307
+ ApiResponse.config do |config|
308
+ config.failure_processor = MyCustomFailureProcessor
309
+ config.parser = MyCustomParser
310
+ config.options[:raise_on_failure] = true
311
+ end
312
+
313
+ response = Faraday.get('https://api.example.com/endpoint_that_will_fail')
314
+ ApiResponse::Presenter.call(response) # => raise BadRequestError
315
+ ```
316
+
317
+ ## Contributing
318
+
319
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/golifox/api_response.git)
320
+
321
+ ## License
322
+ See `LICENSE` file.
323
+
324
+
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'api_response/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'api-response-presenter'
9
+ s.author = 'David Rybolovlev'
10
+ s.email = 'i@golifox.ru'
11
+ s.license = 'MIT'
12
+ s.version = ApiResponse::VERSION.dup
13
+
14
+ s.summary = 'Gem for presenting API responses from Faraday and RestClient.'
15
+ s.description = s.summary
16
+ s.files = Dir['README.md', 'LICENSE', 'CHANGELOG.md', 'api_response_presenter.gemspec', 'lib/**/*.rb']
17
+ s.executables = []
18
+ s.require_paths = ['lib']
19
+
20
+ s.metadata = {'rubygems_mfa_required' => 'true'}
21
+
22
+ s.required_ruby_version = '>= 2.7.4'
23
+
24
+ s.add_runtime_dependency 'dry-configurable', '~> 1.0'
25
+ s.add_runtime_dependency 'dry-initializer', '~> 3.0'
26
+ s.add_runtime_dependency 'dry-monads', '~> 1.6'
27
+ s.add_runtime_dependency 'dry-types', '~> 1.5'
28
+ s.add_runtime_dependency 'oj', '~> 3.13'
29
+ s.add_runtime_dependency 'zeitwerk', '~> 2.4'
30
+
31
+ s.add_development_dependency 'rake'
32
+ s.add_development_dependency 'rspec'
33
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oj'
4
+
5
+ module ApiResponse
6
+ class Parser
7
+ extend Dry::Initializer
8
+
9
+ param :response, type: Types.Interface(:body)
10
+ option :config, default: -> { ApiResponse.config }
11
+
12
+ def call
13
+ Oj.load(response.body, mode: :compat, symbol_keys: true)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiResponse
4
+ class Presenter
5
+ extend Dry::Initializer
6
+
7
+ param :response
8
+ option :config, default: -> { ApiResponse.config.dup }
9
+ option :options, type: Types::Hash, default: -> { {} }
10
+
11
+ def self.call(response, **options, &block)
12
+ new(response, options: options).call(&block)
13
+ end
14
+
15
+ def call(&block)
16
+ if block
17
+ Processor.call(response, config: config, options: options, &block)
18
+ else
19
+ Processor.call(response, config: config, options: options)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-monads'
4
+
5
+ module ApiResponse
6
+ class Processor
7
+ class Failure
8
+ include Dry::Monads[:result]
9
+ extend Dry::Initializer
10
+
11
+ param :response, type: Types.Interface(:body)
12
+ option :config, default: -> { ApiResponse.config }
13
+
14
+ def call
15
+ return response if config.raw_response
16
+ return build_error_monad if config.monad
17
+
18
+ begin
19
+ return response_body if config.error_json
20
+ rescue StandardError
21
+ return config.default_return_value || response.body
22
+ end
23
+
24
+ config.default_return_value
25
+ end
26
+
27
+ private
28
+
29
+ def response_body
30
+ @response_body ||= config.parser.new(response, config: config).call
31
+ end
32
+
33
+ def build_error_monad
34
+ status = config.default_status || prepare_status(response)
35
+ error = config.default_error || response_body.fetch(:error, nil) || response_body
36
+ error_key = config.default_error_key || response_body.fetch(:error_key, nil)
37
+
38
+ Failure({error: error, error_key: error_key, status: status})
39
+ end
40
+
41
+ def prepare_status(response)
42
+ code = case config.adapter
43
+ when :faraday, :excon then response.status
44
+ else response&.code
45
+ end
46
+
47
+ prepared_default_status || prepared_response_status(code)
48
+ end
49
+
50
+ def prepared_response_status(code)
51
+ config.symbol_status ? ApiResponse::Types::STATUS_CODE_TO_SYMBOL[code.to_i] : code.to_i
52
+ end
53
+
54
+ def prepared_default_status
55
+ if config.symbol_status && config.default_status.is_a?(Integer)
56
+ ApiResponse::Types::SYMBOL_TO_STATUS_CODE[config.default_status]
57
+ else
58
+ config.default_status
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/monads'
4
+
5
+ module ApiResponse
6
+ class Processor
7
+ class Success
8
+ class ExtractError < StandardError; end
9
+
10
+ class StructError < StandardError; end
11
+
12
+ include Dry::Monads[:result]
13
+ extend Dry::Initializer
14
+
15
+ param :response, type: Types.Interface(:body)
16
+ option :config, default: -> { ApiResponse.config }
17
+
18
+ def call
19
+ return response if config.raw_response
20
+
21
+ result = extract_from_body
22
+ result = build_struct(result) if config.struct
23
+
24
+ config.monad ? Success(result) : result
25
+ end
26
+
27
+ private
28
+
29
+ def response_body
30
+ @response_body ||= config.parser.new(response, config: config).call
31
+ end
32
+
33
+ def extract_from_body
34
+ config.extract_from_body.call(response_body) || response_body
35
+ rescue EncodingError => e
36
+ raise ExtractError, e.message
37
+ rescue StandardError
38
+ response.body
39
+ end
40
+
41
+ def build_struct(extracted)
42
+ case extracted
43
+ when Hash then config.struct.new(extracted)
44
+ when Array then extracted.map { |item| config.struct.new(**item) }
45
+ end
46
+ rescue StandardError => e
47
+ raise StructError, e.message
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiResponse
4
+ class Processor
5
+ extend Dry::Initializer
6
+
7
+ param :response
8
+ option :config, default: -> { ApiResponse.config.dup }
9
+ option :options, default: -> { {} }
10
+
11
+ def self.call(response, options:, config: ApiResponse.config.dup, &block)
12
+ config = config.dup
13
+ yield(config) if block
14
+ config.update(options)
15
+
16
+ new(response, config: config, options: options).call
17
+ end
18
+
19
+ def call
20
+ processor = success? ? config.success_processor : config.failure_processor
21
+
22
+ processor.new(response, config: config).call
23
+ end
24
+
25
+ private
26
+
27
+ def success?
28
+ case config.adapter
29
+ when :faraday, :excon
30
+ response.status.to_i < 400
31
+ else
32
+ response.code.to_i < 400
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-types'
4
+
5
+ module ApiResponse
6
+ module Types
7
+ include Dry.Types
8
+
9
+ STATUS_CODE_TO_SYMBOL = {
10
+ 100 => :continue,
11
+ 101 => :switching_protocols,
12
+ 102 => :processing,
13
+ 103 => :early_hints,
14
+ 200 => :ok,
15
+ 201 => :created,
16
+ 202 => :accepted,
17
+ 203 => :non_authoritative_information,
18
+ 204 => :no_content,
19
+ 205 => :reset_content,
20
+ 206 => :partial_content,
21
+ 207 => :multi_status,
22
+ 208 => :already_reported,
23
+ 226 => :im_used,
24
+ 300 => :multiple_choices,
25
+ 301 => :moved_permanently,
26
+ 302 => :found,
27
+ 303 => :see_other,
28
+ 304 => :not_modified,
29
+ 305 => :use_proxy,
30
+ 306 => :'(unused)',
31
+ 307 => :temporary_redirect,
32
+ 308 => :permanent_redirect,
33
+ 400 => :bad_request,
34
+ 401 => :unauthorized,
35
+ 402 => :payment_required,
36
+ 403 => :forbidden,
37
+ 404 => :not_found,
38
+ 405 => :method_not_allowed,
39
+ 406 => :not_acceptable,
40
+ 407 => :proxy_authentication_required,
41
+ 408 => :request_timeout,
42
+ 409 => :conflict,
43
+ 410 => :gone,
44
+ 411 => :length_required,
45
+ 412 => :precondition_failed,
46
+ 413 => :payload_too_large,
47
+ 414 => :uri_too_long,
48
+ 415 => :unsupported_media_type,
49
+ 416 => :range_not_satisfiable,
50
+ 417 => :expectation_failed,
51
+ 421 => :misdirected_request,
52
+ 422 => :unprocessable_entity,
53
+ 423 => :locked,
54
+ 424 => :failed_dependency,
55
+ 425 => :too_early,
56
+ 426 => :upgrade_required,
57
+ 428 => :precondition_required,
58
+ 429 => :too_many_requests,
59
+ 431 => :request_header_fields_too_large,
60
+ 451 => :unavailable_for_legal_reasons,
61
+ 500 => :internal_server_error,
62
+ 501 => :not_implemented,
63
+ 502 => :bad_gateway,
64
+ 503 => :service_unavailable,
65
+ 504 => :gateway_timeout,
66
+ 505 => :http_version_not_supported,
67
+ 506 => :variant_also_negotiates,
68
+ 507 => :insufficient_storage,
69
+ 508 => :loop_detected,
70
+ 509 => :bandwidth_limit_exceeded,
71
+ 510 => :not_extended,
72
+ 511 => :network_authentication_required
73
+ }.freeze
74
+ end
75
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiResponse
4
+ VERSION = '0.0.1'
5
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zeitwerk'
4
+ loader = Zeitwerk::Loader.for_gem
5
+ loader.setup
6
+
7
+ require 'dry-configurable'
8
+ require 'dry-initializer'
9
+
10
+ module ApiResponse
11
+ extend Dry::Configurable
12
+
13
+ setting :adapter, default: :faraday
14
+ setting :monad, default: false
15
+ setting :extract_from_body, default: ->(b) { b }
16
+ setting :struct, default: nil
17
+ setting :raw_response, default: false
18
+ setting :error_json, default: false
19
+ setting :default_return_value, default: nil
20
+ setting :default_status, default: :conflict
21
+ setting :symbol_status, default: true
22
+ setting :default_error_key, default: :external_api_error
23
+ setting :default_error, default: 'External Api error'
24
+
25
+ setting :success_processor, default: Processor::Success
26
+ setting :failure_processor, default: Processor::Failure
27
+ setting :parser, default: Parser
28
+ setting :options, default: {}
29
+ end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: api-response-presenter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - David Rybolovlev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-03-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-configurable
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-initializer
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-monads
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dry-types
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.5'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: oj
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.13'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.13'
83
+ - !ruby/object:Gem::Dependency
84
+ name: zeitwerk
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.4'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Gem for presenting API responses from Faraday and RestClient.
126
+ email: i@golifox.ru
127
+ executables: []
128
+ extensions: []
129
+ extra_rdoc_files: []
130
+ files:
131
+ - CHANGELOG.md
132
+ - LICENSE
133
+ - README.md
134
+ - api_response_presenter.gemspec
135
+ - lib/api_response.rb
136
+ - lib/api_response/parser.rb
137
+ - lib/api_response/presenter.rb
138
+ - lib/api_response/processor.rb
139
+ - lib/api_response/processor/failure.rb
140
+ - lib/api_response/processor/success.rb
141
+ - lib/api_response/types.rb
142
+ - lib/api_response/version.rb
143
+ homepage:
144
+ licenses:
145
+ - MIT
146
+ metadata:
147
+ rubygems_mfa_required: 'true'
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: 2.7.4
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubygems_version: 3.1.6
164
+ signing_key:
165
+ specification_version: 4
166
+ summary: Gem for presenting API responses from Faraday and RestClient.
167
+ test_files: []