-
Notifications
You must be signed in to change notification settings - Fork 214
Fix-prevented "?" from being added in case query is empty #1650
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
Fix-prevented "?" from being added in case query is empty #1650
Conversation
|
@swift-ci please test |
jrflat
left a comment
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.
These changes have compatibility implications far beyond the behavior of URL.appending(queryItems:). I think we'll need to re-focus on addressing just that.
In general, an empty query component (just the ?) is valid to have in a URL string. Some URL libraries, such as Python urlparse/requests and libcurl, choose to strip ? from the string if the component is empty. However, other RFC 3986 libraries and all WHATWG URL-based libraries (as far as I'm aware) keep the ? and treat the query as empty.
That's also been the behavior of CFURL, NSURL, URL, and URLComponents for the past many years, so changing this now for URL and URLComponents would be quite risky and create inconsistencies within our APIs.
I think we'll need to go back to the previous change of only adding:
public func appending(queryItems: [URLQueryItem]) -> URL {
guard !queryItems.isEmpty else { return self }
and discuss whether we think the compatibility risk of that targeted change is worth the convenience of clients not needing to check if the array is empty beforehand.
| result += percentEncodedPath | ||
| } | ||
| if let percentEncodedQuery { | ||
| if let percentEncodedQuery, !percentEncodedQuery.isEmpty { |
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.
If a client wants to add an empty query component to the string via:
comp.query = "" or comp.percentEncodedQuery = "" or comp.queryItems = []
this would make that use-case impossible.
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.
That's not the same outcome though, http://example.com/ has no query and http://example.com/? has an empty query. These are semantically different according to URL standards.
In fact, RFC 3986 lists this exact example in Section 6.2.3 (emphasis mine):
[Regarding] the following four URIs ...
http://example.com http://example.com/ http://example.com:/ http://example.com:80/... Normalization should not remove delimiters when their associated
component is empty unless licensed to do so by the scheme
specification. For example, the URI "http://example.com/?" cannot be
assumed to be equivalent to any of the examples above.
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.
but what's the use case for appending an empty query?
An application/server could definitely distinguish between having no query vs. an empty query. For example, an application might decide that:
"http://example.com/user/home?name=John" --> The user has visited their profile and set their name to "John"
"http://example.com/user/home?" --> The user has visited their profile, but has not set any attributes
"http://example.com/user/home" --> The user has not visited their profile, we should show an onboarding presentation
In this case, they might have logic to call url.appending(queryItems: userAttributes) if the user has already visited their profile, even if userAttributes is empty.
But overall, I think the main point is that the URL standards differentiate no query vs. empty query, so we shouldn't restrict how developers might use this semantic difference.
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.
You are totally right
| } | ||
|
|
||
| if let query = parseInfo.query { | ||
| if let query = parseInfo.query, !query.isEmpty { |
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.
This strips empty query components from URL strings that require percent-encoding, but otherwise does not strip the query component, which seems inconsistent. For example, this would give us:
print(URL(string: "https://example.com/path?")) // "https://example.com/path?"
print(URL(string: "https://example.com/pa th?")) // "https://example.com/pa%20th"
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.
I can add this implantation if this is the only thing that is left 😅 but this may rise another question should we remove "?" in case of the API's user added explicitly "?" in the url in this case:
print(URL(string: "https://example.com/path?"))
| } | ||
| result += path | ||
| if let query { | ||
| if let query, !query.isEmpty { |
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.
This affects .absoluteURL resolution. If the base URL has an empty query component that should be used according to RFC 3986, it will not include it in the resolved absolute string.
Actually after reviewing this comment and searching about RFC I think there is no issue in the first place, and it makes total sense that empty query should add ?. So it seems like there is no issue to fix actually 😅 Please 🙏🏻 recommend for me any issue to work on, I can't find any I really want to contribute 😁 @jrflat |
|
Thanks for following up on this! I already responded to some of these points in a comment above, but I'll clarify a bit here, too.
The first solution only alters the behavior of
I agree that some developers might find the current behavior unexpected, but I certainly disagree that the behavior is "fundamentally incorrect." As mentioned in my comment above, URL standards explicitly distinguish no query from empty query, and there's reasonable use-cases for both of them, so
I wholeheartedly agree that legacy compatibility should not always prevent us from fixing incorrect behavior. It's all about weighing the benefits of fixing the behavior vs. the risk of changing it. As we've modernized the Swift |
Sorry didn't see this until I finished writing up my essay above 😆 I'll look out for any good issues, and as always feel free to check out any of the open issues! Thanks again @EngOmarElsayed! |
Prevent appending
?to URL when query items array is emptyMotivation:
When calling
URL.appending(queryItems:)with an empty array, the current implementation appends a trailing?to the URL (e.g.,"www.google.com"becomes"www.google.com?"). This produces potentially invalid URLs and forces users to add defensive checks before calling the method.Related to this issue #1548
Modifications:
Added an extra check before adding
"?\(query)"to prevent "?" from being added in case of an empty query.Result:
Calling
url.appending(queryItems: [])now returns the original URL without any modifications, allowing developers to chain URL construction methods without defensive empty-array checks:Testing: