smplkit 2.0.2 → 2.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18065d9f25f15af5d853a49d46f1263388359c7acaffa765ca7b98e5d2420a98
4
- data.tar.gz: 7f386e4671aa2d5e339264007ba46a7b41c9a9cfa47ff1580514aee3248ebd86
3
+ metadata.gz: 11737e77b9c137965fcfb0f98d42cca4180131afd974aaa4d7b7107813dbc709
4
+ data.tar.gz: bf468bfdf381a811581a9a7dabd03aaec863bf37e77a13e7788128179987d430
5
5
  SHA512:
6
- metadata.gz: e57e30ebc91651ba5376fc6fde90f5ba4550320470d5e9ae7192b15d4b2e5ce0a5492cdc753f2d04892f897cd7d29422c42c6c3e96008a675c53f1a9dfd24983
7
- data.tar.gz: 03eca6e00372adf7cebbdebc719f98ffdc3b2ae0768ba788e1205d29abbeeef8755e32a58a14cee8ac52685274de53ecd15bad4c8c07009fac99f4bd7dcfd132
6
+ metadata.gz: 4956121ef13bc54257048233a7adcb8a35b7d7d8bcfd82e86eecf4776334c33dcfb30cd12dc486d9df0165d493863a4eaa61df0123a6f926d3f2018dff80fe5c
7
+ data.tar.gz: b6522291313374b8315de8d9883db6af3cdbf1a6d31dee45f2f49aea9c6c7d03b74e309481ffe1adf5f85fdd0cec8244d4c7114ff188ff70be8c3ea7a5f38d66
@@ -0,0 +1,217 @@
1
+ =begin
2
+ #smplkit Audit API
3
+
4
+ #Append-only change-history substrate for smpl.* resources and customer-application events. ADR-047.
5
+
6
+ The version of the OpenAPI document: 0.1.0
7
+
8
+ Generated by: https://openapi-generator.tech
9
+ Generator version: 7.22.0
10
+
11
+ =end
12
+
13
+ require 'date'
14
+ require 'time'
15
+
16
+ module SmplkitGeneratedClient::Audit
17
+ # Attribute set for a usage resource. The shape mirrors the ``/api/v1/usage`` contract used by config, flags, and logging — three fields, no per-product extras. Per-period limits live in the product catalog (``GET /api/v1/products``); the usage endpoint reports counts only.
18
+ class UsageAttributes < ApiModelBase
19
+ attr_accessor :limit_key
20
+
21
+ attr_accessor :period
22
+
23
+ attr_accessor :value
24
+
25
+ # Attribute mapping from ruby-style variable name to JSON key.
26
+ def self.attribute_map
27
+ {
28
+ :'limit_key' => :'limit_key',
29
+ :'period' => :'period',
30
+ :'value' => :'value'
31
+ }
32
+ end
33
+
34
+ # Returns attribute mapping this model knows about
35
+ def self.acceptable_attribute_map
36
+ attribute_map
37
+ end
38
+
39
+ # Returns all the JSON keys this model knows about
40
+ def self.acceptable_attributes
41
+ acceptable_attribute_map.values
42
+ end
43
+
44
+ # Attribute type mapping.
45
+ def self.openapi_types
46
+ {
47
+ :'limit_key' => :'String',
48
+ :'period' => :'String',
49
+ :'value' => :'Integer'
50
+ }
51
+ end
52
+
53
+ # List of attributes with nullable: true
54
+ def self.openapi_nullable
55
+ Set.new([
56
+ ])
57
+ end
58
+
59
+ # Initializes the object
60
+ # @param [Hash] attributes Model attributes in the form of hash
61
+ def initialize(attributes = {})
62
+ if (!attributes.is_a?(Hash))
63
+ fail ArgumentError, "The input argument (attributes) must be a hash in `SmplkitGeneratedClient::Audit::UsageAttributes` initialize method"
64
+ end
65
+
66
+ # check to see if the attribute exists and convert string to symbol for hash key
67
+ acceptable_attribute_map = self.class.acceptable_attribute_map
68
+ attributes = attributes.each_with_object({}) { |(k, v), h|
69
+ if (!acceptable_attribute_map.key?(k.to_sym))
70
+ fail ArgumentError, "`#{k}` is not a valid attribute in `SmplkitGeneratedClient::Audit::UsageAttributes`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
71
+ end
72
+ h[k.to_sym] = v
73
+ }
74
+
75
+ if attributes.key?(:'limit_key')
76
+ self.limit_key = attributes[:'limit_key']
77
+ else
78
+ self.limit_key = nil
79
+ end
80
+
81
+ if attributes.key?(:'period')
82
+ self.period = attributes[:'period']
83
+ else
84
+ self.period = nil
85
+ end
86
+
87
+ if attributes.key?(:'value')
88
+ self.value = attributes[:'value']
89
+ else
90
+ self.value = nil
91
+ end
92
+ end
93
+
94
+ # Show invalid properties with the reasons. Usually used together with valid?
95
+ # @return Array for valid properties with the reasons
96
+ def list_invalid_properties
97
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
98
+ invalid_properties = Array.new
99
+ if @limit_key.nil?
100
+ invalid_properties.push('invalid value for "limit_key", limit_key cannot be nil.')
101
+ end
102
+
103
+ if @period.nil?
104
+ invalid_properties.push('invalid value for "period", period cannot be nil.')
105
+ end
106
+
107
+ if @value.nil?
108
+ invalid_properties.push('invalid value for "value", value cannot be nil.')
109
+ end
110
+
111
+ invalid_properties
112
+ end
113
+
114
+ # Check to see if the all the properties in the model are valid
115
+ # @return true if the model is valid
116
+ def valid?
117
+ warn '[DEPRECATED] the `valid?` method is obsolete'
118
+ return false if @limit_key.nil?
119
+ return false if @period.nil?
120
+ return false if @value.nil?
121
+ true
122
+ end
123
+
124
+ # Custom attribute writer method with validation
125
+ # @param [Object] limit_key Value to be assigned
126
+ def limit_key=(limit_key)
127
+ if limit_key.nil?
128
+ fail ArgumentError, 'limit_key cannot be nil'
129
+ end
130
+
131
+ @limit_key = limit_key
132
+ end
133
+
134
+ # Custom attribute writer method with validation
135
+ # @param [Object] period Value to be assigned
136
+ def period=(period)
137
+ if period.nil?
138
+ fail ArgumentError, 'period cannot be nil'
139
+ end
140
+
141
+ @period = period
142
+ end
143
+
144
+ # Custom attribute writer method with validation
145
+ # @param [Object] value Value to be assigned
146
+ def value=(value)
147
+ if value.nil?
148
+ fail ArgumentError, 'value cannot be nil'
149
+ end
150
+
151
+ @value = value
152
+ end
153
+
154
+ # Checks equality by comparing each attribute.
155
+ # @param [Object] Object to be compared
156
+ def ==(o)
157
+ return true if self.equal?(o)
158
+ self.class == o.class &&
159
+ limit_key == o.limit_key &&
160
+ period == o.period &&
161
+ value == o.value
162
+ end
163
+
164
+ # @see the `==` method
165
+ # @param [Object] Object to be compared
166
+ def eql?(o)
167
+ self == o
168
+ end
169
+
170
+ # Calculates hash code according to all attributes.
171
+ # @return [Integer] Hash code
172
+ def hash
173
+ [limit_key, period, value].hash
174
+ end
175
+
176
+ # Builds the object from hash
177
+ # @param [Hash] attributes Model attributes in the form of hash
178
+ # @return [Object] Returns the model itself
179
+ def self.build_from_hash(attributes)
180
+ return nil unless attributes.is_a?(Hash)
181
+ attributes = attributes.transform_keys(&:to_sym)
182
+ transformed_hash = {}
183
+ openapi_types.each_pair do |key, type|
184
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
185
+ transformed_hash["#{key}"] = nil
186
+ elsif type =~ /\AArray<(.*)>/i
187
+ # check to ensure the input is an array given that the attribute
188
+ # is documented as an array but the input is not
189
+ if attributes[attribute_map[key]].is_a?(Array)
190
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
191
+ end
192
+ elsif !attributes[attribute_map[key]].nil?
193
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
194
+ end
195
+ end
196
+ new(transformed_hash)
197
+ end
198
+
199
+ # Returns the object in the form of hash
200
+ # @return [Hash] Returns the object in the form of hash
201
+ def to_hash
202
+ hash = {}
203
+ self.class.attribute_map.each_pair do |attr, param|
204
+ value = self.send(attr)
205
+ if value.nil?
206
+ is_nullable = self.class.openapi_nullable.include?(attr)
207
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
208
+ end
209
+
210
+ hash[param] = _to_hash(value)
211
+ end
212
+ hash
213
+ end
214
+
215
+ end
216
+
217
+ end
@@ -45,7 +45,7 @@ module SmplkitGeneratedClient::Audit
45
45
  {
46
46
  :'id' => :'String',
47
47
  :'type' => :'String',
48
- :'attributes' => :'Hash<String, Object>'
48
+ :'attributes' => :'UsageAttributes'
49
49
  }
