Skip to content

Commit 5b61af8

Browse files
committed
BARN-77: validate context override keys; update specs and use shared_examples
1 parent b560c84 commit 5b61af8

File tree

2 files changed

+151
-79
lines changed

2 files changed

+151
-79
lines changed

lib/parameter_substitution.rb

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# See lib/parameter_substitution/readme.md
44

55
require 'active_support/all'
6+
require 'set'
67
require "parameter_substitution/context"
78
require "parameter_substitution/parse_error"
89
require "parameter_substitution/parser"
@@ -63,17 +64,17 @@ def configure
6364
ParameterSubstitution.config = config
6465
end
6566

66-
def find_tokens(string_with_tokens, mapping: {}, context_overrides: nil)
67+
def find_tokens(string_with_tokens, mapping: {}, context_overrides: {})
6768
context = build_context(string_with_tokens, mapping, context_overrides)
6869
parse_expression(context).substitution_parameter_names
6970
end
7071

71-
def find_formatters(string_with_tokens, mapping: {}, context_overrides: nil)
72+
def find_formatters(string_with_tokens, mapping: {}, context_overrides: {})
7273
context = build_context(string_with_tokens, mapping, context_overrides)
7374
parse_expression(context).method_names
7475
end
7576

76-
def find_warnings(string_with_tokens, mapping: {}, context_overrides: nil)
77+
def find_warnings(string_with_tokens, mapping: {}, context_overrides: {})
7778
context = build_context(string_with_tokens, mapping, context_overrides)
7879
parse_expression(context).parameter_and_method_warnings || []
7980
end
@@ -83,16 +84,42 @@ def find_warnings(string_with_tokens, mapping: {}, context_overrides: nil)
8384
# Build context with optional overrides
8485
# @param [String] string_with_tokens The input string containing tokens
8586
# @param [Hash] mapping The mapping of parameters to values
86-
# @param [Hash, nil] context_overrides Optional overrides for context attributes
87+
# @param [Hash] context_overrides Optional overrides for context attributes
8788
# @return [ParameterSubstitution::Context] The constructed context
89+
# @raise [ArgumentError] if context_overrides contains invalid keys
8890
def build_context(string_with_tokens, mapping, context_overrides)
89-
override_options = context_overrides || {}
91+
validate_context_overrides!(context_overrides)
92+
9093
base_options = {
9194
input: string_with_tokens,
9295
mapping: mapping
9396
}
9497

95-
ParameterSubstitution::Context.new(**override_options.merge(base_options))
98+
ParameterSubstitution::Context.new(**context_overrides.merge(base_options))
99+
end
100+
101+
# @param [Hash] context_overrides The overrides to validate
102+
# @raise [ArgumentError] if context_overrides contains invalid keys
103+
def validate_context_overrides!(context_overrides)
104+
return if context_overrides.empty?
105+
106+
valid_keys = %i[
107+
required_parameters
108+
parameter_start
109+
parameter_end
110+
destination_encoding
111+
allow_unknown_replacement_parameters
112+
allow_nil
113+
allow_unmatched_parameter_end
114+
].to_set
115+
116+
invalid_keys = context_overrides.keys.reject { |key| valid_keys.include?(key.to_sym) }
117+
118+
if invalid_keys.any?
119+
invalid_keys_list = invalid_keys.join(", ")
120+
valid_keys_list = valid_keys.sort.join(", ")
121+
raise ArgumentError, "Invalid context_overrides keys: #{invalid_keys_list}. Valid keys are: #{valid_keys_list}"
122+
end
96123
end
97124

98125

spec/lib/parameter_substitution_spec.rb

Lines changed: 118 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,44 @@ def self.find(_key); end
7373
let(:expression) { "<call.start_time.blank_if_nil><do_a_barrel_roll.downcase>" }
7474
let(:mapping) { { 'call.start_time' => 'hello' } }
7575

