smplkit 1.0.12 → 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,55 +407,52 @@ 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)
402
430
  Smplkit::Config::Config.new(
403
- self, key: key, name: name, description: description,
404
- parent_id: parent.is_a?(Smplkit::Config::Config) ? parent.key : parent
431
+ self,
432
+ key: key,
433
+ name: name || Smplkit::Helpers.key_to_display_name(key),
434
+ description: description,
435
+ parent_id: parent.is_a?(Smplkit::Config::Config) ? parent.key : parent
405
436
  )
406
437
  end
407
438
 
408
439
  def _create_config(config)
409
- body = Smplkit::Config::Helpers.build_config_request_body(config)
410
- resp = http_post("/api/v1/configs", body)
411
- 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))
412
442
  end
413
443
 
414
444
  def _update_config(config)
415
- body = Smplkit::Config::Helpers.build_config_request_body(config)
416
- resp = http_put("/api/v1/configs/#{config.key}", body)
417
- 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))
418
447
  end
419
448
 
420
449
  # Build the parent-chain for a given config, walking +parent_id+
421
450
  # pointers across the full config list. Mirrors the Python SDK's
422
451
  # client-side resolution — there is no server +/chain+ endpoint.
423
- #
424
- # Each chain entry is a Hash matching the wire shape that
425
- # +Smplkit::Config::Helpers.resolve_chain+ consumes.
426
452
  def fetch_chain(target_key)
427
453
  all_configs = list
428
454
  by_key = all_configs.to_h { |c| [c.key, c] }
429
- by_id = all_configs.to_h { |c| [c.id, c] }
455
+ by_id = all_configs.to_h { |c| [c.id, c] }
430
456
 
431
457
  current = by_key[target_key]
432
458
  return [] unless current
@@ -447,6 +473,46 @@ module Smplkit
447
473
 
448
474
  private
449
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
+
450
516
  def config_to_chain_entry(config)
451
517
  items_hash = {}
452
518
  config.items.each do |item|
@@ -466,10 +532,8 @@ module Smplkit
466
532
  end
467
533
 
468
534
  class FlagsNamespace
469
- include HttpHelpers
470
-
471
- def initialize(http)
472
- @http = http
535
+ def initialize(api_client)
536
+ @api = SmplkitGeneratedClient::Flags::FlagsApi.new(api_client)
473
537
  @buffer = Management::FlagRegistrationBuffer.new
474
538
  end
475
539
 
@@ -482,24 +546,31 @@ module Smplkit
482
546
  batch = @buffer.drain
483
547
  return if batch.empty?
484
548
 
485
- body = { "data" => { "type" => "flag_bulk_register", "attributes" => { "flags" => batch } } }
486
- 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) }
487
557
  rescue StandardError => e
488
558
  Smplkit.debug("registration", "flag flush failed: #{e.class}: #{e.message}")
489
559
  end
490
560
 
491
561
  def list
492
- list_resp = http_list("/api/v1/flags")
493
- 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)) }
494
564
  end
495
565
 
496
566
  def get(id)
497
- resp = http_get("/api/v1/flags/#{id}")
498
- flag_from_resource(resp["data"])
567
+ response = ErrorMapping.call { @api.get_flag(id) }
568
+ flag_from_resource(ResourceShim.from_model(response.data))
499
569
  end
500
570
 
501
571
  def delete(id)
502
- http_delete("/api/v1/flags/#{id}")
572
+ ErrorMapping.call { @api.delete_flag(id) }
573
+ true
503
574
  end
504
575
 
505
576
  def new_boolean_flag(id, default:, name: nil, description: nil, values: nil)
@@ -531,29 +602,67 @@ module Smplkit
531
602
  end
532
603
 
533
604
  def _create_flag(flag)
534
- body = Smplkit::Flags::Helpers.build_flag_request_body(flag)
535
- resp = http_post("/api/v1/flags", body)
536
- flag_from_resource(resp["data"])
605
+ response = ErrorMapping.call { @api.create_flag(flag_body(flag)) }
606
+ flag_from_resource(ResourceShim.from_model(response.data))
537
607
  end
538
608
 
539
609
  def _update_flag(flag)
540
- body = Smplkit::Flags::Helpers.build_flag_request_body(flag)
541
- resp = http_put("/api/v1/flags/#{flag.id}", body)
542
- 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))
543
612
  end
544
613
 
545
614
  def fetch_flag(id)
546
- resp = http_get("/api/v1/flags/#{id}")
547
- 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))
548
617
  end
549
618
 
550
619
  def list_flags
551
- body = http_list("/api/v1/flags")
552
- 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)) }
553
622
  end
554
623
 
555
624
  private
556
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
+
557
666
  def flag_from_resource(resource)
558
667
  d = Smplkit::Flags::Helpers.flag_dict_from_json(resource)
559
668
  klass =
@@ -574,10 +683,8 @@ module Smplkit
574
683
  end
575
684
 
576
685
  class LoggersNamespace
577
- include HttpHelpers
578
-
579
- def initialize(http)
580
- @http = http
686
+ def initialize(api_client)
687
+ @api = SmplkitGeneratedClient::Logging::LoggersApi.new(api_client)
581
688
  @buffer = Management::LoggerRegistrationBuffer.new
582
689
  end
583
690
 
@@ -591,54 +698,86 @@ module Smplkit
591
698
  batch = @buffer.drain
592
699
  return if batch.empty?
593
700
 
594
- body = { "data" => { "type" => "logger_bulk_register", "attributes" => { "loggers" => batch } } }
595
- 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) }
596
712
  rescue StandardError => e
597
713
  Smplkit.debug("registration", "logger flush failed: #{e.class}: #{e.message}")
598
714
  end
599
715
 
600
716
  def list
601
- list_resp = http_list("/api/v1/loggers")
602
- 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
603
721
  end
604
722
 
605
723
  def get(id)
606
724
  normalized = Smplkit::Logging::Normalize.normalize_logger_name(id)
607
- resp = http_get("/api/v1/loggers/#{normalized}")
608
- 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))
609
727
  end
610
728
 
611
729
  def delete(id)
612
730
  normalized = Smplkit::Logging::Normalize.normalize_logger_name(id)
613
- http_delete("/api/v1/loggers/#{normalized}")
731
+ ErrorMapping.call { @api.delete_logger(normalized) }
732
+ true
614
733
  end
615
734
 
616
735
  def _update_logger(logger)
617
- body = Smplkit::Logging::Helpers.build_logger_body(logger)
618
- resp = http_put("/api/v1/loggers/#{logger.id || logger.name}", body)
619
- 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
+ )
620
758
  end
621
759
  end
622
760
 
623
761
  class LogGroupsNamespace
624
- include HttpHelpers
625
-
626
- def initialize(http)
627
- @http = http
762
+ def initialize(api_client)
763
+ @api = SmplkitGeneratedClient::Logging::LogGroupsApi.new(api_client)
628
764
  end
629
765
 
630
766
  def list
631
- list_resp = http_list("/api/v1/log_groups")
632
- 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
633
771
  end
634
772
 
635
773
  def get(key)
636
- resp = http_get("/api/v1/log_groups/#{key}")
637
- 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))
638
776
  end
639
777
 
640
778
  def delete(key)
641
- http_delete("/api/v1/log_groups/#{key}")
779
+ ErrorMapping.call { @api.delete_log_group(key) }
780
+ true
642
781
  end
643
782
 
644
783
  def new_log_group(key, name: nil, level: nil, description: nil, parent: nil)
@@ -650,15 +789,30 @@ module Smplkit
650
789
  end
651
790
 
652
791
  def _create_log_group(group)
653
- body = Smplkit::Logging::Helpers.build_log_group_body(group)
654
- resp = http_post("/api/v1/log_groups", body)
655
- 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))
656
794
  end
657
795
 
658
796
  def _update_log_group(group)
659
- body = Smplkit::Logging::Helpers.build_log_group_body(group)
660
- resp = http_put("/api/v1/log_groups/#{group.key}", body)
661
- 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
+ )
662
816
  end
663
817
  end
664
818
  end