resteze 0.4.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +38 -0
- data/CLAUDE.md +9 -0
- data/README.md +45 -4
- data/docs/ACTIONS.md +329 -0
- data/docs/ADVANCED_USAGE.md +29 -45
- data/docs/API.md +19 -25
- data/docs/CONFIGURATION.md +281 -0
- data/docs/ERROR_HANDLING.md +94 -27
- data/docs/TESTING.md +21 -0
- data/lib/resteze/action.rb +99 -0
- data/lib/resteze/actions/validation.rb +56 -0
- data/lib/resteze/actions.rb +117 -0
- data/lib/resteze/api_module.rb +11 -5
- data/lib/resteze/api_resource.rb +28 -3
- data/lib/resteze/authenticating.rb +28 -0
- data/lib/resteze/authenticator/api_key.rb +45 -0
- data/lib/resteze/authenticator/basic.rb +40 -0
- data/lib/resteze/authenticator/bearer.rb +37 -0
- data/lib/resteze/authenticator/mtls.rb +73 -0
- data/lib/resteze/authenticator/strategy.rb +133 -0
- data/lib/resteze/authenticator.rb +187 -0
- data/lib/resteze/client.rb +59 -11
- data/lib/resteze/errors.rb +125 -4
- data/lib/resteze/instrumentation.rb +35 -0
- data/lib/resteze/list.rb +2 -2
- data/lib/resteze/middleware/raise_error.rb +38 -6
- data/lib/resteze/rate_limit.rb +44 -0
- data/lib/resteze/rate_limiter/fixed_window.rb +107 -0
- data/lib/resteze/rate_limiter/leaky_bucket.rb +40 -0
- data/lib/resteze/rate_limiter/sliding_window.rb +37 -0
- data/lib/resteze/rate_limiter/strategy.rb +58 -0
- data/lib/resteze/rate_limiter/token_bucket.rb +39 -0
- data/lib/resteze/rate_limiter.rb +229 -0
- data/lib/resteze/rate_limiting.rb +30 -0
- data/lib/resteze/response.rb +37 -1
- data/lib/resteze/result.rb +18 -0
- data/lib/resteze/save.rb +25 -3
- data/lib/resteze/synchronized_state.rb +29 -0
- data/lib/resteze/testing/minitest/configuration.rb +3 -1
- data/lib/resteze/testing/minitest/object.rb +30 -0
- data/lib/resteze/testing/rspec/configure.rb +3 -1
- data/lib/resteze/testing/rspec/object.rb +32 -2
- data/lib/resteze/version.rb +1 -1
- data/lib/resteze.rb +101 -11
- metadata +23 -2
data/lib/resteze/response.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module Resteze
|
|
2
2
|
class Response
|
|
3
|
+
include Resteze::ApiModule
|
|
4
|
+
|
|
3
5
|
OK = 200
|
|
4
6
|
attr_accessor :data, :http_body, :http_headers, :http_status, :request_id
|
|
5
7
|
|
|
@@ -15,15 +17,37 @@ module Resteze
|
|
|
15
17
|
status: response.http_status,
|
|
16
18
|
headers: response.http_headers
|
|
17
19
|
)
|
|
20
|
+
|
|
21
|
+
error = body_error(response)
|
|
22
|
+
raise error if error
|
|
18
23
|
end
|
|
19
24
|
end
|
|
20
25
|
|
|
26
|
+
# Override in MyApi::Response for APIs that report failures inside a
|
|
27
|
+
# successful HTTP response. Receives the parsed Response after the
|
|
28
|
+
# status-based middleware has accepted it; return an exception to raise
|
|
29
|
+
# it, or nil to accept the response.
|
|
30
|
+
#
|
|
31
|
+
# module MyApi
|
|
32
|
+
# class Response
|
|
33
|
+
# def self.body_error(response)
|
|
34
|
+
# data = response.data
|
|
35
|
+
# return unless data.is_a?(Hash) && data[:errors].present?
|
|
36
|
+
#
|
|
37
|
+
# ApiError.from_response(response, data[:message])
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
# end
|
|
41
|
+
def self.body_error(_response)
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
21
45
|
def self.batch_response?(headers)
|
|
22
46
|
headers["Content-Type".freeze].to_s.include?("boundary=batchresponse".freeze)
|
|
23
47
|
end
|
|
24
48
|
|
|
25
49
|
def self.parse_body(body, status: nil, headers: {})
|
|
26
|
-
return body if unparsable_body?(status:, headers:)
|
|
50
|
+
return body if body.blank? || unparsable_body?(status:, headers:)
|
|
27
51
|
|
|
28
52
|
type = mime_type(headers)&.symbol || :json
|
|
29
53
|
case type
|
|
@@ -45,8 +69,20 @@ module Resteze
|
|
|
45
69
|
Mime::LOOKUP[mime_type] || Mime::Type.lookup_by_extension(:json)
|
|
46
70
|
end
|
|
47
71
|
|
|
72
|
+
# Exactly 200 OK. Use #success? for the whole 2xx range.
|
|
48
73
|
def ok?
|
|
49
74
|
http_status == OK
|
|
50
75
|
end
|
|
76
|
+
|
|
77
|
+
# Any 2xx status, matching Faraday::Response#success?.
|
|
78
|
+
def success?
|
|
79
|
+
(200..299).cover?(http_status)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Whatever the module's RateLimiter makes of this response (see
|
|
83
|
+
# Resteze::RateLimiter#rate_limit), or nil.
|
|
84
|
+
def rate_limit
|
|
85
|
+
@rate_limit ||= api_module.try(:rate_limiter)&.rate_limit(self)
|
|
86
|
+
end
|
|
51
87
|
end
|
|
52
88
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Resteze
|
|
2
|
+
# The default return value of a custom action: a loose, dot-accessible view
|
|
3
|
+
# of the parsed response body, with the Response it came from attached.
|
|
4
|
+
class Result < Hashie::Mash
|
|
5
|
+
include Resteze::ApiModule
|
|
6
|
+
|
|
7
|
+
disable_warnings
|
|
8
|
+
|
|
9
|
+
attr_reader :http_response
|
|
10
|
+
|
|
11
|
+
def self.from_response(response)
|
|
12
|
+
data = response.data
|
|
13
|
+
new(data.is_a?(Hash) ? data : {}).tap do |result|
|
|
14
|
+
result.instance_variable_set(:@http_response, response)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/resteze/save.rb
CHANGED
|
@@ -6,24 +6,40 @@ module Resteze
|
|
|
6
6
|
resp = request(
|
|
7
7
|
save_method,
|
|
8
8
|
save_resource_path,
|
|
9
|
-
params:
|
|
9
|
+
params: save_params,
|
|
10
10
|
headers: save_headers
|
|
11
11
|
)
|
|
12
12
|
process_save_response(resp)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def save_method
|
|
16
|
-
|
|
16
|
+
save_action.http_method
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# While :create / :update keep their REST defaults, an instance-level
|
|
20
|
+
# resource_path override is honored exactly as in 0.4.0.
|
|
19
21
|
def save_resource_path
|
|
20
|
-
|
|
22
|
+
action = save_action
|
|
23
|
+
if action.rest_default?
|
|
24
|
+
persisted? ? resource_path : self.class.resource_path
|
|
25
|
+
else
|
|
26
|
+
persisted? ? action.path_for(id) : action.path_for
|
|
27
|
+
end
|
|
21
28
|
end
|
|
22
29
|
|
|
23
30
|
def as_save_json
|
|
24
31
|
as_json
|
|
25
32
|
end
|
|
26
33
|
|
|
34
|
+
# The body sent by #save: as_save_json, plus the id param when the
|
|
35
|
+
# :update action carries the id as a param rather than in the path.
|
|
36
|
+
def save_params
|
|
37
|
+
params = as_save_json
|
|
38
|
+
return params unless persisted? && save_action.id == :param
|
|
39
|
+
|
|
40
|
+
{ save_action.id_param.to_s => id }.merge(params)
|
|
41
|
+
end
|
|
42
|
+
|
|
27
43
|
def save_headers
|
|
28
44
|
respond_to?(:retrieve_headers) ? retrieve_headers : {}
|
|
29
45
|
end
|
|
@@ -31,5 +47,11 @@ module Resteze
|
|
|
31
47
|
def process_save_response(response)
|
|
32
48
|
merge_from(response.data)
|
|
33
49
|
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def save_action
|
|
54
|
+
self.class.action_for(persisted? ? :update : :create)
|
|
55
|
+
end
|
|
34
56
|
end
|
|
35
57
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Resteze
|
|
2
|
+
# A mutex-guarded state hash for objects shared by every thread in a
|
|
3
|
+
# process, such as a module's RateLimiter and Authenticator. Including
|
|
4
|
+
# classes call `initialize_state` from their constructor and keep all
|
|
5
|
+
# mutable state in the hash rather than in instance variables.
|
|
6
|
+
module SynchronizedState
|
|
7
|
+
def initialize_state
|
|
8
|
+
@state_mutex = Mutex.new
|
|
9
|
+
@state = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# A deep-copied snapshot of the state.
|
|
13
|
+
def state
|
|
14
|
+
synchronize(&:deep_dup)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def update_state(**values)
|
|
18
|
+
synchronize { |state| state.merge!(values) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Yields the live state hash under the lock. Re-entrant, so `state` and
|
|
22
|
+
# `update_state` may be called inside the block.
|
|
23
|
+
def synchronize
|
|
24
|
+
return yield(@state) if @state_mutex.owned?
|
|
25
|
+
|
|
26
|
+
@state_mutex.synchronize { yield(@state) }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -3,10 +3,12 @@ module Resteze
|
|
|
3
3
|
module Configuration
|
|
4
4
|
extend ActiveSupport::Concern
|
|
5
5
|
|
|
6
|
-
# Assert that a config property
|
|
6
|
+
# Assert that a config property can be assigned and reads back the
|
|
7
|
+
# assigned value. The original value is restored afterwards.
|
|
7
8
|
def assert_config_property(property, value = nil, subject: nil)
|
|
8
9
|
subject ||= (defined?(@subject) && @subject) || subject()
|
|
9
10
|
original_value = subject.send(property)
|
|
11
|
+
subject.send("#{property}=", value)
|
|
10
12
|
|
|
11
13
|
assert_equal value, subject.send(property), "config property #{property} value did not match expectation"
|
|
12
14
|
ensure
|
|
@@ -4,6 +4,9 @@ module Resteze
|
|
|
4
4
|
module Object
|
|
5
5
|
include ActiveSupport::Concern
|
|
6
6
|
|
|
7
|
+
ACTION_READERS = { method: :http_method, on: :on, id: :id, id_param: :id_param,
|
|
8
|
+
returns: :returns, as: :generated_name }.freeze
|
|
9
|
+
|
|
7
10
|
private
|
|
8
11
|
|
|
9
12
|
# rubocop:disable Metrics/AbcSize
|
|
@@ -18,6 +21,33 @@ module Resteze
|
|
|
18
21
|
# rubocop:enable Metrics/AbcSize
|
|
19
22
|
# rubocop:enable Naming/PredicatePrefix
|
|
20
23
|
|
|
24
|
+
# Assert that an action is declared, optionally checking its resolved
|
|
25
|
+
# options. `path:` is compared against the full routed path, using the
|
|
26
|
+
# id "ID" for member actions.
|
|
27
|
+
# rubocop:disable-next Naming/PredicatePrefix
|
|
28
|
+
def has_action(name, **options)
|
|
29
|
+
action = subject.actions[name.to_sym]
|
|
30
|
+
|
|
31
|
+
assert action, "Expected #{subject} to have action: #{name}"
|
|
32
|
+
assert_action_options(action, options)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def assert_action_options(action, options)
|
|
36
|
+
ACTION_READERS.each do |option, reader|
|
|
37
|
+
next unless options.key?(option)
|
|
38
|
+
|
|
39
|
+
assert_equal options[option], action.public_send(reader),
|
|
40
|
+
"Expected #{subject} action #{action.name} to have #{option}: #{options[option].inspect}"
|
|
41
|
+
end
|
|
42
|
+
assert_action_path(action, options[:path]) if options.key?(:path)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def assert_action_path(action, path)
|
|
46
|
+
actual = action.member? ? action.path_for("ID") : action.path_for
|
|
47
|
+
|
|
48
|
+
assert_equal path, actual, "Expected #{subject} action #{action.name} to route to #{path}"
|
|
49
|
+
end
|
|
50
|
+
|
|
21
51
|
def assert_property_transformed(name, transformed: true)
|
|
22
52
|
translation = subject.translations_hash.dig(subject.translations.key(name.to_sym), name.to_sym)
|
|
23
53
|
transformation_defined = translation.is_a?(Proc) || subject.transformation_exists?(name.to_sym)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
module Resteze
|
|
2
2
|
module Testing
|
|
3
3
|
module Configuration
|
|
4
|
-
# Assert that a config property
|
|
4
|
+
# Assert that a config property can be assigned and reads back the
|
|
5
|
+
# assigned value. The original value is restored afterwards.
|
|
5
6
|
def assert_config_property(property, value = nil, subject: nil)
|
|
6
7
|
subject ||= (defined?(@subject) && @subject) || self.subject
|
|
7
8
|
original_value = subject.send(property)
|
|
9
|
+
subject.send("#{property}=", value)
|
|
8
10
|
|
|
9
11
|
expect(subject.send(property)).to eq(value), "config property #{property} value did not match expectation"
|
|
10
12
|
ensure
|
|
@@ -4,8 +4,11 @@ module Resteze
|
|
|
4
4
|
module Object
|
|
5
5
|
extend RSpec::Matchers
|
|
6
6
|
|
|
7
|
+
ACTION_READERS = { method: :http_method, on: :on, id: :id, id_param: :id_param,
|
|
8
|
+
returns: :returns, as: :generated_name }.freeze
|
|
9
|
+
|
|
7
10
|
# rubocop:disable Metrics/AbcSize
|
|
8
|
-
# rubocop:disable Naming/PredicatePrefix
|
|
11
|
+
# rubocop:disable-next Naming/PredicatePrefix
|
|
9
12
|
def has_property(name, **options)
|
|
10
13
|
expect(subject.property?(name.to_sym)).to be(true), "Expected #{subject} to have property: #{name}"
|
|
11
14
|
assert_property_default(name, options[:default]) if options.key?(:default)
|
|
@@ -13,7 +16,6 @@ module Resteze
|
|
|
13
16
|
assert_property_transformed(name, transformed: options[:transformed]) if options.key?(:transformed)
|
|
14
17
|
assert_property_required(name, required: options[:required]) if options.key?(:required)
|
|
15
18
|
end
|
|
16
|
-
# rubocop:enable Naming/PredicatePrefix
|
|
17
19
|
|
|
18
20
|
def assert_property_transformed(name, transformed: true)
|
|
19
21
|
translation = subject.translations_hash.dig(subject.translations.key(name.to_sym), name.to_sym)
|
|
@@ -27,6 +29,34 @@ module Resteze
|
|
|
27
29
|
end
|
|
28
30
|
# rubocop:enable Metrics/AbcSize
|
|
29
31
|
|
|
32
|
+
# Assert that an action is declared, optionally checking its resolved
|
|
33
|
+
# options. `path:` is compared against the full routed path, using the
|
|
34
|
+
# id "ID" for member actions.
|
|
35
|
+
# rubocop:disable-next Naming/PredicatePrefix
|
|
36
|
+
def has_action(name, **options)
|
|
37
|
+
action = subject.actions[name.to_sym]
|
|
38
|
+
|
|
39
|
+
expect(action).not_to be_nil, "Expected #{subject} to have action: #{name}"
|
|
40
|
+
assert_action_options(action, options)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def assert_action_options(action, options)
|
|
44
|
+
ACTION_READERS.each do |option, reader|
|
|
45
|
+
next unless options.key?(option)
|
|
46
|
+
|
|
47
|
+
expect(action.public_send(reader)).to eq(options[option]),
|
|
48
|
+
"Expected #{subject} action #{action.name} to have #{option}: " \
|
|
49
|
+
"#{options[option].inspect}"
|
|
50
|
+
end
|
|
51
|
+
assert_action_path(action, options[:path]) if options.key?(:path)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def assert_action_path(action, path)
|
|
55
|
+
actual = action.member? ? action.path_for("ID") : action.path_for
|
|
56
|
+
|
|
57
|
+
expect(actual).to eq(path), "Expected #{subject} action #{action.name} to route to #{path}"
|
|
58
|
+
end
|
|
59
|
+
|
|
30
60
|
def assert_property_required(name, required: true)
|
|
31
61
|
if required
|
|
32
62
|
expect(subject.required?(name.to_sym)).to be(true), "Expected #{subject} to require property: #{name}"
|
data/lib/resteze/version.rb
CHANGED
data/lib/resteze.rb
CHANGED
|
@@ -10,6 +10,8 @@ require_relative "resteze/version"
|
|
|
10
10
|
# API Concerns
|
|
11
11
|
require "resteze/api_module"
|
|
12
12
|
require "resteze/request"
|
|
13
|
+
require "resteze/actions/validation"
|
|
14
|
+
require "resteze/actions"
|
|
13
15
|
require "resteze/api_resource"
|
|
14
16
|
require "resteze/list"
|
|
15
17
|
require "resteze/save"
|
|
@@ -17,12 +19,21 @@ require "resteze/save"
|
|
|
17
19
|
# API resource support classes
|
|
18
20
|
require "resteze/object"
|
|
19
21
|
|
|
20
|
-
# API Classes
|
|
21
|
-
|
|
22
|
+
# API Classes (each file requires what it needs at load time, so this
|
|
23
|
+
# list stays alphabetical)
|
|
24
|
+
require "resteze/action"
|
|
25
|
+
require "resteze/authenticating"
|
|
26
|
+
require "resteze/authenticator"
|
|
22
27
|
require "resteze/client"
|
|
23
28
|
require "resteze/errors"
|
|
29
|
+
require "resteze/instrumentation"
|
|
24
30
|
require "resteze/list_object"
|
|
31
|
+
require "resteze/rate_limit"
|
|
32
|
+
require "resteze/rate_limiter"
|
|
33
|
+
require "resteze/rate_limiting"
|
|
25
34
|
require "resteze/response"
|
|
35
|
+
require "resteze/result"
|
|
36
|
+
require "resteze/synchronized_state"
|
|
26
37
|
require "resteze/util"
|
|
27
38
|
|
|
28
39
|
# Middlewares
|
|
@@ -59,6 +70,59 @@ module Resteze
|
|
|
59
70
|
def default_api_key(attribute)
|
|
60
71
|
attribute.to_s
|
|
61
72
|
end
|
|
73
|
+
|
|
74
|
+
def default_id_param(klass)
|
|
75
|
+
:"#{klass.resource_slug.singularize}_id"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
ACTION_DEFAULT_OPTIONS = %i[method id id_param].freeze
|
|
79
|
+
|
|
80
|
+
# Declares API-wide defaults for one named action; resources merge these
|
|
81
|
+
# beneath their own declarations.
|
|
82
|
+
def action(name, **options)
|
|
83
|
+
name = name.to_sym
|
|
84
|
+
Resteze::Action.validate_options!(name, options)
|
|
85
|
+
action_declarations[name] = options
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def action_declarations
|
|
90
|
+
@action_declarations ||= {}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# With options, merges them into the API-wide defaults applied to every
|
|
94
|
+
# action; without options, returns those defaults.
|
|
95
|
+
def action_defaults(**options)
|
|
96
|
+
@action_defaults ||= {}
|
|
97
|
+
return @action_defaults if options.empty?
|
|
98
|
+
|
|
99
|
+
Resteze::Action.validate_options!(:defaults, options, allowed: ACTION_DEFAULT_OPTIONS)
|
|
100
|
+
@action_defaults.merge!(options)
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Chooses how the API authenticates, without opening the module's
|
|
105
|
+
# Authenticator: `authenticate_with :api_key, header: "X-Api-Key",
|
|
106
|
+
# value: -> { MyApi.api_key }`. Call it once per scheme to combine
|
|
107
|
+
# them. Reopen MyApi::Authenticator only to override its lifecycle.
|
|
108
|
+
def authenticate_with(name, **)
|
|
109
|
+
component_class(:Authenticator).strategy(name, **)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Chooses how the client paces itself, without opening the module's
|
|
113
|
+
# RateLimiter: `rate_limit_with :fixed_window, limit: 100, window: 60`.
|
|
114
|
+
# Reopen MyApi::RateLimiter to override `retry?` and the rest.
|
|
115
|
+
def rate_limit_with(name, **)
|
|
116
|
+
component_class(:RateLimiter).strategy(name, **)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def component_class(name)
|
|
122
|
+
raise ArgumentError, "call this on a module that includes Resteze, after the include" unless include?(Resteze)
|
|
123
|
+
|
|
124
|
+
const_get(name, false)
|
|
125
|
+
end
|
|
62
126
|
end
|
|
63
127
|
|
|
64
128
|
extend ClassMethods
|
|
@@ -70,7 +134,37 @@ module Resteze
|
|
|
70
134
|
:read_timeout,
|
|
71
135
|
:logger,
|
|
72
136
|
:proxy
|
|
73
|
-
|
|
137
|
+
|
|
138
|
+
# One RateLimiter per API module, shared by every thread. Defaults to
|
|
139
|
+
# an instance of the module's own RateLimiter class; assign another to
|
|
140
|
+
# replace it (nil restores the default). Whatever is assigned is told
|
|
141
|
+
# which module it serves, so it raises this module's errors.
|
|
142
|
+
def rate_limiter
|
|
143
|
+
@rate_limiter || @component_mutex.synchronize { @rate_limiter ||= adopt(self::RateLimiter.new) }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def rate_limiter=(limiter)
|
|
147
|
+
@rate_limiter = adopt(limiter)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# One Authenticator per API module, shared by every thread, with the
|
|
151
|
+
# same defaults and replacement rules as rate_limiter.
|
|
152
|
+
def authenticator
|
|
153
|
+
@authenticator || @component_mutex.synchronize { @authenticator ||= adopt(self::Authenticator.new) }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def authenticator=(authenticator)
|
|
157
|
+
@authenticator = adopt(authenticator)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Tells a shared component which module it serves, so a replacement
|
|
161
|
+
# of any class raises this module's errors.
|
|
162
|
+
def adopt(component)
|
|
163
|
+
component.api_module = self if component.respond_to?(:api_module=)
|
|
164
|
+
component
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
@component_mutex = Mutex.new
|
|
74
168
|
|
|
75
169
|
configure do |config|
|
|
76
170
|
config.api_base = "http://example.com/"
|
|
@@ -84,19 +178,15 @@ module Resteze
|
|
|
84
178
|
const_set :Object, Class.new(Resteze::Object)
|
|
85
179
|
const_set :Client, Class.new(Resteze::Client)
|
|
86
180
|
const_set :Response, Class.new(Resteze::Response)
|
|
181
|
+
const_set :Result, Class.new(Resteze::Result)
|
|
87
182
|
const_set :Error, Class.new(Resteze::Error)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const_set error_type, Class.new(error_class) unless const_defined?("#{self}::#{error_type}")
|
|
92
|
-
end
|
|
183
|
+
const_set :RateLimiter, Class.new(Resteze::RateLimiter)
|
|
184
|
+
const_set :Authenticator, Class.new(Resteze::Authenticator)
|
|
185
|
+
Resteze::Errors.define(self, self::Error)
|
|
93
186
|
|
|
94
187
|
# Namespace other exceptions that we want to be able to catch and make sense of
|
|
95
188
|
# without needing lower level implementation details
|
|
96
189
|
const_set :NotImplementedError, Class.new(NotImplementedError)
|
|
97
|
-
const_set :ResourceNotFound, Class.new(Faraday::ResourceNotFound)
|
|
98
|
-
const_set :UnprocessableEntityError, Class.new(Faraday::UnprocessableEntityError)
|
|
99
|
-
const_set :ConflictError, Class.new(Faraday::ConflictError)
|
|
100
190
|
|
|
101
191
|
# Setup our Magic Constants
|
|
102
192
|
const_set(:ListObject, Class.new(Hashie::Array)).class_eval do
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: resteze
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- JD Hendrickson
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-11-13 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: actionpack
|
|
@@ -83,14 +83,25 @@ files:
|
|
|
83
83
|
- Rakefile
|
|
84
84
|
- assets/resteze.png
|
|
85
85
|
- assets/resteze.svg
|
|
86
|
+
- docs/ACTIONS.md
|
|
86
87
|
- docs/ADVANCED_USAGE.md
|
|
87
88
|
- docs/API.md
|
|
88
89
|
- docs/CONFIGURATION.md
|
|
89
90
|
- docs/ERROR_HANDLING.md
|
|
90
91
|
- docs/TESTING.md
|
|
91
92
|
- lib/resteze.rb
|
|
93
|
+
- lib/resteze/action.rb
|
|
94
|
+
- lib/resteze/actions.rb
|
|
95
|
+
- lib/resteze/actions/validation.rb
|
|
92
96
|
- lib/resteze/api_module.rb
|
|
93
97
|
- lib/resteze/api_resource.rb
|
|
98
|
+
- lib/resteze/authenticating.rb
|
|
99
|
+
- lib/resteze/authenticator.rb
|
|
100
|
+
- lib/resteze/authenticator/api_key.rb
|
|
101
|
+
- lib/resteze/authenticator/basic.rb
|
|
102
|
+
- lib/resteze/authenticator/bearer.rb
|
|
103
|
+
- lib/resteze/authenticator/mtls.rb
|
|
104
|
+
- lib/resteze/authenticator/strategy.rb
|
|
94
105
|
- lib/resteze/client.rb
|
|
95
106
|
- lib/resteze/errors.rb
|
|
96
107
|
- lib/resteze/instrumentation.rb
|
|
@@ -98,9 +109,19 @@ files:
|
|
|
98
109
|
- lib/resteze/list_object.rb
|
|
99
110
|
- lib/resteze/middleware/raise_error.rb
|
|
100
111
|
- lib/resteze/object.rb
|
|
112
|
+
- lib/resteze/rate_limit.rb
|
|
113
|
+
- lib/resteze/rate_limiter.rb
|
|
114
|
+
- lib/resteze/rate_limiter/fixed_window.rb
|
|
115
|
+
- lib/resteze/rate_limiter/leaky_bucket.rb
|
|
116
|
+
- lib/resteze/rate_limiter/sliding_window.rb
|
|
117
|
+
- lib/resteze/rate_limiter/strategy.rb
|
|
118
|
+
- lib/resteze/rate_limiter/token_bucket.rb
|
|
119
|
+
- lib/resteze/rate_limiting.rb
|
|
101
120
|
- lib/resteze/request.rb
|
|
102
121
|
- lib/resteze/response.rb
|
|
122
|
+
- lib/resteze/result.rb
|
|
103
123
|
- lib/resteze/save.rb
|
|
124
|
+
- lib/resteze/synchronized_state.rb
|
|
104
125
|
- lib/resteze/testing/minitest.rb
|
|
105
126
|
- lib/resteze/testing/minitest/configuration.rb
|
|
106
127
|
- lib/resteze/testing/minitest/object.rb
|