rest-easy 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 +7 -0
- data/lib/rest_easy/attribute.rb +71 -0
- data/lib/rest_easy/auth/basic.rb +23 -0
- data/lib/rest_easy/auth/null.rb +17 -0
- data/lib/rest_easy/auth/psk.rb +23 -0
- data/lib/rest_easy/auth.rb +6 -0
- data/lib/rest_easy/conventions.rb +70 -0
- data/lib/rest_easy/meta.rb +32 -0
- data/lib/rest_easy/refinements.rb +17 -0
- data/lib/rest_easy/resource.rb +747 -0
- data/lib/rest_easy/settings.rb +14 -0
- data/lib/rest_easy/version.rb +5 -0
- data/lib/rest_easy.rb +198 -0
- metadata +168 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/configurable"
|
|
4
|
+
|
|
5
|
+
module RestEasy
|
|
6
|
+
class Settings
|
|
7
|
+
extend Dry::Configurable
|
|
8
|
+
|
|
9
|
+
setting :base_url, default: "https://example.com", reader: true
|
|
10
|
+
setting :max_retries, default: 3, reader: true
|
|
11
|
+
setting :authentication, default: Auth::Null.new, reader: true
|
|
12
|
+
setting :attribute_convention, default: :PascalCase, reader: true
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/rest_easy.rb
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubygems"
|
|
4
|
+
require "dry/inflector"
|
|
5
|
+
require "dry/types"
|
|
6
|
+
require "faraday"
|
|
7
|
+
require "zeitwerk"
|
|
8
|
+
|
|
9
|
+
loader = Zeitwerk::Loader.for_gem
|
|
10
|
+
loader.ignore("#{__dir__}/__rest_easy")
|
|
11
|
+
loader.ignore("#{__dir__}/rest_easy/__*.rb")
|
|
12
|
+
loader.inflector.inflect(
|
|
13
|
+
"psk" => "PSK"
|
|
14
|
+
)
|
|
15
|
+
loader.setup
|
|
16
|
+
|
|
17
|
+
module RestEasy
|
|
18
|
+
# Make Boolean available as a bare type constant (Ruby has no built-in Boolean)
|
|
19
|
+
::Object.const_set(:Boolean, Dry::Types["params.bool"]) unless defined?(::Boolean)
|
|
20
|
+
|
|
21
|
+
# ── Error hierarchy ──────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
class Error < StandardError; end
|
|
24
|
+
class AttributeError < Error; end
|
|
25
|
+
|
|
26
|
+
class MissingAttributeError < AttributeError
|
|
27
|
+
attr_reader :attribute_name
|
|
28
|
+
|
|
29
|
+
def initialize(attribute_name)
|
|
30
|
+
@attribute_name = attribute_name
|
|
31
|
+
super("Missing required attribute: #{attribute_name}")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class ConstraintError < AttributeError
|
|
36
|
+
attr_reader :attribute_name, :value
|
|
37
|
+
|
|
38
|
+
def initialize(attribute_name, value, message = nil)
|
|
39
|
+
@attribute_name = attribute_name
|
|
40
|
+
@value = value
|
|
41
|
+
super(message || "Constraint violation for attribute '#{attribute_name}' with value: #{value.inspect}")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class RequestError < Error
|
|
46
|
+
attr_reader :response
|
|
47
|
+
|
|
48
|
+
def initialize(response_or_message = nil)
|
|
49
|
+
if response_or_message.respond_to?(:status)
|
|
50
|
+
@response = response_or_message
|
|
51
|
+
super("Request failed: #{response_or_message.status}")
|
|
52
|
+
else
|
|
53
|
+
super(response_or_message)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
class AuthenticationError < Error; end
|
|
58
|
+
class RemoteServerError < Error; end
|
|
59
|
+
class RateLimitError < Error; end
|
|
60
|
+
|
|
61
|
+
# ── Types ────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
module Types
|
|
64
|
+
include Dry.Types()
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# ── Module extension (ClassMethods) ──────────────────────────────────
|
|
68
|
+
|
|
69
|
+
module ClassMethods
|
|
70
|
+
def config
|
|
71
|
+
self::Settings.config
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def settings(&block)
|
|
75
|
+
self::Settings.class_eval(&block) if block_given?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def configure(&block)
|
|
79
|
+
if block_given?
|
|
80
|
+
if block.arity == 0
|
|
81
|
+
dsl = Resource::ConfigureDSL.new(self::Settings.config)
|
|
82
|
+
dsl.instance_eval(&block)
|
|
83
|
+
else
|
|
84
|
+
yield self::Settings.config
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def connection(&block)
|
|
90
|
+
if block_given?
|
|
91
|
+
@connection_block = block
|
|
92
|
+
end
|
|
93
|
+
@connection_block
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def faraday_connection
|
|
97
|
+
@faraday_connection ||= Faraday.new(url: config.base_url) do |f|
|
|
98
|
+
f.request :json
|
|
99
|
+
f.response :json
|
|
100
|
+
@connection_block&.call(f)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def authentication
|
|
105
|
+
config.authentication
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# ── HTTP primitives ─────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
def get(path:, params: {}, headers: {})
|
|
111
|
+
request_with_auth(:get, path, params:, headers:)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def post(path:, body: nil, headers: {})
|
|
115
|
+
request_with_auth(:post, path, body:, headers:)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def put(path:, body: nil, headers: {})
|
|
119
|
+
request_with_auth(:put, path, body:, headers:)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def delete(path:, headers: {})
|
|
123
|
+
request_with_auth(:delete, path, headers:)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def request_with_auth(method, path, body: nil, params: {}, headers: {})
|
|
129
|
+
auth = config.authentication
|
|
130
|
+
max_retries = config.max_retries
|
|
131
|
+
attempts = 0
|
|
132
|
+
|
|
133
|
+
begin
|
|
134
|
+
response = faraday_connection.run_request(method, path, body, nil) do |req|
|
|
135
|
+
req.params.update(params) if params.any?
|
|
136
|
+
headers.each { |k, v| req.headers[k] = v }
|
|
137
|
+
auth.apply(req)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
raise RequestError.new(response) unless response.success?
|
|
141
|
+
response.body
|
|
142
|
+
|
|
143
|
+
rescue RequestError => e
|
|
144
|
+
attempts += 1
|
|
145
|
+
if attempts <= max_retries
|
|
146
|
+
auth.on_rejected(e.response)
|
|
147
|
+
retry
|
|
148
|
+
end
|
|
149
|
+
raise
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# ── Type bridge ─────────────────────────────────────────────────────
|
|
156
|
+
# Make bare Ruby types work with Dry::Types features like `constrained`
|
|
157
|
+
# inside Class.new blocks where constant lookup uses lexical scope.
|
|
158
|
+
|
|
159
|
+
TYPE_BRIDGE = {
|
|
160
|
+
::String => Types::Coercible::String,
|
|
161
|
+
::Integer => Types::Coercible::Integer,
|
|
162
|
+
::Float => Types::Coercible::Float
|
|
163
|
+
}.freeze
|
|
164
|
+
|
|
165
|
+
[::String, ::Integer, ::Float].each do |klass|
|
|
166
|
+
unless klass.respond_to?(:constrained)
|
|
167
|
+
klass.define_singleton_method(:constrained) do |**opts|
|
|
168
|
+
RestEasy::TYPE_BRIDGE[self].constrained(**opts)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# ── Module setup ─────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
CHECK_ANCESTORS = false
|
|
176
|
+
|
|
177
|
+
class << self
|
|
178
|
+
def extended(base)
|
|
179
|
+
super
|
|
180
|
+
|
|
181
|
+
# Guard against double registration and constant collisions
|
|
182
|
+
if base.const_defined?(:ExtendedByRestEasy, CHECK_ANCESTORS)
|
|
183
|
+
raise Error, "Double registration of #{base}."
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
if base.const_defined?(:Settings, CHECK_ANCESTORS)
|
|
187
|
+
raise Error, "#{base} already defines Settings. RestEasy needs this constant."
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Clone settings so each API module gets its own config state.
|
|
191
|
+
settings = Class.new(Settings)
|
|
192
|
+
|
|
193
|
+
base.const_set(:Settings, settings)
|
|
194
|
+
base.const_set(:ExtendedByRestEasy, true)
|
|
195
|
+
base.extend ClassMethods
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rest-easy
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jonas Schubert Erlandsson
|
|
8
|
+
- Claude Code
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2026-03-19 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: dry-types
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - "~>"
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: '1.2'
|
|
21
|
+
type: :runtime
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - "~>"
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: '1.2'
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: zeitwerk
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - "~>"
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: '2.6'
|
|
35
|
+
type: :runtime
|
|
36
|
+
prerelease: false
|
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - "~>"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '2.6'
|
|
42
|
+
- !ruby/object:Gem::Dependency
|
|
43
|
+
name: dry-inflector
|
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - "~>"
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: 0.2.1
|
|
49
|
+
type: :runtime
|
|
50
|
+
prerelease: false
|
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - "~>"
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: 0.2.1
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
name: dry-configurable
|
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - "~>"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0.14'
|
|
63
|
+
type: :runtime
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - "~>"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0.14'
|
|
70
|
+
- !ruby/object:Gem::Dependency
|
|
71
|
+
name: faraday
|
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - "~>"
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '2.0'
|
|
77
|
+
type: :runtime
|
|
78
|
+
prerelease: false
|
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - "~>"
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '2.0'
|
|
84
|
+
- !ruby/object:Gem::Dependency
|
|
85
|
+
name: bundler
|
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '0'
|
|
91
|
+
type: :development
|
|
92
|
+
prerelease: false
|
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: '0'
|
|
98
|
+
- !ruby/object:Gem::Dependency
|
|
99
|
+
name: rake
|
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0'
|
|
105
|
+
type: :development
|
|
106
|
+
prerelease: false
|
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
108
|
+
requirements:
|
|
109
|
+
- - ">="
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
version: '0'
|
|
112
|
+
- !ruby/object:Gem::Dependency
|
|
113
|
+
name: rspec
|
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
|
115
|
+
requirements:
|
|
116
|
+
- - ">="
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: '0'
|
|
119
|
+
type: :development
|
|
120
|
+
prerelease: false
|
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
122
|
+
requirements:
|
|
123
|
+
- - ">="
|
|
124
|
+
- !ruby/object:Gem::Version
|
|
125
|
+
version: '0'
|
|
126
|
+
description: Boilerplate for REST API libraries, using on dry-rb
|
|
127
|
+
email:
|
|
128
|
+
- jonas@accodeing.com
|
|
129
|
+
executables: []
|
|
130
|
+
extensions: []
|
|
131
|
+
extra_rdoc_files: []
|
|
132
|
+
files:
|
|
133
|
+
- lib/rest_easy.rb
|
|
134
|
+
- lib/rest_easy/attribute.rb
|
|
135
|
+
- lib/rest_easy/auth.rb
|
|
136
|
+
- lib/rest_easy/auth/basic.rb
|
|
137
|
+
- lib/rest_easy/auth/null.rb
|
|
138
|
+
- lib/rest_easy/auth/psk.rb
|
|
139
|
+
- lib/rest_easy/conventions.rb
|
|
140
|
+
- lib/rest_easy/meta.rb
|
|
141
|
+
- lib/rest_easy/refinements.rb
|
|
142
|
+
- lib/rest_easy/resource.rb
|
|
143
|
+
- lib/rest_easy/settings.rb
|
|
144
|
+
- lib/rest_easy/version.rb
|
|
145
|
+
homepage: https://github.com/accodeing/rest-easy
|
|
146
|
+
licenses:
|
|
147
|
+
- MIT
|
|
148
|
+
metadata: {}
|
|
149
|
+
post_install_message:
|
|
150
|
+
rdoc_options: []
|
|
151
|
+
require_paths:
|
|
152
|
+
- lib
|
|
153
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
154
|
+
requirements:
|
|
155
|
+
- - ">="
|
|
156
|
+
- !ruby/object:Gem::Version
|
|
157
|
+
version: 3.1.0
|
|
158
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
159
|
+
requirements:
|
|
160
|
+
- - ">="
|
|
161
|
+
- !ruby/object:Gem::Version
|
|
162
|
+
version: '0'
|
|
163
|
+
requirements: []
|
|
164
|
+
rubygems_version: 3.3.7
|
|
165
|
+
signing_key:
|
|
166
|
+
specification_version: 4
|
|
167
|
+
summary: Boilerplate for REST API libraries, using on dry-rb
|
|
168
|
+
test_files: []
|