Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle non-permitted params when comparing rules to request body #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions lib/typed_params/comparison.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
module TypedParams
class Comparison
# Example usage:
# Expected types for arguments indicated on the next line.
# Comparison.new(rules_format: RulesFormat, request_body: RequestBody)
#
def initialize(rules_format:, request_body:)
@rules_format, @request_body = rules_format, request_body
end

# Example usage:
# comparison.errors =>
# Returns nil when there are no errors when comparing the
# rules_format to the request_body.
# Otherwise, returns an object where each key is the path to the
# problematic parameter and whose value describes the problem.
def errors
@errors = {}

# Iterate over each param path and validate the corresponding rule
request_body_paths.each do |path|
param_with_rule = ParameterWithRule.new(
rule: @rules_format.find(*path),
parameter: @request_body.find(*path)
)

error = param_with_rule.validate
path_string = path.join("/")
@errors[path_string] = param_with_rule.validate if error
end

# Iterate over each rule path and validate the corresponding param
rule_paths.each do |path|
param_with_rule = ParameterWithRule.new(
rule: @rules_format.find(*path),
Expand All @@ -23,9 +46,8 @@ def errors

private

# List of parameters corresponding to the rules locations.
def parameters
@parameters ||= paths.map { |path| @request_body.find(*path) }
def request_body_paths
@request_body.paths
end

def rule_paths
Expand All @@ -41,6 +63,8 @@ def initialize(parameter:, rule:)
# Return nil if the parameter meets the rule's constraint(s).
# Otherwise return a tuple conforming to (path_to_param, reason_not_valid).
def validate
return "non_permitted_param" if rule.nil?

if !parameter.value.is_a?(rule.condition)
"param_must_be_#{rule.condition.name.downcase}"
end
Expand Down
8 changes: 1 addition & 7 deletions lib/typed_params/request_body.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,8 @@ def paths
# Returns a Parameter instance when the path points to an existing param.
# Returns nil otherwise.
def find(path)
param = parameter_at(path)
param = @parameters.dig(*path)
Parameter.new(path, param) if param
end

private

def parameter_at(chunks)
@parameters.dig(*chunks)
end
end
end
14 changes: 4 additions & 10 deletions lib/typed_params/rules_format.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,11 @@ def paths
end

# find(["data", "attributes", "name"])
# Raises an error if `rule` not found.
# Returns a Rule instance when the path points to an existing param.
# Returns a Rule instance when the path points to an existing rule.
# Returns nil otherwise.
def find(path)
rule = @rules.dig(*path) || raise(RuleNotFound)
Rule.new(path, rule)
end

private

def rule_at(chunks)
@rules.dig(*chunks)
rule = @rules.dig(*path)
Rule.new(path, rule) if rule
end
end
end
16 changes: 13 additions & 3 deletions spec/typed_params/comparison_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,20 @@
end

context "when the request body does not adhere to the rules format" do
let(:name) { 1_000 }
context "when a non-specified parameter is included in the request body" do
let(:params) { { name: "M@", email: "[email protected]" } }

it "returns an object describing the invalid attribute" do
expect(subject).to eq({ "name" => "param_must_be_string" })
it "adds an error for the non-whitelisted param" do
expect(subject).to eq({ "email" => "non_permitted_param" })
end
end

context "when the require parameter is of the wrong type" do
let(:name) { 1_000 }

it "returns an object describing the invalid attribute" do
expect(subject).to eq({ "name" => "param_must_be_string" })
end
end
end
end
Expand Down
16 changes: 11 additions & 5 deletions spec/typed_params/request_body_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@
TypedParams::Parameter.new(path, parameters['data']['attributes'])
end

context "when the path argument mixes symbols and strings" do
let(:path) { [ :data, "attributes" ] }
context "when there a rule at the given path" do
context "when the path argument mixes symbols and strings" do
let(:path) { [ :data, "attributes" ] }

it { is_expected.to eq paramter }
end
it { is_expected.to eq paramter }
end

context "when there is no rule at the given path" do
let(:path) { %w(foo bar) }

it { is_expected.to eq paramter }
it { is_expected.to eq nil }
end
end
end

describe "#paths" do
Expand Down
4 changes: 1 addition & 3 deletions spec/typed_params/rules_format_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@
context "when there is no rule at the given path" do
let(:path) { %w(foo bar) }

it "raises an error" do
expect { subject }.to raise_error TypedParams::RuleNotFound
end
it { is_expected.to eq nil }
end
end
end