ruby_api_pack_core 0.1.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/.all-contributorsrc +23 -0
- data/.circleci/config.yml +44 -0
- data/.claude/settings.json +36 -0
- data/.claude/settings.local.json +36 -0
- data/.coderabbit.yaml +75 -0
- data/.codex/README.md +31 -0
- data/.codex/change-watch.md +19 -0
- data/.codex/release-readiness.md +35 -0
- data/.editorconfig +22 -0
- data/.rspec +3 -0
- data/.rubocop.yml +70 -0
- data/AGENTS.md +117 -0
- data/CHANGELOG.md +48 -0
- data/CLAUDE.md +105 -0
- data/CODEX.md +66 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +111 -0
- data/COPILOT.md +30 -0
- data/JULES.md +34 -0
- data/MIT-LICENSE +21 -0
- data/README.md +281 -0
- data/ROADMAP.md +42 -0
- data/Rakefile +12 -0
- data/SECURITY.md +46 -0
- data/TODO.md +32 -0
- data/lib/ruby_api_pack_core/configurable.rb +25 -0
- data/lib/ruby_api_pack_core/connection/base.rb +94 -0
- data/lib/ruby_api_pack_core/handlers/response_validator.rb +31 -0
- data/lib/ruby_api_pack_core/version.rb +5 -0
- data/lib/ruby_api_pack_core.rb +12 -0
- data/ruby_api_pack_core.code-workspace +8 -0
- metadata +111 -0
data/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# Ruby API Pack Core
|
|
2
|
+
|
|
3
|
+
## Repository Snapshot
|
|
4
|
+
|
|
5
|
+
| Field | Value |
|
|
6
|
+
|-------|-------|
|
|
7
|
+
| Project team | `project-ruby` |
|
|
8
|
+
| Repository role | Shared HTTP client foundation for all `ruby_api_pack_*` gems |
|
|
9
|
+
| Package/artifact | `ruby_api_pack_core` |
|
|
10
|
+
| Current version/status | 0.1.0 |
|
|
11
|
+
|
|
12
|
+
## Standard Workflow
|
|
13
|
+
|
|
14
|
+
1. Read [AGENTS.md](AGENTS.md), then the agent-specific guide for the task.
|
|
15
|
+
2. Check [TODO.md](TODO.md) and [ROADMAP.md](ROADMAP.md) for current scope.
|
|
16
|
+
3. Make the smallest repo-local change that satisfies the task.
|
|
17
|
+
4. Run `bundle exec rspec`, `bundle exec rubocop`, and
|
|
18
|
+
`gem build ruby_api_pack_core.gemspec` when validation is required or
|
|
19
|
+
practical.
|
|
20
|
+
5. Update docs and changelog history only when behavior, public contracts, or
|
|
21
|
+
release-relevant metadata changed.
|
|
22
|
+
|
|
23
|
+
## Documentation Map
|
|
24
|
+
|
|
25
|
+
| Guide | Path |
|
|
26
|
+
|-------|------|
|
|
27
|
+
| Agent rules | [AGENTS.md](AGENTS.md) |
|
|
28
|
+
| Claude Code | [CLAUDE.md](CLAUDE.md) |
|
|
29
|
+
| Codex | [CODEX.md](CODEX.md) |
|
|
30
|
+
| Copilot | [COPILOT.md](COPILOT.md) |
|
|
31
|
+
| Jules | [JULES.md](JULES.md) |
|
|
32
|
+
| Roadmap | [ROADMAP.md](ROADMAP.md) |
|
|
33
|
+
| Todo | [TODO.md](TODO.md) |
|
|
34
|
+
| Changelog | [CHANGELOG.md](CHANGELOG.md) |
|
|
35
|
+
| Security | [SECURITY.md](SECURITY.md) |
|
|
36
|
+
| Code of Conduct | [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) |
|
|
37
|
+
| Contributing | [CONTRIBUTING.md](CONTRIBUTING.md) |
|
|
38
|
+
|
|
39
|
+
[](https://rubygems.org/gems/ruby_api_pack_core)
|
|
40
|
+
[](https://github.com/phcdevworks/ruby_api_pack_core/actions/workflows/test.yml)
|
|
41
|
+
[](MIT-LICENSE)
|
|
42
|
+
|
|
43
|
+
`ruby_api_pack_core` is the shared HTTP client foundation consumed by every
|
|
44
|
+
`ruby_api_pack_*` gem (ActiveCampaign, Cloudways, WordPress, and any future
|
|
45
|
+
vendor pack). It owns the connection wrapper's HTTParty/Oj plumbing, a
|
|
46
|
+
generic response-shape validator, and the `configure`/`configuration`
|
|
47
|
+
singleton pattern, so each vendor-specific gem only has to implement its own
|
|
48
|
+
authentication headers, `Configuration` fields, and resource endpoint
|
|
49
|
+
classes — instead of every gem reinventing the same request/response
|
|
50
|
+
boilerplate.
|
|
51
|
+
|
|
52
|
+
[Contributing](CONTRIBUTING.md) | [Code of Conduct](CODE_OF_CONDUCT.md) |
|
|
53
|
+
[Changelog](CHANGELOG.md) | [Roadmap](ROADMAP.md) |
|
|
54
|
+
[Security Policy](SECURITY.md) | [AI Guide](AGENTS.md)
|
|
55
|
+
|
|
56
|
+
## Source of Truth
|
|
57
|
+
|
|
58
|
+
The gem's public behavior is defined by its connection base class, response
|
|
59
|
+
validator, configurable mixin, and specs. Keep those surfaces aligned — every
|
|
60
|
+
`ruby_api_pack_*` gem depends on this contract staying stable.
|
|
61
|
+
|
|
62
|
+
| Layer | Path | Rule |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| Gem entry point | `lib/ruby_api_pack_core.rb` | Loads the connection, validator, and configurable surfaces |
|
|
65
|
+
| Connection base | `lib/ruby_api_pack_core/connection/base.rb` | Template-method HTTParty/Oj connection wrapper; subclasses implement `#auth_headers` |
|
|
66
|
+
| Response validator | `lib/ruby_api_pack_core/handlers/response_validator.rb` | Generic `expected_type: :array`/`:hash` response-shape validation |
|
|
67
|
+
| Configurable | `lib/ruby_api_pack_core/configurable.rb` | Shared `configure`/`configuration` singleton mixin |
|
|
68
|
+
| Version | `lib/ruby_api_pack_core/version.rb` | Gem release version |
|
|
69
|
+
| Specs | `spec/` | Contract and regression coverage, exercised against dummy subclasses/modules |
|
|
70
|
+
|
|
71
|
+
After behavior changes, run:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bundle exec rspec
|
|
75
|
+
bundle exec rubocop
|
|
76
|
+
gem build ruby_api_pack_core.gemspec
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## What This Gem Owns
|
|
80
|
+
|
|
81
|
+
- A template-method `RubyApiPackCore::Connection::Base` class providing
|
|
82
|
+
`api_get`/`api_post`/`api_put`/`api_delete`, URL building, `200..299`
|
|
83
|
+
status handling, content-type-checked JSON parsing via `Oj`, and
|
|
84
|
+
descriptive error messages
|
|
85
|
+
- `RubyApiPackCore::Handlers::ResponseValidator`, a generic response-shape
|
|
86
|
+
validator (`expected_type: :array`/`:hash`) with Rails-aware error logging
|
|
87
|
+
- `RubyApiPackCore::Configurable`, a mixin providing the standard
|
|
88
|
+
`MyGem.configure { |c| ... }` / `MyGem.configuration` singleton pattern
|
|
89
|
+
- RSpec coverage for all of the above, exercised against synthetic dummy
|
|
90
|
+
subclasses and modules
|
|
91
|
+
|
|
92
|
+
## What This Gem Does Not Own
|
|
93
|
+
|
|
94
|
+
- Any vendor-specific API knowledge — API tokens, OAuth flows, Basic Auth,
|
|
95
|
+
endpoint paths, or resource classes belong in the consuming
|
|
96
|
+
`ruby_api_pack_*` gem
|
|
97
|
+
- Host application models, persistence, background jobs, or authorization
|
|
98
|
+
- User-facing Rails controllers or UI components
|
|
99
|
+
- Storage of production credentials or API tokens
|
|
100
|
+
|
|
101
|
+
## Installation
|
|
102
|
+
|
|
103
|
+
Add the gem to your application's Gemfile:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
gem "ruby_api_pack_core"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Install dependencies:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
bundle install
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Or install the gem directly:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
gem install ruby_api_pack_core
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
In practice, most consumers won't install this gem directly — it's a runtime
|
|
122
|
+
dependency declared by each `ruby_api_pack_*` gem's own gemspec.
|
|
123
|
+
|
|
124
|
+
## Building a New API Pack on `ruby_api_pack_core`
|
|
125
|
+
|
|
126
|
+
A new vendor API pack gem needs three pieces built on top of this
|
|
127
|
+
foundation:
|
|
128
|
+
|
|
129
|
+
### 1. Configuration
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
module RubyApiPackExample
|
|
133
|
+
class Configuration
|
|
134
|
+
attr_accessor :api_url, :api_token
|
|
135
|
+
|
|
136
|
+
def initialize
|
|
137
|
+
@api_url = 'https://api.example.com/v1'
|
|
138
|
+
@api_token = nil
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
module RubyApiPackExample
|
|
144
|
+
extend RubyApiPackCore::Configurable
|
|
145
|
+
|
|
146
|
+
def self.configuration_class
|
|
147
|
+
Configuration
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
This gives consumers the standard pattern:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
RubyApiPackExample.configure do |config|
|
|
156
|
+
config.api_url = ENV.fetch("EXAMPLE_API_URL")
|
|
157
|
+
config.api_token = ENV.fetch("EXAMPLE_API_TOKEN")
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 2. Connection wrapper
|
|
162
|
+
|
|
163
|
+
Subclass `RubyApiPackCore::Connection::Base` and implement only
|
|
164
|
+
`#auth_headers` — everything else (URL building, verb dispatch, status
|
|
165
|
+
handling, JSON parsing) is inherited:
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
module RubyApiPackExample
|
|
169
|
+
module Connection
|
|
170
|
+
class ExampleConnect < RubyApiPackCore::Connection::Base
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
def auth_headers
|
|
174
|
+
{ 'Api-Token' => RubyApiPackExample.configuration.api_token }
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 3. Resource classes
|
|
182
|
+
|
|
183
|
+
Extend `RubyApiPackCore::Handlers::ResponseValidator` and follow the
|
|
184
|
+
`ENDPOINT` constant + `class << self` + private `connection(id = nil)`
|
|
185
|
+
factory pattern used by every existing `ruby_api_pack_*` resource class:
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
module RubyApiPackExample
|
|
189
|
+
module Api
|
|
190
|
+
class Widgets
|
|
191
|
+
extend RubyApiPackCore::Handlers::ResponseValidator
|
|
192
|
+
|
|
193
|
+
ENDPOINT = '/widgets'
|
|
194
|
+
|
|
195
|
+
class << self
|
|
196
|
+
def widget_list
|
|
197
|
+
validate_response(connection.api_get, expected_type: :array)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def widget_by_id(id)
|
|
201
|
+
validate_response(connection(id).api_get, expected_type: :hash)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
private
|
|
205
|
+
|
|
206
|
+
def connection(id = nil)
|
|
207
|
+
path = id ? "#{ENDPOINT}/#{id}" : ENDPOINT
|
|
208
|
+
Connection::ExampleConnect.new(RubyApiPackExample.configuration.api_url, path)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
This is the same architecture used by `ruby_api_pack_active_campaign`,
|
|
217
|
+
`ruby_api_pack_cloudways`, and `ruby_api_pack_wordpress` — see those gems for
|
|
218
|
+
worked, real-world examples.
|
|
219
|
+
|
|
220
|
+
## Error Handling
|
|
221
|
+
|
|
222
|
+
`Connection::Base` raises a plain `RuntimeError` (via `raise "..."`) for
|
|
223
|
+
non-2xx responses, unparseable JSON, or an unexpected content type. This
|
|
224
|
+
matches the existing behavior of every `ruby_api_pack_*` gem. A typed error
|
|
225
|
+
class hierarchy is tracked in [ROADMAP.md](ROADMAP.md) as a future
|
|
226
|
+
enhancement, but is not yet implemented — do not assume specific error
|
|
227
|
+
classes are raised.
|
|
228
|
+
|
|
229
|
+
## Development
|
|
230
|
+
|
|
231
|
+
Install dependencies:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
bundle install
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Run the test suite:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
bundle exec rspec
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Run style checks:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
bundle exec rubocop
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Build the gem locally:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
gem build ruby_api_pack_core.gemspec
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Documentation and AI Guides
|
|
256
|
+
|
|
257
|
+
This repository follows the PHCDevworks documentation model used across the
|
|
258
|
+
Spectre and Rails/Ruby workspaces:
|
|
259
|
+
|
|
260
|
+
- `AGENTS.md` defines shared AI boundaries.
|
|
261
|
+
- `CLAUDE.md`, `CODEX.md`, `COPILOT.md`, and `JULES.md` define agent-specific
|
|
262
|
+
working rules.
|
|
263
|
+
- `.github/copilot-instructions.md` and `.github/codex-instructions.md` provide
|
|
264
|
+
GitHub-integrated assistant guidance.
|
|
265
|
+
- `CHANGELOG.md`, `ROADMAP.md`, and `TODO.md` keep release and planning context
|
|
266
|
+
visible.
|
|
267
|
+
|
|
268
|
+
## Contributing
|
|
269
|
+
|
|
270
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, coding standards, pull
|
|
271
|
+
request expectations, and release hygiene.
|
|
272
|
+
|
|
273
|
+
## Security
|
|
274
|
+
|
|
275
|
+
Please do not report vulnerabilities through public issues. Follow
|
|
276
|
+
[SECURITY.md](SECURITY.md) for responsible disclosure.
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
The gem is available as open source under the terms of the
|
|
281
|
+
[MIT License](MIT-LICENSE).
|
data/ROADMAP.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
This roadmap tracks the direction for `ruby_api_pack_core` as the shared HTTP
|
|
4
|
+
client foundation for every `ruby_api_pack_*` gem. It is planning context, not
|
|
5
|
+
a release promise.
|
|
6
|
+
|
|
7
|
+
## Current Focus
|
|
8
|
+
|
|
9
|
+
- Keep `Connection::Base`, `Handlers::ResponseValidator`, and `Configurable`
|
|
10
|
+
stable now that `ruby_api_pack_active_campaign`, `ruby_api_pack_cloudways`,
|
|
11
|
+
and `ruby_api_pack_wordpress` all depend on this gem.
|
|
12
|
+
- Keep this gem free of any vendor-specific knowledge.
|
|
13
|
+
|
|
14
|
+
## Near-Term
|
|
15
|
+
|
|
16
|
+
- Confirm all three consuming gems pass their own validation gates against
|
|
17
|
+
the published `ruby_api_pack_core` version.
|
|
18
|
+
- Expand README examples showing how a new `ruby_api_pack_*` gem should
|
|
19
|
+
subclass `Connection::Base` and extend `Configurable`.
|
|
20
|
+
- Consider a typed error class hierarchy (e.g.
|
|
21
|
+
`RubyApiPackCore::Errors::HttpError`,
|
|
22
|
+
`RubyApiPackCore::Errors::ParseError`) shared by all consuming gems, since
|
|
23
|
+
every gem currently raises plain `RuntimeError`s independently.
|
|
24
|
+
- Confirm supported Ruby versions across CI and gem metadata.
|
|
25
|
+
|
|
26
|
+
## Later
|
|
27
|
+
|
|
28
|
+
- Consider an optional retry/backoff hook on `Connection::Base` if more than
|
|
29
|
+
one consuming gem needs rate-limit handling (currently only
|
|
30
|
+
`ruby_api_pack_cloudways` retries on rate limits, implemented locally).
|
|
31
|
+
- Add a generator or documented scaffold for creating a new `ruby_api_pack_*`
|
|
32
|
+
gem from this foundation.
|
|
33
|
+
- Evaluate whether `Handlers::ResponseValidator`'s `expected_key` unwrapping
|
|
34
|
+
style (used historically by `ruby_api_pack_cloudways`) should be offered as
|
|
35
|
+
an alternative mode alongside `expected_type`.
|
|
36
|
+
|
|
37
|
+
## Out of Scope
|
|
38
|
+
|
|
39
|
+
- Any vendor-specific API knowledge (tokens, endpoint paths, resource classes)
|
|
40
|
+
- Host application data models or persistence
|
|
41
|
+
- User-facing Rails controllers or views
|
|
42
|
+
- Storage or management of production secrets
|
data/Rakefile
ADDED
data/SECURITY.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
PHCDevworks applies security fixes to the current release line of this gem.
|
|
6
|
+
Please use the latest published version of `ruby_api_pack_core` whenever
|
|
7
|
+
possible.
|
|
8
|
+
|
|
9
|
+
## Reporting a Vulnerability
|
|
10
|
+
|
|
11
|
+
Do not open a public issue for security problems.
|
|
12
|
+
|
|
13
|
+
Use GitHub Security Advisories for this repository whenever possible. If that
|
|
14
|
+
is not available, contact the maintainers through GitHub.
|
|
15
|
+
|
|
16
|
+
Include:
|
|
17
|
+
|
|
18
|
+
1. A clear description of the issue and its impact
|
|
19
|
+
2. Steps to reproduce or a proof of concept
|
|
20
|
+
3. Affected versions, if known
|
|
21
|
+
4. Any suggested mitigation
|
|
22
|
+
|
|
23
|
+
## Response Expectations
|
|
24
|
+
|
|
25
|
+
1. We aim to acknowledge reports within 48 hours.
|
|
26
|
+
2. We aim to provide an initial assessment within 5 business days.
|
|
27
|
+
3. We will coordinate disclosure timing with the reporter when possible.
|
|
28
|
+
|
|
29
|
+
## Security Guidance
|
|
30
|
+
|
|
31
|
+
- Keep Ruby, Bundler, HTTParty, and development dependencies up to date.
|
|
32
|
+
- This gem carries no vendor credentials of its own — API tokens, OAuth
|
|
33
|
+
secrets, and Basic Auth credentials live in the consuming
|
|
34
|
+
`ruby_api_pack_*` gems' configuration objects, not here.
|
|
35
|
+
- Because every `ruby_api_pack_*` gem depends on this one, a vulnerability
|
|
36
|
+
here has broader blast radius than a vulnerability in a single vendor pack
|
|
37
|
+
— treat reports against `Connection::Base` and
|
|
38
|
+
`Handlers::ResponseValidator` with correspondingly higher urgency.
|
|
39
|
+
- Review automated dependency updates and advisories before release.
|
|
40
|
+
- Specs use synthetic fixtures and mocked HTTParty responses only — never
|
|
41
|
+
record real vendor API traffic against this repository.
|
|
42
|
+
|
|
43
|
+
## Contact
|
|
44
|
+
|
|
45
|
+
For non-sensitive security questions, open an issue or discussion in this
|
|
46
|
+
repository.
|
data/TODO.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# TODO
|
|
2
|
+
|
|
3
|
+
## Documentation
|
|
4
|
+
|
|
5
|
+
- [x] Standardize README structure.
|
|
6
|
+
- [x] Add AI operating guides.
|
|
7
|
+
- [x] Standardize community health docs.
|
|
8
|
+
- [x] Add changelog and roadmap.
|
|
9
|
+
- [ ] Add a worked example of scaffolding a new `ruby_api_pack_*` gem from
|
|
10
|
+
this foundation.
|
|
11
|
+
|
|
12
|
+
## Ruby Gem
|
|
13
|
+
|
|
14
|
+
- [x] Extract shared connection wrapper, response validator, and
|
|
15
|
+
configurable pattern from `ruby_api_pack_active_campaign`,
|
|
16
|
+
`ruby_api_pack_cloudways`, and `ruby_api_pack_wordpress`.
|
|
17
|
+
- [ ] Consider a custom error class hierarchy for HTTP and parse failures,
|
|
18
|
+
shared across all consuming gems.
|
|
19
|
+
- [ ] Consider timeout configuration for HTTParty requests.
|
|
20
|
+
- [ ] Evaluate whether `Handlers::ResponseValidator` should support
|
|
21
|
+
Cloudways's historical `expected_key` unwrapping mode alongside
|
|
22
|
+
`expected_type`.
|
|
23
|
+
|
|
24
|
+
## Requested by Downstream
|
|
25
|
+
|
|
26
|
+
(none yet)
|
|
27
|
+
|
|
28
|
+
## Release
|
|
29
|
+
|
|
30
|
+
- [x] Confirm gemspec metadata links point to the repository and changelog.
|
|
31
|
+
- [x] Document RubyGems release steps.
|
|
32
|
+
- [x] Confirm supported Ruby versions in CI match the gemspec.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyApiPackCore
|
|
4
|
+
# Extend a top-level gem module with this to get the standard
|
|
5
|
+
# `MyGem.configure { |c| ... }` / `MyGem.configuration` singleton pattern.
|
|
6
|
+
#
|
|
7
|
+
# The extending module must implement `.configuration_class`, returning the
|
|
8
|
+
# Configuration class to instantiate, e.g.:
|
|
9
|
+
#
|
|
10
|
+
# module MyGem
|
|
11
|
+
# extend RubyApiPackCore::Configurable
|
|
12
|
+
#
|
|
13
|
+
# def self.configuration_class
|
|
14
|
+
# Configuration
|
|
15
|
+
# end
|
|
16
|
+
# end
|
|
17
|
+
module Configurable
|
|
18
|
+
attr_accessor :configuration
|
|
19
|
+
|
|
20
|
+
def configure
|
|
21
|
+
self.configuration ||= configuration_class.new
|
|
22
|
+
yield(configuration)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'httparty'
|
|
4
|
+
require 'oj'
|
|
5
|
+
|
|
6
|
+
module RubyApiPackCore
|
|
7
|
+
module Connection
|
|
8
|
+
# Shared HTTParty + Oj request/response plumbing for API pack connection
|
|
9
|
+
# wrappers. Subclasses provide the vendor-specific piece by implementing
|
|
10
|
+
# `#auth_headers` (a Hash merged into every request's headers). Everything
|
|
11
|
+
# else -- URL building, verb dispatch, status handling, JSON parsing, and
|
|
12
|
+
# error messages -- is identical across packs.
|
|
13
|
+
class Base
|
|
14
|
+
attr_reader :api_url_base, :api_path
|
|
15
|
+
|
|
16
|
+
def initialize(api_url_base, api_path)
|
|
17
|
+
@api_url_base = api_url_base
|
|
18
|
+
@api_path = api_path
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def api_get(params = {})
|
|
22
|
+
response = HTTParty.get(
|
|
23
|
+
full_url,
|
|
24
|
+
headers: auth_headers,
|
|
25
|
+
query: params,
|
|
26
|
+
ssl_version: :TLSv1_2
|
|
27
|
+
)
|
|
28
|
+
handle_response(response)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def api_post(params = {})
|
|
32
|
+
response = HTTParty.post(
|
|
33
|
+
full_url,
|
|
34
|
+
headers: auth_headers.merge('Content-Type' => 'application/json'),
|
|
35
|
+
body: params.to_json,
|
|
36
|
+
ssl_version: :TLSv1_2
|
|
37
|
+
)
|
|
38
|
+
handle_response(response)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def api_put(params = {})
|
|
42
|
+
response = HTTParty.put(
|
|
43
|
+
full_url,
|
|
44
|
+
headers: auth_headers.merge('Content-Type' => 'application/json'),
|
|
45
|
+
body: params.to_json,
|
|
46
|
+
ssl_version: :TLSv1_2
|
|
47
|
+
)
|
|
48
|
+
handle_response(response)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def api_delete(params = {})
|
|
52
|
+
response = HTTParty.delete(
|
|
53
|
+
full_url,
|
|
54
|
+
headers: auth_headers,
|
|
55
|
+
query: params,
|
|
56
|
+
ssl_version: :TLSv1_2
|
|
57
|
+
)
|
|
58
|
+
handle_response(response)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# Subclasses must implement this, returning a Hash of auth headers
|
|
64
|
+
# (e.g. an API-token header, an OAuth bearer token, or Basic auth).
|
|
65
|
+
def auth_headers
|
|
66
|
+
raise NotImplementedError, "#{self.class} must implement #auth_headers"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def full_url
|
|
70
|
+
"#{@api_url_base}#{@api_path}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def handle_response(response)
|
|
74
|
+
case response.code
|
|
75
|
+
when 200..299
|
|
76
|
+
parse_response(response)
|
|
77
|
+
else
|
|
78
|
+
raise "Error: Received status #{response.code} - #{response.body}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def parse_response(response)
|
|
83
|
+
return {} if response.body.nil? || response.body.empty?
|
|
84
|
+
|
|
85
|
+
content_type = response.headers&.fetch('content-type', nil)
|
|
86
|
+
raise "Unexpected response: #{response.body}" unless content_type&.include?('application/json')
|
|
87
|
+
|
|
88
|
+
Oj.load(response.body, mode: :strict)
|
|
89
|
+
rescue Oj::ParseError => e
|
|
90
|
+
raise "Error parsing response: #{e.message}. Raw body: #{response.body}"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyApiPackCore
|
|
4
|
+
module Handlers
|
|
5
|
+
module ResponseValidator
|
|
6
|
+
def validate_response(response, expected_type: :any)
|
|
7
|
+
case expected_type
|
|
8
|
+
when :array
|
|
9
|
+
raise "Expected an Array, got #{response.class}: #{response.inspect}" unless response.is_a?(Array)
|
|
10
|
+
when :hash
|
|
11
|
+
raise "Expected a Hash, got #{response.class}: #{response.inspect}" unless response.is_a?(Hash)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
response
|
|
15
|
+
rescue StandardError => e
|
|
16
|
+
log_error("Error validating response: #{e.message}")
|
|
17
|
+
raise "An error occurred while processing the response: #{e.message}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def log_error(message)
|
|
23
|
+
if defined?(Rails)
|
|
24
|
+
Rails.logger.error(message)
|
|
25
|
+
else
|
|
26
|
+
puts "[ERROR] #{message}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'httparty'
|
|
4
|
+
require 'oj'
|
|
5
|
+
|
|
6
|
+
require_relative 'ruby_api_pack_core/version'
|
|
7
|
+
require_relative 'ruby_api_pack_core/configurable'
|
|
8
|
+
require_relative 'ruby_api_pack_core/handlers/response_validator'
|
|
9
|
+
require_relative 'ruby_api_pack_core/connection/base'
|
|
10
|
+
|
|
11
|
+
module RubyApiPackCore
|
|
12
|
+
end
|