resteze 0.3.1 → 0.4.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.
@@ -0,0 +1,681 @@
1
+ # Configuration Guide
2
+
3
+ ## Table of Contents
4
+ - [Basic Configuration](#basic-configuration)
5
+ - [Environment-based Configuration](#environment-based-configuration)
6
+ - [Dynamic Configuration](#dynamic-configuration)
7
+ - [Connection Configuration](#connection-configuration)
8
+ - [Logging Configuration](#logging-configuration)
9
+ - [Proxy Configuration](#proxy-configuration)
10
+ - [Advanced Configuration Patterns](#advanced-configuration-patterns)
11
+
12
+ ## Basic Configuration
13
+
14
+ ### Minimal Setup
15
+
16
+ ```ruby
17
+ require 'resteze'
18
+
19
+ module MyApi
20
+ include Resteze
21
+
22
+ configure do |config|
23
+ config.api_base = 'https://api.example.com/'
24
+ end
25
+ end
26
+ ```
27
+
28
+ ### Standard Configuration
29
+
30
+ ```ruby
31
+ module MyApi
32
+ include Resteze
33
+
34
+ configure do |config|
35
+ # API endpoint
36
+ config.api_base = 'https://api.example.com/'
37
+
38
+ # Timeouts (in seconds)
39
+ config.open_timeout = 30 # Connection timeout
40
+ config.read_timeout = 60 # Read timeout
41
+
42
+ # Logging
43
+ config.logger = Logger.new($stdout)
44
+
45
+ # Proxy (optional)
46
+ config.proxy = ENV['HTTP_PROXY']
47
+ end
48
+ end
49
+ ```
50
+
51
+ ### Custom Configuration Properties
52
+
53
+ ```ruby
54
+ module MyApi
55
+ include Resteze
56
+
57
+ # Define custom properties
58
+ class << self
59
+ attr_accessor :api_key,
60
+ :api_secret,
61
+ :environment,
62
+ :rate_limit,
63
+ :retry_count,
64
+ :cache_ttl
65
+ end
66
+
67
+ configure do |config|
68
+ # Standard Resteze config
69
+ config.api_base = 'https://api.example.com/'
70
+ config.open_timeout = 30
71
+ config.read_timeout = 60
72
+
73
+ # Custom config
74
+ config.api_key = ENV['API_KEY']
75
+ config.api_secret = ENV['API_SECRET']
76
+ config.environment = ENV['API_ENV'] || 'production'
77
+ config.rate_limit = 100 # requests per minute
78
+ config.retry_count = 3
79
+ config.cache_ttl = 300 # 5 minutes
80
+ end
81
+ end
82
+ ```
83
+
84
+ ## Environment-based Configuration
85
+
86
+ ### Using Rails Environments
87
+
88
+ ```ruby
89
+ module MyApi
90
+ include Resteze
91
+
92
+ configure do |config|
93
+ case Rails.env
94
+ when 'production'
95
+ config.api_base = 'https://api.example.com/'
96
+ config.logger = Rails.logger
97
+ config.open_timeout = 30
98
+ config.read_timeout = 60
99
+ when 'staging'
100
+ config.api_base = 'https://staging-api.example.com/'
101
+ config.logger = Rails.logger
102
+ config.open_timeout = 45
103
+ config.read_timeout = 90
104
+ when 'development'
105
+ config.api_base = 'http://localhost:3000/'
106
+ config.logger = Logger.new($stdout)
107
+ config.logger.level = Logger::DEBUG
108
+ config.open_timeout = 60
109
+ config.read_timeout = 120
110
+ when 'test'
111
+ config.api_base = 'http://test.local/'
112
+ config.logger = Logger.new(nil) # Disable logging in tests
113
+ config.open_timeout = 5
114
+ config.read_timeout = 5
115
+ end
116
+ end
117
+ end
118
+ ```
119
+
120
+ ### Using Environment Variables
121
+
122
+ ```ruby
123
+ module MyApi
124
+ include Resteze
125
+
126
+ configure do |config|
127
+ # Required environment variables
128
+ config.api_base = ENV.fetch('MY_API_URL') do
129
+ raise "MY_API_URL environment variable is required"
130
+ end
131
+
132
+ # Optional with defaults
133
+ config.open_timeout = ENV.fetch('MY_API_OPEN_TIMEOUT', 30).to_i
134
+ config.read_timeout = ENV.fetch('MY_API_READ_TIMEOUT', 60).to_i
135
+
136
+ # Conditional configuration
137
+ if ENV['MY_API_DEBUG'] == 'true'
138
+ config.logger = Logger.new($stdout)
139
+ config.logger.level = Logger::DEBUG
140
+ else
141
+ config.logger = Logger.new('log/my_api.log')
142
+ config.logger.level = Logger::INFO
143
+ end
144
+
145
+ # Proxy configuration
146
+ config.proxy = ENV['HTTP_PROXY'] if ENV['HTTP_PROXY']
147
+ end
148
+ end
149
+ ```
150
+
151
+ ### Using Configuration Files
152
+
153
+ ```ruby
154
+ # config/my_api.yml
155
+ production:
156
+ api_base: "https://api.example.com/"
157
+ api_key: <%= ENV['MY_API_KEY'] %>
158
+ open_timeout: 30
159
+ read_timeout: 60
160
+
161
+ development:
162
+ api_base: "http://localhost:3000/"
163
+ api_key: "development-key"
164
+ open_timeout: 60
165
+ read_timeout: 120
166
+
167
+ test:
168
+ api_base: "http://test.local/"
169
+ api_key: "test-key"
170
+ open_timeout: 5
171
+ read_timeout: 5
172
+ ```
173
+
174
+ ```ruby
175
+ # Load configuration from YAML
176
+ require 'yaml'
177
+ require 'erb'
178
+
179
+ module MyApi
180
+ include Resteze
181
+
182
+ # Load config file
183
+ config_file = File.join(Rails.root, 'config', 'my_api.yml')
184
+ config_data = YAML.safe_load(ERB.new(File.read(config_file)).result)
185
+ settings = config_data[Rails.env]
186
+
187
+ configure do |config|
188
+ settings.each do |key, value|
189
+ config.send("#{key}=", value)
190
+ end
191
+ end
192
+ end
193
+ ```
194
+
195
+ ## Dynamic Configuration
196
+
197
+ ### Runtime Configuration Changes
198
+
199
+ ```ruby
200
+ module MyApi
201
+ include Resteze
202
+
203
+ class << self
204
+ def reconfigure
205
+ yield self if block_given?
206
+ end
207
+
208
+ def reset_configuration!
209
+ configure do |config|
210
+ config.api_base = default_api_base
211
+ config.open_timeout = 30
212
+ config.read_timeout = 60
213
+ config.logger = Logger.new($stdout)
214
+ end
215
+ end
216
+
217
+ private
218
+
219
+ def default_api_base
220
+ 'https://api.example.com/'
221
+ end
222
+ end
223
+ end
224
+
225
+ # Usage
226
+ MyApi.reconfigure do |config|
227
+ config.api_base = 'https://new-api.example.com/'
228
+ config.api_key = 'new-key'
229
+ end
230
+
231
+ # Reset to defaults
232
+ MyApi.reset_configuration!
233
+ ```
234
+
235
+ ### Per-Request Configuration
236
+
237
+ ```ruby
238
+ module MyApi
239
+ class Client < Resteze::Client
240
+ def with_timeout(open: nil, read: nil)
241
+ old_open = api_module.open_timeout
242
+ old_read = api_module.read_timeout
243
+
244
+ api_module.open_timeout = open if open
245
+ api_module.read_timeout = read if read
246
+
247
+ yield
248
+ ensure
249
+ api_module.open_timeout = old_open
250
+ api_module.read_timeout = old_read
251
+ end
252
+ end
253
+
254
+ class User < ApiResource
255
+ # Use custom timeout for slow endpoints
256
+ def self.export_all
257
+ Client.active_client.with_timeout(read: 300) do
258
+ request(:get, "#{resource_path}/export")
259
+ end
260
+ end
261
+ end
262
+ end
263
+ ```
264
+
265
+ ### Multi-tenant Configuration
266
+
267
+ ```ruby
268
+ module MyApi
269
+ include Resteze
270
+
271
+ class << self
272
+ def for_tenant(tenant_id)
273
+ Thread.current[:my_api_tenant] = tenant_id
274
+ yield
275
+ ensure
276
+ Thread.current[:my_api_tenant] = nil
277
+ end
278
+
279
+ def current_tenant
280
+ Thread.current[:my_api_tenant]
281
+ end
282
+ end
283
+
284
+ class Client < Resteze::Client
285
+ def request_headers
286
+ headers = super
287
+ if api_module.current_tenant
288
+ headers['X-Tenant-ID'] = api_module.current_tenant
289
+ end
290
+ headers
291
+ end
292
+
293
+ def self.api_url(path = "")
294
+ base = if api_module.current_tenant
295
+ "https://#{api_module.current_tenant}.api.example.com/"
296
+ else
297
+ api_module.api_base
298
+ end
299
+ [base.chomp("/"), path].join
300
+ end
301
+ end
302
+ end
303
+
304
+ # Usage
305
+ MyApi.for_tenant('acme') do
306
+ user = MyApi::User.retrieve('123') # Uses acme.api.example.com
307
+ end
308
+ ```
309
+
310
+ ## Connection Configuration
311
+
312
+ ### Faraday Connection Setup
313
+
314
+ ```ruby
315
+ module MyApi
316
+ class Client < Resteze::Client
317
+ def self.default_connection
318
+ @default_connection ||= Faraday.new do |conn|
319
+ # Request middleware (order matters!)
320
+ conn.request :json # Encode request bodies as JSON
321
+ conn.request :retry, max: 3, interval: 0.5 # Retry failed requests
322
+ conn.request :authorization, 'Bearer', -> { api_module.api_key }
323
+
324
+ # Response middleware
325
+ conn.response :json, content_type: /\bjson$/ # Parse JSON responses
326
+ conn.response :logger, api_module.logger, bodies: true # Log requests
327
+
328
+ # Error handling
329
+ conn.use Middleware::RaiseError
330
+
331
+ # Adapter (must be last)
332
+ conn.adapter :net_http_persistent # Use persistent connections
333
+ end
334
+ end
335
+ end
336
+ end
337
+ ```
338
+
339
+ ### SSL/TLS Configuration
340
+
341
+ ```ruby
342
+ module MyApi
343
+ class Client < Resteze::Client
344
+ def self.default_connection
345
+ @default_connection ||= Faraday.new(ssl: ssl_options) do |conn|
346
+ conn.use Middleware::RaiseError
347
+ conn.adapter Faraday.default_adapter
348
+ end
349
+ end
350
+
351
+ private
352
+
353
+ def self.ssl_options
354
+ {
355
+ # Certificate verification
356
+ verify: true, # Verify SSL certificates
357
+
358
+ # Client certificates
359
+ client_cert: OpenSSL::X509::Certificate.new(File.read('client.crt')),
360
+ client_key: OpenSSL::PKey::RSA.new(File.read('client.key')),
361
+
362
+ # CA bundle
363
+ ca_file: '/path/to/ca_bundle.pem',
364
+
365
+ # Minimum TLS version
366
+ version: :TLSv1_2,
367
+
368
+ # Cipher suites
369
+ ciphers: 'HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA'
370
+ }
371
+ rescue => e
372
+ Rails.logger.error "Failed to load SSL certificates: #{e.message}"
373
+ { verify: true } # Fall back to default verification
374
+ end
375
+ end
376
+ end
377
+ ```
378
+
379
+ ### Custom Adapters
380
+
381
+ ```ruby
382
+ module MyApi
383
+ class Client < Resteze::Client
384
+ def self.default_connection
385
+ @default_connection ||= Faraday.new do |conn|
386
+ conn.use Middleware::RaiseError
387
+
388
+ # Choose adapter based on environment
389
+ case Rails.env
390
+ when 'production'
391
+ # Use Typhoeus for better performance
392
+ conn.adapter :typhoeus
393
+ when 'test'
394
+ # Use test adapter for testing
395
+ conn.adapter :test do |stub|
396
+ stub.get('/users/123') { [200, {}, { id: 123 }.to_json] }
397
+ end
398
+ else
399
+ # Default adapter
400
+ conn.adapter Faraday.default_adapter
401
+ end
402
+ end
403
+ end
404
+ end
405
+ end
406
+ ```
407
+
408
+ ## Logging Configuration
409
+
410
+ ### Custom Logger
411
+
412
+ ```ruby
413
+ require 'logger'
414
+
415
+ module MyApi
416
+ include Resteze
417
+
418
+ class ApiLogger < Logger
419
+ def format_message(severity, timestamp, progname, msg)
420
+ {
421
+ severity: severity,
422
+ timestamp: timestamp.iso8601,
423
+ service: 'my_api',
424
+ message: msg
425
+ }.to_json + "\n"
426
+ end
427
+ end
428
+
429
+ configure do |config|
430
+ config.logger = ApiLogger.new('log/my_api.log', 'daily')
431
+ config.logger.level = Rails.env.production? ? Logger::INFO : Logger::DEBUG
432
+ end
433
+ end
434
+ ```
435
+
436
+ ### Structured Logging
437
+
438
+ ```ruby
439
+ module MyApi
440
+ class Client < Resteze::Client
441
+ private
442
+
443
+ def log_request(context)
444
+ logger.info({
445
+ event: 'api_request',
446
+ method: context.method,
447
+ path: context.path,
448
+ headers: sanitize_headers(context.headers),
449
+ params: sanitize_params(context.query_params)
450
+ }.to_json)
451
+ end
452
+
453
+ def log_response(context, request_start, response)
454
+ logger.info({
455
+ event: 'api_response',
456
+ method: context.method,
457
+ path: context.path,
458
+ status: response.status,
459
+ duration_ms: ((Time.now - request_start) * 1000).round,
460
+ request_id: response.headers['x-request-id']
461
+ }.to_json)
462
+ end
463
+
464
+ def sanitize_headers(headers)
465
+ headers.transform_values do |value|
466
+ value.to_s.include?('Bearer') ? '[REDACTED]' : value
467
+ end
468
+ end
469
+
470
+ def sanitize_params(params)
471
+ params.transform_values do |value|
472
+ sensitive_param?(value) ? '[REDACTED]' : value
473
+ end
474
+ end
475
+
476
+ def sensitive_param?(value)
477
+ value.to_s.match?(/password|token|secret|key/i)
478
+ end
479
+ end
480
+ end
481
+ ```
482
+
483
+ ## Proxy Configuration
484
+
485
+ ### Basic Proxy
486
+
487
+ ```ruby
488
+ module MyApi
489
+ include Resteze
490
+
491
+ configure do |config|
492
+ config.proxy = 'http://proxy.example.com:8080'
493
+ end
494
+ end
495
+ ```
496
+
497
+ ### Authenticated Proxy
498
+
499
+ ```ruby
500
+ module MyApi
501
+ include Resteze
502
+
503
+ configure do |config|
504
+ config.proxy = {
505
+ uri: 'http://proxy.example.com:8080',
506
+ user: ENV['PROXY_USER'],
507
+ password: ENV['PROXY_PASSWORD']
508
+ }
509
+ end
510
+ end
511
+ ```
512
+
513
+ ### Conditional Proxy
514
+
515
+ ```ruby
516
+ module MyApi
517
+ include Resteze
518
+
519
+ configure do |config|
520
+ # Only use proxy in production
521
+ if Rails.env.production?
522
+ config.proxy = ENV['HTTP_PROXY']
523
+ end
524
+
525
+ # Or use different proxies per environment
526
+ config.proxy = case Rails.env
527
+ when 'production'
528
+ 'http://prod-proxy.example.com:8080'
529
+ when 'staging'
530
+ 'http://staging-proxy.example.com:8080'
531
+ else
532
+ nil # No proxy for development/test
533
+ end
534
+ end
535
+ end
536
+ ```
537
+
538
+ ## Advanced Configuration Patterns
539
+
540
+ ### Configuration with Validation
541
+
542
+ ```ruby
543
+ module MyApi
544
+ include Resteze
545
+
546
+ class Configuration
547
+ REQUIRED_SETTINGS = %i[api_base api_key].freeze
548
+ VALID_ENVIRONMENTS = %w[production staging development test].freeze
549
+
550
+ def self.validate!(config)
551
+ REQUIRED_SETTINGS.each do |setting|
552
+ value = config.send(setting)
553
+ if value.nil? || value.to_s.empty?
554
+ raise "Configuration error: #{setting} is required"
555
+ end
556
+ end
557
+
558
+ # Validate API base URL
559
+ unless config.api_base =~ URI::DEFAULT_PARSER.make_regexp
560
+ raise "Configuration error: api_base must be a valid URL"
561
+ end
562
+
563
+ # Validate environment
564
+ if config.respond_to?(:environment)
565
+ unless VALID_ENVIRONMENTS.include?(config.environment)
566
+ raise "Configuration error: invalid environment '#{config.environment}'"
567
+ end
568
+ end
569
+
570
+ # Validate timeouts
571
+ if config.open_timeout <= 0 || config.read_timeout <= 0
572
+ raise "Configuration error: timeouts must be positive"
573
+ end
574
+ end
575
+ end
576
+
577
+ configure do |config|
578
+ config.api_base = ENV['MY_API_URL']
579
+ config.api_key = ENV['MY_API_KEY']
580
+ config.open_timeout = 30
581
+ config.read_timeout = 60
582
+
583
+ # Validate configuration
584
+ Configuration.validate!(config)
585
+ end
586
+ end
587
+ ```
588
+
589
+ ### Feature Flags Configuration
590
+
591
+ ```ruby
592
+ module MyApi
593
+ include Resteze
594
+
595
+ class << self
596
+ attr_accessor :features
597
+
598
+ def feature_enabled?(feature)
599
+ features && features[feature] == true
600
+ end
601
+ end
602
+
603
+ configure do |config|
604
+ config.api_base = 'https://api.example.com/'
605
+
606
+ # Feature flags
607
+ config.features = {
608
+ caching: Rails.env.production?,
609
+ retry: true,
610
+ circuit_breaker: Rails.env.production?,
611
+ detailed_logging: Rails.env.development?,
612
+ batch_requests: true
613
+ }
614
+ end
615
+
616
+ class Client < Resteze::Client
617
+ def execute_request(method, path, **options)
618
+ # Use feature flags
619
+ if api_module.feature_enabled?(:caching) && method == :get
620
+ cached_request(method, path, **options)
621
+ else
622
+ super
623
+ end
624
+ end
625
+
626
+ private
627
+
628
+ def cached_request(method, path, **options)
629
+ cache_key = "#{path}:#{options.hash}"
630
+ Rails.cache.fetch(cache_key, expires_in: 5.minutes) do
631
+ super(method, path, **options)
632
+ end
633
+ end
634
+ end
635
+ end
636
+ ```
637
+
638
+ ### Configuration Registry
639
+
640
+ ```ruby
641
+ module MyApi
642
+ include Resteze
643
+
644
+ class ConfigurationRegistry
645
+ def self.configurations
646
+ @configurations ||= {}
647
+ end
648
+
649
+ def self.register(name, &block)
650
+ configurations[name] = block
651
+ end
652
+
653
+ def self.apply(name)
654
+ config_block = configurations[name]
655
+ raise "Unknown configuration: #{name}" unless config_block
656
+
657
+ MyApi.configure(&config_block)
658
+ end
659
+ end
660
+
661
+ # Register configurations
662
+ ConfigurationRegistry.register(:production) do |config|
663
+ config.api_base = 'https://api.example.com/'
664
+ config.api_key = ENV['PROD_API_KEY']
665
+ config.open_timeout = 30
666
+ config.read_timeout = 60
667
+ end
668
+
669
+ ConfigurationRegistry.register(:development) do |config|
670
+ config.api_base = 'http://localhost:3000/'
671
+ config.api_key = 'dev-key'
672
+ config.open_timeout = 60
673
+ config.read_timeout = 120
674
+ config.logger = Logger.new($stdout)
675
+ config.logger.level = Logger::DEBUG
676
+ end
677
+
678
+ # Apply configuration
679
+ ConfigurationRegistry.apply(Rails.env.to_sym)
680
+ end
681
+ ```