-
-
Notifications
You must be signed in to change notification settings - Fork 159
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
Replace avram parsing with ParamParser #1616
Merged
matthewmcgarvey
merged 1 commit into
luckyframework:master
from
matthewmcgarvey:param-parser
Nov 20, 2021
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -479,17 +479,17 @@ module Lucky::Routable | |
{% end %} | ||
end | ||
|
||
result = {{ base_type }}.adapter.parse(val) | ||
result = Lucky::ParamParser.parse(val, {{ base_type }}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the line where we are switching from Avram code to this new module |
||
|
||
if result.is_a? Avram::Type::SuccessfulCast | ||
result.value | ||
else | ||
if result.nil? | ||
raise Lucky::InvalidParamError.new( | ||
param_name: "{{ type_declaration.var.id }}", | ||
param_value: val.to_s, | ||
param_type: "{{ type }}" | ||
) | ||
end | ||
|
||
result | ||
end | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of this was directly taken from Avram https://github.com/luckyframework/avram/blob/9a5a921bb05cc3031d9436d2fdc6927606314876/spec/type_extensions/time_spec.cr#L3-L27
@stephendolan saved me from having to figure out all these tests myself 😅