50
50
  end
51
51
 
@@ -84,9 +84,7 @@ module SmplkitGeneratedClient::Audit
84
84
  end
85
85
 
86
86
  if attributes.key?(:'attributes')
87
- if (value = attributes[:'attributes']).is_a?(Hash)
88
- self.attributes = value
89
- end
87
+ self.attributes = attributes[:'attributes']
90
88
  else
91
89
  self.attributes = nil
92
90
  end
@@ -39,6 +39,7 @@ require 'smplkit_audit_client/models/http_header'
39
39
  require 'smplkit_audit_client/models/retry_failed_deliveries_summary'
40
40
  require 'smplkit_audit_client/models/test_forwarder_request'
41
41
  require 'smplkit_audit_client/models/test_forwarder_response'
42
+ require 'smplkit_audit_client/models/usage_attributes'
42
43
  require 'smplkit_audit_client/models/usage_resource'
43
44
  require 'smplkit_audit_client/models/usage_response'
44
45
 
@@ -0,0 +1,48 @@
1
+ =begin
2
+ #smplkit Audit API
3
+
4
+ #Append-only change-history substrate for smpl.* resources and customer-application events. ADR-047.
5
+
6
+ The version of the OpenAPI document: 0.1.0
7
+
8
+ Generated by: https://openapi-generator.tech
9
+ Generator version: 7.22.0
10
+
11
+ =end
12
+
13
+ require 'spec_helper'
14
+ require 'json'
15
+ require 'date'
16
+
17
+ # Unit tests for SmplkitGeneratedClient::Audit::UsageAttributes
18
+ # Automatically generated by openapi-generator (https://openapi-generator.tech)
19
+ # Please update as you see appropriate
20
+ describe SmplkitGeneratedClient::Audit::UsageAttributes do
21
+ #let(:instance) { SmplkitGeneratedClient::Audit::UsageAttributes.new }
22
+
23
+ describe 'test an instance of UsageAttributes' do
24
+ it 'should create an instance of UsageAttributes' do
25
+ # uncomment below to test the instance creation
26
+ #expect(instance).to be_instance_of(SmplkitGeneratedClient::Audit::UsageAttributes)
27
+ end
28
+ end
29
+
30
+ describe 'test attribute "limit_key"' do
31
+ it 'should work' do
32
+ # assertion here. ref: https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/
33
+ end
34
+ end
35
+
36
+ describe 'test attribute "period"' do
37
+ it 'should work' do
38
+ # assertion here. ref: https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/
39
+ end
40
+ end
41
+
42
+ describe 'test attribute "value"' do
43
+ it 'should work' do
44
+ # assertion here. ref: https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/
45
+ end
46
+ end
47
+
48
+ end
@@ -79,6 +79,9 @@ module Smplkit
79
79
  # +mgmt.flags.*+. Per-request context is set via
