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 private record GenericConverterType(T), value : Int32 do include URI::Params::Serializable @[URI::Params::Field(converter: T)] @value : Int32 end class ParentType include URI::Params::Serializable getter name : String end class ChildType < ParentType end private record SimpleTypeInitializeOpts, value : Int32 do include URI::Params::Serializable def initialize(**opts) @value = opts.size end 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 it "works when type has constructor with double splat parameter (#16140)" do SimpleTypeInitializeOpts.from_www_form("value=123").value.should eq(123) end it "supports generic type variables in converters" do GenericConverterType(MyConverter).from_www_form("value=123").value.should eq(1230) end end