openfeature-flagsmith-provider 0.1.1
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/.context.md +316 -0
- data/.gitignore +11 -0
- data/.rspec +4 -0
- data/.rubocop.yml +5 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +22 -0
- data/FLAGSMITH_PROVIDER_DESIGN.md +393 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +126 -0
- data/README.md +324 -0
- data/Rakefile +6 -0
- data/lib/openfeature/flagsmith/error/errors.rb +78 -0
- data/lib/openfeature/flagsmith/options.rb +82 -0
- data/lib/openfeature/flagsmith/provider.rb +265 -0
- data/lib/openfeature/flagsmith/version.rb +7 -0
- data/openfeature-flagsmith-provider.gemspec +38 -0
- metadata +176 -0
data/README.md
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# OpenFeature Flagsmith Provider for Ruby
|
|
2
|
+
|
|
3
|
+
This is the Ruby provider for [Flagsmith](https://www.flagsmith.com/) feature flags, implementing the [OpenFeature](https://openfeature.dev/) standard.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
| Status | Feature | Description |
|
|
8
|
+
|--------|---------|-------------|
|
|
9
|
+
| ✅ | Flag Evaluation | Support for all OpenFeature flag types |
|
|
10
|
+
| ✅ | Boolean Flags | Evaluate boolean feature flags |
|
|
11
|
+
| ✅ | String Flags | Evaluate string feature flags |
|
|
12
|
+
| ✅ | Number Flags | Evaluate numeric feature flags (int, float) |
|
|
13
|
+
| ✅ | Object Flags | Evaluate JSON object/array flags |
|
|
14
|
+
| ✅ | Evaluation Context | Support for user identity and traits |
|
|
15
|
+
| ✅ | Environment Flags | Evaluate flags at environment level |
|
|
16
|
+
| ✅ | Identity Flags | Evaluate flags for specific users |
|
|
17
|
+
| ✅ | Remote Evaluation | Default remote evaluation mode |
|
|
18
|
+
| ✅ | Local Evaluation | Optional local evaluation mode |
|
|
19
|
+
| ✅ | Error Handling | Comprehensive error handling |
|
|
20
|
+
| ✅ | Type Validation | Strict type checking |
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Add this line to your application's Gemfile:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
gem 'openfeature-flagsmith-provider'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
And then execute:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bundle install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or install it yourself as:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
gem install openfeature-flagsmith-provider
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
### Basic Setup
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
require 'open_feature/sdk'
|
|
48
|
+
require 'openfeature/flagsmith/provider'
|
|
49
|
+
require 'openfeature/flagsmith/options'
|
|
50
|
+
|
|
51
|
+
# Configure the Flagsmith provider
|
|
52
|
+
options = OpenFeature::Flagsmith::Options.new(
|
|
53
|
+
environment_key: 'your_flagsmith_environment_key'
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
provider = OpenFeature::Flagsmith::Provider.new(options: options)
|
|
57
|
+
|
|
58
|
+
# Set the provider in OpenFeature
|
|
59
|
+
OpenFeature::SDK.configure do |config|
|
|
60
|
+
config.provider = provider
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get a client
|
|
64
|
+
client = OpenFeature::SDK.build_client
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Evaluating Flags
|
|
68
|
+
|
|
69
|
+
#### Boolean Flags
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# Simple boolean flag
|
|
73
|
+
enabled = client.fetch_boolean_value(
|
|
74
|
+
flag_key: 'new_feature',
|
|
75
|
+
default_value: false
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# With user context
|
|
79
|
+
evaluation_context = OpenFeature::SDK::EvaluationContext.new(
|
|
80
|
+
targeting_key: 'user_123',
|
|
81
|
+
email: 'user@example.com',
|
|
82
|
+
age: 30
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
enabled = client.fetch_boolean_value(
|
|
86
|
+
flag_key: 'new_feature',
|
|
87
|
+
default_value: false,
|
|
88
|
+
evaluation_context: evaluation_context
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### String Flags
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
theme = client.fetch_string_value(
|
|
96
|
+
flag_key: 'theme',
|
|
97
|
+
default_value: 'light',
|
|
98
|
+
evaluation_context: evaluation_context
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### Number Flags
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
max_items = client.fetch_integer_value(
|
|
106
|
+
flag_key: 'max_items',
|
|
107
|
+
default_value: 10,
|
|
108
|
+
evaluation_context: evaluation_context
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
rate_limit = client.fetch_float_value(
|
|
112
|
+
flag_key: 'rate_limit',
|
|
113
|
+
default_value: 1.5,
|
|
114
|
+
evaluation_context: evaluation_context
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Object Flags
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
config = client.fetch_object_value(
|
|
122
|
+
flag_key: 'app_config',
|
|
123
|
+
default_value: {timeout: 30},
|
|
124
|
+
evaluation_context: evaluation_context
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Configuration Options
|
|
129
|
+
|
|
130
|
+
The `Options` class accepts the following configuration parameters:
|
|
131
|
+
|
|
132
|
+
| Option | Type | Default | Required | Description |
|
|
133
|
+
|--------|------|---------|----------|-------------|
|
|
134
|
+
| `environment_key` | String | - | **Yes** | Your Flagsmith environment key |
|
|
135
|
+
| `api_url` | String | `https://edge.api.flagsmith.com/api/v1/` | No | Custom Flagsmith API URL (for self-hosting) |
|
|
136
|
+
| `enable_local_evaluation` | Boolean | `false` | No | Enable local evaluation mode |
|
|
137
|
+
| `request_timeout_seconds` | Integer | `10` | No | HTTP request timeout in seconds |
|
|
138
|
+
| `enable_analytics` | Boolean | `false` | No | Enable Flagsmith analytics |
|
|
139
|
+
| `environment_refresh_interval_seconds` | Integer | `60` | No | Polling interval for local evaluation mode |
|
|
140
|
+
|
|
141
|
+
### Configuration Examples
|
|
142
|
+
|
|
143
|
+
#### Default Configuration
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
options = OpenFeature::Flagsmith::Options.new(
|
|
147
|
+
environment_key: 'your_key'
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Custom API URL (Self-Hosted)
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
options = OpenFeature::Flagsmith::Options.new(
|
|
155
|
+
environment_key: 'your_key',
|
|
156
|
+
api_url: 'https://flagsmith.yourcompany.com/api/v1/'
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### Local Evaluation Mode
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
options = OpenFeature::Flagsmith::Options.new(
|
|
164
|
+
environment_key: 'your_key',
|
|
165
|
+
enable_local_evaluation: true,
|
|
166
|
+
environment_refresh_interval_seconds: 30
|
|
167
|
+
)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### With Analytics
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
options = OpenFeature::Flagsmith::Options.new(
|
|
174
|
+
environment_key: 'your_key',
|
|
175
|
+
enable_analytics: true
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Evaluation Context
|
|
180
|
+
|
|
181
|
+
The provider supports OpenFeature evaluation contexts to pass user information and traits to Flagsmith:
|
|
182
|
+
|
|
183
|
+
### Targeting Key → Identity
|
|
184
|
+
|
|
185
|
+
The `targeting_key` maps to Flagsmith's identity identifier. **Note:** Flagsmith requires an identity to evaluate traits, so if you provide traits without a `targeting_key`, they will be ignored and evaluation falls back to environment-level flags.
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
evaluation_context = OpenFeature::SDK::EvaluationContext.new(
|
|
189
|
+
targeting_key: 'user@example.com'
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Context Fields → Traits
|
|
194
|
+
|
|
195
|
+
All other context fields are passed as Flagsmith traits:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
evaluation_context = OpenFeature::SDK::EvaluationContext.new(
|
|
199
|
+
targeting_key: 'user_123',
|
|
200
|
+
email: 'user@example.com',
|
|
201
|
+
plan: 'premium',
|
|
202
|
+
age: 30
|
|
203
|
+
)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
This will evaluate flags for identity `user_123` with traits:
|
|
207
|
+
- `email`: "user@example.com"
|
|
208
|
+
- `plan`: "premium"
|
|
209
|
+
- `age`: 30
|
|
210
|
+
|
|
211
|
+
### Environment-Level vs Identity-Specific
|
|
212
|
+
|
|
213
|
+
**Without `targeting_key` (Environment-level):**
|
|
214
|
+
```ruby
|
|
215
|
+
# Evaluates flags at environment level
|
|
216
|
+
client.fetch_boolean_value(
|
|
217
|
+
flag_key: 'feature',
|
|
218
|
+
default_value: false
|
|
219
|
+
)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**With `targeting_key` (Identity-specific):**
|
|
223
|
+
```ruby
|
|
224
|
+
# Evaluates flags for specific user identity
|
|
225
|
+
evaluation_context = OpenFeature::SDK::EvaluationContext.new(
|
|
226
|
+
targeting_key: 'user_123'
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
client.fetch_boolean_value(
|
|
230
|
+
flag_key: 'feature',
|
|
231
|
+
default_value: false,
|
|
232
|
+
evaluation_context: evaluation_context
|
|
233
|
+
)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Error Handling
|
|
237
|
+
|
|
238
|
+
The provider handles errors gracefully and returns the default value with appropriate error codes:
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
result = client.fetch_boolean_details(
|
|
242
|
+
flag_key: 'unknown_flag',
|
|
243
|
+
default_value: false
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
puts result.value # => false (default value)
|
|
247
|
+
puts result.error_code # => FLAG_NOT_FOUND
|
|
248
|
+
puts result.error_message # => "Flag 'unknown_flag' not found"
|
|
249
|
+
puts result.reason # => DEFAULT
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Error Codes
|
|
253
|
+
|
|
254
|
+
| Error Code | Description |
|
|
255
|
+
|------------|-------------|
|
|
256
|
+
| `FLAG_NOT_FOUND` | The requested flag does not exist |
|
|
257
|
+
| `TYPE_MISMATCH` | The flag value type doesn't match the requested type |
|
|
258
|
+
| `PROVIDER_NOT_READY` | The Flagsmith client is not properly initialized |
|
|
259
|
+
| `PARSE_ERROR` | Failed to parse the flag value |
|
|
260
|
+
| `INVALID_CONTEXT` | The evaluation context is invalid |
|
|
261
|
+
| `GENERAL` | A general error occurred |
|
|
262
|
+
|
|
263
|
+
## Reasons
|
|
264
|
+
|
|
265
|
+
The provider returns appropriate reasons for flag evaluations:
|
|
266
|
+
|
|
267
|
+
| Reason | Description |
|
|
268
|
+
|--------|-------------|
|
|
269
|
+
| `TARGETING_MATCH` | Flag evaluated with user identity (targeting_key provided) |
|
|
270
|
+
| `STATIC` | Flag evaluated at environment level (no targeting_key) |
|
|
271
|
+
| `DEFAULT` | Default value returned due to flag not found |
|
|
272
|
+
| `ERROR` | An error occurred during evaluation |
|
|
273
|
+
| `DISABLED` | The flag was disabled, and the default value was returned |
|
|
274
|
+
|
|
275
|
+
**Note**: Both remote and local evaluation modes use the same reason mapping (STATIC/TARGETING_MATCH). Local evaluation performs flag evaluation locally but still evaluates the flag state, it doesn't return cached results.
|
|
276
|
+
|
|
277
|
+
## Development
|
|
278
|
+
|
|
279
|
+
### Running Tests
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
bundle install
|
|
283
|
+
bundle exec rspec
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Running Linter
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
bundle exec rubocop
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Building the Gem
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
gem build openfeature-flagsmith-provider.gemspec
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Contributing
|
|
299
|
+
|
|
300
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
301
|
+
|
|
302
|
+
1. Fork the repository
|
|
303
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
304
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
305
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
306
|
+
5. Open a Pull Request
|
|
307
|
+
|
|
308
|
+
## License
|
|
309
|
+
|
|
310
|
+
Apache 2.0 - See [LICENSE](LICENSE) for more information.
|
|
311
|
+
|
|
312
|
+
## Links
|
|
313
|
+
|
|
314
|
+
- [Flagsmith Documentation](https://docs.flagsmith.com/)
|
|
315
|
+
- [OpenFeature Documentation](https://openfeature.dev/)
|
|
316
|
+
- [OpenFeature Ruby SDK](https://github.com/open-feature/ruby-sdk)
|
|
317
|
+
- [Ruby SDK Contrib Repository](https://github.com/open-feature/ruby-sdk-contrib)
|
|
318
|
+
|
|
319
|
+
## Support
|
|
320
|
+
|
|
321
|
+
For issues related to:
|
|
322
|
+
- **This provider**: [GitHub Issues](https://github.com/open-feature/ruby-sdk-contrib/issues)
|
|
323
|
+
- **Flagsmith**: [Flagsmith Support](https://www.flagsmith.com/contact-us)
|
|
324
|
+
- **OpenFeature**: [OpenFeature Community](https://openfeature.dev/community/)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open_feature/sdk/provider/error_code"
|
|
4
|
+
|
|
5
|
+
module OpenFeature
|
|
6
|
+
module Flagsmith
|
|
7
|
+
# Base error class for Flagsmith provider
|
|
8
|
+
class FlagsmithError < StandardError
|
|
9
|
+
attr_reader :error_code, :error_message
|
|
10
|
+
|
|
11
|
+
def initialize(error_code, error_message)
|
|
12
|
+
@error_code = error_code
|
|
13
|
+
@error_message = error_message
|
|
14
|
+
super(error_message)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Raised when a flag is not found in Flagsmith
|
|
19
|
+
class FlagNotFoundError < FlagsmithError
|
|
20
|
+
def initialize(flag_key)
|
|
21
|
+
super(
|
|
22
|
+
SDK::Provider::ErrorCode::FLAG_NOT_FOUND,
|
|
23
|
+
"Flag not found: #{flag_key}"
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Raised when there's a type mismatch between expected and actual flag value
|
|
29
|
+
class TypeMismatchError < FlagsmithError
|
|
30
|
+
def initialize(expected_types, actual_type)
|
|
31
|
+
super(
|
|
32
|
+
SDK::Provider::ErrorCode::TYPE_MISMATCH,
|
|
33
|
+
"Expected type #{expected_types}, but got #{actual_type}"
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Raised when the Flagsmith client is not ready or properly initialized
|
|
39
|
+
class ProviderNotReadyError < FlagsmithError
|
|
40
|
+
def initialize(message = "Flagsmith provider is not ready")
|
|
41
|
+
super(
|
|
42
|
+
SDK::Provider::ErrorCode::PROVIDER_NOT_READY,
|
|
43
|
+
message
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Raised when there's an error parsing flag values
|
|
49
|
+
class ParseError < FlagsmithError
|
|
50
|
+
def initialize(message)
|
|
51
|
+
super(
|
|
52
|
+
SDK::Provider::ErrorCode::PARSE_ERROR,
|
|
53
|
+
"Failed to parse flag value: #{message}"
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Raised for general Flagsmith SDK errors
|
|
59
|
+
class FlagsmithClientError < FlagsmithError
|
|
60
|
+
def initialize(message)
|
|
61
|
+
super(
|
|
62
|
+
SDK::Provider::ErrorCode::GENERAL,
|
|
63
|
+
"Flagsmith client error: #{message}"
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Raised when evaluation context is invalid
|
|
69
|
+
class InvalidContextError < FlagsmithError
|
|
70
|
+
def initialize(message)
|
|
71
|
+
super(
|
|
72
|
+
SDK::Provider::ErrorCode::INVALID_CONTEXT,
|
|
73
|
+
"Invalid evaluation context: #{message}"
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module OpenFeature
|
|
6
|
+
module Flagsmith
|
|
7
|
+
# Configuration options for the Flagsmith OpenFeature provider
|
|
8
|
+
class Options
|
|
9
|
+
attr_reader :environment_key, :api_url, :enable_local_evaluation,
|
|
10
|
+
:request_timeout_seconds, :enable_analytics,
|
|
11
|
+
:environment_refresh_interval_seconds
|
|
12
|
+
|
|
13
|
+
DEFAULT_API_URL = "https://edge.api.flagsmith.com/api/v1/"
|
|
14
|
+
DEFAULT_REQUEST_TIMEOUT = 10
|
|
15
|
+
DEFAULT_REFRESH_INTERVAL = 60
|
|
16
|
+
|
|
17
|
+
def initialize(
|
|
18
|
+
environment_key:,
|
|
19
|
+
api_url: DEFAULT_API_URL,
|
|
20
|
+
enable_local_evaluation: false,
|
|
21
|
+
request_timeout_seconds: DEFAULT_REQUEST_TIMEOUT,
|
|
22
|
+
enable_analytics: false,
|
|
23
|
+
environment_refresh_interval_seconds: DEFAULT_REFRESH_INTERVAL
|
|
24
|
+
)
|
|
25
|
+
validate_environment_key(environment_key: environment_key)
|
|
26
|
+
validate_api_url(api_url: api_url)
|
|
27
|
+
validate_timeout(timeout: request_timeout_seconds)
|
|
28
|
+
validate_refresh_interval(interval: environment_refresh_interval_seconds)
|
|
29
|
+
|
|
30
|
+
@environment_key = environment_key
|
|
31
|
+
@api_url = api_url
|
|
32
|
+
@enable_local_evaluation = enable_local_evaluation
|
|
33
|
+
@request_timeout_seconds = request_timeout_seconds
|
|
34
|
+
@enable_analytics = enable_analytics
|
|
35
|
+
@environment_refresh_interval_seconds = environment_refresh_interval_seconds
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def local_evaluation?
|
|
39
|
+
@enable_local_evaluation
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def analytics_enabled?
|
|
43
|
+
@enable_analytics
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def validate_environment_key(environment_key: nil)
|
|
49
|
+
if environment_key.nil? || environment_key.to_s.strip.empty?
|
|
50
|
+
raise ArgumentError, "environment_key is required and cannot be empty"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def validate_api_url(api_url: nil)
|
|
55
|
+
return if api_url.nil?
|
|
56
|
+
|
|
57
|
+
uri = URI.parse(api_url)
|
|
58
|
+
unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
59
|
+
raise ArgumentError, "Invalid URL for api_url: #{api_url}"
|
|
60
|
+
end
|
|
61
|
+
rescue URI::InvalidURIError
|
|
62
|
+
raise ArgumentError, "Invalid URL for api_url: #{api_url}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def validate_timeout(timeout: nil)
|
|
66
|
+
return if timeout.nil?
|
|
67
|
+
|
|
68
|
+
unless timeout.is_a?(Integer) && timeout.positive?
|
|
69
|
+
raise ArgumentError, "request_timeout_seconds must be a positive integer"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def validate_refresh_interval(interval: nil)
|
|
74
|
+
return if interval.nil?
|
|
75
|
+
|
|
76
|
+
unless interval.is_a?(Integer) && interval.positive?
|
|
77
|
+
raise ArgumentError, "environment_refresh_interval_seconds must be a positive integer"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|