Add HTTPRequestBuilder - #657
Conversation
| } | ||
| private var urlComponents: URLComponents |
There was a problem hiding this comment.
| } | |
| private var urlComponents: URLComponents | |
| } | |
| private var urlComponents: URLComponents |
🤓
| func set(path: String) -> Self { | ||
| assert(!path.contains("?") && !path.contains("#"), "Path should not have query or fragment: \(path)") | ||
|
|
||
| self.path = path | ||
| return self | ||
| } |
There was a problem hiding this comment.
How do you feel about creating a type to encapsulate this?
extension URL {
struct Path {
let value: String
/// `Path` cannot not contain URL query fragments (? and #)
init(from string: String) {
assert(!string.contains("?") && !string.contains("#"), "Path should not have query or fragment: \(path)")
value = string
}The end result is the same: The app will crash if the path is invalid, but I feel explicitly wrapping it in a dedicated type will make what to do more obvious to consumers.
There was a problem hiding this comment.
I see value in this dedicated type, if we have more places that need to refer to a "URL path". But for this one method in HTTPRequestBuilder, I don't feel like it's worth the effort?
| func query(name: String, value: String?, override: Bool = false) -> Self { | ||
| append(query: [URLQueryItem(name: name, value: value)], override: override) | ||
| } |
There was a problem hiding this comment.
All the methods before this use "set" as their verb.
I think if it would be good to use it consistently, to have a homogeneous usage at call site. E.g. from
var request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org/")!)
.set(method: .post)
.body(json: 42)to
var request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org/")!)
.set(method: .post)
.set(jsonBody: 42)or
var request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org/")!)
.setMethod(.post)
.setJSON(body: 42)There was a problem hiding this comment.
Good catch! I took the other direction: removing the "set" part, because it's redundant considering this is a "builder" type. See b52e98d.
| func body(form: [String: String]) -> Self { | ||
| headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8" | ||
| bodyBuilder = { req in | ||
| var url = URLComponents(string: "https://wordpress.com")! |
There was a problem hiding this comment.
Should this be
| var url = URLComponents(string: "https://wordpress.com")! | |
| var url = self.urlComponents |
I'm surprised https://wordpress.com/ is hardcoded here but maybe I'm missing something about the intended use? E.g., only requests to the WordPress.com API can send URL form encoded requests?
There was a problem hiding this comment.
This function needs a URL encoded string. I'm just using a temporary URLComponents to construct a query which is url encoded.
| // FIXME: Not implemented yet | ||
| func body(xmlrpc: Any /* XMLRPCRequest */) -> Self { | ||
| body(xml: { | ||
| fatalError("To be implemented") | ||
| }) | ||
| } | ||
|
|
||
| // FIXME: Not implemented yet | ||
| func appendXMLRPCArgument(value: Any) -> Self { | ||
| fatalError("To be implemented") | ||
| } |
There was a problem hiding this comment.
Much of a muchness, but what do you think of moving these specific methods into an extension? All I've seen so far in HTTPRequestBuilder is standard HTTP. These are the only methods that implement an additional spec.
| request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org/hello")!) | ||
| .set(path: "/world") | ||
| .build() | ||
| XCTAssertEqual(request.url?.absoluteString, "https://wordpress.org/world") |
There was a problem hiding this comment.
This behavior, which I read as "when the given path is absolute, override the original URL path" is surprising.
Or maybe is just me applying the expectation from NSString appendingPathComponent:
# from the REPL
2> let s: NSString = "https://foo.test/bar"
s: NSMutableString = "https://foo.test/bar"
3> s.appendingPathComponent("/baz")
$R0: String = "https:/foo.test/bar/baz"
4> s.appendingPathComponent("bang")
$R1: String = "https:/foo.test/bar/bang"
5>
I acknowledge that NSString explicitly says "appending", though, whereas this API gives no similar clear guardrail on what path would do.
There was a problem hiding this comment.
After a second look, I do agree with you that this behaviour is not very intuitive.
I have updated the API to only allow appending path. See 4bb8360
| func testDefaultMethod() throws { | ||
| let request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!).build() | ||
| XCTAssertEqual(request.url?.absoluteString, "https://wordpress.org") | ||
| XCTAssertEqual(request.httpMethod, "GET") | ||
| } |
There was a problem hiding this comment.
This test made me want to suggest to triangulate setting the URL, that is add at least two different examples (cc @wordpress-mobile/mobile-ui-testing-squad)
XCTAssertEqual(
try HTTPRequestBuilder(url: URL(string: "https://wordpress.org/")!).build().url?.absoluteString,
"https://wordpress.org/"
)
XCTAssertEqual(
try HTTPRequestBuilder(url: URL(string: "https://wordpress.com/")!).build().url?.absoluteString,
"https://wordpress.com/"
)The value of this approach is that it gives additional confidence in the robustness of the tests. But that's mostly redundant when we have more tests below that implicitly exercise this behavior.
However, something I noticed and that we could use to add a variety of initial URL inputs is that there are no tests for how set(method:) behaves. Granted, that's pretty straightforward, but given how core this object will be, I think "overtesting" it wouldn't be a waste. The same goes for the HTTPRequestBuilder.Method allowsHTTPBody logic.
Co-authored-by: Gio Lodi <gio.lodi@automattic.com>
mokagio
left a comment
There was a problem hiding this comment.
Thank you for taking my suggestions into consideration.
| XCTAssertTrue(HTTPRequestBuilder.Method.patch.allowsHTTPBody) | ||
|
|
||
| try XCTAssertEqual(HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!).method(.put).build().httpMethod, "PUT") | ||
| XCTAssertTrue(HTTPRequestBuilder.Method.put.allowsHTTPBody) |
There was a problem hiding this comment.
Nice touch grouping the tests this way.
I don't subscribe to the "one assertion per test" rule that some tests enthusiast argue for. This kind of functional/semantic grouping strikes a good balance between not repeating the func test... cruft and having tests that point to the same behavior.
Description
This new type will be used to construct
URLRequests for all HTTP API requests.Testing Details
See the unit tests. Let me know if you think there are more should be added.
CHANGELOG.mdif necessary.