76+
shared_examples "passes context_overrides to Context" do
77+
it "calls Context.new with the expected overrides" do
78+
allow(ParameterSubstitution::Context).to receive(:new).and_call_original
79+
subject
80+
expect(ParameterSubstitution::Context).to have_received(:new).once.with(
81+
hash_including(test_context_overrides.merge(
82+
input: test_expression,
83+
mapping: test_mapping
84+
))
85+
)
86+
end
87+
end
88+
89+
shared_examples "validates context_overrides" do
90+
context 'when context_overrides attempts to override base options' do
91+
context "when input is provided in context_overrides" do
92+
let(:test_context_overrides) { { input: "<different>" } }
93+
it "raises error" do
94+
expect { subject }.to raise_error(ArgumentError, /Invalid context_overrides keys: input/)
95+
end
96+
end
97+
98+
context "when mapping is provided in context_overrides" do
99+
let(:test_context_overrides) { { mapping: { 'different' => 'value' } } }
100+
it "raises error" do
101+
expect { subject }.to raise_error(ArgumentError, /Invalid context_overrides keys: mapping/)
102+
end
103+
end
104+
end
105+
106+
context 'when context_overrides contains invalid keys' do
107+
let(:test_context_overrides) { { invalid_key: "value", another_invalid: "value2" } }
108+
it "raises error with list of invalid keys" do
109+
expect { subject }.to raise_error(ArgumentError, /Invalid context_overrides keys: invalid_key, another_invalid/)
110+
end
111+
end
112+
end
113+
76114
context "#find_tokens" do
77115
it "returns tokens up to first dot when no mapping is provided" do
78116
expect(ParameterSubstitution.find_tokens(expression)).to eq(['call', 'do_a_barrel_roll'])
@@ -83,29 +121,29 @@ def self.find(_key); end
83121
end
84122

85123
context 'with non-default delimiters' do
86-
let(:square_expression) { "[call.start_time.blank_if_nil][do_a_barrel_roll.downcase]" }
87-
88-
it "returns tokens up to first dot when no mapping is provided" do
89-
context_overrides = { parameter_start: "[", parameter_end: "]" }
90-
expect(ParameterSubstitution.find_tokens(square_expression, context_overrides: context_overrides)).to eq(['call', 'do_a_barrel_roll'])
91-
end
92-
93-
it "returns tokens that exist in mapping when one is provided" do
94-
context_overrides = { parameter_start: "[", parameter_end: "]" }
95-
expect(ParameterSubstitution.find_tokens(square_expression, mapping: mapping, context_overrides: context_overrides)).to eq(['call.start_time', 'do_a_barrel_roll'])
124+
include_examples "passes context_overrides to Context" do
125+
let(:test_expression) { "[call.start_time.blank_if_nil][do_a_barrel_roll.downcase]" }
126+
let(:test_mapping) { {} }
127+
let(:test_context_overrides) do
128+
{
129+
required_parameters: ["param1", "param2"],
130+
parameter_start: "[",
131+
parameter_end: "]",
132+
destination_encoding: :json,
133+
allow_unknown_replacement_parameters: true,
134+
allow_nil: true,
135+
allow_unmatched_parameter_end: true
136+
}
137+
end
138+
subject { ParameterSubstitution.find_tokens(test_expression, mapping: test_mapping, context_overrides: test_context_overrides) }
96139
end
97140
end
98141

99-
context 'when context_overrides attempts to override base options' do
100-
it "ignores input override and uses method parameter" do
101-
context_overrides = { input: "<different>" }
102-
expect(ParameterSubstitution.find_tokens(expression, context_overrides: context_overrides)).to eq(['call', 'do_a_barrel_roll'])
103-
end
104-
105-
it "ignores mapping override and uses method parameter" do
106-
context_overrides = { mapping: { 'different' => 'value' } }
107-
expect(ParameterSubstitution.find_tokens(expression, mapping: mapping, context_overrides: context_overrides)).to eq(['call.start_time', 'do_a_barrel_roll'])
108-
end
142+
include_examples "validates context_overrides" do
143+
let(:test_expression) { expression }
144+
let(:test_mapping) { mapping }
145+
let(:test_context_overrides) { {} }
146+
subject { ParameterSubstitution.find_tokens(test_expression, mapping: test_mapping, context_overrides: test_context_overrides) }
109147
end
110148
end
111149

@@ -119,29 +157,29 @@ def self.find(_key); end
119157
end
120158

121159
context 'with non-default delimiters' do
122-
let(:square_expression) { "[call.start_time.blank_if_nil][do_a_barrel_roll.downcase]" }
123-
124-
it "returns all formatters after first dot when no mapping is provided" do
125-
context_overrides = { parameter_start: "[", parameter_end: "]" }
126-
expect(ParameterSubstitution.find_formatters(square_expression, context_overrides: context_overrides)).to eq(['start_time', 'blank_if_nil', 'downcase'])
127-
end
128-
129-
it "returns formatters after all tokens when mapping is provided" do
130-
context_overrides = { parameter_start: "[", parameter_end: "]" }
131-
expect(ParameterSubstitution.find_formatters(square_expression, mapping: mapping, context_overrides: context_overrides)).to eq(['blank_if_nil', 'downcase'])
160+
include_examples "passes context_overrides to Context" do
161+
let(:test_expression) { "[call.start_time.blank_if_nil][do_a_barrel_roll.downcase]" }
162+
let(:test_mapping) { {} }
163+
let(:test_context_overrides) do
164+
{
165+
required_parameters: ["param1", "param2"],
166+
parameter_start: "[",
167+
parameter_end: "]",
168+
destination_encoding: :json,
169+
allow_unknown_replacement_parameters: true,
170+
allow_nil: true,
171+
allow_unmatched_parameter_end: true
172+
}
173+
end
174+
subject { ParameterSubstitution.find_formatters(test_expression, mapping: test_mapping, context_overrides: test_context_overrides) }
132175
end
133176
end
134177

135-
context 'when context_overrides attempts to override base options' do
136-
it "ignores input override and uses method parameter" do
137-
context_overrides = { input: "<different>" }
138-
expect(ParameterSubstitution.find_formatters(expression, context_overrides: context_overrides)).to eq(['start_time', 'blank_if_nil', 'downcase'])
139-
end
140-
141-
it "ignores mapping override and uses method parameter" do
142-
context_overrides = { mapping: { 'different' => 'value' } }
143-
expect(ParameterSubstitution.find_formatters(expression, mapping: mapping, context_overrides: context_overrides)).to eq(['blank_if_nil', 'downcase'])
144-
end
178+
include_examples "validates context_overrides" do
179+
let(:test_expression) { expression }
180+
let(:test_mapping) { mapping }
181+
let(:test_context_overrides) { {} }
182+
subject { ParameterSubstitution.find_formatters(test_expression, mapping: test_mapping, context_overrides: test_context_overrides) }
145183
end
146184
end
147185

@@ -152,6 +190,26 @@ def self.find(_key); end
152190
let(:expression_with_bad_params_and_methods) { "<bobby.test1.test2><bobby2.test3.test4>" }
153191
let(:expression_with_mixed_bad_params_and_methods) { "<bobby.test1.test2><foo.test3.test4>" }
154192

