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 nested JSON and JSON arrays in params properly #1338

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
15 changes: 15 additions & 0 deletions spec/amber/validations/params_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@ require "../../spec_helper"
module Amber::Validators
describe Params do
describe "#validation" do
context "JSON param" do
it "parses json array as [] of JSON::Any" do
params = json_params_builder({ x: [ 1, 2, 3 ], y: {z: [ 1, 2 ] } }.to_json)
validator = Validators::Params.new(params)

validator.validation do
required(:x)
required("y")
end

validator.validate!["x"].should be_a Array(JSON::Any)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe returning a JSON::PullParser would be better here, so people could use JSON::Serializable to decode these parameters. 🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When was that introduced in the language? I don't recall seeing this at the time I wrote this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@a-alhusaini it's been part of the std library for a few years

https://crystal-lang.org/api/1.10.1/JSON/PullParser.html

validator.validate!["y"].should be_a JSON::Any
end
end

context "required params" do
context "when missing" do
it "is not valid and has 2 errors" do
Expand Down
5 changes: 5 additions & 0 deletions spec/support/helpers/validations_helper.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
module ValidationsHelper
def json_params_builder(body = "")
request = HTTP::Request.new("POST", "/", HTTP::Headers{"Content-Type" => "application/json"}, body)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is hard coded as a POST request. Does that mean that it can only be used with POST requests, or can it be used with PUT and PATCH requests too?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well this is just a test, but we should be testing POST PATCH AND PUT

params = Amber::Router::Params.new(request)
end

def params_builder(body = "")
request = HTTP::Request.new("GET", "")
params = Amber::Router::Params.new(request)
Expand Down
23 changes: 19 additions & 4 deletions src/amber/validators/params.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Amber::Validators
class BaseRule
getter predicate : (String -> Bool)
getter field : String
getter value : String?
getter value : String | Array(JSON::Any) | JSON::Any | Nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON::Any::Type includes Array(JSON::Any), so is it redundant to include both, or is there something I'm missing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was my thought as well, so I don't think we need the extra Array(JSON::Any)

getter present : Bool

def initialize(field : String | Symbol, @msg : String?, @allow_blank : Bool = true)
Expand All @@ -31,9 +31,23 @@ module Amber::Validators
end

private def call_predicate(params : Amber::Router::Params)
@value = params[@field]
@present = params.has_key?(@field)

begin
a-alhusaini marked this conversation as resolved.
Show resolved Hide resolved
value = JSON.parse(params[@field])

if value.is_a? Array(JSON::Any)
@value = value
elsif value.as_a?
@value = value.as_a
else
@value = value
end
rescue
@value = params[@field]
end


return true if params[@field].blank? && @allow_blank

@predicate.call params[@field] unless @predicate.nil?
Expand Down Expand Up @@ -82,10 +96,11 @@ module Amber::Validators
class Params
getter raw_params : Amber::Router::Params
getter rules = [] of BaseRule
getter params = {} of String => String?
getter params = {} of String => String | Array(JSON::Any) | JSON::Any | Nil
getter errors = [] of Error

def initialize(@raw_params); end
def initialize(@raw_params)
end

# This will allow params to respond to HTTP::Params methods.
# For example: [], []?, add, delete, each, fetch, etc.
Expand Down