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

Don't print query params if they are empty #5340

Merged
merged 1 commit into from
Dec 3, 2017

Conversation

paulcsmith
Copy link
Contributor

@paulcsmith paulcsmith commented Dec 1, 2017

I found this while investigating this issue on Lucky:
luckyframework/lucky#214

It turns out that because I parsed the query params in the handler mentioned in the above link, it
set the URI#query to "". This caused HTTP::Request#resource to add
an extra ? to the end of the path, even though the query params were
empty.

This PR fixes the issue by also checking for an empty string before
printing ?

src/uri.cr Outdated
@@ -128,7 +128,7 @@ class URI
def full_path
String.build do |str|
str << (@path.try { |p| !p.empty? } ? @path : "/")
str << "?" << @query if @query
str << "?" << @query if @query && @query != ""
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea!

@paulcsmith paulcsmith force-pushed the ps-fix-uri-full-path branch 2 times, most recently from 121c070 to 997baf9 Compare December 1, 2017 20:38
src/uri.cr Outdated
@@ -128,7 +128,7 @@ class URI
def full_path
String.build do |str|
str << (@path.try { |p| !p.empty? } ? @path : "/")
str << "?" << @query if @query
str << "?" << @query if @query && [email protected](&.empty?)
Copy link
Member

Choose a reason for hiding this comment

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

Better: if (query = @query) && !query.empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like that 👍

@paulcsmith paulcsmith force-pushed the ps-fix-uri-full-path branch from 997baf9 to 326aa17 Compare December 1, 2017 20:41
src/uri.cr Outdated
@@ -128,7 +128,7 @@ class URI
def full_path
String.build do |str|
str << (@path.try { |p| !p.empty? } ? @path : "/")
str << "?" << @query if @query
str << "?" << @query if (query = @query) && !query.empty?
Copy link
Contributor

Choose a reason for hiding this comment

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

I would personally prefer move the assign and condition to the left side (a regular if)

if (query = @query) && !query.empty?
  str << "?" << query
end

Copy link
Contributor

Choose a reason for hiding this comment

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

You should probably replace this with the following which is much clearer as to your intent.

str << "?" << @query if !@query.to_s.empty?

More importantly since @query is literally the same thing as query owing to the getter setting it to itself is just weird. I wanted to see if there was some dark magic I was ignorant of, but as far as i can tell this assignment makes no sense.

class Foo
  getter x
  setter x
  def initialize(@x : String?)
  end
  def test : Bool
    (x = @x) ? true : false
  end
  def test2 : Bool
    x ? true : false
  end
  def test3 : Bool
     @x ? true : false
  end

end

then checking it in icr...

icr(0.23.1) > f = Foo.new("something")
 => #<Foo:0x107231e60 @x="something">
icr(0.23.1) > f.test
 => true
icr(0.23.1) > f.test2
 => true
icr(0.23.1) > f.test3
 => true
icr(0.23.1) > f = Foo.new(nil)
 => #<Foo:0x1070c8e40 @x=nil>
icr(0.23.1) > f.test
 => false
icr(0.23.1) > f.test2
 => false
icr(0.23.1) > f.test3
 => false

Copy link
Contributor Author

@paulcsmith paulcsmith Dec 2, 2017

Choose a reason for hiding this comment

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

@masukomi Thanks for the feedback and the detailed response. That example is not quite right for showing the problem. Here's one that illustrates the issue: https://play.crystal-lang.org/#/r/36hx

class Foo
  @query : String?
  
  def full_path
    String.build do |str|
      str << @query if @query && @query.empty?
    end
  end
end

Foo.new.full_path

Results in:

in line 6: undefined method 'empty?' for Nil (compile-time type is (String | Nil))

I wish I could find the documentation for this, but the gist is that instance variable can change at any time so you need to set it to a local variable first so the compiler can be sure that it is still not nil. Does that make sense?

[email protected]_s.empty? would work, but it requires making an intermediate string object if the query is nil, which IMO is not worth it for slightly improved readability

src/uri.cr Outdated
@@ -128,7 +128,7 @@ class URI
def full_path
String.build do |str|
str << (@path.try { |p| !p.empty? } ? @path : "/")
str << "?" << @query if @query
str << "?" << @query if (query = @query) && !query.empty?
Copy link
Contributor

