Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Add HTTPRequestBuilder - #657

Merged
crazytonyli merged 7 commits into
trunkfrom
url-session-helpers-1
Dec 12, 2023
Merged

Add HTTPRequestBuilder#657
crazytonyli merged 7 commits into
trunkfrom
url-session-helpers-1

Conversation

@crazytonyli

Copy link
Copy Markdown
Contributor

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.


  • Please check here if your pull request includes additional test coverage.
  • I have considered if this change warrants release notes and have added them to the appropriate section in the CHANGELOG.md if necessary.

Comment on lines +14 to +15
}
private var urlComponents: URLComponents

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
}
private var urlComponents: URLComponents
}
private var urlComponents: URLComponents

🤓

Comment thread WordPressKit/HTTPRequestBuilder.swift Outdated
Comment on lines +34 to +39
func set(path: String) -> Self {
assert(!path.contains("?") && !path.contains("#"), "Path should not have query or fragment: \(path)")

self.path = path
return self
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, fair enough.

Comment on lines +46 to +48
func query(name: String, value: String?, override: Bool = false) -> Self {
append(query: [URLQueryItem(name: name, value: value)], override: override)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch! I took the other direction: removing the "set" part, because it's redundant considering this is a "builder" type. See b52e98d.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fine by me 😄

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")!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this be

Suggested change
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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This function needs a URL encoded string. I'm just using a temporary URLComponents to construct a query which is url encoded.

Comment thread WordPressKit/HTTPRequestBuilder.swift Outdated
Comment on lines +85 to +95
// 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")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment thread WordPressKit/HTTPRequestBuilder.swift Outdated
Comment on lines +39 to +42
request = try HTTPRequestBuilder(url: URL(string: "https://wordpress.org/hello")!)
.set(path: "/world")
.build()
XCTAssertEqual(request.url?.absoluteString, "https://wordpress.org/world")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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

Comment on lines +8 to +12
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")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

@mokagio mokagio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

@crazytonyli
crazytonyli merged commit 76de798 into trunk Dec 12, 2023
@crazytonyli
crazytonyli deleted the url-session-helpers-1 branch December 12, 2023 01:36
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants