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

Add URI::Params::Serializable #14684

Merged
Merged
151 changes: 151 additions & 0 deletions spec/std/uri/params/from_www_form_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
require "spec"
require "uri/params/serializable"

private enum Color
Red
Green
Blue
end

describe ".from_www_form" do
it Array do
Array(Int32).from_www_form(URI::Params.new({"values" => ["1", "2"]}), "values").should eq [1, 2]
Array(Int32).from_www_form(URI::Params.new({"values[]" => ["1", "2"]}), "values").should eq [1, 2]
Array(String).from_www_form(URI::Params.new({"values" => ["", ""]}), "values").should eq ["", ""]
end

describe Bool do
it "a truthy value" do
Bool.from_www_form("true").should be_true
Bool.from_www_form("on").should be_true
Bool.from_www_form("yes").should be_true
Bool.from_www_form("1").should be_true
end

it "a falsey value" do
Bool.from_www_form("false").should be_false
Bool.from_www_form("off").should be_false
Bool.from_www_form("no").should be_false
Bool.from_www_form("0").should be_false
end

it "any other value" do
Bool.from_www_form("foo").should be_nil
Bool.from_www_form("").should be_nil
end
end

describe String do
it "scalar string" do
String.from_www_form("John Doe").should eq "John Doe"
end

it "with key" do
String.from_www_form(URI::Params.new({"name" => ["John Doe"]}), "name").should eq "John Doe"
end

it "with missing key" do
String.from_www_form(URI::Params.new({"" => ["John Doe"]}), "name").should be_nil
end

it "with alternate casing" do
String.from_www_form(URI::Params.new({"Name" => ["John Doe"]}), "name").should be_nil
end

it "empty value" do
String.from_www_form(URI::Params.new({"name" => [""]}), "name").should eq ""
end
end

describe Enum do
it "valid value" do
Color.from_www_form("green").should eq Color::Green
end

it "invalid value" do
expect_raises ArgumentError do
Color.from_www_form ""
end
end
end

describe Time do
it "valid value" do
Time.from_www_form("2016-11-16T09:55:48-03:00").to_utc.should eq(Time.utc(2016, 11, 16, 12, 55, 48))
Time.from_www_form("2016-11-16T09:55:48-0300").to_utc.should eq(Time.utc(2016, 11, 16, 12, 55, 48))
Time.from_www_form("20161116T095548-03:00").to_utc.should eq(Time.utc(2016, 11, 16, 12, 55, 48))
end

it "invalid value" do
expect_raises Time::Format::Error do
Time.from_www_form ""
end
end
end

describe Nil do
it "valid values" do
Nil.from_www_form("").should be_nil
end

it "invalid value" do
expect_raises ArgumentError do
Nil.from_www_form "null"
end
end
end

describe Number do
describe Int do
it "valid numbers" do
Int64.from_www_form("123").should eq 123_i64
UInt8.from_www_form("7").should eq 7_u8
Int64.from_www_form("-12").should eq -12_i64
end

it "with whitespace" do
expect_raises ArgumentError do
Int32.from_www_form(" 123")
end
end

it "empty value" do
expect_raises ArgumentError do
Int16.from_www_form ""
end
end
end

describe Float do
it "valid numbers" do
Float32.from_www_form("123.0").should eq 123_f32
Float64.from_www_form("123.0").should eq 123_f64
end

it "with whitespace" do
expect_raises ArgumentError do
Float64.from_www_form(" 123.0")
end
end

it "empty value" do
expect_raises Exception do
Float64.from_www_form ""
end
Comment on lines +132 to +134
Copy link
Member Author

@Blacksmoke16 Blacksmoke16 Aug 14, 2024

Choose a reason for hiding this comment

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

This might be worth its own issue:

$ crystal eval 'pp Float64.new "", whitespace: false'
Unhandled exception: Index out of bounds (IndexError)
  from /usr/lib/crystal/string.cr:996:16 in '[]'
  from /usr/lib/crystal/string.cr:755:40 in 'to_f64?'
  from /usr/lib/crystal/string.cr:710:5 in 'to_f64'
  from /usr/lib/crystal/float.cr:327:5 in 'new:whitespace'
  from eval:1:1 in '__crystal_main'
  from /usr/lib/crystal/crystal/main.cr:118:5 in 'main_user_code'
  from /usr/lib/crystal/crystal/main.cr:104:7 in 'main'
  from /usr/lib/crystal/crystal/main.cr:130:3 in 'main'
  from /usr/lib/libc.so.6 in '??'
  from /usr/lib/libc.so.6 in '__libc_start_main'
  from /home/george/.cache/crystal/crystal-run-eval.tmp in '_start'
  from ???

I'd expect this to bubble up an ArgumentError. At the moment it seems to bubble up an exception from the implementation when using whitespace: false.

end
end
end

describe Union do
it "valid" do
String?.from_www_form(URI::Params.parse("name=John Doe"), "name").should eq "John Doe"
String?.from_www_form(URI::Params.parse("name="), "name").should eq ""
end

it "invalid" do
expect_raises ArgumentError do
(Int32 | Float64).from_www_form(URI::Params.parse("name=John Doe"), "name")
end
end
end
end
133 changes: 133 additions & 0 deletions spec/std/uri/params/serializable_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
require "spec"
require "uri/params/serializable"

