-
-
Notifications
You must be signed in to change notification settings - Fork 159
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Replace avram parsing with Lucky::ParamParser (#1616)
- Loading branch information
1 parent
10b19c4
commit e13a8a6
Showing
3 changed files
with
200 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
require "../spec_helper" | ||
|
||
enum TimeComponent | ||
Year | ||
Month | ||
Day | ||
Hour | ||
Minute | ||
Second | ||
end | ||
|
||
struct FormattedTime | ||
property label : String | ||
property value : String | ||
property components : Array(TimeComponent) | ||
|
||
def initialize(@label : String, @value : String, excluded_components : Array(TimeComponent) = [] of TimeComponent) | ||
@components = [ | ||
TimeComponent::Year, | ||
TimeComponent::Month, | ||
TimeComponent::Day, | ||
TimeComponent::Hour, | ||
TimeComponent::Minute, | ||
TimeComponent::Second, | ||
] - excluded_components | ||
end | ||
end | ||
|
||
macro numbers_tests(klass, input, output) | ||
describe "parse {{ klass }}" do | ||
it "turns string into number" do | ||
Lucky::ParamParser.parse({{ input }}, {{ klass }}).should eq({{ output }}) | ||
end | ||
|
||
it "returns nil if param not number" do | ||
Lucky::ParamParser.parse("abc", {{ klass }}).should be_nil | ||
end | ||
|
||
it "returns nil if param blank" do | ||
Lucky::ParamParser.parse("", {{ klass }}).should be_nil | ||
end | ||
end | ||
end | ||
|
||
describe Lucky::ParamParser do | ||
numbers_tests(Int16, "1", 1_i16) | ||
numbers_tests(Int32, "12", 12) | ||
numbers_tests(Int64, "144", 144_i64) | ||
numbers_tests(Float64, "1.23", 1.23_f64) | ||
|
||
describe "parse String" do | ||
it "does not change the value" do | ||
Lucky::ParamParser.parse("foo", String).should eq("foo") | ||
end | ||
end | ||
|
||
describe "parse Bool" do | ||
it "parses forms of true" do | ||
Lucky::ParamParser.parse("true", Bool).should be_true | ||
Lucky::ParamParser.parse("1", Bool).should be_true | ||
end | ||
|
||
it "parses forms of false" do | ||
Lucky::ParamParser.parse("false", Bool).should be_false | ||
Lucky::ParamParser.parse("0", Bool).should be_false | ||
end | ||
|
||
it "returns nil for other values" do | ||
Lucky::ParamParser.parse("asdf", Bool).should be_nil | ||
end | ||
end | ||
|
||
describe "parse UUID" do | ||
it "parses uuid string" do | ||
uuid = "0881a13e-e283-45a0-9dba-6d05463eec45" | ||
|
||
Lucky::ParamParser.parse(uuid, UUID).should eq(UUID.new(uuid)) | ||
end | ||
|
||
it "returns nil if not uuid" do | ||
Lucky::ParamParser.parse("INVALID", UUID).should be_nil | ||
end | ||
end | ||
|
||
describe "parse Time" do | ||
it "parses various formats successfully" do | ||
time = Time.utc | ||
[ | ||
FormattedTime.new("ISO 8601", time.to_s("%FT%X%z")), | ||
FormattedTime.new("RFC 2822", time.to_rfc2822), | ||
FormattedTime.new("RFC 3339", time.to_rfc3339), | ||
FormattedTime.new("DateTime HTML Input", time.to_s("%Y-%m-%dT%H:%M:%S")), | ||
FormattedTime.new("DateTime HTML Input (no seconds)", time.to_s("%Y-%m-%dT%H:%M"), excluded_components: [TimeComponent::Second]), | ||
FormattedTime.new("HTTP Date", time.to_s("%a, %d %b %Y %H:%M:%S GMT")), | ||
].each do |formatted_time| | ||
result = Lucky::ParamParser.parse(formatted_time.value, Time) | ||
|
||
result.should_not be_nil | ||
result = result.not_nil! | ||
result.year.should eq(time.year) if formatted_time.components.includes? TimeComponent::Year | ||
result.month.should eq(time.month) if formatted_time.components.includes? TimeComponent::Month | ||
result.day.should eq(time.day) if formatted_time.components.includes? TimeComponent::Day | ||
result.hour.should eq(time.hour) if formatted_time.components.includes? TimeComponent::Hour | ||
result.minute.should eq(time.minute) if formatted_time.components.includes? TimeComponent::Minute | ||
result.second.should eq(time.second) if formatted_time.components.includes? TimeComponent::Second | ||
end | ||
end | ||
|
||
it "returns nil if unable to parse" do | ||
Lucky::ParamParser.parse("INVALID", Time).should be_nil | ||
end | ||
end | ||
|
||
describe "parse Array(T)" do | ||
it "handles strings" do | ||
Lucky::ParamParser.parse(["a", "b"], Array(String)).should eq(["a", "b"]) | ||
end | ||
|
||
it "handles numbers" do | ||
Lucky::ParamParser.parse(["1", "2"], Array(Int32)).should eq([1, 2]) | ||
end | ||
|
||
it "handles bools" do | ||
Lucky::ParamParser.parse(["1", "0", "true"], Array(Bool)).should eq([true, false, true]) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
module Lucky::ParamParser | ||
TIME_FORMATS = [ | ||
Time::Format::ISO_8601_DATE_TIME, | ||
Time::Format::RFC_2822, | ||
Time::Format::RFC_3339, | ||
# HTML datetime-local inputs are basically RFC 3339 without the timezone: | ||
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local | ||
Time::Format.new("%Y-%m-%dT%H:%M:%S", Time::Location::UTC), | ||
Time::Format.new("%Y-%m-%dT%H:%M", Time::Location::UTC), | ||
# Dates and times go last, otherwise it will parse strings with both | ||
# dates *and* times incorrectly. | ||
Time::Format::HTTP_DATE, | ||
Time::Format::ISO_8601_DATE, | ||
Time::Format::ISO_8601_TIME, | ||
] | ||
|
||
def self.parse(param : String, klass : String.class) : String | ||
param | ||
end | ||
|
||
def self.parse(param : String, klass : Int16.class) : Int16? | ||
param.to_i16? | ||
end | ||
|
||
def self.parse(param : String, klass : Int32.class) : Int32? | ||
param.to_i? | ||
end | ||
|
||
def self.parse(param : String, klass : Int64.class) : Int64? | ||
param.to_i64? | ||
end | ||
|
||
def self.parse(param : String, klass : Float64.class) : Float64? | ||
param.to_f? | ||
end | ||
|
||
def self.parse(param : String, klass : Bool.class) : Bool? | ||
if %w(true 1).includes? param | ||
true | ||
elsif %w(false 0).includes? param | ||
false | ||
else | ||
nil | ||
end | ||
end | ||
|
||
def self.parse(param : String, klass : UUID.class) : UUID? | ||
UUID.new(param) | ||
rescue | ||
nil | ||
end | ||
|
||
def self.parse(param : String, klass : Time.class) : Time? | ||
TIME_FORMATS.each do |format| | ||
begin | ||
parsed = format.parse(param) | ||
return parsed if parsed | ||
rescue e : Time::Format::Error | ||
nil | ||
end | ||
end | ||
end | ||
|
||
def self.parse(param : Array(String), klass : Array(T).class) : Array(T)? forall T | ||
casts = param.map { |val| parse(val, T) } | ||
|
||
casts.any?(Nil) ? nil : casts.map(&.not_nil!) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters