Low-level utilities for interacting with APIs using JWToken authentication and transferring data in the JSON format.
- iOS 15+ as the code uses the
async/await
syntax.
Say, your API returns the following JSON packet on successful authentication:
{ "token": "<some JWT token>" }
... or an error if something goes wrong:
{
"error": {
"statusCode": 40123,
"name": "something_went_wrong",
"message": "Some human-readable explanation"
}
}
The above can be represented by a catch
-able Swift error object:
struct ErrorResponse: Decodable {
let error: ApiError
}
struct ApiError: Decodable {
let statusCode: Int
let name: String
let message: String
}
extension ApiError: Error {}
Definition of success and failure responses in Swift:
public struct TokenResponse: Decodable {
public let token: String
}
extension TokenResponse {
enum CodingKeys: CodingKey {
case token
}
public init(from decoder: Decoder) throws {
guard let container = try? decoder.container(keyedBy: CodingKeys.self),
let token = try? container.decode(String.self, forKey: .token) else {
throw try ErrorResponse(from: decoder).error
}
self.init(token: token)
}
}
Handling the request:
let client: JsonApiCompatible = JsonApiClient()
let url = URL(string: "https://fake.api.url/users/login")
let credentials = [
"login": "[email protected]",
"password": "topS3cret"
]
var jwtToken = ""
do {
let result: TokenResponse = try await client.post(url!, credentials)
jwtToken = result.token
} catch let error as ApiError {
// Handle API errors
} catch {
// Handle other errors
}
- Define the token API endpoint response model, e.g.:
struct Token: Decodable {
let token: String
}
- Provide the endpoint URL and credentials, e.g.:
let url = URL(string: "https://fake.api.url/users/login")
let credentials = [
"email": "[email protected]",
"password": "topS3cret"
]
- Make a
POST
request, e.g.:
let client: JsonApiCompatible = JsonApiClient()
let result: Token = try await client.post(url!, credentials)
print("My JWT token: \(result.token)")
This request is an equivalent of the following Curl command:
curl -X 'POST' \
'https://fake.api.url/users/login' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"email": "[email protected]",
"password": "topS3cret"
}'
- Define the API endpoint response model, e.g.:
struct Book: Decodable {
let id: Int
let title: String
let author: String
}
- Provide the endpoint URL and token, e.g.:
let endpoint = URL(string: "https://fake.api.url/books")
let jwtToken = "my.valid.jwtoken"
- Make a
GET
request, e.g.:
let client: JsonApiCompatible = JsonApiClient()
let books: [Book] = try await client.get(endpoint, token: jwtToken)
This request is an equivalent of the following Curl command:
curl -X 'GET' \
'https://fake.api.url/books' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer my.valid.jwtoken'
Similar to previous examples, you can use the following utility:
let client: JsonApiCompatible = JsonApiClient()
let result: <<YourTypeHere>> = try await client.post(endpoint, token: jwtToken, dictionary: data)
The package exports a protocol and a class implementating it.
public protocol JsonApiCompatible {
// Methods for handling requests that do not return anything
func put(url: URL, dictionary: [String: Any]) async throws -> URLResponse
func patch(url: URL, dictionary: [String: Any]) async throws -> URLResponse
func post(url: URL, dictionary: [String: Any]) async throws -> URLResponse
// Methods with dynamically defined types
func post<T: Decodable>(url: URL, dictionary: [String: Any]) async throws -> T
func post<T: Decodable>(url: URL, dictionary: [String: Any], token: String) async throws -> T
func get<T: Decodable>(url: URL, token: String) async throws -> T
// The methods below currently assume the response is empty
// TODO: They should be rewritten to return URLResponse like the methods above
func delete(url: URL, token: String) async throws
func patch(url: URL, dictionary: [String: Any], token: String) async throws
}
The class implementing the JsonApiCompatible
protocol is called JsonApiClient
.
The code is based on an article by Donny Wals covering concurrency in the latest version of the Swift programming language.