private record SimpleType, page : Int32, strict : Bool, per_page : UInt8 do
include URI::Params::Serializable
end

private record SimpleTypeDefaults, page : Int32, strict : Bool, per_page : Int32 = 10 do
include URI::Params::Serializable
end

private record SimpleTypeNilable, page : Int32, strict : Bool, per_page : Int32? = nil do
include URI::Params::Serializable
end

private record SimpleTypeNilableDefault, page : Int32, strict : Bool, per_page : Int32? = 20 do
include URI::Params::Serializable
end

record Filter, status : String?, total : Float64? do
include URI::Params::Serializable
end

record Search, filter : Filter?, limit : Int32 = 25, offset : Int32 = 0 do
include URI::Params::Serializable
end

record GrandChild, name : String do
include URI::Params::Serializable
end

record Child, status : String?, grand_child : GrandChild do
include URI::Params::Serializable
end

record Parent, child : Child do
include URI::Params::Serializable
end

module MyConverter
def self.from_www_form(params : URI::Params, name : String)
params[name].to_i * 10
end
end

private record ConverterType, value : Int32 do
include URI::Params::Serializable

@[URI::Params::Field(converter: MyConverter)]
@value : Int32
end

class ParentType
include URI::Params::Serializable

getter name : String
end

class ChildType < ParentType
end

describe URI::Params::Serializable do
describe ".from_www_form" do
it "simple type" do
SimpleType.from_www_form("page=10&strict=true&per_page=5").should eq SimpleType.new(10, true, 5)
end

it "missing required property" do
expect_raises URI::SerializableError, "Missing required property: 'page'." do
SimpleType.from_www_form("strict=true&per_page=5")
end
end

it "with default values" do
SimpleTypeDefaults.from_www_form("page=10&strict=off").should eq SimpleTypeDefaults.new(10, false, 10)
end

it "with nilable values" do
SimpleTypeNilable.from_www_form("page=10&strict=true").should eq SimpleTypeNilable.new(10, true, nil)
end

it "with nilable default" do
SimpleTypeNilableDefault.from_www_form("page=10&strict=true").should eq SimpleTypeNilableDefault.new(10, true, 20)
end

it "with custom converter" do
ConverterType.from_www_form("value=10").should eq ConverterType.new(100)
end

it "child type" do
ChildType.from_www_form("name=Fred").name.should eq "Fred"
end

describe "nested type" do
it "happy path" do
Search.from_www_form("offset=10&filter[status]=active&filter[total]=3.14")
.should eq Search.new Filter.new("active", 3.14), offset: 10
end

it "missing nilable nested data" do
Search.from_www_form("offset=10")
.should eq Search.new Filter.new(nil, nil), offset: 10
end

it "missing required nested property" do
expect_raises URI::SerializableError, "Missing required property: 'child[grand_child][name]'." do
Parent.from_www_form("child[status]=active")
end
end

it "doubly nested" do
Parent.from_www_form("child[status]=active&child[grand_child][name]=Fred")
.should eq Parent.new Child.new("active", GrandChild.new("Fred"))
end
end
end

describe "#to_www_form" do
it "simple type" do
SimpleType.new(10, true, 5).to_www_form.should eq "page=10&strict=true&per_page=5"
end

it "nested type path" do
Search.new(Filter.new("active", 3.14), offset: 10).to_www_form
.should eq "filter%5Bstatus%5D=active&filter%5Btotal%5D=3.14&limit=25&offset=10"
end

it "doubly nested" do
Parent.new(Child.new("active", GrandChild.new("Fred"))).to_www_form
.should eq "child%5Bstatus%5D=active&child%5Bgrand_child%5D%5Bname%5D=Fred"
end
end
end
60 changes: 60 additions & 0 deletions spec/std/uri/params/to_www_form_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require "spec"
require "uri/params/serializable"

private enum Color
Red
Green
BlueGreen
end

describe "#to_www_form" do
it Number do
URI::Params.build do |builder|
12.to_www_form builder, "value"
end.should eq "value=12"
end

it Enum do
URI::Params.build do |builder|
Color::BlueGreen.to_www_form builder, "value"
end.should eq "value=blue_green"
end

it String do
URI::Params.build do |builder|
"12".to_www_form builder, "value"
end.should eq "value=12"
end

it Bool do
URI::Params.build do |builder|
false.to_www_form builder, "value"
end.should eq "value=false"
end

it Nil do
URI::Params.build do |builder|
nil.to_www_form builder, "value"
end.should eq "value="
end

it Time do
URI::Params.build do |builder|
Time.utc(2024, 8, 6, 9, 48, 10).to_www_form builder, "value"
end.should eq "value=2024-08-06T09%3A48%3A10Z"
end

describe Array do
it "of a single type" do
URI::Params.build do |builder|
[1, 2, 3].to_www_form builder, "value"
end.should eq "value=1&value=2&value=3"
end

it "of a union of types" do
URI::Params.build do |builder|
[1, false, "foo"].to_www_form builder, "value"
end.should eq "value=1&value=false&value=foo"
end
end
end
1 change: 1 addition & 0 deletions src/docs_main.cr
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ require "./string_pool"
require "./string_scanner"
require "./unicode/unicode"
require "./uri"
require "./uri/params/serializable"
require "./uuid"
require "./uuid/json"
require "./syscall"
Expand Down
Loading
Loading