Choose a reason for hiding this comment

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

You should probably replace this with the following which is much clearer as to your intent.

str << "?" << @query if !@query.to_s.empty?

More importantly since @query is literally the same thing as query owing to the getter setting it to itself is just weird. I wanted to see if there was some dark magic I was ignorant of, but as far as i can tell this assignment makes no sense.

class Foo
  getter x
  setter x
  def initialize(@x : String?)
  end
  def test : Bool
    (x = @x) ? true : false
  end
  def test2 : Bool
    x ? true : false
  end
  def test3 : Bool
     @x ? true : false
  end

end

then checking it in icr...

icr(0.23.1) > f = Foo.new("something")
 => #<Foo:0x107231e60 @x="something">
icr(0.23.1) > f.test
 => true
icr(0.23.1) > f.test2
 => true
icr(0.23.1) > f.test3
 => true
icr(0.23.1) > f = Foo.new(nil)
 => #<Foo:0x1070c8e40 @x=nil>
icr(0.23.1) > f.test
 => false
icr(0.23.1) > f.test2
 => false
icr(0.23.1) > f.test3
 => false

@paulcsmith paulcsmith force-pushed the ps-fix-uri-full-path branch from 326aa17 to b50d7a3 Compare December 2, 2017 03:03
src/uri.cr Outdated
@@ -128,7 +128,7 @@ class URI
def full_path
String.build do |str|
str << (@path.try { |p| !p.empty? } ? @path : "/")
str << "?" << @query if @query
str << "?" << @query unless @query.try(&.empty?)
Copy link
Contributor Author

@paulcsmith paulcsmith Dec 2, 2017

Choose a reason for hiding this comment

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

I think this is pretty clean and eliminates the need to create an intermediate variable. I think this is a pretty small change, is reasonably easy to understand, and is unlikely to change much in the future. So it's probably ok like this. However, if it needs to change to change to be merged in, I'm happy to change it!

Copy link
Member

Choose a reason for hiding this comment

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

This won't work: if @query is nil, @query.try(&.empty?) will return nil which is falsey and thus add a question mark. You need add a spec for this case.

And I still think my previous proposal (query = @query) && !query.empty? is probably the best boolean expression for this, but maybe put it in a proper if branch to spare an additional ivar lookup:

if (query = @query) && !query.empty?
  str << '?' << query
end

Copy link
Contributor

Choose a reason for hiding this comment

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

this please

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I noticed this didn't work and updated it last night. Did you see the latest change using try https://github.com/crystal-lang/crystal/pull/5340/files? I think it is easier to understand, but maybe that's just me. @RX14 if you'd still prefer assigning to the intermediate variable instead of the try that I used, LMK and I'll change it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I personally think if branching is easier to understand over a .try block, but performance wise, a try block might show better performance than if branch.

https://gist.github.com/luislavena/4096693021ca38f6be4c7539625716a3

The difference might be marginal, so leave that decision to core in relation to readability/simplicity vs performance.

Cheers.

@paulcsmith paulcsmith force-pushed the ps-fix-uri-full-path branch 3 times, most recently from 4cf0442 to c13c4f1 Compare December 2, 2017 10:40
I found this while investigating this issue on Lucky:
luckyframework/lucky#214

It turns out that because I parsed the query params in that handler it
set the URI#query to `""`. This caused `HTTP::Request#resource` to add
an extra `?` to the end of the path, even though the query params were
empty.

This PR fixes the issue by also checking for an empty string before
printing `?`.
@paulcsmith paulcsmith force-pushed the ps-fix-uri-full-path branch from c13c4f1 to aa3f3d4 Compare December 2, 2017 18:09
@paulcsmith
Copy link
Contributor Author

I switched it to what @straight-shoota suggested: #5340 (comment)

@bcardiff bcardiff added this to the Next milestone Dec 3, 2017
@bcardiff bcardiff merged commit b747f5f into crystal-lang:master Dec 3, 2017
@bcardiff
Copy link
Member

bcardiff commented Dec 3, 2017

Thanks @paulcsmith !

@paulcsmith paulcsmith deleted the ps-fix-uri-full-path branch December 3, 2017 02:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants