diff --git a/lib/open_feature/sdk/hooks/hints.rb b/lib/open_feature/sdk/hooks/hints.rb new file mode 100644 index 0000000..8140fff --- /dev/null +++ b/lib/open_feature/sdk/hooks/hints.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module OpenFeature + module SDK + module Hooks + class Hints < DelegateClass(Hash) + ALLOWED_TYPES = [String, Symbol, Numeric, TrueClass, FalseClass, Time, Hash, Array].freeze + + def initialize(hash = {}) + hash.each_key { |key| raise ArgumentError, "Only String or Symbol are allowed as keys" unless key.is_a?(String) || key.is_a?(Symbol) } + hash.each_value do |v| + raise ArgumentError, "Only #{ALLOWED_TYPES.join(", ")} are allowed as values." unless ALLOWED_TYPES.any? { |t| v.is_a?(t) } + end + @hash = hash.dup + super(@hash) + freeze + end + end + end + end +end diff --git a/spec/open_feature/sdk/hooks/hints_spec.rb b/spec/open_feature/sdk/hooks/hints_spec.rb new file mode 100644 index 0000000..f73d253 --- /dev/null +++ b/spec/open_feature/sdk/hooks/hints_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "spec_helper" + +require "open_feature/sdk/hooks/hints" + +RSpec.describe OpenFeature::SDK::Hooks::Hints do + let(:hint_hash) { {key: [], nested: {key: []}} } + subject(:hints) { described_class.new(hint_hash) } + context "Immutability" do + it "is frozen" do + expect(hints).to be_frozen + end + + it "does not allow addition of new keys" do + expect { hints[:new_key] = "new_value" }.to raise_error(FrozenError) + end + + it "does allow modification of existing values" do + expect(hints[:key]).to_not be_frozen + expect { hints[:key] << "abc" }.to_not raise_error + end + + it "does not allow deletion of keys" do + expect { hints.delete(:key) }.to raise_error(FrozenError) + end + + it "allows reading of keys" do + expect(hints[:key]).to eq([]) + end + + it "only allows string keys" do + expect { described_class.new(1 => []) }.to raise_error(ArgumentError) do |e| + expect(e.message).to include("Only String or Symbol are allowed as keys") + end + end + + it "only allows values of certain types" do + expect { described_class.new(key: Object.new) }.to raise_error(ArgumentError) do |e| + expect(e.message).to include("Only String, Symbol, Numeric, TrueClass, FalseClass, Time, Hash, Array are allowed as values") + end + end + + it "does not freeze the original hash" do + expect(hint_hash).not_to be_frozen + end + end +end