193+
shared_examples "validates context_overrides for find_warnings" do
194+
context 'when context_overrides attempts to override base options' do
195+
context "when input is provided in context_overrides" do
196+
let(:test_context_overrides) { { input: "<different>" } }
197+
it "raises error" do
198+
expect { subject }.to raise_error(ArgumentError, /Invalid context_overrides keys: input/)
199+
end
200+
end
201+
202+
context "when mapping is provided in context_overrides" do
203+
let(:test_expression) { expression_with_bad_params }
204+
let(:test_mapping) { {} }
205+
let(:test_context_overrides) { { mapping: { 'bobby' => 'value', 'bobby2' => 'value' } } }
206+
it "raises error" do
207+
expect { subject }.to raise_error(ArgumentError, /Invalid context_overrides keys: mapping/)
208+
end
209+
end
210+
end
211+
end
212+
155213
context "when parameters are valid" do
156214
it "returns empty array" do
157215
expect(ParameterSubstitution.find_warnings(expression_with_valid_params, mapping: default_mapping))
@@ -181,42 +239,29 @@ def self.find(_key); end
181239
end
182240

183241
context "with non-default delimiters" do
184-
let(:square_expression_with_valid_params) { "[foo]" }
185-
let(:square_expression_with_bad_params) { "[bobby][bobby2]" }
186-
let(:square_expression_with_bad_methods) { "[foo.test1.test2][foo.test3.test4][black.test1.test2]" }
187-
188-
it "returns empty array for valid parameters" do
189-
context_overrides = { parameter_start: "[", parameter_end: "]" }
190-
expect(ParameterSubstitution.find_warnings(square_expression_with_valid_params, mapping: default_mapping, context_overrides: context_overrides))
191-
.to eq([])
192-
end
193-
194-
it "returns warnings for invalid parameters" do
195-
context_overrides = { parameter_start: "[", parameter_end: "]" }
196-
expect(ParameterSubstitution.find_warnings(square_expression_with_bad_params, context_overrides: context_overrides))
197-
.to eq(["Unknown param 'bobby'", "Unknown param 'bobby2'"])
198-
end
199-
200-
it "returns warnings for invalid methods" do
201-
context_overrides = { parameter_start: "[", parameter_end: "]" }
202-
expect(ParameterSubstitution.find_warnings(square_expression_with_bad_methods, mapping: default_mapping, context_overrides: context_overrides))
203-
.to eq(["Unknown methods 'test1', 'test2', 'test3', 'test4' used on parameter 'foo'",
204-
"Unknown methods 'test1', 'test2' used on parameter 'black'"])
242+
include_examples "passes context_overrides to Context" do
243+
let(:test_expression) { "[foo]" }
244+
let(:test_mapping) { default_mapping }
245+
let(:test_context_overrides) do
246+
{
247+
required_parameters: ["param1", "param2"],
248+
parameter_start: "[",
249+
parameter_end: "]",
250+
destination_encoding: :json,
251+
allow_unknown_replacement_parameters: true,
252+
allow_nil: true,
253+
allow_unmatched_parameter_end: true
254+
}
255+
end
256+
subject { ParameterSubstitution.find_warnings(test_expression, mapping: test_mapping, context_overrides: test_context_overrides) }
205257
end
206258
end
207259

208-
context 'when context_overrides attempts to override base options' do
209-
it "ignores input override and uses method parameter" do
210-
context_overrides = { input: "<different>" }
211-
expect(ParameterSubstitution.find_warnings(expression_with_valid_params, mapping: default_mapping, context_overrides: context_overrides))
212-
.to eq([])
213-
end
214-
215-
it "ignores mapping override and uses method parameter" do
216-
context_overrides = { mapping: { 'bobby' => 'value', 'bobby2' => 'value' } }
217-
expect(ParameterSubstitution.find_warnings(expression_with_bad_params, mapping: {}, context_overrides: context_overrides))
218-
.to eq(["Unknown param 'bobby'", "Unknown param 'bobby2'"])
219-
end
260+
include_examples "validates context_overrides for find_warnings" do
261+
let(:test_expression) { expression_with_valid_params }
262+
let(:test_mapping) { default_mapping }
263+
let(:test_context_overrides) { {} }
264+
subject { ParameterSubstitution.find_warnings(test_expression, mapping: test_mapping, context_overrides: test_context_overrides) }
220265
end
221266
end
222267
end

0 commit comments

Comments
 (0)