api-response-presenter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []