smithy-client 1.0.0.pre0 → 1.0.0.pre1
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 +2 -0
- data/VERSION +1 -1
- data/lib/smithy-client/anonymous_provider.rb +12 -0
- data/lib/smithy-client/auth_option.rb +23 -0
- data/lib/smithy-client/auth_scheme.rb +25 -0
- data/lib/smithy-client/auth_schemes/anonymous.rb +18 -0
- data/lib/smithy-client/auth_schemes/http_api_key.rb +18 -0
- data/lib/smithy-client/auth_schemes/http_basic.rb +18 -0
- data/lib/smithy-client/auth_schemes/http_bearer.rb +18 -0
- data/lib/smithy-client/auth_schemes/http_digest.rb +18 -0
- data/lib/smithy-client/base.rb +200 -0
- data/lib/smithy-client/block_io.rb +36 -0
- data/lib/smithy-client/configuration.rb +222 -0
- data/lib/smithy-client/default_params.rb +91 -0
- data/lib/smithy-client/dynamic_errors.rb +82 -0
- data/lib/smithy-client/endpoint_rules.rb +186 -0
- data/lib/smithy-client/handler.rb +29 -0
- data/lib/smithy-client/handler_builder.rb +33 -0
- data/lib/smithy-client/handler_context.rb +67 -0
- data/lib/smithy-client/handler_list.rb +197 -0
- data/lib/smithy-client/handler_list_entry.rb +102 -0
- data/lib/smithy-client/http/error_inspector.rb +87 -0
- data/lib/smithy-client/http/headers.rb +122 -0
- data/lib/smithy-client/http/request.rb +57 -0
- data/lib/smithy-client/http/response.rb +178 -0
- data/lib/smithy-client/http_api_key_provider.rb +18 -0
- data/lib/smithy-client/http_bearer_provider.rb +18 -0
- data/lib/smithy-client/http_login_provider.rb +19 -0
- data/lib/smithy-client/identities/anonymous.rb +10 -0
- data/lib/smithy-client/identities/http_api_key.rb +18 -0
- data/lib/smithy-client/identities/http_bearer.rb +18 -0
- data/lib/smithy-client/identities/http_login.rb +22 -0
- data/lib/smithy-client/identity.rb +15 -0
- data/lib/smithy-client/log_formatter.rb +215 -0
- data/lib/smithy-client/log_param_filter.rb +88 -0
- data/lib/smithy-client/log_param_formatter.rb +65 -0
- data/lib/smithy-client/managed_file.rb +14 -0
- data/lib/smithy-client/net_http/connection_pool.rb +297 -0
- data/lib/smithy-client/net_http/handler.rb +160 -0
- data/lib/smithy-client/net_http/patches.rb +28 -0
- data/lib/smithy-client/networking_error.rb +16 -0
- data/lib/smithy-client/pageable_response.rb +138 -0
- data/lib/smithy-client/param_converter.rb +243 -0
- data/lib/smithy-client/param_validator.rb +213 -0
- data/lib/smithy-client/plugin.rb +144 -0
- data/lib/smithy-client/plugin_list.rb +141 -0
- data/lib/smithy-client/plugins/anonymous_auth.rb +23 -0
- data/lib/smithy-client/plugins/checksum_required.rb +51 -0
- data/lib/smithy-client/plugins/content_length.rb +26 -0
- data/lib/smithy-client/plugins/default_params.rb +22 -0
- data/lib/smithy-client/plugins/host_prefix.rb +69 -0
- data/lib/smithy-client/plugins/http_api_key_auth.rb +37 -0
- data/lib/smithy-client/plugins/http_basic_auth.rb +47 -0
- data/lib/smithy-client/plugins/http_bearer_auth.rb +37 -0
- data/lib/smithy-client/plugins/http_digest_auth.rb +60 -0
- data/lib/smithy-client/plugins/idempotency_token.rb +34 -0
- data/lib/smithy-client/plugins/logging.rb +56 -0
- data/lib/smithy-client/plugins/net_http.rb +163 -0
- data/lib/smithy-client/plugins/pageable_response.rb +37 -0
- data/lib/smithy-client/plugins/param_converter.rb +32 -0
- data/lib/smithy-client/plugins/param_validator.rb +30 -0
- data/lib/smithy-client/plugins/protocol.rb +66 -0
- data/lib/smithy-client/plugins/raise_response_errors.rb +33 -0
- data/lib/smithy-client/plugins/request_compression.rb +200 -0
- data/lib/smithy-client/plugins/response_target.rb +71 -0
- data/lib/smithy-client/plugins/retry_errors.rb +125 -0
- data/lib/smithy-client/plugins/sign_requests.rb +24 -0
- data/lib/smithy-client/plugins/stub_responses.rb +102 -0
- data/lib/smithy-client/protocol_spec_matcher.rb +60 -0
- data/lib/smithy-client/refreshing_identity_provider.rb +65 -0
- data/lib/smithy-client/request.rb +76 -0
- data/lib/smithy-client/response.rb +48 -0
- data/lib/smithy-client/retry/adaptive.rb +66 -0
- data/lib/smithy-client/retry/client_rate_limiter.rb +142 -0
- data/lib/smithy-client/retry/quota.rb +58 -0
- data/lib/smithy-client/retry/standard.rb +52 -0
- data/lib/smithy-client/retry.rb +36 -0
- data/lib/smithy-client/rpc_v2_cbor/protocol.rb +38 -0
- data/lib/smithy-client/rpc_v2_cbor/request_builder.rb +76 -0
- data/lib/smithy-client/rpc_v2_cbor/response_parser.rb +86 -0
- data/lib/smithy-client/rpc_v2_cbor/response_stubber.rb +34 -0
- data/lib/smithy-client/service_error.rb +57 -0
- data/lib/smithy-client/signer.rb +16 -0
- data/lib/smithy-client/signers/anonymous.rb +13 -0
- data/lib/smithy-client/signers/http_api_key.rb +52 -0
- data/lib/smithy-client/signers/http_basic.rb +23 -0
- data/lib/smithy-client/signers/http_bearer.rb +19 -0
- data/lib/smithy-client/signers/http_digest.rb +21 -0
- data/lib/smithy-client/stubbing/data_applicator.rb +61 -0
- data/lib/smithy-client/stubbing/empty_stub.rb +69 -0
- data/lib/smithy-client/stubbing/endpoint_provider.rb +22 -0
- data/lib/smithy-client/stubbing/protocol.rb +29 -0
- data/lib/smithy-client/stubbing/stub_data.rb +25 -0
- data/lib/smithy-client/stubbing.rb +14 -0
- data/lib/smithy-client/stubs.rb +212 -0
- data/lib/smithy-client/util.rb +15 -0
- data/lib/smithy-client/waiters/poller.rb +93 -0
- data/lib/smithy-client/waiters/waiter.rb +113 -0
- data/lib/smithy-client.rb +66 -1
- metadata +163 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe9f255b34cb45c0052d24be6f0f8415d129311b369e3a801b2a8945bb43ef47
|
4
|
+
data.tar.gz: 6ee595a5adb44533dddc82fa08e80d40aa944ec82dfd76b71216c556d54d0396
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8b6968ff630c4178da4f630fe3977a7112a88b84d6aeecccc515ed68566f4dbcb9ed1b0a0ec4bfa03a2f6d9b3430962d488327e902d597d58be8b569645302a
|
7
|
+
data.tar.gz: '0298dcc75712c68d05ce931f582e0873340e7059d7a238dfbc6f14877868040cf6401b7bb75c1874d4b1e24feb5f37468d20a80e7b8ae18a133ca81476c29f9c'
|
data/CHANGELOG.md
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.0.
|
1
|
+
1.0.0.pre1
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
# Object that represents an auth option, returned by Auth Resolvers.
|
6
|
+
class AuthOption
|
7
|
+
def initialize(scheme_id:, identity_properties: {}, signer_properties: {})
|
8
|
+
@scheme_id = scheme_id
|
9
|
+
@identity_properties = identity_properties
|
10
|
+
@signer_properties = signer_properties
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [String]
|
14
|
+
attr_reader :scheme_id
|
15
|
+
|
16
|
+
# @return [Hash]
|
17
|
+
attr_reader :identity_properties
|
18
|
+
|
19
|
+
# @return [Hash]
|
20
|
+
attr_reader :signer_properties
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
# Base class for all AuthScheme classes.
|
6
|
+
class AuthScheme
|
7
|
+
def initialize(scheme_id:, signer:, identity_type:)
|
8
|
+
@scheme_id = scheme_id
|
9
|
+
@signer = signer
|
10
|
+
@identity_type = identity_type
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [String]
|
14
|
+
attr_reader :scheme_id
|
15
|
+
|
16
|
+
# @return [IdentityProvider, nil]
|
17
|
+
def identity_provider(identity_provider = {})
|
18
|
+
identity_provider[@identity_type]
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Signer]
|
22
|
+
attr_reader :signer
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
module AuthSchemes
|
6
|
+
# Auth scheme for no authentication.
|
7
|
+
class Anonymous < AuthScheme
|
8
|
+
def initialize(options = {})
|
9
|
+
super(
|
10
|
+
scheme_id: 'smithy.api#noAuth',
|
11
|
+
signer: options.fetch(:signer, Signers::Anonymous.new),
|
12
|
+
identity_type: Identities::Anonymous
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
module AuthSchemes
|
6
|
+
# Auth scheme for HTTP API keys.
|
7
|
+
class HttpApiKey < AuthScheme
|
8
|
+
def initialize(options = {})
|
9
|
+
super(
|
10
|
+
scheme_id: 'smithy.api#httpApiKeyAuth',
|
11
|
+
signer: options.fetch(:signer, Signers::HttpApiKey.new),
|
12
|
+
identity_type: Identities::HttpApiKey
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
module AuthSchemes
|
6
|
+
# Auth scheme for HTTP Basic.
|
7
|
+
class HttpBasic < AuthScheme
|
8
|
+
def initialize(options = {})
|
9
|
+
super(
|
10
|
+
scheme_id: 'smithy.api#httpBasicAuth',
|
11
|
+
signer: options.fetch(:signer, Signers::HttpBasic.new),
|
12
|
+
identity_type: Identities::HttpLogin
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
module AuthSchemes
|
6
|
+
# Auth scheme for HTTP Bearer tokens.
|
7
|
+
class HttpBearer < AuthScheme
|
8
|
+
def initialize(options = {})
|
9
|
+
super(
|
10
|
+
scheme_id: 'smithy.api#httpBearerAuth',
|
11
|
+
signer: options.fetch(:signer, Signers::HttpBearer.new),
|
12
|
+
identity_type: Identities::HttpBearer
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
module AuthSchemes
|
6
|
+
# Auth scheme for HTTP Digest.
|
7
|
+
class HttpDigest < AuthScheme
|
8
|
+
def initialize(options = {})
|
9
|
+
super(
|
10
|
+
scheme_id: 'smithy.api#httpDigestAuth',
|
11
|
+
signer: options.fetch(:signer, Signers::HttpDigest.new),
|
12
|
+
identity_type: Identities::HttpLogin
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
# Base class for all service clients.
|
6
|
+
class Base
|
7
|
+
include HandlerBuilder
|
8
|
+
|
9
|
+
def initialize(plugins, options)
|
10
|
+
@config = build_config(plugins, options)
|
11
|
+
@handlers = build_handler_list(plugins)
|
12
|
+
after_initialize(plugins)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Struct]
|
16
|
+
attr_reader :config
|
17
|
+
|
18
|
+
# @return [HandlerList]
|
19
|
+
attr_reader :handlers
|
20
|
+
|
21
|
+
# Builds and returns a {Request} for the named operation. The request will not have been sent.
|
22
|
+
# @param [Symbol] operation_name
|
23
|
+
# @return [Request]
|
24
|
+
def build_request(operation_name, params = {})
|
25
|
+
Request.new(
|
26
|
+
handlers: @handlers.for(operation_name),
|
27
|
+
context: context_for(operation_name, params)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Array<Symbol>] Returns a list of valid request operation
|
32
|
+
# names. These are valid arguments to {#build_input} and are also
|
33
|
+
# valid methods.
|
34
|
+
def operation_names
|
35
|
+
self.class.service.operation_names
|
36
|
+
end
|
37
|
+
|
38
|
+
# @api private
|
39
|
+
def inspect
|
40
|
+
"#<#{self.class.name || 'Smithy::Client::Base'}>"
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Constructs a {Configuration} object and gives each plugin the
|
46
|
+
# opportunity to register options with default values.
|
47
|
+
def build_config(plugins, options)
|
48
|
+
config = Configuration.new
|
49
|
+
config.add_option(:service)
|
50
|
+
plugins.each do |plugin|
|
51
|
+
plugin.add_options(config) if plugin.respond_to?(:add_options)
|
52
|
+
end
|
53
|
+
config.build!(options.merge(service: self.class.service))
|
54
|
+
end
|
55
|
+
|
56
|
+
# Gives each plugin the opportunity to register handlers for this client.
|
57
|
+
def build_handler_list(plugins)
|
58
|
+
plugins.each_with_object(HandlerList.new) do |plugin, handlers|
|
59
|
+
plugin.add_handlers(handlers, @config) if plugin.respond_to?(:add_handlers)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Gives each plugin the opportunity to modify this client.
|
64
|
+
def after_initialize(plugins)
|
65
|
+
plugins.reverse.each do |plugin|
|
66
|
+
plugin.after_initialize(self) if plugin.respond_to?(:after_initialize)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [HandlerContext]
|
71
|
+
def context_for(operation_name, params)
|
72
|
+
HandlerContext.new(
|
73
|
+
operation_name: operation_name,
|
74
|
+
operation: config.service.operation(operation_name),
|
75
|
+
client: self,
|
76
|
+
params: params,
|
77
|
+
config: config
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def waiter(waiter_name, options = {})
|
82
|
+
waiter_class = waiters[waiter_name]
|
83
|
+
raise Waiters::NoSuchWaiterError.new(waiter_name, waiters.keys) unless waiter_class
|
84
|
+
|
85
|
+
waiter_class.new(options.merge(client: self))
|
86
|
+
end
|
87
|
+
|
88
|
+
class << self
|
89
|
+
def new(options = {})
|
90
|
+
plugins = build_plugins
|
91
|
+
options = options.dup
|
92
|
+
options[:plugins]&.freeze
|
93
|
+
before_initialize(plugins, options)
|
94
|
+
client = allocate
|
95
|
+
client.send(:initialize, plugins, options)
|
96
|
+
client
|
97
|
+
end
|
98
|
+
|
99
|
+
# Registers a plugin with this client.
|
100
|
+
#
|
101
|
+
# @example Register a plugin
|
102
|
+
#
|
103
|
+
# ClientClass.add_plugin(PluginClass)
|
104
|
+
#
|
105
|
+
# @example Register a plugin with an object
|
106
|
+
#
|
107
|
+
# plugin = MyPluginClass.new(options)
|
108
|
+
# ClientClass.add_plugin(plugin)
|
109
|
+
#
|
110
|
+
# @param [Class, Symbol, String, Object] plugin
|
111
|
+
# @see .clear_plugins
|
112
|
+
# @see .set_plugins
|
113
|
+
# @see .remove_plugin
|
114
|
+
# @see .plugins
|
115
|
+
# @return [void]
|
116
|
+
def add_plugin(plugin)
|
117
|
+
@plugins.add(plugin)
|
118
|
+
end
|
119
|
+
|
120
|
+
# @see .clear_plugins
|
121
|
+
# @see .set_plugins
|
122
|
+
# @see .add_plugin
|
123
|
+
# @see .plugins
|
124
|
+
# @return [void]
|
125
|
+
def remove_plugin(plugin)
|
126
|
+
@plugins.remove(plugin)
|
127
|
+
end
|
128
|
+
|
129
|
+
# @see .set_plugins
|
130
|
+
# @see .add_plugin
|
131
|
+
# @see .remove_plugin
|
132
|
+
# @see .plugins
|
133
|
+
# @return [void]
|
134
|
+
def clear_plugins
|
135
|
+
@plugins.set([])
|
136
|
+
end
|
137
|
+
|
138
|
+
# @param [Array<Plugin>] plugins
|
139
|
+
# @see .clear_plugins
|
140
|
+
# @see .add_plugin
|
141
|
+
# @see .remove_plugin
|
142
|
+
# @see .plugins
|
143
|
+
# @return [void]
|
144
|
+
def plugins=(plugins)
|
145
|
+
@plugins.set(plugins)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns the list of registered plugins for this Client. Plugins are
|
149
|
+
# inherited from the client super class when the client is defined.
|
150
|
+
# @see .clear_plugins
|
151
|
+
# @see .set_plugins
|
152
|
+
# @see .add_plugin
|
153
|
+
# @see .remove_plugin
|
154
|
+
# @return [Array<Plugin>]
|
155
|
+
def plugins
|
156
|
+
Array(@plugins).freeze
|
157
|
+
end
|
158
|
+
|
159
|
+
# @return [Schema::Shapes::ServiceShape]
|
160
|
+
def service
|
161
|
+
@service ||= Schema::Shapes::ServiceShape.new
|
162
|
+
end
|
163
|
+
|
164
|
+
# @param [ServiceShape] service
|
165
|
+
attr_writer :service
|
166
|
+
|
167
|
+
# @option options [ServiceShape] :service (ServiceShape.new)
|
168
|
+
# @option options [Array<Plugin>] :plugins ([]) A list of plugins to
|
169
|
+
# add to the client class created.
|
170
|
+
# @return [Class<Client::Base>]
|
171
|
+
def define(options = {})
|
172
|
+
subclass = Class.new(self)
|
173
|
+
subclass.service = options[:service] || service
|
174
|
+
Array(options[:plugins]).each do |plugin|
|
175
|
+
subclass.add_plugin(plugin)
|
176
|
+
end
|
177
|
+
subclass
|
178
|
+
end
|
179
|
+
alias extend define
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def build_plugins
|
184
|
+
plugins.map { |plugin| plugin.is_a?(Class) ? plugin.new : plugin }
|
185
|
+
end
|
186
|
+
|
187
|
+
def before_initialize(plugins, options)
|
188
|
+
plugins.each do |plugin|
|
189
|
+
plugin.before_initialize(self, options) if plugin.respond_to?(:before_initialize)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def inherited(subclass)
|
194
|
+
super
|
195
|
+
subclass.instance_variable_set('@plugins', PluginList.new)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
# IO object for response targets.
|
6
|
+
class BlockIO
|
7
|
+
# @param [Hash] headers (nil)
|
8
|
+
# @param [Proc] block
|
9
|
+
def initialize(headers = nil, &block)
|
10
|
+
@headers = headers
|
11
|
+
@block = block
|
12
|
+
@size = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Integer]
|
16
|
+
attr_reader :size
|
17
|
+
|
18
|
+
# @param [String] chunk
|
19
|
+
# @return [Integer]
|
20
|
+
def write(chunk)
|
21
|
+
@block.call(chunk, @headers)
|
22
|
+
chunk.bytesize
|
23
|
+
ensure
|
24
|
+
chunk.bytesize.tap { |chunk_size| @size += chunk_size }
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [Integer] bytes (nil)
|
28
|
+
# @param [String] output_buffer (nil)
|
29
|
+
# @return [String, nil]
|
30
|
+
def read(bytes = nil, output_buffer = nil)
|
31
|
+
data = bytes ? nil : ''
|
32
|
+
output_buffer ? output_buffer.replace(data || '') : data
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Smithy
|
4
|
+
module Client
|
5
|
+
# Configuration is used to define possible configuration options and
|
6
|
+
# then build read-only structures with user-supplied data.
|
7
|
+
#
|
8
|
+
# ## Adding Configuration Options
|
9
|
+
#
|
10
|
+
# Add configuration options with optional default values. These are used
|
11
|
+
# when building configuration objects.
|
12
|
+
#
|
13
|
+
# configuration = Configuration.new
|
14
|
+
#
|
15
|
+
# configuration.add_option(:max_retries, 3)
|
16
|
+
# configuration.add_option(:use_ssl, true)
|
17
|
+
#
|
18
|
+
# cfg = configuration.build!
|
19
|
+
# #=> #<struct max_retires=3 use_ssl=true>
|
20
|
+
#
|
21
|
+
# ## Building Configuration Objects
|
22
|
+
#
|
23
|
+
# Calling {#build!} on a {Configuration} object causes it to return
|
24
|
+
# a read-only (frozen) struct. Options passed to {#build!} are merged
|
25
|
+
# on top of any default options.
|
26
|
+
#
|
27
|
+
# configuration = Configuration.new
|
28
|
+
# configuration.add_option(:color, 'red')
|
29
|
+
#
|
30
|
+
# # default
|
31
|
+
# cfg1 = configuration.build!
|
32
|
+
# cfg1.color #=> 'red'
|
33
|
+
#
|
34
|
+
# # supplied color
|
35
|
+
# cfg2 = configuration.build!(color: 'blue')
|
36
|
+
# cfg2.color #=> 'blue'
|
37
|
+
#
|
38
|
+
# ## Accepted Options
|
39
|
+
#
|
40
|
+
# If you try to {#build!} a {Configuration} object with an unknown
|
41
|
+
# option, an `ArgumentError` is raised.
|
42
|
+
#
|
43
|
+
# configuration = Configuration.new
|
44
|
+
# configuration.add_option(:color)
|
45
|
+
# configuration.add_option(:size)
|
46
|
+
# configuration.add_option(:category)
|
47
|
+
#
|
48
|
+
# configuration.build!(price: 100)
|
49
|
+
# #=> raises an ArgumentError, :price was not added as an option
|
50
|
+
#
|
51
|
+
class Configuration
|
52
|
+
# @api private
|
53
|
+
Defaults = Class.new(Array) do
|
54
|
+
def each(&block)
|
55
|
+
reverse.to_a.each(&block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
@defaults = Hash.new { |h, k| h[k] = Defaults.new }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Adds a getter method that returns the named option or a default
|
64
|
+
# value. Default values can be passed as a static positional argument
|
65
|
+
# or via a block.
|
66
|
+
#
|
67
|
+
# # defaults to nil
|
68
|
+
# configuration.add_option(:name)
|
69
|
+
#
|
70
|
+
# # with a string default
|
71
|
+
# configuration.add_option(:name, 'John Doe')
|
72
|
+
#
|
73
|
+
# # with a dynamic default value, evaluated once when calling #build!
|
74
|
+
# configuration.add_option(:name, 'John Doe')
|
75
|
+
# configuration.add_option(:username) do |config|
|
76
|
+
# config.name.gsub(/\W+/, '').downcase
|
77
|
+
# end
|
78
|
+
# cfg = configuration.build!
|
79
|
+
# cfg.name #=> 'John Doe'
|
80
|
+
# cfg.username #=> 'johndoe'
|
81
|
+
#
|
82
|
+
# @param [Symbol] name The name of the configuration option. This will
|
83
|
+
# be used to define a getter by the same name.
|
84
|
+
#
|
85
|
+
# @param [Object] default The default value for this option. You can specify
|
86
|
+
# a default by passing a value, a `Proc` object or a block argument.
|
87
|
+
# Procs and blocks are evaluated when {#build!} is called.
|
88
|
+
#
|
89
|
+
# @return [self]
|
90
|
+
def add_option(name, default = nil, &block)
|
91
|
+
default = DynamicDefault.new(block) if block_given?
|
92
|
+
@defaults[name.to_sym] << default
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# Constructs and returns a configuration structure.
|
97
|
+
# Values not present in `options` will default to those supplied via
|
98
|
+
# add option.
|
99
|
+
#
|
100
|
+
# configuration = Configuration.new
|
101
|
+
# configuration.add_option(:enabled, true)
|
102
|
+
#
|
103
|
+
# cfg1 = configuration.build!
|
104
|
+
# cfg1.enabled #=> true
|
105
|
+
#
|
106
|
+
# cfg2 = configuration.build!(enabled: false)
|
107
|
+
# cfg2.enabled #=> false
|
108
|
+
#
|
109
|
+
# If you pass in options to `#build!` that have not been defined,
|
110
|
+
# then an `ArgumentError` will be raised.
|
111
|
+
#
|
112
|
+
# configuration = Configuration.new
|
113
|
+
# configuration.add_option(:enabled, true)
|
114
|
+
#
|
115
|
+
# # oops, spelling error for :enabled
|
116
|
+
# cfg = configuration.build!(enabld: true)
|
117
|
+
# #=> raises ArgumentError
|
118
|
+
#
|
119
|
+
# The object returned is a frozen `Struct`.
|
120
|
+
#
|
121
|
+
# configuration = Configuration.new
|
122
|
+
# configuration.add_option(:enabled, true)
|
123
|
+
#
|
124
|
+
# cfg = configuration.build!
|
125
|
+
# cfg.enabled #=> true
|
126
|
+
# cfg[:enabled] #=> true
|
127
|
+
# cfg['enabled'] #=> true
|
128
|
+
#
|
129
|
+
# @param [Hash] options ({}) A hash of configuration options.
|
130
|
+
# @return [Struct] Returns a frozen configuration `Struct`.
|
131
|
+
def build!(options = {})
|
132
|
+
struct = empty_struct
|
133
|
+
apply_options(struct, options)
|
134
|
+
apply_defaults(struct, options)
|
135
|
+
struct
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def empty_struct
|
141
|
+
Struct.new(*@defaults.keys.sort).new
|
142
|
+
end
|
143
|
+
|
144
|
+
def apply_options(struct, options)
|
145
|
+
options.each do |opt, value|
|
146
|
+
struct[opt] = value
|
147
|
+
rescue NameError
|
148
|
+
msg = "invalid configuration option `#{opt.inspect}'"
|
149
|
+
raise ArgumentError, msg
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def apply_defaults(struct, options)
|
154
|
+
@defaults.each do |opt_name, defaults|
|
155
|
+
struct[opt_name] = defaults unless options.key?(opt_name)
|
156
|
+
end
|
157
|
+
DefaultResolver.new(struct).resolve
|
158
|
+
end
|
159
|
+
|
160
|
+
# @api private
|
161
|
+
class DynamicDefault
|
162
|
+
attr_accessor :block
|
163
|
+
|
164
|
+
def initialize(block = nil)
|
165
|
+
@block = block
|
166
|
+
end
|
167
|
+
|
168
|
+
def call(*)
|
169
|
+
@block.call(*)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# @api private
|
174
|
+
class DefaultResolver
|
175
|
+
def initialize(struct)
|
176
|
+
@struct = struct
|
177
|
+
@members = Set.new(@struct.members)
|
178
|
+
end
|
179
|
+
|
180
|
+
def resolve
|
181
|
+
@members.each { |opt_name| value_at(opt_name) }
|
182
|
+
end
|
183
|
+
|
184
|
+
def respond_to?(method_name, *args)
|
185
|
+
@members.include?(method_name) or super
|
186
|
+
end
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
def value_at(opt_name)
|
191
|
+
value = @struct[opt_name]
|
192
|
+
if value.is_a?(Defaults)
|
193
|
+
resolve_defaults(opt_name, value)
|
194
|
+
else
|
195
|
+
value
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def resolve_defaults(opt_name, defaults)
|
200
|
+
defaults.each do |default|
|
201
|
+
default = default.call(self) if default.is_a?(DynamicDefault)
|
202
|
+
@struct[opt_name] = default
|
203
|
+
break unless default.nil?
|
204
|
+
end
|
205
|
+
@struct[opt_name]
|
206
|
+
end
|
207
|
+
|
208
|
+
def respond_to_missing?(method_name, *args)
|
209
|
+
@members.include?(method_name) || super
|
210
|
+
end
|
211
|
+
|
212
|
+
def method_missing(method_name, *args)
|
213
|
+
if @members.include?(method_name)
|
214
|
+
value_at(method_name)
|
215
|
+
else
|
216
|
+
super
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|