client-api-builder 0.5.5 → 0.6.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/.cursor.json +29 -0
- data/.github/workflows/ci.yml +53 -0
- data/.rubocop.yml +79 -0
- data/.ruby-version +1 -1
- data/ARCHITECTURE.md +223 -0
- data/CLAUDE.md +92 -0
- data/Gemfile +3 -5
- data/Gemfile.lock +79 -46
- data/README.md +527 -17
- data/client-api-builder.gemspec +20 -4
- data/examples/basic_auth_example_client.rb +6 -5
- data/examples/imdb_datasets_client.rb +2 -0
- data/examples/lorem_ipsum_client.rb +3 -1
- data/lib/client-api-builder.rb +1 -0
- data/lib/client_api_builder/active_support_log_subscriber.rb +1 -0
- data/lib/client_api_builder/active_support_notifications.rb +8 -6
- data/lib/client_api_builder/nested_router.rb +3 -3
- data/lib/client_api_builder/net_http_request.rb +37 -5
- data/lib/client_api_builder/query_params.rb +9 -3
- data/lib/client_api_builder/router.rb +212 -125
- data/lib/client_api_builder/section.rb +11 -11
- data/script/console +1 -1
- metadata +22 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae35befd024cba590ae2f644877243c536612ef9ce0e66c6c7c69f2481a7fc20
|
|
4
|
+
data.tar.gz: cd843e5f96ed9a63f9fb4012ab94b7ebe7b8bd443fa2cec35fe04e1bf50e0806
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a97833f34c6dde60bc8edbe9d38180e6f2904884ceb5b85a38e43358856e6e9c95f57e06f4356c0a10fe50d33801513e89132b452482bd689abf610a9ac53894
|
|
7
|
+
data.tar.gz: 9686a7f14ed8144f3e310cddf9abdf0681bfd237149397adba9d1376ca5d47a106530e099e165d22264bd7178b663e026a9e0658a2e95ef6a9b1fb1c3beb874e
|
data/.cursor.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rules": [
|
|
3
|
+
{
|
|
4
|
+
"name": "Ruby spec file",
|
|
5
|
+
"pattern": "^lib/(.+)\\.rb$",
|
|
6
|
+
"target": "spec/${1}_spec.rb"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "Ruby implementation file",
|
|
10
|
+
"pattern": "^spec/(.+)_spec\\.rb$",
|
|
11
|
+
"target": "lib/${1}.rb"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Related client_api_builder files",
|
|
15
|
+
"pattern": "^(?:lib|spec)/client_api_builder/(.+)\\.rb$",
|
|
16
|
+
"related": [
|
|
17
|
+
"lib/client_api_builder/${1}.rb",
|
|
18
|
+
"spec/client_api_builder/${1}_spec.rb"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "Main library file",
|
|
23
|
+
"pattern": "^(?:lib|spec)/client_api_builder/.+\\.rb$",
|
|
24
|
+
"related": [
|
|
25
|
+
"lib/client-api-builder.rb"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
name: RuboCop
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Set up Ruby
|
|
17
|
+
uses: ruby/setup-ruby@v1
|
|
18
|
+
with:
|
|
19
|
+
ruby-version: '3.4'
|
|
20
|
+
bundler-cache: true
|
|
21
|
+
|
|
22
|
+
- name: Run RuboCop
|
|
23
|
+
run: bundle exec rubocop --format github
|
|
24
|
+
|
|
25
|
+
test:
|
|
26
|
+
name: Tests (Ruby ${{ matrix.ruby }})
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
strategy:
|
|
29
|
+
fail-fast: false
|
|
30
|
+
matrix:
|
|
31
|
+
ruby: ['3.2', '3.3', '3.4']
|
|
32
|
+
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- name: Set up Ruby ${{ matrix.ruby }}
|
|
37
|
+
uses: ruby/setup-ruby@v1
|
|
38
|
+
with:
|
|
39
|
+
ruby-version: ${{ matrix.ruby }}
|
|
40
|
+
bundler-cache: true
|
|
41
|
+
|
|
42
|
+
- name: Run tests
|
|
43
|
+
run: bundle exec rspec
|
|
44
|
+
|
|
45
|
+
- name: Upload coverage to Codecov
|
|
46
|
+
if: matrix.ruby == '3.4'
|
|
47
|
+
uses: codecov/codecov-action@v4
|
|
48
|
+
with:
|
|
49
|
+
files: coverage/coverage.xml
|
|
50
|
+
fail_ci_if_error: false
|
|
51
|
+
verbose: true
|
|
52
|
+
env:
|
|
53
|
+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.2
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
Exclude:
|
|
6
|
+
- 'bin/**/*'
|
|
7
|
+
- 'vendor/**/*'
|
|
8
|
+
- 'coverage/**/*'
|
|
9
|
+
|
|
10
|
+
# Relaxed metrics for existing codebase
|
|
11
|
+
Metrics/MethodLength:
|
|
12
|
+
Max: 100
|
|
13
|
+
|
|
14
|
+
Metrics/AbcSize:
|
|
15
|
+
Max: 80
|
|
16
|
+
|
|
17
|
+
Metrics/ClassLength:
|
|
18
|
+
Max: 200
|
|
19
|
+
|
|
20
|
+
Metrics/ModuleLength:
|
|
21
|
+
Max: 320
|
|
22
|
+
|
|
23
|
+
Metrics/CyclomaticComplexity:
|
|
24
|
+
Max: 30
|
|
25
|
+
|
|
26
|
+
Metrics/PerceivedComplexity:
|
|
27
|
+
Max: 30
|
|
28
|
+
|
|
29
|
+
Metrics/BlockLength:
|
|
30
|
+
Exclude:
|
|
31
|
+
- 'spec/**/*'
|
|
32
|
+
- '*.gemspec'
|
|
33
|
+
|
|
34
|
+
Metrics/ParameterLists:
|
|
35
|
+
Max: 7
|
|
36
|
+
|
|
37
|
+
# Style preferences
|
|
38
|
+
Style/Documentation:
|
|
39
|
+
Enabled: false
|
|
40
|
+
|
|
41
|
+
Style/FrozenStringLiteralComment:
|
|
42
|
+
EnforcedStyle: always
|
|
43
|
+
|
|
44
|
+
Layout/LineLength:
|
|
45
|
+
Max: 170
|
|
46
|
+
Exclude:
|
|
47
|
+
- 'spec/**/*'
|
|
48
|
+
|
|
49
|
+
# File naming - allow hyphenated gem name
|
|
50
|
+
Naming/FileName:
|
|
51
|
+
Exclude:
|
|
52
|
+
- 'lib/client-api-builder.rb'
|
|
53
|
+
|
|
54
|
+
# Allow short parameter names for unused exception variables
|
|
55
|
+
Naming/MethodParameterName:
|
|
56
|
+
AllowedNames:
|
|
57
|
+
- e
|
|
58
|
+
- _e
|
|
59
|
+
- io
|
|
60
|
+
|
|
61
|
+
# Gemspec settings
|
|
62
|
+
Gemspec/RequiredRubyVersion:
|
|
63
|
+
Enabled: false
|
|
64
|
+
|
|
65
|
+
# Style relaxations for existing code patterns
|
|
66
|
+
Style/OptionalBooleanParameter:
|
|
67
|
+
Enabled: false
|
|
68
|
+
|
|
69
|
+
Style/StringConcatenation:
|
|
70
|
+
Enabled: false
|
|
71
|
+
|
|
72
|
+
Style/FormatStringToken:
|
|
73
|
+
Enabled: false
|
|
74
|
+
|
|
75
|
+
# Note: Previously had exclusions for Style/ClassVars, Style/PerlBackrefs, and
|
|
76
|
+
# Lint/RescueException in router.rb - these have been fixed:
|
|
77
|
+
# - ClassVars: Changed to thread-local storage
|
|
78
|
+
# - PerlBackrefs: Changed to Regexp.last_match
|
|
79
|
+
# - RescueException: Changed to StandardError
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.4.2
|
data/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Client API Builder Architecture
|
|
2
|
+
|
|
3
|
+
This document describes the internal architecture and design of the Client API Builder gem.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Client API Builder is a Ruby gem that provides a declarative way to create API clients. It uses a modular architecture with several key components working together to provide a flexible and extensible API client framework.
|
|
8
|
+
|
|
9
|
+
## File Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
lib/
|
|
13
|
+
├── client-api-builder.rb # Main entry point, autoloads, error classes
|
|
14
|
+
└── client_api_builder/
|
|
15
|
+
├── router.rb # Core Router module with route DSL
|
|
16
|
+
├── nested_router.rb # NestedRouter class for hierarchical APIs
|
|
17
|
+
├── section.rb # Section module for creating nested routers
|
|
18
|
+
├── net_http_request.rb # Net::HTTP request execution and streaming
|
|
19
|
+
├── query_params.rb # Custom query parameter builder
|
|
20
|
+
├── active_support_notifications.rb # ActiveSupport instrumentation
|
|
21
|
+
└── active_support_log_subscriber.rb # ActiveSupport logging
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Core Components
|
|
25
|
+
|
|
26
|
+
### 1. Router Module (`ClientApiBuilder::Router`)
|
|
27
|
+
|
|
28
|
+
The `Router` module is the core component that provides the main functionality for defining and executing API requests.
|
|
29
|
+
|
|
30
|
+
**Class Methods** (defined in `ClassMethods`):
|
|
31
|
+
- `base_url`: Sets the base URL for all requests
|
|
32
|
+
- `header`: Adds headers to requests (supports values, symbols, or procs)
|
|
33
|
+
- `route`: Defines API endpoints with dynamic method generation
|
|
34
|
+
- `body_builder`: Configures request body formatting (`:to_json`, `:to_query`, `:query_params`, or custom)
|
|
35
|
+
- `query_builder`: Configures query parameter formatting
|
|
36
|
+
- `query_param`: Adds query parameters to all requests
|
|
37
|
+
- `connection_option`: Sets Net::HTTP connection options
|
|
38
|
+
- `configure_retries`: Sets retry behavior (max_retries, sleep time)
|
|
39
|
+
- `namespace`: Groups routes under a common path prefix
|
|
40
|
+
|
|
41
|
+
**Instance Methods**:
|
|
42
|
+
- `build_headers`: Constructs request headers, evaluating procs/symbols
|
|
43
|
+
- `build_connection_options`: Merges default and request-specific options
|
|
44
|
+
- `build_query`: Formats query parameters using configured builder
|
|
45
|
+
- `build_body`: Formats request body using configured builder
|
|
46
|
+
- `build_uri`: Constructs full URI with base_url, path, and query
|
|
47
|
+
- `handle_response`: Processes API responses, parses JSON by default
|
|
48
|
+
- `request_wrapper`: Manages request execution with retry and instrumentation
|
|
49
|
+
- `root_router`: Returns self (overridden in NestedRouter)
|
|
50
|
+
|
|
51
|
+
**Instance Attributes** (via `attr_reader`):
|
|
52
|
+
- `response`: The last Net::HTTP response object
|
|
53
|
+
- `request_options`: Hash of method, uri, body, headers, connection_options
|
|
54
|
+
- `total_request_time`: Duration of last request in seconds
|
|
55
|
+
- `request_attempts`: Number of attempts for last request
|
|
56
|
+
|
|
57
|
+
### 2. Route Code Generation
|
|
58
|
+
|
|
59
|
+
The `route` class method dynamically generates two methods per endpoint using `generate_route_code`:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
route :get_user, '/users/:id', expected_response_code: 200
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Generates:
|
|
66
|
+
- `get_user_raw_response(id:, **options, &block)` - Makes HTTP request, sets `@response` and `@request_options`
|
|
67
|
+
- `get_user(id:, **options, &block)` - Wraps raw_response with retry logic, response code validation, and response handling
|
|
68
|
+
|
|
69
|
+
**Path Parameters**: Extracted from `:param` or `{param}` syntax in path
|
|
70
|
+
**Body/Query Parameters**: Extracted from `body:` and `query:` options using symbol values
|
|
71
|
+
|
|
72
|
+
### 3. HTTP Method Auto-Detection
|
|
73
|
+
|
|
74
|
+
When `method:` is not specified in route options, `auto_detect_http_method` infers it from the method name:
|
|
75
|
+
|
|
76
|
+
| Prefix Pattern | HTTP Method |
|
|
77
|
+
|---------------|-------------|
|
|
78
|
+
| `post`, `create`, `add`, `insert` | POST |
|
|
79
|
+
| `put`, `update`, `modify`, `change` | PUT |
|
|
80
|
+
| `patch` | PATCH |
|
|
81
|
+
| `delete`, `remove` | DELETE |
|
|
82
|
+
| (default) | GET |
|
|
83
|
+
|
|
84
|
+
### 4. Nested Router (`ClientApiBuilder::NestedRouter`)
|
|
85
|
+
|
|
86
|
+
Enables hierarchical API client organization:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
section :users do
|
|
90
|
+
route :list, '/'
|
|
91
|
+
route :get, '/:id'
|
|
92
|
+
end
|
|
93
|
+
# Usage: client.users.get(id: 123)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Key behaviors:
|
|
97
|
+
- Includes `ClientApiBuilder::Router` module
|
|
98
|
+
- Stores `root_router` reference to access shared state
|
|
99
|
+
- Stores `nested_router_options` passed from section definition
|
|
100
|
+
- Overrides `base_url` to fall back to root_router's base_url
|
|
101
|
+
- Delegates `handle_response` to root_router
|
|
102
|
+
- Overrides `get_instance_method` to access root_router's instance variables in paths
|
|
103
|
+
|
|
104
|
+
### 5. Section Module (`ClientApiBuilder::Section`)
|
|
105
|
+
|
|
106
|
+
Creates nested routers dynamically using `InheritanceHelper::ClassBuilder::Utils.create_class`:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
def section(name, nested_router_options={}, &block)
|
|
110
|
+
# Creates: MyClient::UsersNestedRouter < ClientApiBuilder::NestedRouter
|
|
111
|
+
# Defines: MyClient.users_router (class method)
|
|
112
|
+
# Defines: MyClient#users (instance method, memoized)
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 6. NetHTTP::Request Module
|
|
117
|
+
|
|
118
|
+
Provides HTTP request execution using Net::HTTP:
|
|
119
|
+
|
|
120
|
+
**Methods**:
|
|
121
|
+
- `request(method:, uri:, body:, headers:, connection_options:)` - Standard request with optional block
|
|
122
|
+
- `stream(...)` - Streams response body in chunks via `read_body`
|
|
123
|
+
- `stream_to_io(..., io:)` - Writes streamed chunks to an IO object
|
|
124
|
+
- `stream_to_file(..., file:)` - Opens file and streams to it
|
|
125
|
+
|
|
126
|
+
**Supported HTTP Methods** (via `METHOD_TO_NET_HTTP_CLASS`):
|
|
127
|
+
`copy`, `delete`, `get`, `head`, `lock`, `mkcol`, `move`, `options`, `patch`, `post`, `propfind`, `proppatch`, `put`, `trace`, `unlock`
|
|
128
|
+
|
|
129
|
+
### 7. QueryParams Class
|
|
130
|
+
|
|
131
|
+
Standalone query parameter builder (used when ActiveSupport unavailable):
|
|
132
|
+
|
|
133
|
+
- Handles nested hashes with bracket notation: `user[name]=John`
|
|
134
|
+
- Handles arrays: `ids[]=1&ids[]=2`
|
|
135
|
+
- Configurable separators: `name_value_separator` (default `=`), `param_separator` (default `&`)
|
|
136
|
+
- Supports custom escape proc
|
|
137
|
+
|
|
138
|
+
### 8. ActiveSupport Integration
|
|
139
|
+
|
|
140
|
+
**ActiveSupportNotifications** (conditionally included when ActiveSupport defined):
|
|
141
|
+
- Overrides `instrument_request` to use `ActiveSupport::Notifications.instrument`
|
|
142
|
+
- Event name: `client_api_builder.request`
|
|
143
|
+
- Payload includes `client: self`
|
|
144
|
+
|
|
145
|
+
**ActiveSupportLogSubscriber**:
|
|
146
|
+
- Subscribes to `client_api_builder.request` events for logging
|
|
147
|
+
|
|
148
|
+
## Design Patterns
|
|
149
|
+
|
|
150
|
+
### Module Inclusion Pattern
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
module ClientApiBuilder
|
|
154
|
+
module Router
|
|
155
|
+
def self.included(base)
|
|
156
|
+
base.extend InheritanceHelper::Methods
|
|
157
|
+
base.extend ClassMethods
|
|
158
|
+
base.include ::ClientApiBuilder::Section
|
|
159
|
+
base.include ::ClientApiBuilder::NetHTTP::Request
|
|
160
|
+
base.include(::ClientApiBuilder::ActiveSupportNotifications) if defined?(ActiveSupport)
|
|
161
|
+
base.send(:attr_reader, :response, :request_options, :total_request_time, :request_attempts)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Builder Pattern
|
|
168
|
+
|
|
169
|
+
Request components built separately then combined:
|
|
170
|
+
```ruby
|
|
171
|
+
__uri__ = build_uri(__path__, __query__, __options__)
|
|
172
|
+
__body__ = build_body(__body__, __options__)
|
|
173
|
+
__headers__ = build_headers(__options__)
|
|
174
|
+
__connection_options__ = build_connection_options(__options__)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Configuration Inheritance
|
|
178
|
+
|
|
179
|
+
Uses `inheritance-helper` gem's `add_value_to_class_method` for configuration that properly inherits to subclasses:
|
|
180
|
+
```ruby
|
|
181
|
+
def base_url(url = nil)
|
|
182
|
+
return default_options[:base_url] unless url
|
|
183
|
+
add_value_to_class_method(:default_options, base_url: url)
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Configuration Hierarchy
|
|
188
|
+
|
|
189
|
+
1. **Default Options**: `Router.default_options` returns frozen hash with defaults
|
|
190
|
+
2. **Class-level Configuration**: Set through DSL methods, stored via `add_value_to_class_method`
|
|
191
|
+
3. **Instance-level**: Access class config, can override in method calls
|
|
192
|
+
4. **Request-level**: `**__options__` parameter on generated methods
|
|
193
|
+
|
|
194
|
+
## Error Handling
|
|
195
|
+
|
|
196
|
+
- `ClientApiBuilder::Error`: Base error class
|
|
197
|
+
- `ClientApiBuilder::UnexpectedResponse`: Raised when response code doesn't match expected codes
|
|
198
|
+
- Stores `response` for inspection
|
|
199
|
+
- Response procs: Per-route custom response handling stored in `default_options[:response_procs]`
|
|
200
|
+
- Retry on exception: `retry_request?` method (always returns true by default, override to customize)
|
|
201
|
+
|
|
202
|
+
## Streaming Support
|
|
203
|
+
|
|
204
|
+
Routes can specify streaming behavior:
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
route :download, '/file', stream: :file # stream_to_file, requires file: argument
|
|
208
|
+
route :stream, '/events', stream: :io # stream_to_io, requires io: argument
|
|
209
|
+
route :process, '/data', stream: :block # stream with block for each chunk
|
|
210
|
+
route :download, '/file', stream: true # alias for :file
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Dependencies
|
|
214
|
+
|
|
215
|
+
- `inheritance-helper`: Class inheritance and configuration management
|
|
216
|
+
- `json`: JSON parsing and serialization (stdlib)
|
|
217
|
+
- `net/http`: HTTP request handling (stdlib)
|
|
218
|
+
- `cgi`: URL encoding in QueryParams (stdlib)
|
|
219
|
+
- `active_support` (optional): Enhanced query building and instrumentation
|
|
220
|
+
|
|
221
|
+
## Thread Safety
|
|
222
|
+
|
|
223
|
+
The library is not thread-safe. Each client instance maintains state (`@response`, `@request_options`, etc.) that would cause race conditions if shared across threads. Create separate client instances per thread.
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Client API Builder is a Ruby gem for creating API clients through declarative configuration. It uses Ruby's module inclusion pattern with `ClientApiBuilder::Router` as the core component.
|
|
8
|
+
|
|
9
|
+
## Common Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install dependencies
|
|
13
|
+
bundle install
|
|
14
|
+
|
|
15
|
+
# Run all tests
|
|
16
|
+
bundle exec rspec
|
|
17
|
+
|
|
18
|
+
# Run a single test file
|
|
19
|
+
bundle exec rspec spec/client_api_builder/router_spec.rb
|
|
20
|
+
|
|
21
|
+
# Run a specific test by line number
|
|
22
|
+
bundle exec rspec spec/client_api_builder/router_spec.rb:42
|
|
23
|
+
|
|
24
|
+
# Run linter
|
|
25
|
+
bundle exec rubocop
|
|
26
|
+
|
|
27
|
+
# Build the gem
|
|
28
|
+
gem build client-api-builder.gemspec
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Architecture
|
|
32
|
+
|
|
33
|
+
### Core Components
|
|
34
|
+
|
|
35
|
+
- **Router** (`lib/client_api_builder/router.rb`): Main module providing `route`, `base_url`, `header`, `body_builder`, `query_builder`, and `configure_retries` class methods. Uses `InheritanceHelper::Methods` for configuration inheritance.
|
|
36
|
+
|
|
37
|
+
- **NestedRouter** (`lib/client_api_builder/nested_router.rb`): Enables hierarchical API organization. Maintains reference to `root_router` and shares configuration with parent.
|
|
38
|
+
|
|
39
|
+
- **Section** (`lib/client_api_builder/section.rb`): Provides `section` class method for creating nested route groups via dynamically generated classes.
|
|
40
|
+
|
|
41
|
+
- **NetHTTP::Request** (`lib/client_api_builder/net_http_request.rb`): HTTP request execution using `Net::HTTP`. Handles standard requests and streaming (`:file`, `:io`, `:block` modes).
|
|
42
|
+
|
|
43
|
+
- **QueryParams** (`lib/client_api_builder/query_params.rb`): Custom query parameter builder used when ActiveSupport's `to_query` is unavailable.
|
|
44
|
+
|
|
45
|
+
- **ActiveSupportNotifications/LogSubscriber**: Optional integration for logging and instrumentation when ActiveSupport is present.
|
|
46
|
+
|
|
47
|
+
### Route Code Generation
|
|
48
|
+
|
|
49
|
+
The `route` class method in Router uses `generate_route_code` to dynamically create two methods per route:
|
|
50
|
+
1. `method_name_raw_response` - Makes the HTTP request
|
|
51
|
+
2. `method_name` - Wraps the request with retry logic and response handling
|
|
52
|
+
|
|
53
|
+
### HTTP Method Auto-Detection
|
|
54
|
+
|
|
55
|
+
Methods are auto-detected from route names: `post/create/add/insert` → POST, `put/update/modify/change` → PUT, `patch` → PATCH, `delete/remove` → DELETE, others → GET.
|
|
56
|
+
|
|
57
|
+
### Configuration Hierarchy
|
|
58
|
+
|
|
59
|
+
1. `default_options` class method (base defaults)
|
|
60
|
+
2. Class-level configuration via DSL methods
|
|
61
|
+
3. Instance-level overrides
|
|
62
|
+
4. Request-level options (`**__options__`)
|
|
63
|
+
|
|
64
|
+
## Key Patterns
|
|
65
|
+
|
|
66
|
+
- Module inclusion with `self.included(base)` extending ClassMethods and including InstanceMethods
|
|
67
|
+
- `add_value_to_class_method` from `inheritance-helper` for configuration inheritance
|
|
68
|
+
- Response procs stored per method name for custom response handling
|
|
69
|
+
- `root_router` method for accessing the top-level router from nested routers
|
|
70
|
+
|
|
71
|
+
## Dependencies
|
|
72
|
+
|
|
73
|
+
- `inheritance-helper` (runtime): Class inheritance and method management
|
|
74
|
+
- `webmock` (test): HTTP request stubbing
|
|
75
|
+
- `activesupport` (optional): Enhanced query param building and instrumentation
|
|
76
|
+
|
|
77
|
+
## Code Commits
|
|
78
|
+
|
|
79
|
+
Format using angular formatting:
|
|
80
|
+
```
|
|
81
|
+
<type>(<scope>): <short summary>
|
|
82
|
+
```
|
|
83
|
+
- **type**: build|ci|docs|feat|fix|perf|refactor|test
|
|
84
|
+
- **scope**: The feature or component of the service we're working on
|
|
85
|
+
- **summary**: Summary in present tense. Not capitalized. No period at the end.
|
|
86
|
+
|
|
87
|
+
## Documentation Maintenance
|
|
88
|
+
|
|
89
|
+
When modifying the codebase, keep documentation in sync:
|
|
90
|
+
- **ARCHITECTURE.md** - Update when adding/removing classes, changing component relationships, or altering data flow patterns
|
|
91
|
+
- **README.md** - Update when adding new features, changing public APIs, or modifying usage examples
|
|
92
|
+
- **Code comments** - Update inline documentation when changing method signatures or behavior
|
data/Gemfile
CHANGED
|
@@ -5,13 +5,11 @@ source 'http://rubygems.org'
|
|
|
5
5
|
gem 'inheritance-helper'
|
|
6
6
|
|
|
7
7
|
group :development do
|
|
8
|
-
gem 'rake'
|
|
9
|
-
gem 'rubocop'
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
group :spec do
|
|
13
8
|
gem 'activesupport'
|
|
9
|
+
gem 'rake'
|
|
14
10
|
gem 'rspec'
|
|
11
|
+
gem 'rubocop'
|
|
15
12
|
gem 'simplecov'
|
|
13
|
+
gem 'simplecov-cobertura'
|
|
16
14
|
gem 'webmock'
|
|
17
15
|
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,73 +1,105 @@
|
|
|
1
1
|
GEM
|
|
2
2
|
remote: http://rubygems.org/
|
|
3
3
|
specs:
|
|
4
|
-
activesupport (
|
|
5
|
-
|
|
4
|
+
activesupport (8.1.2)
|
|
5
|
+
base64
|
|
6
|
+
bigdecimal
|
|
7
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
8
|
+
connection_pool (>= 2.2.5)
|
|
9
|
+
drb
|
|
6
10
|
i18n (>= 1.6, < 2)
|
|
11
|
+
json
|
|
12
|
+
logger (>= 1.4.2)
|
|
7
13
|
minitest (>= 5.1)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
securerandom (>= 0.3)
|
|
15
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
16
|
+
uri (>= 0.13.1)
|
|
17
|
+
addressable (2.8.8)
|
|
18
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
19
|
+
ast (2.4.3)
|
|
20
|
+
base64 (0.3.0)
|
|
21
|
+
bigdecimal (4.0.1)
|
|
22
|
+
concurrent-ruby (1.3.6)
|
|
23
|
+
connection_pool (3.0.2)
|
|
24
|
+
crack (1.0.1)
|
|
25
|
+
bigdecimal
|
|
14
26
|
rexml
|
|
15
|
-
diff-lcs (1.
|
|
16
|
-
docile (1.4.
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
diff-lcs (1.6.2)
|
|
28
|
+
docile (1.4.1)
|
|
29
|
+
drb (2.2.3)
|
|
30
|
+
hashdiff (1.2.1)
|
|
31
|
+
i18n (1.14.8)
|
|
19
32
|
concurrent-ruby (~> 1.0)
|
|
20
33
|
inheritance-helper (0.2.5)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
json (2.18.0)
|
|
35
|
+
language_server-protocol (3.17.0.5)
|
|
36
|
+
lint_roller (1.1.0)
|
|
37
|
+
logger (1.7.0)
|
|
38
|
+
minitest (6.0.1)
|
|
39
|
+
prism (~> 1.5)
|
|
40
|
+
parallel (1.27.0)
|
|
41
|
+
parser (3.3.10.1)
|
|
24
42
|
ast (~> 2.4.1)
|
|
25
|
-
|
|
43
|
+
racc
|
|
44
|
+
prism (1.9.0)
|
|
45
|
+
public_suffix (7.0.2)
|
|
46
|
+
racc (1.8.1)
|
|
26
47
|
rainbow (3.1.1)
|
|
27
|
-
rake (13.
|
|
28
|
-
regexp_parser (2.
|
|
29
|
-
rexml (3.
|
|
30
|
-
rspec (3.
|
|
31
|
-
rspec-core (~> 3.
|
|
32
|
-
rspec-expectations (~> 3.
|
|
33
|
-
rspec-mocks (~> 3.
|
|
34
|
-
rspec-core (3.
|
|
35
|
-
rspec-support (~> 3.
|
|
36
|
-
rspec-expectations (3.
|
|
48
|
+
rake (13.3.1)
|
|
49
|
+
regexp_parser (2.11.3)
|
|
50
|
+
rexml (3.4.4)
|
|
51
|
+
rspec (3.13.2)
|
|
52
|
+
rspec-core (~> 3.13.0)
|
|
53
|
+
rspec-expectations (~> 3.13.0)
|
|
54
|
+
rspec-mocks (~> 3.13.0)
|
|
55
|
+
rspec-core (3.13.6)
|
|
56
|
+
rspec-support (~> 3.13.0)
|
|
57
|
+
rspec-expectations (3.13.5)
|
|
37
58
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
38
|
-
rspec-support (~> 3.
|
|
39
|
-
rspec-mocks (3.
|
|
59
|
+
rspec-support (~> 3.13.0)
|
|
60
|
+
rspec-mocks (3.13.7)
|
|
40
61
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
41
|
-
rspec-support (~> 3.
|
|
42
|
-
rspec-support (3.
|
|
43
|
-
rubocop (1.
|
|
62
|
+
rspec-support (~> 3.13.0)
|
|
63
|
+
rspec-support (3.13.7)
|
|
64
|
+
rubocop (1.84.0)
|
|
65
|
+
json (~> 2.3)
|
|
66
|
+
language_server-protocol (~> 3.17.0.2)
|
|
67
|
+
lint_roller (~> 1.1.0)
|
|
44
68
|
parallel (~> 1.10)
|
|
45
|
-
parser (>= 3.
|
|
69
|
+
parser (>= 3.3.0.2)
|
|
46
70
|
rainbow (>= 2.2.2, < 4.0)
|
|
47
|
-
regexp_parser (>=
|
|
48
|
-
|
|
49
|
-
rubocop-ast (>= 1.16.0, < 2.0)
|
|
71
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
72
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
50
73
|
ruby-progressbar (~> 1.7)
|
|
51
|
-
unicode-display_width (>=
|
|
52
|
-
rubocop-ast (1.
|
|
53
|
-
parser (>= 3.
|
|
54
|
-
|
|
55
|
-
|
|
74
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
75
|
+
rubocop-ast (1.49.0)
|
|
76
|
+
parser (>= 3.3.7.2)
|
|
77
|
+
prism (~> 1.7)
|
|
78
|
+
ruby-progressbar (1.13.0)
|
|
79
|
+
securerandom (0.4.1)
|
|
80
|
+
simplecov (0.22.0)
|
|
56
81
|
docile (~> 1.1)
|
|
57
82
|
simplecov-html (~> 0.11)
|
|
58
83
|
simplecov_json_formatter (~> 0.1)
|
|
59
|
-
simplecov-
|
|
84
|
+
simplecov-cobertura (3.1.0)
|
|
85
|
+
rexml
|
|
86
|
+
simplecov (~> 0.19)
|
|
87
|
+
simplecov-html (0.13.2)
|
|
60
88
|
simplecov_json_formatter (0.1.4)
|
|
61
|
-
tzinfo (2.0.
|
|
89
|
+
tzinfo (2.0.6)
|
|
62
90
|
concurrent-ruby (~> 1.0)
|
|
63
|
-
unicode-display_width (2.
|
|
64
|
-
|
|
91
|
+
unicode-display_width (3.2.0)
|
|
92
|
+
unicode-emoji (~> 4.1)
|
|
93
|
+
unicode-emoji (4.2.0)
|
|
94
|
+
uri (1.1.1)
|
|
95
|
+
webmock (3.26.1)
|
|
65
96
|
addressable (>= 2.8.0)
|
|
66
97
|
crack (>= 0.3.2)
|
|
67
98
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
68
99
|
|
|
69
100
|
PLATFORMS
|
|
70
|
-
|
|
101
|
+
arm64-darwin-24
|
|
102
|
+
ruby
|
|
71
103
|
|
|
72
104
|
DEPENDENCIES
|
|
73
105
|
activesupport
|
|
@@ -76,7 +108,8 @@ DEPENDENCIES
|
|
|
76
108
|
rspec
|
|
77
109
|
rubocop
|
|
78
110
|
simplecov
|
|
111
|
+
simplecov-cobertura
|
|
79
112
|
webmock
|
|
80
113
|
|
|
81
114
|
BUNDLED WITH
|
|
82
|
-
2.
|
|
115
|
+
2.6.2
|