smplkit 1.0.13 → 1.0.14

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.
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "faraday"
4
3
  require "json"
5
- require "concurrent"
6
4
 
7
5
  module Smplkit
8
6
  # Top-level management client. Owns the HTTP transports + CRUD APIs for
@@ -21,6 +19,13 @@ module Smplkit
21
19
  #
22
20
  # Constructable both as +Smplkit::ManagementClient.new+ (standalone) and as
23
21
  # +Smplkit::Client#manage+ (shared transports).
22
+ #
23
+ # Each namespace is wired to a generated +SmplkitGeneratedClient+ +ApiClient+
24
+ # under the hood — auth, request encoding, and response parsing flow through
25
+ # the openapi-generator-produced layer in +lib/smplkit/_generated+. The
26
+ # wrapper layer keeps the customer-facing domain models (+Flag+, +Config+,
27
+ # etc.) and converts at the boundary via the existing
28
+ # +<resource>_from_resource+ helpers.
24
29
  class ManagementClient
25
30
  attr_reader :contexts, :context_types, :environments, :account_settings,
26
31
  :config, :flags, :loggers, :log_groups
@@ -39,95 +44,110 @@ module Smplkit
39
44
  Smplkit.enable_debug if cfg.debug
40
45
 
41
46
  @resolved = cfg
42
- @app_http = build_http(ConfigResolution.service_url(cfg.scheme, "app", cfg.base_domain), cfg.api_key)
43
- @config_http = build_http(ConfigResolution.service_url(cfg.scheme, "config", cfg.base_domain), cfg.api_key)
44
- @flags_http = build_http(ConfigResolution.service_url(cfg.scheme, "flags", cfg.base_domain), cfg.api_key)
45
- @logging_http = build_http(ConfigResolution.service_url(cfg.scheme, "logging", cfg.base_domain), cfg.api_key)
46
-
47
- @contexts = ContextsNamespace.new(@app_http)
48
- @context_types = ContextTypesNamespace.new(@app_http)
49
- @environments = EnvironmentsNamespace.new(@app_http)
50
- @account_settings = AccountSettingsNamespace.new(@app_http)
51
- @config = ConfigNamespace.new(@config_http)
52
- @flags = FlagsNamespace.new(@flags_http)
53
- @loggers = LoggersNamespace.new(@logging_http)
54
- @log_groups = LogGroupsNamespace.new(@logging_http)
47
+
48
+ @app_api_client = build_api_client(SmplkitGeneratedClient::App, "app", cfg)
49
+ @config_api_client = build_api_client(SmplkitGeneratedClient::Config, "config", cfg)
50
+ @flags_api_client = build_api_client(SmplkitGeneratedClient::Flags, "flags", cfg)
51
+ @logging_api_client = build_api_client(SmplkitGeneratedClient::Logging, "logging", cfg)
52
+
53
+ @contexts = ContextsNamespace.new(@app_api_client)
54
+ @context_types = ContextTypesNamespace.new(@app_api_client)
55
+ @environments = EnvironmentsNamespace.new(@app_api_client)
56
+ @account_settings = AccountSettingsNamespace.new(@app_api_client)
57
+ @config = ConfigNamespace.new(@config_api_client)
58
+ @flags = FlagsNamespace.new(@flags_api_client)
59
+ @loggers = LoggersNamespace.new(@logging_api_client)
60
+ @log_groups = LogGroupsNamespace.new(@logging_api_client)
55
61
  end
56
62
 
57
63
  def close
58
- [@app_http, @config_http, @flags_http, @logging_http].each do |conn|
59
- conn.close if conn.respond_to?(:close)
60
- end
64
+ # The generated ApiClient owns Faraday connections that release on GC.
65
+ # No explicit shutdown is exposed; this stub keeps the API stable.
61
66
  end
62
67
 
63
68
  def _resolved = @resolved
64
- def _app_http = @app_http
65
- def _config_http = @config_http
66
- def _flags_http = @flags_http
67
- def _logging_http = @logging_http
69
+ def _app_http = @app_api_client
70
+ def _config_http = @config_api_client
71
+ def _flags_http = @flags_api_client
72
+ def _logging_http = @logging_api_client
68
73
 
69
74
  private
70
75
 
71
- def build_http(base_url, api_key)
72
- Faraday.new(url: base_url) do |f|
73
- f.request :authorization, "Bearer", api_key
74
- f.headers["Content-Type"] = "application/vnd.api+json"
75
- f.headers["Accept"] = "application/vnd.api+json"
76
- f.headers["User-Agent"] = "smplkit-ruby-sdk/#{Smplkit::VERSION}"
77
- f.adapter Faraday.default_adapter
76
+ def build_api_client(generated_module, subdomain, cfg)
77
+ configuration = generated_module::Configuration.new
78
+ configuration.scheme = cfg.scheme
79
+ configuration.host = "#{subdomain}.#{cfg.base_domain}"
80
+ configuration.base_path = ""
81
+ configuration.access_token = cfg.api_key
82
+ configuration.debugging = cfg.debug
83
+ generated_module::ApiClient.new(configuration).tap do |client|
84
+ client.default_headers["User-Agent"] = "smplkit-ruby-sdk/#{Smplkit::VERSION}"
78
85
  end
