confidence-openfeature-provider 0.1.4 → 0.2.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.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 375cd3b343a316624712b6c7415665754bd356618435d9d7f455f42ac8bd797b
|
|
4
|
+
data.tar.gz: 3de4965a6f56a01da59102593b5f1bf88a71fb2e2da887391bdc39366a67c497
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb95289e73c34bbce61de2e8c88ea61e688001bcddbcadc3bffec3d22daba866e22a3a2e21eecd9c26d04e28a1bc13037c80ec704a5a427144d11e733c4f3047
|
|
7
|
+
data.tar.gz: 03d0d7e9d6cdb1121ec77254d915a4215bc198f79283eb73ef74176a065d8ea7ea72d51884fb0b8b0b97d657a7171a4f39aac68579a437e68a2ed0295a925f0f
|
|
@@ -6,6 +6,8 @@ require "json"
|
|
|
6
6
|
require "uri"
|
|
7
7
|
require "net/http"
|
|
8
8
|
require "net/https"
|
|
9
|
+
# Time.iso8601, used to coerce a String event_time.
|
|
10
|
+
require "time"
|
|
9
11
|
|
|
10
12
|
module Confidence
|
|
11
13
|
module OpenFeature
|
|
@@ -20,12 +22,15 @@ module Confidence
|
|
|
20
22
|
US = new("https://resolver.us.confidence.dev/v1")
|
|
21
23
|
end
|
|
22
24
|
|
|
25
|
+
# The events API is a single global endpoint, unlike the regional
|
|
26
|
+
# resolver hosts above.
|
|
27
|
+
EVENTS_URI = "https://events.confidence.dev/v1"
|
|
28
|
+
|
|
23
29
|
class APIClient
|
|
24
|
-
def initialize(client_secret:, region: Region::EU)
|
|
25
|
-
uri = URI.parse(region.uri)
|
|
30
|
+
def initialize(client_secret:, region: Region::EU, events_uri: EVENTS_URI)
|
|
26
31
|
@client_secret = client_secret
|
|
27
|
-
@
|
|
28
|
-
@
|
|
32
|
+
@uri = URI.parse(region.uri)
|
|
33
|
+
@events_uri = URI.parse(events_uri)
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
def resolve_one(flag:, context: {}, apply: true)
|
|
@@ -40,6 +45,41 @@ module Confidence
|
|
|
40
45
|
result
|
|
41
46
|
end
|
|
42
47
|
|
|
48
|
+
# Publishes a single event to the Confidence events API.
|
|
49
|
+
#
|
|
50
|
+
# +event_name+ is the event definition id, sent as
|
|
51
|
+
# "eventDefinitions/#{event_name}". +payload+ is an arbitrary hash.
|
|
52
|
+
# +event_time+ defaults to now; pass it to backdate an event.
|
|
53
|
+
#
|
|
54
|
+
# Returns nil on success. Raises APIError if the request itself fails
|
|
55
|
+
# and EventPublishError if the batch is accepted but the event is
|
|
56
|
+
# refused, so rejections are never silently discarded.
|
|
57
|
+
def track(event_name:, payload: {}, event_time: nil)
|
|
58
|
+
now = Time.now
|
|
59
|
+
result = post_json("/v1/events:publish", {
|
|
60
|
+
clientSecret: @client_secret,
|
|
61
|
+
sendTime: rfc3339(now),
|
|
62
|
+
sdk: {id: "SDK_ID_RUBY_PROVIDER", version: VERSION},
|
|
63
|
+
events: [{
|
|
64
|
+
eventDefinition: "eventDefinitions/#{event_name}",
|
|
65
|
+
eventTime: rfc3339(event_time || now),
|
|
66
|
+
payload: payload || {}
|
|
67
|
+
}]
|
|
68
|
+
}, uri: @events_uri, label: "events:publish")
|
|
69
|
+
|
|
70
|
+
rejections = (result["errors"] || []).map do |error|
|
|
71
|
+
Rejection.new(error["index"], error["reason"], error["message"])
|
|
72
|
+
end
|
|
73
|
+
unless rejections.empty?
|
|
74
|
+
raise EventPublishError.new(
|
|
75
|
+
"events:publish refused #{rejections.length} event(s): " +
|
|
76
|
+
rejections.map { |r| "[#{r.index}] #{r.reason} #{r.message}".strip }.join(", "),
|
|
77
|
+
rejections
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
|
|
43
83
|
def resolve(flags: [], context: {}, apply: true)
|
|
44
84
|
result = post_json("/v1/flags:resolve", {
|
|
45
85
|
clientSecret: @client_secret,
|
|
@@ -47,7 +87,7 @@ module Confidence
|
|
|
47
87
|
apply: apply,
|
|
48
88
|
flags: flags,
|
|
49
89
|
sdk: {id: "SDK_ID_RUBY_PROVIDER", version: VERSION}
|
|
50
|
-
})
|
|
90
|
+
}, uri: @uri, label: "flags:resolve")
|
|
51
91
|
|
|
52
92
|
resolved_flags = result["resolvedFlags"] || []
|
|
53
93
|
resolved_flags.map do |flag|
|
|
@@ -61,21 +101,79 @@ module Confidence
|
|
|
61
101
|
|
|
62
102
|
private
|
|
63
103
|
|
|
64
|
-
|
|
104
|
+
# A fresh Net::HTTP per request, because one instance cannot be shared
|
|
105
|
+
# across threads. Net::HTTP#request auto-starts the connection when the
|
|
106
|
+
# receiver is not already started, mutating its @started and @socket:
|
|
107
|
+
#
|
|
108
|
+
# unless started?
|
|
109
|
+
# start { req['connection'] ||= 'close'; return request(req, ...) }
|
|
110
|
+
# end
|
|
111
|
+
#
|
|
112
|
+
# Two threads entering that on the same object both open a connection and
|
|
113
|
+
# both assign @socket, so one clobbers the other and the loser reads or
|
|
114
|
+
# writes a socket the winner may already have closed.
|
|
115
|
+
#
|
|
116
|
+
# Per-request instantiation costs nothing here: the instance was never
|
|
117
|
+
# explicitly started, so every request already opened a connection, sent
|
|
118
|
+
# "Connection: close" and closed it again. There was no reuse to lose.
|
|
119
|
+
# Takes a URI so an additional endpoint can share it.
|
|
120
|
+
def build_agent(uri)
|
|
121
|
+
agent = Net::HTTP.new(uri.host, uri.port)
|
|
122
|
+
agent.use_ssl = uri.scheme == "https"
|
|
123
|
+
agent
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# getutc rather than utc: the latter mutates its receiver, which would
|
|
127
|
+
# convert a caller-supplied event_time to UTC in place.
|
|
128
|
+
#
|
|
129
|
+
# Accepts a String as well as a Time. Spec 6.2.2 permits string custom
|
|
130
|
+
# fields, and an "event_time" entry in tracking event details is the only
|
|
131
|
+
# route to set the event time through the spec-conformant +track+, so a
|
|
132
|
+
# caller passing an ISO-8601 string is expected rather than exceptional.
|
|
133
|
+
# An unparseable value raises TypeMismatchError rather than falling back
|
|
134
|
+
# to "now": a silently wrong timestamp is harder to diagnose than a
|
|
135
|
+
# logged failure, and +track+ turns the raise into a warning.
|
|
136
|
+
def rfc3339(time)
|
|
137
|
+
coerce_time(time).getutc.strftime("%Y-%m-%dT%H:%M:%S.%LZ")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def coerce_time(time)
|
|
141
|
+
return time if time.is_a?(Time)
|
|
142
|
+
|
|
143
|
+
if time.is_a?(String)
|
|
144
|
+
begin
|
|
145
|
+
return Time.iso8601(time)
|
|
146
|
+
rescue ArgumentError => ex
|
|
147
|
+
raise TypeMismatchError.new(
|
|
148
|
+
"event_time #{time.inspect} is not a valid ISO-8601 timestamp: #{ex.message}"
|
|
149
|
+
)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
raise TypeMismatchError.new(
|
|
154
|
+
"event_time must be a Time or an ISO-8601 String, got #{time.class}"
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Takes the target URI rather than a prepared agent so that every request
|
|
159
|
+
# still builds its own, per build_agent above. +label+ names the endpoint
|
|
160
|
+
# in errors; both callers pass it explicitly, which keeps "which host does
|
|
161
|
+
# this go to" a decision at the call site.
|
|
162
|
+
def post_json(path, body, uri:, label:)
|
|
65
163
|
headers = {"Content-Type" => "application/json"}
|
|
66
164
|
request = Net::HTTP::Post.new(path, headers)
|
|
67
165
|
request.body = JSON.dump(body)
|
|
68
|
-
response =
|
|
166
|
+
response = build_agent(uri).request(request)
|
|
69
167
|
|
|
70
168
|
code = response.code.to_i
|
|
71
169
|
if code != 200
|
|
72
|
-
raise APIError.new("
|
|
170
|
+
raise APIError.new("#{label} HTTP #{response.code} #{response.message}")
|
|
73
171
|
end
|
|
74
172
|
|
|
75
173
|
begin
|
|
76
174
|
JSON.parse(response.body)
|
|
77
175
|
rescue JSON::ParserError => ex
|
|
78
|
-
raise APIError.new("
|
|
176
|
+
raise APIError.new("#{label} malformed JSON: #{ex}")
|
|
79
177
|
end
|
|
80
178
|
end
|
|
81
179
|
|
|
@@ -89,6 +187,9 @@ module Confidence
|
|
|
89
187
|
variant.nil? || value.nil?
|
|
90
188
|
end
|
|
91
189
|
end
|
|
190
|
+
|
|
191
|
+
# A single event the events API refused within an accepted batch.
|
|
192
|
+
Rejection = Struct.new(:index, :reason, :message)
|
|
92
193
|
end
|
|
93
194
|
end
|
|
94
195
|
|
|
@@ -13,5 +13,31 @@ module Confidence
|
|
|
13
13
|
|
|
14
14
|
class TypeMismatchError < BaseError
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
# Raised when tracking event details already carry a "context" key.
|
|
18
|
+
#
|
|
19
|
+
# The evaluation context is merged into the event payload under the
|
|
20
|
+
# reserved "context" key, so a details key of the same name would be
|
|
21
|
+
# ambiguous. Rejecting it matches the other Confidence SDKs, which raise
|
|
22
|
+
# rather than silently overwrite one with the other.
|
|
23
|
+
class InvalidContextInPayloadError < BaseError
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Raised when the events API accepts the batch but refuses individual
|
|
27
|
+
# events. The batch call still returns HTTP 200 in that case, so without
|
|
28
|
+
# this the rejections would be silently dropped.
|
|
29
|
+
#
|
|
30
|
+
# +rejections+ holds one Rejection per refused event, each carrying the
|
|
31
|
+
# index of the event in the published batch, the reason (for example
|
|
32
|
+
# EVENT_DEFINITION_NOT_FOUND or EVENT_SCHEMA_VALIDATION_FAILED) and an
|
|
33
|
+
# optional message.
|
|
34
|
+
class EventPublishError < BaseError
|
|
35
|
+
attr_reader :rejections
|
|
36
|
+
|
|
37
|
+
def initialize(message, rejections = [])
|
|
38
|
+
super(message)
|
|
39
|
+
@rejections = rejections
|
|
40
|
+
end
|
|
41
|
+
end
|
|
16
42
|
end
|
|
17
43
|
end
|
|
@@ -11,9 +11,27 @@ module Confidence
|
|
|
11
11
|
|
|
12
12
|
# Error_code and error_message seemingly not used by OpenFeature SDK.
|
|
13
13
|
# Including here for compatibility.
|
|
14
|
+
#
|
|
15
|
+
# flag_metadata became part of the contract in openfeature-sdk 0.6.1:
|
|
16
|
+
# EvaluationDetails delegates it to whatever the provider returns, so
|
|
17
|
+
# omitting the member raises NoMethodError on the fetch_*_details path.
|
|
18
|
+
# Defaulting and immutability mirror Provider::ResolutionDetails.
|
|
19
|
+
EMPTY_FLAG_METADATA = {}.freeze
|
|
20
|
+
|
|
14
21
|
ResolutionDetails = Struct.new(
|
|
15
|
-
:value, :reason, :variant, :error_code, :error_message
|
|
16
|
-
)
|
|
22
|
+
:value, :reason, :variant, :error_code, :error_message, :flag_metadata
|
|
23
|
+
) do
|
|
24
|
+
def flag_metadata
|
|
25
|
+
raw = self[:flag_metadata]
|
|
26
|
+
if raw.nil?
|
|
27
|
+
EMPTY_FLAG_METADATA
|
|
28
|
+
elsif raw.frozen?
|
|
29
|
+
raw
|
|
30
|
+
else
|
|
31
|
+
raw.dup.freeze
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
17
35
|
|
|
18
36
|
def initialize(api_client:, apply_on_resolve: true)
|
|
19
37
|
@api_client = api_client
|
|
@@ -56,8 +74,96 @@ module Confidence
|
|
|
56
74
|
)
|
|
57
75
|
end
|
|
58
76
|
|
|
77
|
+
# Publishes an event to Confidence.
|
|
78
|
+
#
|
|
79
|
+
# Signature matches OpenFeature requirement 6.1.1.1 and the shape the
|
|
80
|
+
# OpenFeature Ruby SDK client invokes providers with:
|
|
81
|
+
#
|
|
82
|
+
# @provider.track(name, evaluation_context:, tracking_event_details:)
|
|
83
|
+
#
|
|
84
|
+
# Returns nothing, and never raises: the SDK client does not rescue, so
|
|
85
|
+
# an exception here would surface in application code from a
|
|
86
|
+
# fire-and-forget tracking call. Failures are written to stderr. Use
|
|
87
|
+
# +track!+ when you want them raised instead.
|
|
88
|
+
def track(tracking_event_name, evaluation_context: nil, tracking_event_details: nil)
|
|
89
|
+
track!(
|
|
90
|
+
tracking_event_name,
|
|
91
|
+
evaluation_context: evaluation_context,
|
|
92
|
+
tracking_event_details: tracking_event_details
|
|
93
|
+
)
|
|
94
|
+
nil
|
|
95
|
+
rescue => ex
|
|
96
|
+
# Bare rescue is StandardError; anything narrower would let a caller
|
|
97
|
+
# mistake (a non-Hash, say) escape a call that must not raise.
|
|
98
|
+
warn("Confidence: track(#{tracking_event_name.inspect}) failed: #{ex.message}")
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Same as +track+ but raises on failure.
|
|
103
|
+
#
|
|
104
|
+
# Raises APIError if the request fails, EventPublishError if the batch is
|
|
105
|
+
# accepted but the event is refused, InvalidContextInPayloadError on a
|
|
106
|
+
# reserved-key collision and TypeMismatchError if +value+ is not numeric
|
|
107
|
+
# or +event_time+ is not a valid timestamp.
|
|
108
|
+
#
|
|
109
|
+
# +event_time+ backdates the event and accepts a Time or an ISO-8601
|
|
110
|
+
# String. It can also be supplied as an "event_time" entry in
|
|
111
|
+
# +tracking_event_details+, which is the only route available through the
|
|
112
|
+
# spec-conformant +track+; it is removed from the payload rather than
|
|
113
|
+
# published as a custom field.
|
|
114
|
+
#
|
|
115
|
+
# "event_time" is therefore reserved in +tracking_event_details+. A value
|
|
116
|
+
# that is neither a Time nor a parseable ISO-8601 String raises
|
|
117
|
+
# TypeMismatchError rather than being published as an ordinary string
|
|
118
|
+
# field. Failing loudly is deliberate: publishing the event stamped
|
|
119
|
+
# "now" instead of the intended time, or dropping it silently, is far
|
|
120
|
+
# harder to diagnose than a logged failure.
|
|
121
|
+
def track!(tracking_event_name, evaluation_context: nil, tracking_event_details: nil, event_time: nil)
|
|
122
|
+
details = normalize_details(tracking_event_details)
|
|
123
|
+
at = details.delete("event_time") || event_time
|
|
124
|
+
|
|
125
|
+
@api_client.track(
|
|
126
|
+
event_name: tracking_event_name,
|
|
127
|
+
payload: event_payload(details, evaluation_context),
|
|
128
|
+
event_time: at
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
59
132
|
private
|
|
60
133
|
|
|
134
|
+
# Mirrors PayloadMerger in the other Confidence SDKs: the tracking event
|
|
135
|
+
# details sit at the top level of the payload and the evaluation context
|
|
136
|
+
# is nested under the reserved "context" key.
|
|
137
|
+
def event_payload(details, evaluation_context)
|
|
138
|
+
if details.key?("context")
|
|
139
|
+
raise InvalidContextInPayloadError.new(
|
|
140
|
+
'tracking event details may not contain a "context" key; it is ' \
|
|
141
|
+
"reserved for the evaluation context"
|
|
142
|
+
)
|
|
143
|
+
end
|
|
144
|
+
details.merge("context" => context_hash(evaluation_context))
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Requirement 6.2.1: tracking event details define an optional numeric
|
|
148
|
+
# +value+. Requirement 6.2.2: custom fields keyed by string.
|
|
149
|
+
def normalize_details(tracking_event_details)
|
|
150
|
+
return {} if tracking_event_details.nil?
|
|
151
|
+
unless tracking_event_details.is_a?(Hash)
|
|
152
|
+
raise TypeMismatchError.new(
|
|
153
|
+
"tracking event details must be a Hash, got #{tracking_event_details.class}"
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
details = tracking_event_details.transform_keys(&:to_s)
|
|
158
|
+
value = details["value"]
|
|
159
|
+
if !value.nil? && !value.is_a?(Numeric)
|
|
160
|
+
raise TypeMismatchError.new(
|
|
161
|
+
"tracking event details 'value' must be numeric, got #{value.class}"
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
details
|
|
165
|
+
end
|
|
166
|
+
|
|
61
167
|
def evaluate(flag_key:, default_value:, evaluation_context: nil, validator: nil)
|
|
62
168
|
parts = flag_key.split(".")
|
|
63
169
|
flag_id = parts.shift
|
|
@@ -104,10 +210,15 @@ module Confidence
|
|
|
104
210
|
|
|
105
211
|
def context_hash(evaluation_context)
|
|
106
212
|
return {} if evaluation_context.nil?
|
|
213
|
+
# Direct callers may pass a plain Hash; the SDK client always passes an
|
|
214
|
+
# EvaluationContext.
|
|
215
|
+
return evaluation_context.dup if evaluation_context.is_a?(Hash)
|
|
107
216
|
|
|
108
217
|
# In SDK 0.4+, EvaluationContext stores all fields in a hash
|
|
109
218
|
# targeting_key is a special field that can be accessed via .targeting_key
|
|
110
|
-
evaluation_context.fields.dup
|
|
219
|
+
return evaluation_context.fields.dup if evaluation_context.respond_to?(:fields)
|
|
220
|
+
|
|
221
|
+
{}
|
|
111
222
|
end
|
|
112
223
|
end
|
|
113
224
|
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: confidence-openfeature-provider
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Confidence Team
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: openfeature-sdk
|
|
@@ -16,28 +15,34 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 0.
|
|
18
|
+
version: 0.6.1
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 0.
|
|
25
|
+
version: 0.6.1
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: openssl
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
30
29
|
requirements:
|
|
31
|
-
- - "
|
|
30
|
+
- - ">="
|
|
32
31
|
- !ruby/object:Gem::Version
|
|
33
32
|
version: '3.3'
|
|
33
|
+
- - "<"
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '5.0'
|
|
34
36
|
type: :runtime
|
|
35
37
|
prerelease: false
|
|
36
38
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
39
|
requirements:
|
|
38
|
-
- - "
|
|
40
|
+
- - ">="
|
|
39
41
|
- !ruby/object:Gem::Version
|
|
40
42
|
version: '3.3'
|
|
43
|
+
- - "<"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '5.0'
|
|
41
46
|
- !ruby/object:Gem::Dependency
|
|
42
47
|
name: rake
|
|
43
48
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -58,14 +63,14 @@ dependencies:
|
|
|
58
63
|
requirements:
|
|
59
64
|
- - "~>"
|
|
60
65
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: 3.
|
|
66
|
+
version: 3.13.2
|
|
62
67
|
type: :development
|
|
63
68
|
prerelease: false
|
|
64
69
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
70
|
requirements:
|
|
66
71
|
- - "~>"
|
|
67
72
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: 3.
|
|
73
|
+
version: 3.13.2
|
|
69
74
|
- !ruby/object:Gem::Dependency
|
|
70
75
|
name: standard
|
|
71
76
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -114,15 +119,14 @@ dependencies:
|
|
|
114
119
|
requirements:
|
|
115
120
|
- - "~>"
|
|
116
121
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: 2.
|
|
122
|
+
version: 3.2.0
|
|
118
123
|
type: :development
|
|
119
124
|
prerelease: false
|
|
120
125
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
126
|
requirements:
|
|
122
127
|
- - "~>"
|
|
123
128
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: 2.
|
|
125
|
-
description:
|
|
129
|
+
version: 3.2.0
|
|
126
130
|
email:
|
|
127
131
|
- TBD@TBD.com
|
|
128
132
|
executables: []
|
|
@@ -143,7 +147,6 @@ metadata:
|
|
|
143
147
|
changelog_uri: https://github.com/spotify/confidence-resolver/blob/main/openfeature-provider/ruby/CHANGELOG.md
|
|
144
148
|
bug_tracker_uri: https://github.com/spotify/confidence-resolver/issues
|
|
145
149
|
documentation_uri: https://github.com/spotify/confidence-resolver/blob/main/openfeature-provider/ruby/README.md
|
|
146
|
-
post_install_message:
|
|
147
150
|
rdoc_options: []
|
|
148
151
|
require_paths:
|
|
149
152
|
- lib
|
|
@@ -151,15 +154,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
151
154
|
requirements:
|
|
152
155
|
- - ">="
|
|
153
156
|
- !ruby/object:Gem::Version
|
|
154
|
-
version: '3.
|
|
157
|
+
version: '3.4'
|
|
155
158
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
156
159
|
requirements:
|
|
157
160
|
- - ">="
|
|
158
161
|
- !ruby/object:Gem::Version
|
|
159
162
|
version: '0'
|
|
160
163
|
requirements: []
|
|
161
|
-
rubygems_version: 3.
|
|
162
|
-
signing_key:
|
|
164
|
+
rubygems_version: 3.6.9
|
|
163
165
|
specification_version: 4
|
|
164
166
|
summary: Confidence provider for the OpenFeature SDK
|
|
165
167
|
test_files: []
|