Add more stuff to api route

This commit is contained in:
Daniel Saidi 2021-05-01 22:36:22 +02:00
parent 2fbcc30ee2
commit caa93fc529
4 changed files with 69 additions and 46 deletions

View File

@ -1,12 +1,13 @@
# Release notes
I will bump revision by revision, until SwiftKit has all functionality that it should have from iExtra. I will then bump it to `1.0.0`.
Until 1.0, breaking changes can occur in minor versions.
## 0.5.1
## 0.6.0
### ✨ New features
* `ApiRoute` has more explicit properties for working with post data.
* `iCloudDocumentSync` is a new protocol for syncing iCloud document changes.
* `String+Slugify` is a new extension that can convert a string to a slugified version.
* `StandardiCloudDocumentSync` is a new class for syncing iCloud document changes.

View File

@ -10,41 +10,60 @@ import Foundation
/**
This protocol represents an external api route, e.g. `login`
or `user`. The `path` will be appended to the environment's
url when performing requests.
The `queryParams` dictionary is a collection of string data.
When the route is handled with `GET`
You can url encode any query param with `urlEncode`, if you
plan on sending it with GET.
or `user`. Each route is a separate action that defines all
information required to perform an api request.
*/
public protocol ApiRoute {
/**
The route's environment-relative path, that is appended
to the environment's url when performing a request.
*/
var path: String { get }
/**
The route's optional post data, that should be added as
`httpBody` when performing a request. When defined, the
property takes precedence over `postParams`.
*/
var postData: Data? { get }
/**
The route's optional post data dictionary, which should
be added as a .utf8 encoded `httpBody` data string when
performing a request.
*/
var postParams: [String: String] { get }
/**
The route's optional query data dictionary, that should
be added as a .utf8 encoded `httpBody` data string when
performing a request.
*/
var queryParams: [String: String] { get }
}
public extension ApiRoute {
/**
Convert the route's `queryItems` collection to a string
that can be used for form data requests.
Convert the route `formDataParams` to `.utf8` data that
can be used in form data requests.
*/
var formDataString: String {
queryItems.map { "\($0.name)=\($0.value ?? "")" }.joined(separator: "&")
var postParamsData: Data? {
postParamsString?.data(using: .utf8)
}
/**
This function returns a `URLRequest` that is configured
with `application/x-www-form-urlencoded` `Content-Type`
and the query params of the route applied as `httpBody`,
using `POST` as `httpMethod`.
Convert the route `formDataParams` to a string that can
be used in form data requests.
*/
func formDataRequest(for env: ApiEnvironment) -> URLRequest {
var req = request(for: env, httpMethod: .post)
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpBody = formDataString.data(using: .utf8)
return req
var postParamsString: String? {
var params = URLComponents()
params.queryItems = postParams
.map { URLQueryItem(name: $0.key, value: urlEncode($0.value)) }
.sorted { $0.name < $1.name }
return params.query
}
/**
@ -52,10 +71,21 @@ public extension ApiRoute {
*/
var queryItems: [URLQueryItem] {
queryParams
.map { URLQueryItem(name: $0.key, value: $0.value) }
.map { URLQueryItem(name: $0.key, value: urlEncode($0.value)) }
.sorted { $0.name < $1.name }
}
/**
Create a `URLRequest` that is configured for being used
with `application/x-www-form-urlencoded` `Content-Type`.
*/
func formDataRequest(for env: ApiEnvironment) -> URLRequest {
var req = request(for: env, httpMethod: .post)
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpBody = postParamsData
return req
}
/**
This function returns a `URLRequest` that is configured
for the given `httpMethod` and the route's `queryItems`.
@ -75,6 +105,7 @@ public extension ApiRoute {
guard let requestUrl = components.url else { fatalError("Could not create URLRequest for \(url.absoluteString)") }
var request = URLRequest(url: requestUrl)
request.httpMethod = httpMethod
request.httpBody = postData ?? postParamsData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
return request
}

View File

@ -19,7 +19,7 @@ class Bundle_BundleInformationTests: QuickSpec {
it("implements BundleInformation (empty due to SPM") {
let bundle = Bundle.main
expect(bundle.buildNumber).to(equal("17501"))
expect(Int(bundle.buildNumber)).to(beGreaterThan(17501))
expect(bundle.versionNumber).to(equal("0.0.0"))
}
}

View File

@ -18,16 +18,11 @@ class ApiRouteTests: QuickSpec {
let env = TestEnvironment()
let route = TestRoute()
describe("form data string") {
describe("post params string") {
it("is correctly configured") {
let str = route.formDataString
expect(str).to(equal("anyone?=there?&hello=world"))
}
it("handles ampersands") {
let str = route.formDataString
expect(str).to(equal("anyone?=there?&hello=world"))
it("url encodes values") {
let str = route.postParamsString
expect(str).to(equal("baz?=BAM%3F&foo&=bar%26"))
}
}
@ -35,8 +30,8 @@ class ApiRouteTests: QuickSpec {
it("is correctly configured") {
let req = route.formDataRequest(for: env)
let expectedData = "anyone?=there?&hello=world".data(using: .utf8)
expect(req.url?.absoluteString).to(equal("http://example.com/1/2/3?anyone?=there?&hello=world"))
let expectedData = "baz?=BAM%3F&foo&=bar%26".data(using: .utf8)
expect(req.url?.absoluteString).to(equal("http://example.com/1/2/3?anyone?=there%253F&hello%26=world%2526"))
expect(req.allHTTPHeaderFields?["Content-Type"]).to(equal("application/x-www-form-urlencoded"))
expect(req.httpBody).to(equal(expectedData))
}
@ -48,9 +43,9 @@ class ApiRouteTests: QuickSpec {
let items = route.queryItems.sorted { $0.name < $1.name }
expect(items.count).to(equal(2))
expect(items[0].name).to(equal("anyone?"))
expect(items[0].value).to(equal("there?"))
expect(items[1].name).to(equal("hello"))
expect(items[1].value).to(equal("world"))
expect(items[0].value).to(equal("there%3F"))
expect(items[1].name).to(equal("hello&"))
expect(items[1].value).to(equal("world%26"))
}
}
@ -58,7 +53,7 @@ class ApiRouteTests: QuickSpec {
it("is correctly configured") {
let req = route.request(for: env)
expect(req.url?.absoluteString).to(equal("http://example.com/1/2/3?anyone?=there?&hello=world"))
expect(req.url?.absoluteString).to(equal("http://example.com/1/2/3?anyone?=there%253F&hello%26=world%2526"))
expect(req.allHTTPHeaderFields?["Content-Type"]).to(equal("application/json"))
}
}
@ -86,11 +81,7 @@ private struct TestEnvironment: ApiEnvironment {
private struct TestRoute: ApiRoute {
var path: String { "1/2/3" }
var queryParams: [String: String] { ["hello": "world", "anyone?": "there?"] }
}
private struct TestAmpersandRoute: ApiRoute {
var path: String { "1/2/3" }
var queryParams: [String: String] { ["hello": "world", "anyone?": "there?"] }
var postData: Data? { nil }
var postParams: [String : String] { ["foo&": "bar&", "baz?": "BAM?"] }
var queryParams: [String: String] { ["hello&": "world&", "anyone?": "there?"] }
}