-
Notifications
You must be signed in to change notification settings - Fork 195
URI Templating #1198
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
URI Templating #1198
Conversation
@swift-ci please test |
@swift-ci please test |
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.
Could you also add a benchmark suite for this functionality? You can pretty easily get a start by copying one of the existing formatting ones.
/// The template string needs to be a valid RFC 6570 template. | ||
/// | ||
/// This will parse the template and throw an error if the template is invalid. | ||
public init(_ template: String) throws { |
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.
Do we need to add availability annotations to this, too, even though the type it is an extension
of is new?
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.
IIRC, having @available
on the type is sufficient.
} | ||
} | ||
|
||
private struct InvalidTemplateExpression: Swift.Error { |
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.
Hm, I wonder how we can make it easier to ensure this error isn't leaked into any public functions.
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.
None of the public functions are throwing. That should be enough, no?
Sources/FoundationEssentials/URL/URITemplate_Substitution.swift
Outdated
Show resolved
Hide resolved
// MARK: - | ||
|
||
extension String { | ||
public init(_ key: URL.Template.VariableName) { |
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.
Was this part of the proposal? Adding a public, no-arg init to String
seems very broad.
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.
Oh, I see it now. I'm not sure about it now that I see it in code. I know it is strongly typed, but it can cause confusion in documentation or at call sites. How critical is it to do this conversion in this way at a call site?
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.
Yes, these were part of the review.
Omitting the first argument label is what’s recommended by the API Design Guidelines for initializers that perform value preserving type conversions.
I’m not sure what alternative would be better.
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.
@parkera would removing this from the implementation require another round of review/block this PR? Or could we maybe mark it internal for now?
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.
@danieleggert does URL.Template.VariableName
conform with ExpressibleByStringLiteral
?
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 think the right way forward is to remove ExpressibleByStringLiteral
. This is only truly useful in small demo code. In “real” code, you’d usually have a fixed set of variable names, and would probably do something like:
extension URL.Template.VariableName {
static var query: URL.Template.VariableName { URL.Template.VariableName("query") }
static var number: URL.Template.VariableName { URL.Template.VariableName("number") }
}
and then use that when constructing the dictionary.
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.
Agreed, or even
static var query: URL.Template.VariableName { .init("query") }
Tests/FoundationEssentialsTests/URITemplatingTests/URITemplate_ExpressionTests.swift
Outdated
Show resolved
Hide resolved
Tests/FoundationEssentialsTests/URITemplatingTests/URITemplate_ExpressionTests.swift
Outdated
Show resolved
Hide resolved
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.
Thanks for pitching and implementing this! I left some comments, some nits and some questions on whether we can merge percent-encoding functionality with implementations in URLParser.swift
.
Another minor nit: would we want to rename the files to URLTemplate...
given that the new type is based on URL
? (I realize the RFC does name it "URI".)
I think we will also need to add the sources to CMakeLists
in the URL folder.
Sources/FoundationEssentials/URL/URITemplate_PercentEncoding.swift
Outdated
Show resolved
Hide resolved
Sources/FoundationEssentials/URL/URITemplate_PercentEncoding.swift
Outdated
Show resolved
Hide resolved
Sources/FoundationEssentials/URL/URITemplate_PercentEncoding.swift
Outdated
Show resolved
Hide resolved
Sources/FoundationEssentials/URL/URITemplate_PercentEncoding.swift
Outdated
Show resolved
Hide resolved
The info about building and running Benchmarks is broken / missing / misleading.
doesn’t work, it needs to be I worked around it with local modificatoins to the |
0a7c43f
to
1ac4137
Compare
@swift-ci please test |
@swift-ci please test |
@swift-ci please test |
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.
Looks good to me - @itingliu can you also review?
@swift-ci please test |
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.
LGTM in general. I left some comments, but feel free to address them in a follow up or dismiss them if not valid.
let escaped = String(literal).normalizedAddingPercentEncoding( | ||
withAllowedCharacters: .unreservedReserved | ||
) | ||
elements.append(.literal(escaped)) |
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.
Just a thought, not blocking: In copyLiteral
and the regex matching function below, you're both working on Substring
rather than String
. And if I read it correctly, it looks like the associated String will later be used in other places that involves some Substring
processing. That makes me wonder if it'd make sense to store the associated value of case literal()
as Substring
instead, so we can avoid Substring-String copying until the last minute, and if this would actually make a difference performance-wise
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.
Good point. My thinking was, that it would be better to store String
because the case literal()
will stay around as part of the URL.Template
— and that instance is likely to remain in-memory for a long time. I don’t want that to keep the complete input String
alive.
} | ||
|
||
extension URL.Template.Expression { | ||
init(_ input: String) throws { |
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.
As an enhancement: perhaps it's a good place to adopt typed throw?
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 may also throw errors from RegEx parsing. I took a stab at using typed throws, but it ended up making the code more complex and more difficult to read.
// But any `%` that are not part of a valid escape sequence, need to be encoded. | ||
guard next != UInt8(ascii: "%") || remainingInput.count < 2 else { | ||
// Is this a valid escape sequence? | ||
if remainingInput[remainingInput.startIndex].isValidHexDigit && remainingInput[remainingInput.startIndex + 1].isValidHexDigit { |
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 remainingInput.count == 1
, is it still fine to do remainingInput[remainingInput.startIndex + 1]
?
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.
The remainingInput.count < 2
in the guard
protects against that. The // Is this a valid escape sequence?
code will only run if next == UInt8(ascii: "%") && remainingInput.count >= 2
.
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.
My bad. I kept reading it as remainingInput.count > 2
🤦♀️
index += 3 | ||
} | ||
} | ||
return String(decoding: outputBuffer[..<index], as: UTF8.self) |
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't tell from the code, but is it guaranteed that index <= outputButter.count
?
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.
Yes.
@swift-ci please test |
@itingliu Thanks for the comments / feedback. I’ve pushed 2 additional commits. |
@swift-ci please test |
`TestSupport` already defines a typealias for `Expression` as `public typealias Expression = Foundation.Expression`. Therefore the top level typealias `private typealias Expression = URL.Template.Expression` caused an error when `TestSupport` is imported. Move the typealias declaration to inside the test suite.
swiftlang#1198 added most of the new files, but missed `URLTemplate_Expression.swift`, add it.
Following swiftlang#1198 and swiftlang#1262, this is needed to build on Windows.
`TestSupport` already defines a typealias for `Expression` as `public typealias Expression = Foundation.Expression`. Therefore the top level typealias `private typealias Expression = URL.Template.Expression` caused an error when `TestSupport` is imported. Move the typealias declaration to inside the test suite.
…RAMEWORK (swiftlang#1261)" This reverts commit 8d63523.
This reverts commit a169ceb.
I'm trying to run benchmarks locally from
AFAIK the benchmarks themselves do not look gated around that requirement? Was this going to be updated soon? |
This adds RFC 6570 URI templates to the Swift URL type.
Evolution proposal: #1186