79
86
  end
80
87
 
81
88
  # ------------------------------------------------------------------
82
- # Sub-namespaces
89
+ # Shared error-mapping wrapper
83
90
  # ------------------------------------------------------------------
84
91
 
85
- # Shared HTTP helpers used by every namespace below.
86
- #
87
- # All methods are prefixed +http_+ to avoid colliding with the public
88
- # +get+ / +list+ accessors on each namespace.
89
- module HttpHelpers
90
- private
92
+ # Wraps a generated-API call and converts any +ApiError+ raised by the
93
+ # generated layer into the +Smplkit::Error+ hierarchy. Connection-level
94
+ # failures (no response from the server) become +Smplkit::ConnectionError+;
95
+ # status-coded failures route through +Errors.raise_for_status+ which
96
+ # emits +NotFoundError+ / +ConflictError+ / +ValidationError+ / +Error+
97
+ # depending on the JSON:API body.
98
+ module ErrorMapping
99
+ module_function
100
+
101
+ def call
102
+ yield
103
+ rescue StandardError => e
104
+ raise unless generated_api_error?(e)
91
105
 
92
- def http_get(path)
93
- response = @http.get(path)
94
- Errors.raise_for_status(response.status, response.body)
95
- response.body.to_s.empty? ? {} : JSON.parse(response.body)
96
- end
106
+ raise Smplkit::ConnectionError, e.message.to_s if e.code.nil? || e.code.zero?
97
107
 
98
- def http_list(path)
99
- body = http_get(path)
100
- body["data"] || []
108
+ Smplkit::Errors.raise_for_status(e.code, e.response_body.to_s)
109
+ # raise_for_status only returns on 2xx; if we get here the generated
110
+ # layer raised on a 2xx (shouldn't happen) so re-raise the original.
111
+ raise
101
112
  end
102
113
 
103
- def http_post(path, body)
104
- response = @http.post(path) do |req|
105
- req.body = body.is_a?(String) ? body : JSON.generate(body)
106
- end
107
- Errors.raise_for_status(response.status, response.body)
108
- response.body.to_s.empty? ? {} : JSON.parse(response.body)
114
+ def generated_api_error?(err)
115
+ klass_name = err.class.name.to_s
116
+ klass_name.start_with?("SmplkitGeneratedClient::") && klass_name.end_with?("::ApiError")
109
117
  end
118
+ end
110
119
 
111
- def http_put(path, body)
112
- response = @http.put(path) do |req|
113
- req.body = body.is_a?(String) ? body : JSON.generate(body)
120
+ # Deep-stringify Hash keys so resources returned by generated +to_hash+
121
+ # (symbol-keyed) match what the wrapper helpers expect (string-keyed).
122
+ module ResourceShim
123
+ module_function
124
+
125
+ def stringify(value)
126
+ case value
127
+ when Hash
128
+ value.each_with_object({}) { |(k, v), out| out[k.to_s] = stringify(v) }
129
+ when Array
130
+ value.map { |v| stringify(v) }
131
+ else
132
+ value
114
133
  end
115
- Errors.raise_for_status(response.status, response.body)
116
- response.body.to_s.empty? ? {} : JSON.parse(response.body)
117
134
  end
118
135
 
119
- def http_delete(path)
120
- response = @http.delete(path)
121
- Errors.raise_for_status(response.status, response.body)
122
- true
136
+ # Convenience: produce a string-keyed Hash from a generated model.
137
+ def from_model(model)
138
+ return {} if model.nil?
139
+
140
+ stringify(model.to_hash)
123
141
  end
124
142
  end
125
143
 
126
- class ContextsNamespace
127
- include HttpHelpers
144
+ # ------------------------------------------------------------------
145
+ # Sub-namespaces
146
+ # ------------------------------------------------------------------
128
147
 
129
- def initialize(http)
130
- @http = http
148
+ class ContextsNamespace
149
+ def initialize(api_client)
150
+ @api = SmplkitGeneratedClient::App::ContextsApi.new(api_client)
131
151
  @buffer = Management::ContextRegistrationBuffer.new
132
152
  end
133
153
 
@@ -142,38 +162,46 @@ module Smplkit
142
162
  batch = @buffer.drain
143
163
  return if batch.empty?
144
164
 
145
- body = { "data" => { "type" => "context_bulk_register", "attributes" => { "contexts" => batch } } }
146
- http_post("/api/v1/contexts/bulk", body)
165
+ items = batch.map do |entry|
166
+ SmplkitGeneratedClient::App::ContextBulkItem.new(
167
+ type: entry["type"], key: entry["key"], attributes: entry["attributes"] || {}
168
+ )
169
+ end
170
+ body = SmplkitGeneratedClient::App::ContextBulkRegister.new(contexts: items)
171
+ ErrorMapping.call { @api.bulk_register_contexts(body) }
147
172
  rescue StandardError => e
148
173
  Smplkit.debug("registration", "context flush failed: #{e.class}: #{e.message}")
149
174
  end
150
175
 
151
176
  def list
152
- list_resp = http_list("/api/v1/contexts")
153
- list_resp.map { |r| context_from_resource(r) }
177
+ response = ErrorMapping.call { @api.list_contexts }
178
+ (response.data || []).map { |r| context_from_resource(ResourceShim.from_model(r)) }
154
179
  end
155
180
 
156
181
  def get(id_or_type, key = nil)
157
182
  type, ckey = split_id(id_or_type, key)
158
- resource = http_get("/api/v1/contexts/#{type}:#{ckey}")
159
- context_from_resource(resource["data"])
183
+ response = ErrorMapping.call { @api.get_context("#{type}:#{ckey}") }
184
+ context_from_resource(ResourceShim.from_model(response.data))
160
185
  end
161
186
 
162
187
  def delete(id_or_type, key = nil)
163
188
  type, ckey = split_id(id_or_type, key)
164
- http_delete("/api/v1/contexts/#{type}:#{ckey}")
189
+ ErrorMapping.call { @api.delete_context("#{type}:#{ckey}") }
190
+ true
165
191
  end
166
192
 
167
193
  def _save_context(ctx)
168
- body = {
169
- "data" => {
170
- "type" => "context",
171
- "id" => ctx.id,
172
- "attributes" => { "type" => ctx.type, "key" => ctx.key, "attributes" => ctx.attributes }.compact
173
- }
174
- }
175
- resp = http_put("/api/v1/contexts/#{ctx.id}", body)
176
- context_from_resource(resp["data"]).tap { |c| c._bind_client(self) }
194
+ body = SmplkitGeneratedClient::App::ContextResponse.new(
195
+ data: SmplkitGeneratedClient::App::ContextResource.new(
196
+ type: "context",
197
+ id: ctx.id,
198
+ attributes: SmplkitGeneratedClient::App::Context.new(
199
+ name: ctx.name, context_type: ctx.type, attributes: ctx.attributes
200
+ )
201
+ )
202
+ )
203
+ response = ErrorMapping.call { @api.update_context(ctx.id, body) }
204
+ context_from_resource(ResourceShim.from_model(response.data)).tap { |c| c._bind_client(self) }
177
205
  end
178
206
 
179
207
  private
@@ -192,7 +220,7 @@ module Smplkit
192
220
  def context_from_resource(resource)
193
221
  attrs = resource["attributes"] || {}
194
222
  Smplkit::Context.new(
195
- attrs["type"] || resource["id"].to_s.split(":").first,
223
+ attrs["context_type"] || attrs["type"] || resource["id"].to_s.split(":").first,
196
224
  attrs["key"] || resource["id"].to_s.split(":", 2).last,
197
225
  attrs["attributes"] || {},
198
226
  name: attrs["name"],
@@ -203,24 +231,23 @@ module Smplkit
203
231
  end
204
232
 
205
233
  class ContextTypesNamespace
206
- include HttpHelpers
207
-
208
- def initialize(http)
209
- @http = http
234
+ def initialize(api_client)
235
+ @api = SmplkitGeneratedClient::App::ContextTypesApi.new(api_client)
210
236
  end
211
237
 
212
238
  def list
213
- list_resp = http_list("/api/v1/context_types")
214
- list_resp.map { |r| from_resource(r) }
239
+ response = ErrorMapping.call { @api.list_context_types }
240
+ (response.data || []).map { |r| from_resource(ResourceShim.from_model(r)) }
215
241
  end
216
242
 
217
243
  def get(key)
218
- resp = http_get("/api/v1/context_types/#{key}")
219
- from_resource(resp["data"])
244
+ response = ErrorMapping.call { @api.get_context_type(key) }
245
+ from_resource(ResourceShim.from_model(response.data))
220
246
  end
221
247
 
222
248
  def delete(key)
223
- http_delete("/api/v1/context_types/#{key}")
249
+ ErrorMapping.call { @api.delete_context_type(key) }
250
+ true
224
251
  end
225
252
 
226
253
  def new_context_type(key, name: nil, description: nil)
@@ -228,25 +255,27 @@ module Smplkit
228
255
  end
229
256
 
230
257
  def _create_context_type(ct)
231
- resp = http_post("/api/v1/context_types", body_for(ct))
232
- from_resource(resp["data"])
258
+ response = ErrorMapping.call { @api.create_context_type(body_for(ct)) }
259
+ from_resource(ResourceShim.from_model(response.data))
233
260
  end
234
261
 
235
262
  def _update_context_type(ct)
236
- resp = http_put("/api/v1/context_types/#{ct.key}", body_for(ct))
237
- from_resource(resp["data"])
263
+ response = ErrorMapping.call { @api.update_context_type(ct.key, body_for(ct)) }
264
+ from_resource(ResourceShim.from_model(response.data))
238
265
  end
239
266
 
240
267
  private
241
268
 
242
269
  def body_for(ct)
243
- {
244
- "data" => {
245
- "type" => "context_type",
246
- "id" => ct.key,
247
- "attributes" => { "key" => ct.key, "name" => ct.name, "description" => ct.description }.compact
248
- }
249
- }
270
+ # ContextType server schema: name, attributes, created_at, updated_at.
271
+ # Customer-side +description+ is wrapper-only; not sent on the wire.
272
+ SmplkitGeneratedClient::App::ContextTypeResponse.new(
273
+ data: SmplkitGeneratedClient::App::ContextTypeResource.new(
274
+ type: "context_type",
275
+ id: ct.key,
276
+ attributes: SmplkitGeneratedClient::App::ContextType.new(name: ct.name)
277
+ )
278
+ )
250
279
  end
251
280
 
252
281
  def from_resource(resource)
@@ -261,24 +290,23 @@ module Smplkit
261
290
  end
262
291
 
263
292
  class EnvironmentsNamespace
264
- include HttpHelpers
265
-
266
- def initialize(http)
267
- @http = http
293
+ def initialize(api_client)
294
+ @api = SmplkitGeneratedClient::App::EnvironmentsApi.new(api_client)
268
295
  end
269
296
 
270
297
  def list
271
- list_resp = http_list("/api/v1/environments")
272
- list_resp.map { |r| from_resource(r) }
298
+ response = ErrorMapping.call { @api.list_environments }
299
+ (response.data || []).map { |r| from_resource(ResourceShim.from_model(r)) }
273
300
  end
274
301
 
275
302
  def get(key)
276
- resp = http_get("/api/v1/environments/#{key}")
277
- from_resource(resp["data"])
303
+ response = ErrorMapping.call { @api.get_environment(key) }
304
+ from_resource(ResourceShim.from_model(response.data))
278
305
  end
279
306
 
280
307
  def delete(key)
281
- http_delete("/api/v1/environments/#{key}")
308
+ ErrorMapping.call { @api.delete_environment(key) }
309
+ true
282
310
  end
283
311
 
284
312
  def new(key, name: nil, color: nil,
@@ -293,31 +321,31 @@ module Smplkit
293
321
  end
294
322
 
295
323
  def _create_environment(env)
296
- resp = http_post("/api/v1/environments", body_for(env))
297
- from_resource(resp["data"])
324
+ response = ErrorMapping.call { @api.create_environment(body_for(env)) }
325
+ from_resource(ResourceShim.from_model(response.data))
298
326
  end
299
327
 
300
328
  def _update_environment(env)
301
- resp = http_put("/api/v1/environments/#{env.key}", body_for(env))
302
- from_resource(resp["data"])
329
+ response = ErrorMapping.call { @api.update_environment(env.key, body_for(env)) }
330
+ from_resource(ResourceShim.from_model(response.data))
303
331
  end
304
332
 
305
333
  private
306
334
 
307
335
  def body_for(env)
308
- {
309
- "data" => {
310
- "type" => "environment",
311
- "id" => env.key,
312
- "attributes" => {
313
- "key" => env.key,
314
- "name" => env.name,
315
- "color" => env.color&.hex,
316
- "classification" => env.classification,
317
- "description" => env.description
318
- }.compact
319
- }
320
- }
336
+ # Environment server schema: name, color, classification.
337
+ # Customer-side +description+ stays wrapper-only.
338
+ SmplkitGeneratedClient::App::EnvironmentResponse.new(
339
+ data: SmplkitGeneratedClient::App::EnvironmentResource.new(
340
+ type: "environment",
341
+ id: env.key,
342
+ attributes: SmplkitGeneratedClient::App::Environment.new(
343
+ name: env.name,
344
+ color: env.color&.hex,
345
+ classification: env.classification
346
+ )
347
+ )
348
+ )
321
349
  end
322
350
 
323
351
  def from_resource(resource)
@@ -335,41 +363,42 @@ module Smplkit
335
363
  end
336
364
 
337
365
  class AccountSettingsNamespace
338
- include HttpHelpers
339
-
340
- def initialize(http)
341
- @http = http
366
+ def initialize(api_client)
367
+ @api = SmplkitGeneratedClient::App::AccountApi.new(api_client)
342
368
  end
343
369
 
344
370
  def get
345
- resp = http_get("/api/v1/accounts/current/settings")
346
- from_resource(resp["data"])
371
+ raw = ErrorMapping.call { @api.get_account_settings }
372
+ from_raw(raw)
347
373
  end
348
374
 
349
375
  def _update_account_settings(settings)
350
- resp = http_put("/api/v1/accounts/current/settings", body_for(settings))
351
- from_resource(resp["data"])
376
+ # The generator pulled this op without wiring a body parameter
377
+ # (the server accepts a free-form JSON object). The +debug_body+
378
+ # opt is the documented escape hatch.
379
+ raw = ErrorMapping.call do
380
+ @api.put_account_settings(debug_body: settings_body(settings))
381
+ end
382
+ from_raw(raw)
352
383
  end
353
384
 
354
385
  private
355
386
 
356
- def body_for(settings)
387
+ def settings_body(settings)
357
388
  {
358
- "data" => {
359
- "type" => "account_settings",
360
- "attributes" => {
361
- "environment_order" => settings.environment_order,
362
- "default_environment" => settings.default_environment
363
- }.compact
364
- }
365
- }
389
+ "environment_order" => settings.environment_order,
390
+ "default_environment" => settings.default_environment
391
+ }.compact
366
392
  end
367
393
 
368
- def from_resource(resource)
369
- attrs = resource["attributes"] || {}
394
+ def from_raw(raw)
395
+ attrs = raw.respond_to?(:to_hash) ? ResourceShim.stringify(raw.to_hash) : (raw || {})
396
+ if attrs.is_a?(Hash) && attrs["data"].is_a?(Hash) && attrs["data"]["attributes"]
397
+ attrs = attrs["data"]["attributes"]
398
+ end
370
399
  Management::AccountSettings.new(
371
400
  self,
372
- id: resource["id"],
401
+ id: attrs["id"],
373
402
  environment_order: attrs["environment_order"] || [],
374
403
  default_environment: attrs["default_environment"],
375
404
  updated_at: attrs["updated_at"]
@@ -378,24 +407,23 @@ module Smplkit
378
407
  end
379
408
 
380
409
  class ConfigNamespace
381
- include HttpHelpers
382
-
383
- def initialize(http)
384
- @http = http
410
+ def initialize(api_client)
411
+ @api = SmplkitGeneratedClient::Config::ConfigsApi.new(api_client)
385
412
  end
386
413
 
387
414
  def list
388
- list_resp = http_list("/api/v1/configs")
389
- list_resp.map { |r| Smplkit::Config::Helpers.config_from_json(self, r) }
415
+ response = ErrorMapping.call { @api.list_configs }
416
+ (response.data || []).map { |r| Smplkit::Config::Helpers.config_from_json(self, ResourceShim.from_model(r)) }
390
417
  end
391
418
 
392
419
  def get(key)
393
- resp = http_get("/api/v1/configs/#{key}")
394
- Smplkit::Config::Helpers.config_from_json(self, resp["data"])
420
+ response = ErrorMapping.call { @api.get_config(key) }
421
+ Smplkit::Config::Helpers.config_from_json(self, ResourceShim.from_model(response.data))
395
422
  end
396
423
 
397
424
  def delete(key)
398
- http_delete("/api/v1/configs/#{key}")
425
+ ErrorMapping.call { @api.delete_config(key) }
426
+ true
399
427
  end
400
428
 
401
429
  def new_config(key, name: nil, description: nil, parent: nil)
@@ -409,27 +437,22 @@ module Smplkit
409
437
  end
410
438
 
411
439
  def _create_config(config)
412
- body = Smplkit::Config::Helpers.build_config_request_body(config)
413
- resp = http_post("/api/v1/configs", body)
414
- Smplkit::Config::Helpers.config_from_json(self, resp["data"])
440
+ response = ErrorMapping.call { @api.create_config(config_body(config)) }
441
+ Smplkit::Config::Helpers.config_from_json(self, ResourceShim.from_model(response.data))
415
442
  end
416
443
 
417
444
  def _update_config(config)
418
- body = Smplkit::Config::Helpers.build_config_request_body(config)
419
- resp = http_put("/api/v1/configs/#{config.key}", body)
420
- Smplkit::Config::Helpers.config_from_json(self, resp["data"])
445
+ response = ErrorMapping.call { @api.update_config(config.key, config_body(config)) }
446
+ Smplkit::Config::Helpers.config_from_json(self, ResourceShim.from_model(response.data))
421
447
  end
422
448
 
423
449
  # Build the parent-chain for a given config, walking +parent_id+
424
450
  # pointers across the full config list. Mirrors the Python SDK's
425
451
  # client-side resolution — there is no server +/chain+ endpoint.
426
- #
427
- # Each chain entry is a Hash matching the wire shape that
428
- # +Smplkit::Config::Helpers.resolve_chain+ consumes.
429
452
  def fetch_chain(target_key)
430
453
  all_configs = list
431
454
  by_key = all_configs.to_h { |c| [c.key, c] }
432
- by_id = all_configs.to_h { |c| [c.id, c] }
455
+ by_id = all_configs.to_h { |c| [c.id, c] }
433
456
 
434
457
  current = by_key[target_key]
435
458
  return [] unless current
@@ -450,6 +473,46 @@ module Smplkit
450
473
 
451
474
  private
452
475
 
476
+ def config_body(config)
477
+ SmplkitGeneratedClient::Config::ConfigResponse.new(
478
+ data: SmplkitGeneratedClient::Config::ConfigResource.new(
479
+ type: "config",
480
+ id: config.key,
481
+ attributes: SmplkitGeneratedClient::Config::Config.new(
482
+ name: config.name,
483
+ description: config.description,
484
+ parent: config.parent_id,
485
+ items: config_items_to_wire(config.items),
486
+ environments: config_envs_to_wire(config.environments)
487
+ )
488
+ )
489
+ )
490
+ end
491
+
492
+ def config_items_to_wire(items)
493
+ return nil if items.nil? || items.empty?
494
+
495
+ items.to_h do |item|
496
+ [item.name, SmplkitGeneratedClient::Config::ConfigItemDefinition.new(
497
+ value: item.value, type: item.type, description: item.description
498
+ )]
499
+ end
500
+ end
501
+
502
+ def config_envs_to_wire(environments)
503
+ return nil if environments.empty?
504
+
505
+ # ConfigItemOverride only carries +value+ on the wire — environment
506
+ # overrides override the value, not the type or description.
507
+ environments.each_with_object({}) do |(env_key, env_obj), out|
508
+ values = env_obj.values_raw.each_with_object({}) do |(k, v), inner|
509
+ v_hash = v.is_a?(Hash) ? v : { "value" => v }
510
+ inner[k] = SmplkitGeneratedClient::Config::ConfigItemOverride.new(value: v_hash["value"])
511
+ end
512
+ out[env_key] = SmplkitGeneratedClient::Config::EnvironmentOverride.new(values: values)
513
+ end
514
+ end
515
+
453
516
  def config_to_chain_entry(config)
454
517
  items_hash = {}
455
518
  config.items.each do |item|
@@ -469,10 +532,8 @@ module Smplkit
469
532
  end
470
533
 
471
534
  class FlagsNamespace
472
- include HttpHelpers
473
-
474
- def initialize(http)
475
- @http = http
535
+ def initialize(api_client)
536
+ @api = SmplkitGeneratedClient::Flags::FlagsApi.new(api_client)
476
537
  @buffer = Management::FlagRegistrationBuffer.new
477
538
  end
478
539
 
@@ -485,24 +546,31 @@ module Smplkit
485
546
  batch = @buffer.drain
486
547
  return if batch.empty?
487
548
 
488
- body = { "data" => { "type" => "flag_bulk_register", "attributes" => { "flags" => batch } } }
489
- http_post("/api/v1/flags/bulk", body)
549
+ flag_items = batch.map do |entry|
550
+ SmplkitGeneratedClient::Flags::FlagBulkItem.new(
551
+ id: entry["id"], type: entry["type"], default: entry["default"],
552
+ service: entry["service"], environment: entry["environment"]
553
+ )
554
+ end
555
+ body = SmplkitGeneratedClient::Flags::FlagBulkRequest.new(flags: flag_items)
556
+ ErrorMapping.call { @api.bulk_register_flags(body) }
490
557
  rescue StandardError => e
491
558
  Smplkit.debug("registration", "flag flush failed: #{e.class}: #{e.message}")
492
559
  end
493
560
 
494
561
  def list
495
- list_resp = http_list("/api/v1/flags")
496
- list_resp.map { |r| flag_from_resource(r) }
562
+ response = ErrorMapping.call { @api.list_flags }
563
+ (response.data || []).map { |r| flag_from_resource(ResourceShim.from_model(r)) }
497
564
  end
498
565
 
499
566
  def get(id)
500
- resp = http_get("/api/v1/flags/#{id}")
501
- flag_from_resource(resp["data"])
567
+ response = ErrorMapping.call { @api.get_flag(id) }
568
+ flag_from_resource(ResourceShim.from_model(response.data))
502
569
  end
503
570
 
504
571
  def delete(id)
505
- http_delete("/api/v1/flags/#{id}")
572
+ ErrorMapping.call { @api.delete_flag(id) }
573
+ true
506
574
  end
507
575
 
508
576
  def new_boolean_flag(id, default:, name: nil, description: nil, values: nil)
@@ -534,29 +602,67 @@ module Smplkit
534
602
  end
535
603
 
536
604
  def _create_flag(flag)
537
- body = Smplkit::Flags::Helpers.build_flag_request_body(flag)
538
- resp = http_post("/api/v1/flags", body)
539
- flag_from_resource(resp["data"])
605
+ response = ErrorMapping.call { @api.create_flag(flag_body(flag)) }
606
+ flag_from_resource(ResourceShim.from_model(response.data))
540
607
  end
541
608
 
542
609
  def _update_flag(flag)
543
- body = Smplkit::Flags::Helpers.build_flag_request_body(flag)
544
- resp = http_put("/api/v1/flags/#{flag.id}", body)
545
- flag_from_resource(resp["data"])
610
+ response = ErrorMapping.call { @api.update_flag(flag.id, flag_body(flag)) }
611
+ flag_from_resource(ResourceShim.from_model(response.data))
546
612
  end
547
613
 
548
614
  def fetch_flag(id)
549
- resp = http_get("/api/v1/flags/#{id}")
550
- Smplkit::Flags::Helpers.flag_dict_from_json(resp["data"])
615
+ response = ErrorMapping.call { @api.get_flag(id) }
616
+ Smplkit::Flags::Helpers.flag_dict_from_json(ResourceShim.from_model(response.data))
551
617
  end
552
618
 
553
619
  def list_flags
554
- body = http_list("/api/v1/flags")
555
- body.map { |r| Smplkit::Flags::Helpers.flag_dict_from_json(r) }
620
+ response = ErrorMapping.call { @api.list_flags }
621
+ (response.data || []).map { |r| Smplkit::Flags::Helpers.flag_dict_from_json(ResourceShim.from_model(r)) }
556
622
  end
557
623
 
558
624
  private
559
625
 
626
+ def flag_body(flag)
627
+ SmplkitGeneratedClient::Flags::FlagResponse.new(
628
+ data: SmplkitGeneratedClient::Flags::FlagResource.new(
629
+ type: "flag",
630
+ id: flag.id,
631
+ attributes: SmplkitGeneratedClient::Flags::Flag.new(
632
+ name: flag.name,
633
+ type: flag.type,
634
+ default: flag.default,
635
+ description: flag.description,
636
+ values: flag_values_to_wire(flag.values),
637
+ environments: flag_envs_to_wire(flag.environments)
638
+ )
639
+ )
640
+ )
641
+ end
642
+
643
+ def flag_values_to_wire(values)
644
+ return nil if values.nil?
645
+
646
+ values.map do |v|
647
+ SmplkitGeneratedClient::Flags::FlagValue.new(name: v.name, value: v.value)
648
+ end
649
+ end
650
+
651
+ def flag_envs_to_wire(environments)
652
+ return nil if environments.empty?
653
+
654
+ environments.each_with_object({}) do |(env_key, env_obj), out|
655
+ rules = env_obj.rules.map do |r|
656
+ SmplkitGeneratedClient::Flags::FlagRule.new(
657
+ logic: r.logic, value: r.value, description: r.description
658
+ )
659
+ end
660
+ out[env_key] = SmplkitGeneratedClient::Flags::FlagEnvironment.new(
661
+ enabled: env_obj.enabled, default: env_obj.default, rules: rules
662
+ )
663
+ end
664
+ end
665
+
560
666
  def flag_from_resource(resource)
561
667
  d = Smplkit::Flags::Helpers.flag_dict_from_json(resource)
562
668
  klass =
@@ -577,10 +683,8 @@ module Smplkit
577
683
  end
578
684
 
579
685
  class LoggersNamespace
580
- include HttpHelpers
581
-
582
- def initialize(http)
583
- @http = http
686
+ def initialize(api_client)
687
+ @api = SmplkitGeneratedClient::Logging::LoggersApi.new(api_client)
584
688
  @buffer = Management::LoggerRegistrationBuffer.new
585
689
  end
586
690
 
@@ -594,54 +698,86 @@ module Smplkit
594
698
  batch = @buffer.drain
595
699
  return if batch.empty?
596
700
 
597
- body = { "data" => { "type" => "logger_bulk_register", "attributes" => { "loggers" => batch } } }
598
- http_post("/api/v1/loggers/bulk", body)
701
+ items = batch.map do |entry|
702
+ SmplkitGeneratedClient::Logging::LoggerBulkItem.new(
703
+ id: entry["id"],
704
+ resolved_level: entry["resolved_level"],
705
+ level: entry["level"],
706
+ service: entry["service"],
707
+ environment: entry["environment"]
708
+ )
709
+ end
710
+ body = SmplkitGeneratedClient::Logging::LoggerBulkRequest.new(loggers: items)
711
+ ErrorMapping.call { @api.bulk_register_loggers(body) }
599
712
  rescue StandardError => e
600
713
  Smplkit.debug("registration", "logger flush failed: #{e.class}: #{e.message}")
601
714
  end
602
715
 
603
716
  def list
604
- list_resp = http_list("/api/v1/loggers")
605
- list_resp.map { |r| Smplkit::Logging::Helpers.logger_resource_to_model(self, r) }
717
+ response = ErrorMapping.call { @api.list_loggers }
718
+ (response.data || []).map do |r|
719
+ Smplkit::Logging::Helpers.logger_resource_to_model(self, ResourceShim.from_model(r))
720
+ end
606
721
  end
607
722
 
608
723
  def get(id)
609
724
  normalized = Smplkit::Logging::Normalize.normalize_logger_name(id)
610
- resp = http_get("/api/v1/loggers/#{normalized}")
611
- Smplkit::Logging::Helpers.logger_resource_to_model(self, resp["data"])
725
+ response = ErrorMapping.call { @api.get_logger(normalized) }
726
+ Smplkit::Logging::Helpers.logger_resource_to_model(self, ResourceShim.from_model(response.data))
612
727
  end
613
728
 
614
729
  def delete(id)
615
730
  normalized = Smplkit::Logging::Normalize.normalize_logger_name(id)
616
- http_delete("/api/v1/loggers/#{normalized}")
731
+ ErrorMapping.call { @api.delete_logger(normalized) }
732
+ true
617
733
  end
618
734
 
619
735
  def _update_logger(logger)
620
- body = Smplkit::Logging::Helpers.build_logger_body(logger)
621
- resp = http_put("/api/v1/loggers/#{logger.id || logger.name}", body)
622
- Smplkit::Logging::Helpers.logger_resource_to_model(self, resp["data"])
736
+ response = ErrorMapping.call { @api.update_logger(logger.id || logger.name, logger_body(logger)) }
737
+ Smplkit::Logging::Helpers.logger_resource_to_model(self, ResourceShim.from_model(response.data))
738
+ end
739
+
740
+ private
741
+
742
+ def logger_body(logger)
743
+ # Logger server schema: name, level, group, managed.
744
+ # +resolved_level+ is read-only, +service+/+environment+ are
745
+ # observed via bulk register, +description+ is wrapper-local.
746
+ SmplkitGeneratedClient::Logging::LoggerResponse.new(
747
+ data: SmplkitGeneratedClient::Logging::LoggerResource.new(
748
+ type: "logger",
749
+ id: logger.id,
750
+ attributes: SmplkitGeneratedClient::Logging::Logger.new(
751
+ name: logger.name,
752
+ level: logger.level&.to_s,
753
+ group: logger.log_group_id,
754
+ managed: logger.managed
755
+ )
756
+ )
757
+ )
623
758
  end
624
759
  end
625
760
 
626
761
  class LogGroupsNamespace
627
- include HttpHelpers
628
-
629
- def initialize(http)
630
- @http = http
762
+ def initialize(api_client)
763
+ @api = SmplkitGeneratedClient::Logging::LogGroupsApi.new(api_client)
631
764
  end
632
765
 
633
766
  def list
634
- list_resp = http_list("/api/v1/log_groups")
635
- list_resp.map { |r| Smplkit::Logging::Helpers.log_group_resource_to_model(self, r) }
767
+ response = ErrorMapping.call { @api.list_log_groups }
768
+ (response.data || []).map do |r|
769
+ Smplkit::Logging::Helpers.log_group_resource_to_model(self, ResourceShim.from_model(r))
770
+ end
636
771
  end
637
772
 
638
773
  def get(key)
639
- resp = http_get("/api/v1/log_groups/#{key}")
640
- Smplkit::Logging::Helpers.log_group_resource_to_model(self, resp["data"])
774
+ response = ErrorMapping.call { @api.get_log_group(key) }
775
+ Smplkit::Logging::Helpers.log_group_resource_to_model(self, ResourceShim.from_model(response.data))
641
776
  end
642
777
 
643
778
  def delete(key)
644
- http_delete("/api/v1/log_groups/#{key}")
779
+ ErrorMapping.call { @api.delete_log_group(key) }
780
+ true
645
781
  end
646
782
 
647
783
  def new_log_group(key, name: nil, level: nil, description: nil, parent: nil)
@@ -653,15 +789,30 @@ module Smplkit
653
789
  end
654
790
 
655
791
  def _create_log_group(group)
656
- body = Smplkit::Logging::Helpers.build_log_group_body(group)
657
- resp = http_post("/api/v1/log_groups", body)
658
- Smplkit::Logging::Helpers.log_group_resource_to_model(self, resp["data"])
792
+ response = ErrorMapping.call { @api.create_log_group(log_group_body(group)) }
793
+ Smplkit::Logging::Helpers.log_group_resource_to_model(self, ResourceShim.from_model(response.data))
659
794
  end
660
795
 
661
796
  def _update_log_group(group)
662
- body = Smplkit::Logging::Helpers.build_log_group_body(group)
663
- resp = http_put("/api/v1/log_groups/#{group.key}", body)
664
- Smplkit::Logging::Helpers.log_group_resource_to_model(self, resp["data"])
797
+ response = ErrorMapping.call { @api.update_log_group(group.key, log_group_body(group)) }
798
+ Smplkit::Logging::Helpers.log_group_resource_to_model(self, ResourceShim.from_model(response.data))
799
+ end
800
+
801
+ private
802
+
803
+ def log_group_body(group)
804
+ # LogGroup server schema: name, level, parent_id (no description).
805
+ SmplkitGeneratedClient::Logging::LogGroupResponse.new(
806
+ data: SmplkitGeneratedClient::Logging::LogGroupResource.new(
807
+ type: "log_group",
808
+ id: group.key,
809
+ attributes: SmplkitGeneratedClient::Logging::LogGroup.new(
810
+ name: group.name,
811
+ level: group.level&.to_s,
812
+ parent_id: group.parent_id
813
+ )
814
+ )
815
+ )
665
816
  end
666
817
  end
667
818
  end