client-api-builder 0.7.2 → 0.8.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/CHANGELOG.md +7 -0
- data/README.md +15 -5
- data/lib/client_api_builder/nested_router.rb +42 -0
- data/lib/client_api_builder/router.rb +19 -7
- data/lib/client_api_builder/section.rb +9 -3
- data/lib/client_api_builder/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e2d55d531b568e6f1cb0bb718ee236ca44eac7951c133ca6f2d294460968a80
|
|
4
|
+
data.tar.gz: d617dc3983f1cde402095e2be735c804f9a657969007a1f601779fe90f01c110
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 288273782ec2072f360e56b3653a80510684fd05772560db6544b438cc558c56a58d8de179cf87636a80dc396789aed431c63aee4681e90ad9bbc11325b294ba
|
|
7
|
+
data.tar.gz: 5a8622d04ae326e393c1b104a79ff8ed30f1f22420b6b3a7f4b1f30aa39b4e29e7929b625d8ff665276dd6ad45e2ee96e327afceb082b9a6c1d7eb227dde2229
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.0](https://github.com/dougyouch/client-api-builder/compare/v0.7.2...v0.8.0) (2026-10-03)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **section:** let sections inherit root client headers, query params and connection options ([8849d98](https://github.com/dougyouch/client-api-builder/commit/8849d9875c86633639bf33c5e9b1eb486f67e921))
|
|
9
|
+
|
|
3
10
|
## [0.7.2](https://github.com/dougyouch/client-api-builder/compare/v0.7.1...v0.7.2) (2026-10-03)
|
|
4
11
|
|
|
5
12
|
|
data/README.md
CHANGED
|
@@ -296,9 +296,9 @@ class MyApiClient
|
|
|
296
296
|
"Bearer #{auth_token}"
|
|
297
297
|
end
|
|
298
298
|
|
|
299
|
-
section
|
|
299
|
+
# Use the root client's headers (Authorization) beneath the section's own
|
|
300
|
+
section :users, inherit: :headers do
|
|
300
301
|
base_url 'https://api.example.com/v2' # Override base URL
|
|
301
|
-
header 'Authorization', :authorization
|
|
302
302
|
|
|
303
303
|
route :list, '/users'
|
|
304
304
|
route :get, '/users/:id'
|
|
@@ -306,7 +306,7 @@ class MyApiClient
|
|
|
306
306
|
end
|
|
307
307
|
|
|
308
308
|
section :posts do
|
|
309
|
-
header 'Authorization', :authorization
|
|
309
|
+
header 'Authorization', :authorization # or declare what it needs itself
|
|
310
310
|
|
|
311
311
|
route :list, '/posts'
|
|
312
312
|
route :get, '/posts/:id'
|
|
@@ -322,7 +322,17 @@ user = client.users.get(id: 123)
|
|
|
322
322
|
posts = client.posts.list
|
|
323
323
|
```
|
|
324
324
|
|
|
325
|
-
A section is its own router class. It uses the parent's `base_url` unless it sets one, but
|
|
325
|
+
A section is its own router class. It uses the parent's `base_url` unless it sets one, but by default nothing else is inherited: declare the headers, query params and connection options it needs inside the section, or opt into the root client's with `inherit:`:
|
|
326
|
+
|
|
327
|
+
```ruby
|
|
328
|
+
section :users, inherit: %i[headers query_params connection_options] do
|
|
329
|
+
# or, inside the block: inherit_from_root :headers
|
|
330
|
+
end
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Inherited settings are the root client's class-level ones, read on every request (so ones declared after the section, or in a subclass of the client, apply too). The section's own settings override them, and per-request options override both; a per-request `nil` header still drops an inherited one. Retries and body/query builders are always the section's own. Any other options passed to `section` are available to it as `nested_router_options`.
|
|
334
|
+
|
|
335
|
+
Symbol and block values given to `header` and `query_param`, `{name}` path values, and response blocks are evaluated on the root client, so they can use its methods and state.
|
|
326
336
|
|
|
327
337
|
### Connection Options
|
|
328
338
|
|
|
@@ -643,7 +653,7 @@ end
|
|
|
643
653
|
| `connection_option(name, value)` | Set Net::HTTP connection options |
|
|
644
654
|
| `configure_retries(max_attempts, sleep = 0.05)` | Configure retry behavior |
|
|
645
655
|
| `route(name, path, options)` | Define an API endpoint |
|
|
646
|
-
| `section(name, options, &block)` | Define nested routes |
|
|
656
|
+
| `section(name, options, &block)` | Define nested routes; `inherit:` opts into the root client's `:headers`, `:query_params` and/or `:connection_options` |
|
|
647
657
|
| `namespace(path, &block)` | Add path prefix to routes in block |
|
|
648
658
|
|
|
649
659
|
### Instance Methods
|
|
@@ -8,6 +8,9 @@ module ClientApiBuilder
|
|
|
8
8
|
class NestedRouter
|
|
9
9
|
include ::ClientApiBuilder::Router
|
|
10
10
|
|
|
11
|
+
# Root client settings a section can opt into with inherit: or inherit_from_root
|
|
12
|
+
INHERITABLE_SETTINGS = %i[headers query_params connection_options].freeze
|
|
13
|
+
|
|
11
14
|
attr_reader :root_router,
|
|
12
15
|
:nested_router_options
|
|
13
16
|
|
|
@@ -20,6 +23,39 @@ module ClientApiBuilder
|
|
|
20
23
|
"\#{escape_path(root_router.#{var})}"
|
|
21
24
|
end
|
|
22
25
|
|
|
26
|
+
# The root client settings this section uses beneath its own; none by default
|
|
27
|
+
def self.inherited_root_settings
|
|
28
|
+
[].freeze
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Opts this section into the root client's class-level headers, query params and/or
|
|
32
|
+
# connection options. They are read per request and the section's own values take precedence.
|
|
33
|
+
def self.inherit_from_root(*settings)
|
|
34
|
+
settings = normalize_inherited_settings(settings)
|
|
35
|
+
redefine_class_method(:inherited_root_settings, (inherited_root_settings | settings).freeze)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.normalize_inherited_settings(settings)
|
|
39
|
+
settings = settings.flatten.map(&:to_sym)
|
|
40
|
+
unknown = settings - INHERITABLE_SETTINGS
|
|
41
|
+
return settings if unknown.empty?
|
|
42
|
+
|
|
43
|
+
raise ArgumentError, "Unknown inherit setting(s): #{unknown.map(&:inspect).join(', ')}. " \
|
|
44
|
+
"Allowed: #{INHERITABLE_SETTINGS.map(&:inspect).join(', ')}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def configured_headers
|
|
48
|
+
inherits_from_root?(:headers) ? root_router.configured_headers.merge(super) : super
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def configured_query_params
|
|
52
|
+
inherits_from_root?(:query_params) ? root_router.configured_query_params.merge(super) : super
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def configured_connection_options
|
|
56
|
+
inherits_from_root?(:connection_options) ? root_router.configured_connection_options.merge(super) : super
|
|
57
|
+
end
|
|
58
|
+
|
|
23
59
|
def base_url
|
|
24
60
|
self.class.base_url || root_router.base_url
|
|
25
61
|
end
|
|
@@ -27,5 +63,11 @@ module ClientApiBuilder
|
|
|
27
63
|
def handle_response(response, options, &)
|
|
28
64
|
root_router.handle_response(response, options, &)
|
|
29
65
|
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def inherits_from_root?(setting)
|
|
70
|
+
self.class.inherited_root_settings.include?(setting)
|
|
71
|
+
end
|
|
30
72
|
end
|
|
31
73
|
end
|
|
@@ -484,29 +484,41 @@ module ClientApiBuilder
|
|
|
484
484
|
# Class-level headers may be method names or blocks; per-request headers are used as given.
|
|
485
485
|
# Values are converted to strings, as Net::HTTP requires; nil values are left out of the request.
|
|
486
486
|
def build_headers(options)
|
|
487
|
-
headers =
|
|
487
|
+
headers = configured_headers
|
|
488
488
|
headers.merge!(options[:headers]) if options[:headers]
|
|
489
489
|
headers.transform_values { |value| value&.to_s }
|
|
490
490
|
end
|
|
491
491
|
|
|
492
|
+
# Class-level headers with symbols and blocks resolved; sections may add the root client's
|
|
493
|
+
def configured_headers
|
|
494
|
+
self.class.default_headers.transform_values { |value| resolve_config_value(value) }
|
|
495
|
+
end
|
|
496
|
+
|
|
492
497
|
def build_connection_options(options)
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
+
connection_options = configured_connection_options
|
|
499
|
+
options[:connection_options] ? connection_options.merge(options[:connection_options]) : connection_options
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
# Class-level connection options; sections may add the root client's
|
|
503
|
+
def configured_connection_options
|
|
504
|
+
self.class.default_connection_options
|
|
498
505
|
end
|
|
499
506
|
|
|
500
507
|
# Class-level query params may be method names or blocks; values from route arguments
|
|
501
508
|
# and per-request options are sent as given.
|
|
502
509
|
def build_query(query, options)
|
|
503
|
-
query_params =
|
|
510
|
+
query_params = configured_query_params
|
|
504
511
|
query_params.merge!(query) if query
|
|
505
512
|
query_params.merge!(options[:query]) if options[:query]
|
|
506
513
|
|
|
507
514
|
query_params.empty? ? nil : self.class.build_query(self, query_params)
|
|
508
515
|
end
|
|
509
516
|
|
|
517
|
+
# Class-level query params with symbols and blocks resolved; sections may add the root client's
|
|
518
|
+
def configured_query_params
|
|
519
|
+
self.class.default_query_params.transform_values { |value| resolve_config_value(value) }
|
|
520
|
+
end
|
|
521
|
+
|
|
510
522
|
# Resolves a class-level header or query_param value: a Symbol calls that method and a
|
|
511
523
|
# Proc is evaluated, both on the root router; anything else is used as is.
|
|
512
524
|
def resolve_config_value(value)
|
|
@@ -12,17 +12,23 @@ module ClientApiBuilder
|
|
|
12
12
|
|
|
13
13
|
# Defines <name>_router (the section's NestedRouter class) and <name> (its router for a
|
|
14
14
|
# client instance) with closures, so anonymous client classes and any option values work.
|
|
15
|
-
|
|
15
|
+
# inherit: opts the section into root client settings (see NestedRouter.inherit_from_root);
|
|
16
|
+
# the remaining options are passed to the section as nested_router_options.
|
|
17
|
+
def section(name, nested_router_options = {}, &block)
|
|
16
18
|
raise ArgumentError, "Invalid section name: #{name.inspect}" unless name.to_s.match?(SECTION_NAME)
|
|
17
19
|
|
|
20
|
+
nested_router_options = nested_router_options.dup
|
|
21
|
+
inherit = ::ClientApiBuilder::NestedRouter.normalize_inherited_settings(Array(nested_router_options.delete(:inherit)))
|
|
22
|
+
|
|
18
23
|
kls = InheritanceHelper::ClassBuilder::Utils.create_class(
|
|
19
24
|
self,
|
|
20
25
|
name,
|
|
21
26
|
::ClientApiBuilder::NestedRouter,
|
|
22
27
|
nil,
|
|
23
|
-
'NestedRouter'
|
|
24
|
-
&
|
|
28
|
+
'NestedRouter'
|
|
25
29
|
)
|
|
30
|
+
kls.inherit_from_root(inherit)
|
|
31
|
+
kls.class_eval(&block) if block
|
|
26
32
|
|
|
27
33
|
define_singleton_method(:"#{name}_router") { kls }
|
|
28
34
|
define_section_accessor(name, nested_router_options)
|