Skip to content

Commit db0432a

Browse files
RyWilliamsbeeme1mr
andauthored
refactor: satisfy requirement 1.3.4 (#186)
Signed-off-by: Ryan Williams <[email protected]> Co-authored-by: Michael Beemer <[email protected]>
1 parent f56b461 commit db0432a

File tree

4 files changed

+161
-120
lines changed

4 files changed

+161
-120
lines changed

lib/open_feature/sdk/client.rb

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@ module SDK
55
# TODO: Write documentation
66
#
77
class Client
8-
RESULT_TYPE = %i[boolean string number integer float object].freeze
8+
TYPE_CLASS_MAP = {
9+
boolean: [TrueClass, FalseClass],
10+
string: [String],
11+
number: [Numeric],
12+
integer: [Integer],
13+
float: [Float],
14+
object: [Array, Hash]
15+
}.freeze
16+
RESULT_TYPE = TYPE_CLASS_MAP.keys.freeze
917
SUFFIXES = %i[value details].freeze
1018

1119
attr_reader :metadata, :evaluation_context
@@ -26,14 +34,28 @@ def initialize(provider:, domain: nil, evaluation_context: nil)
2634
# result = @provider.fetch_boolean_value(flag_key: flag_key, default_value: default_value, evaluation_context: evaluation_context)
2735
# end
2836
def fetch_#{result_type}_#{suffix}(flag_key:, default_value:, evaluation_context: nil)
29-
built_context = EvaluationContextBuilder.new.call(api_context: OpenFeature::SDK.evaluation_context, client_context: self.evaluation_context, invocation_context: evaluation_context)
30-
resolution_details = @provider.fetch_#{result_type}_value(flag_key:, default_value:, evaluation_context: built_context)
31-
evaluation_details = EvaluationDetails.new(flag_key:, resolution_details:)
37+
evaluation_details = fetch_details(type: :#{result_type}, flag_key:, default_value:, evaluation_context:)
3238
#{"evaluation_details.value" if suffix == :value}
3339
end
3440
RUBY
3541
end
3642
end
43+
44+
private
45+
46+
def fetch_details(type:, flag_key:, default_value:, evaluation_context: nil)
47+
built_context = EvaluationContextBuilder.new.call(api_context: OpenFeature::SDK.evaluation_context, client_context: self.evaluation_context, invocation_context: evaluation_context)
48+
49+
resolution_details = @provider.send(:"fetch_#{type}_value", flag_key:, default_value:, evaluation_context: built_context)
50+
51+
if TYPE_CLASS_MAP[type].none? { |klass| resolution_details.value.is_a?(klass) }
52+
resolution_details.value = default_value
53+
resolution_details.error_code = Provider::ErrorCode::TYPE_MISMATCH
54+
resolution_details.reason = Provider::Reason::ERROR
55+
end
56+
57+
EvaluationDetails.new(flag_key:, resolution_details:)
58+
end
3759
end
3860
end
3961
end

lib/open_feature/sdk/provider/in_memory_provider.rb

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,45 +26,41 @@ def add_flag(flag_key:, value:)
2626
end
2727

2828
def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
29-
fetch_value(allowed_classes: [TrueClass, FalseClass], flag_key:, default_value:, evaluation_context:)
29+
fetch_value(flag_key:, default_value:, evaluation_context:)
3030
end
3131

3232
def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
33-
fetch_value(allowed_classes: [String], flag_key:, default_value:, evaluation_context:)
33+
fetch_value(flag_key:, default_value:, evaluation_context:)
3434
end
3535

3636
def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
37-
fetch_value(allowed_classes: [Numeric], flag_key:, default_value:, evaluation_context:)
37+
fetch_value(flag_key:, default_value:, evaluation_context:)
3838
end
3939

4040
def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
41-
fetch_value(allowed_classes: [Integer], flag_key:, default_value:, evaluation_context:)
41+
fetch_value(flag_key:, default_value:, evaluation_context:)
4242
end
4343

4444
def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
45-
fetch_value(allowed_classes: [Float], flag_key:, default_value:, evaluation_context:)
45+
fetch_value(flag_key:, default_value:, evaluation_context:)
4646
end
4747

4848
def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
49-
fetch_value(allowed_classes: [Array, Hash], flag_key:, default_value:, evaluation_context:)
49+
fetch_value(flag_key:, default_value:, evaluation_context:)
5050
end
5151

5252
private
5353

5454
attr_reader :flags
5555

56-
def fetch_value(allowed_classes:, flag_key:, default_value:, evaluation_context:)
56+
def fetch_value(flag_key:, default_value:, evaluation_context:)
5757
value = flags[flag_key]
5858

5959
if value.nil?
6060
return ResolutionDetails.new(value: default_value, error_code: ErrorCode::FLAG_NOT_FOUND, reason: Reason::ERROR)
6161
end
6262

63-
if allowed_classes.any? { |klass| value.is_a?(klass) }
64-
ResolutionDetails.new(value:, reason: Reason::STATIC)
65-
else
66-
ResolutionDetails.new(value: default_value, error_code: ErrorCode::TYPE_MISMATCH, reason: Reason::ERROR)
67-
end
63+
ResolutionDetails.new(value:, reason: Reason::STATIC)
6864
end
6965
end
7066
end

spec/open_feature/sdk/client_spec.rb

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,101 @@
100100
pending
101101
end
102102

103+
context "Requirement 1.3.4" do
104+
context "Guarantee the returned value of any typed flag evaluation method is of the expected type. If the value returned by the underlying provider implementation does not match the expected type, it's to be considered abnormal execution, and the supplied default value should be returned." do
105+
let(:provider) do
106+
OpenFeature::SDK::Provider::InMemoryProvider.new(
107+
{
108+
"bool" => "no",
109+
"str" => 123,
110+
"num" => true,
111+
"int" => "one",
112+
"float" => "1.23",
113+
"obj" => "{}"
114+
}
115+
)
116+
end
117+
118+
context "boolean value" do
119+
let(:flag_key) { "bool" }
120+
let(:default_value) { false }
121+
122+
it "returns default as type mismatch" do
123+
fetched = client.fetch_boolean_details(flag_key:, default_value:)
124+
125+
expect(fetched.value).to be(default_value)
126+
expect(fetched.error_code).to eq(OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH)
127+
expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR)
128+
end
129+
end
130+
131+
context "string value" do
132+
let(:flag_key) { "str" }
133+
let(:default_value) { "default" }
134+
135+
it "returns default as type mismatch" do
136+
fetched = client.fetch_string_details(flag_key:, default_value:)
137+
138+
expect(fetched.value).to be(default_value)
139+
expect(fetched.error_code).to eq(OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH)
140+
expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR)
141+
end
142+
end
143+
144+
context "number value" do
145+
let(:flag_key) { "num" }
146+
let(:default_value) { 4 }
147+
148+
it "returns default as type mismatch" do
149+
fetched = client.fetch_number_details(flag_key:, default_value:)
150+
151+
expect(fetched.value).to be(default_value)
152+
expect(fetched.error_code).to eq(OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH)
153+
expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR)
154+
end
155+
end
156+
157+
context "integer value" do
158+
let(:flag_key) { "int" }
159+
let(:default_value) { 4 }
160+
161+
it "returns default as type mismatch" do
162+
fetched = client.fetch_integer_details(flag_key:, default_value:)
163+
164+
expect(fetched.value).to be(default_value)
165+
expect(fetched.error_code).to eq(OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH)
166+
expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR)
167+
end
168+
end
169+
170+
context "float value" do
171+
let(:flag_key) { "float" }
172+
let(:default_value) { 1.23 }
173+
174+
it "returns default as type mismatch" do
175+
fetched = client.fetch_float_details(flag_key:, default_value:)
176+
177+
expect(fetched.value).to be(default_value)
178+
expect(fetched.error_code).to eq(OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH)
179+
expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR)
180+
end
181+
end
182+
183+
context "object value" do
184+
let(:flag_key) { "obj" }
185+
let(:default_value) { {} }
186+
187+
it "returns default as type mismatch" do
188+
fetched = client.fetch_object_details(flag_key:, default_value:)
189+
190+
expect(fetched.value).to be(default_value)
191+
expect(fetched.error_code).to eq(OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH)
192+
expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR)
193+
end
194+
end
195+
end
196+
end
197+
103198
context "Detailed Feature Evaluation" do
104199
let(:flag_key) { "my-awesome-feature-flag-key" }
105200

0 commit comments

Comments
 (0)