80
80
  # +client.set_context([...])+.
81
81
  class FlagsClient
82
+ INITIAL_START_RETRY_DELAY = 1.0
83
+ MAX_START_RETRY_DELAY = 60.0
84
+
82
85
  def initialize(parent, manage:, metrics:, flags_base_url:, app_base_url:)
83
86
  @parent = parent
84
87
  @manage = manage
@@ -90,6 +93,9 @@ module Smplkit
90
93
 
91
94
  @flag_store = {}
92
95
  @connected = false
96
+ @ws_subscribed = false
97
+ @next_start_attempt_at = 0.0
98
+ @start_retry_delay = INITIAL_START_RETRY_DELAY
93
99
  @cache = ResolutionCache.new
94
100
  @handles = {}
95
101
  @global_listeners = []
@@ -116,24 +122,44 @@ module Smplkit
116
122
 
117
123
  # Eagerly initialize the flags subclient.
118
124
  #
119
- # Drains any pending flag-declaration buffer, fetches all flag
125
+ # Flushes any pending flag-declaration buffer, fetches all flag
120
126
  # definitions, opens the shared WebSocket and subscribes to
121
127
  # +flag_changed+ / +flag_deleted+ / +flags_changed+ events.
122
128
  #
123
129
  # Idempotent — safe to call multiple times. Called automatically on
124
130
  # first +flag.get+ evaluation if not invoked manually.
131
+ #
132
+ # If the flags-service is unhealthy (e.g. a coordinated rebuild where
133
+ # the app pod starts before the schema is loaded), the flush or refresh
134
+ # will fail. Pending declarations stay queued, the client remains
135
+ # disconnected, and the next call retries after an exponentially
136
+ # backed-off delay (capped at +MAX_START_RETRY_DELAY+ seconds).
137
+ # Evaluations during that window fall back to handle defaults.
125
138
  def start
126
139
  return if @connected
140
+ return if Process.clock_gettime(Process::CLOCK_MONOTONIC) < @next_start_attempt_at
127
141
 
128
142
  @environment = @parent._environment
129
- flush_flags_safely
130
- refresh
143
+
144
+ begin
145
+ @manage.flags.flush
146
+ refresh
147
+ rescue StandardError => e
148
+ schedule_start_retry(e)
149
+ return
150
+ end
151
+
131
152
  @connected = true
153
+ @start_retry_delay = INITIAL_START_RETRY_DELAY
154
+ @next_start_attempt_at = 0.0
132
155
 
133
156
  @ws_manager = @parent._ensure_ws
157
+ return if @ws_subscribed
158
+
134
159
  @ws_manager.on("flag_changed") { |data| handle_flag_changed(data) }
135
160
  @ws_manager.on("flag_deleted") { |data| handle_flag_deleted(data) }
136
161
  @ws_manager.on("flags_changed") { |data| handle_flags_changed(data) }
162
+ @ws_subscribed = true
137
163
  end
138
164
 
139
165
  def refresh
@@ -217,10 +243,12 @@ module Smplkit
217
243
  handle
218
244
  end
219
245
 
220
- def flush_flags_safely
221
- @manage.flags.flush
222
- rescue StandardError => e
223
- Smplkit.debug("registration", "bulk flag registration failed: #{e.class}: #{e.message}")
246
+ def schedule_start_retry(exc)
247
+ delay = @start_retry_delay
248
+ @next_start_attempt_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + delay
249
+ @start_retry_delay = [delay * 2, MAX_START_RETRY_DELAY].min
250
+ Smplkit.debug("registration",
251
+ "flags client start failed (will retry in #{delay}s): #{exc.class}: #{exc.message}")
224
252
  end
225
253
 
226
254
  def fetch_all_flags
@@ -42,6 +42,10 @@ module Smplkit
42
42
  end
43
43
 
44
44
  # Thread-safe batch buffer for flag declarations.
45
+ #
46
+ # Use +peek+ + +commit(ids)+ for the send path so a failed POST leaves
47
+ # declarations queued for the next attempt. +drain+ is unconditional and
48
+ # used only by tests/teardown.
45
49
  class FlagRegistrationBuffer
46
50
  def initialize
47
51
  @seen = {}
@@ -61,6 +65,17 @@ module Smplkit
61
65
  end
62
66
  end
63
67
 
68
+ def peek
69
+ @lock.synchronize { @pending.dup }
70
+ end
71
+
72
+ def commit(ids)
73
+ return if ids.nil? || ids.empty?
74
+
75
+ committed = ids.to_set
76
+ @lock.synchronize { @pending.reject! { |item| committed.include?(item["id"]) } }
77
+ end
78
+
64
79
  def drain
65
80
  @lock.synchronize do
66
81
  batch = @pending
@@ -538,11 +538,24 @@ module Smplkit
538
538
 
539
539
  def register(declaration)
540
540
  @buffer.add(declaration)
541
- flush if @buffer.pending_count >= Management::FLAG_BATCH_FLUSH_SIZE
541
+ return unless @buffer.pending_count >= Management::FLAG_BATCH_FLUSH_SIZE
542
+
543
+ begin
544
+ flush
545
+ rescue StandardError => e
546
+ Smplkit.debug("registration", "threshold flag flush failed: #{e.class}: #{e.message}")
547
+ end
542
548
  end
543
549
 
550
+ # POST pending declarations to the bulk endpoint.
551
+ #
552
+ # Items remain in the buffer until the request succeeds, so a flush
553
+ # against an unhealthy service is automatically retried by the next
554
+ # +flush+ call (lazy +start+ retry, periodic background flush, or
555
+ # final flush on close). Raises on failure — callers decide whether
556
+ # to retry.
544
557
  def flush
545
- batch = @buffer.drain
558
+ batch = @buffer.peek
546
559
  return if batch.empty?
547
560
 
548
561
  flag_items = batch.map do |entry|
@@ -553,8 +566,11 @@ module Smplkit
553
566
  end
554
567
  body = SmplkitGeneratedClient::Flags::FlagBulkRequest.new(flags: flag_items)
555
568
  ErrorMapping.call { @api.bulk_register_flags(body) }
556
- rescue StandardError => e
557
- Smplkit.debug("registration", "flag flush failed: #{e.class}: #{e.message}")
569
+ @buffer.commit(batch.map { |b| b["id"] })
570
+ end
571
+
572
+ def pending_count
573
+ @buffer.pending_count
558
574
  end
559
575
 
560
576
  def list
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smplkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Smpl Solutions LLC
@@ -403,6 +403,7 @@ files:
403
403
  - lib/smplkit/_generated/audit/lib/smplkit_audit_client/models/retry_failed_deliveries_summary.rb
404
404
  - lib/smplkit/_generated/audit/lib/smplkit_audit_client/models/test_forwarder_request.rb
405
405
  - lib/smplkit/_generated/audit/lib/smplkit_audit_client/models/test_forwarder_response.rb
406
+ - lib/smplkit/_generated/audit/lib/smplkit_audit_client/models/usage_attributes.rb
406
407
  - lib/smplkit/_generated/audit/lib/smplkit_audit_client/models/usage_resource.rb
407
408
  - lib/smplkit/_generated/audit/lib/smplkit_audit_client/models/usage_response.rb
408
409
  - lib/smplkit/_generated/audit/lib/smplkit_audit_client/version.rb
@@ -430,6 +431,7 @@ files:
430
431
  - lib/smplkit/_generated/audit/spec/models/retry_failed_deliveries_summary_spec.rb
431
432
  - lib/smplkit/_generated/audit/spec/models/test_forwarder_request_spec.rb
432
433
  - lib/smplkit/_generated/audit/spec/models/test_forwarder_response_spec.rb
434
+ - lib/smplkit/_generated/audit/spec/models/usage_attributes_spec.rb
433
435
  - lib/smplkit/_generated/audit/spec/models/usage_resource_spec.rb
434
436
  - lib/smplkit/_generated/audit/spec/models/usage_response_spec.rb
435
437
  - lib/smplkit/_generated/audit/spec/spec_helper.rb