Skip to content

Commit

Permalink
Add template classes for basic requests. Add support for URL path par…
Browse files Browse the repository at this point in the history
…ameters.
  • Loading branch information
Zhendryk committed Jun 21, 2020
1 parent e4482de commit 413d5dc
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 137 deletions.
131 changes: 130 additions & 1 deletion Source/APIRequest.swift
Expand Up @@ -8,7 +8,7 @@
import Foundation


/// Generic API request
/// The protocol to which all API request types adhere.
public protocol APIRequest {

/// Placeholder `Decodable` representing what the expected response type to this `APIRequest` should be.
Expand All @@ -28,4 +28,133 @@ public protocol APIRequest {

/// The URL query parameters of this request, if any.
var urlQueryParameters: [URLQueryItem] { get }

/// The URL path parameters of this request, as name:value pairs, if any.
var urlPathParameters: [String : String] { get set }
}


/// A generic HTTP GET request.
open class GetRequest<T: Decodable> : APIRequest {
public typealias ResponseType = T

public var method: HTTPMethod { return .get }
public var resource: String
public var body: Data?
public var headers: [String : String]
public var urlQueryParameters: [URLQueryItem]
public var urlPathParameters: [String : String]

public init(resource: String, body: Data? = nil, headers: [String : String] = [:], urlQueryParameters: [URLQueryItem] = [], urlPathParameters: [String : String] = [:]) {
self.resource = resource
self.body = body
self.headers = headers
self.urlQueryParameters = urlQueryParameters
self.urlPathParameters = urlPathParameters
if !urlPathParameters.isEmpty {
for urlPathParameter in urlPathParameters {
self.resource = self.resource.replacingOccurrences(of: urlPathParameter.key, with: urlPathParameter.value)
}
}
}
}

/// A generic HTTP POST request.
open class PostRequest<T: Decodable> : APIRequest {
public typealias ResponseType = T

public var method: HTTPMethod { return .post }
public var resource: String
public var body: Data?
public var headers: [String : String]
public var urlQueryParameters: [URLQueryItem]
public var urlPathParameters: [String : String]

public init(resource: String, body: Data? = nil, headers: [String : String] = [:], urlQueryParameters: [URLQueryItem] = [], urlPathParameters: [String : String] = [:]) {
self.resource = resource
self.body = body
self.headers = headers
self.urlQueryParameters = urlQueryParameters
self.urlPathParameters = urlPathParameters
if !urlPathParameters.isEmpty {
for urlPathParameter in urlPathParameters {
self.resource = self.resource.replacingOccurrences(of: urlPathParameter.key, with: urlPathParameter.value)
}
}
}
}

/// A generic HTTP PUT request.
open class PutRequest<T: Decodable> : APIRequest {
public typealias ResponseType = T

public var method: HTTPMethod { return .put }
public var resource: String
public var body: Data?
public var headers: [String : String]
public var urlQueryParameters: [URLQueryItem]
public var urlPathParameters: [String : String]

public init(resource: String, body: Data? = nil, headers: [String : String] = [:], urlQueryParameters: [URLQueryItem] = [], urlPathParameters: [String : String] = [:]) {
self.resource = resource
self.body = body
self.headers = headers
self.urlQueryParameters = urlQueryParameters
self.urlPathParameters = urlPathParameters
if !urlPathParameters.isEmpty {
for urlPathParameter in urlPathParameters {
self.resource = self.resource.replacingOccurrences(of: urlPathParameter.key, with: urlPathParameter.value)
}
}
}
}

/// A generic HTTP PATCH request.
open class PatchRequest<T: Decodable> : APIRequest {
public typealias ResponseType = T

public var method: HTTPMethod { return .patch }
public var resource: String
public var body: Data?
public var headers: [String : String]
public var urlQueryParameters: [URLQueryItem]
public var urlPathParameters: [String : String]

public init(resource: String, body: Data? = nil, headers: [String : String] = [:], urlQueryParameters: [URLQueryItem] = [], urlPathParameters: [String : String] = [:]) {
self.resource = resource
self.body = body
self.headers = headers
self.urlQueryParameters = urlQueryParameters
self.urlPathParameters = urlPathParameters
if !urlPathParameters.isEmpty {
for urlPathParameter in urlPathParameters {
self.resource = self.resource.replacingOccurrences(of: urlPathParameter.key, with: urlPathParameter.value)
}
}
}
}

/// A generic HTTP DELETE request.
open class DeleteRequest<T: Decodable> : APIRequest {
public typealias ResponseType = T

public var method: HTTPMethod { return .delete }
public var resource: String
public var body: Data?
public var headers: [String : String]
public var urlQueryParameters: [URLQueryItem]
public var urlPathParameters: [String : String]

public init(resource: String, body: Data? = nil, headers: [String : String] = [:], urlQueryParameters: [URLQueryItem] = [], urlPathParameters: [String : String] = [:]) {
self.resource = resource
self.body = body
self.headers = headers
self.urlQueryParameters = urlQueryParameters
self.urlPathParameters = urlPathParameters
if !urlPathParameters.isEmpty {
for urlPathParameter in urlPathParameters {
self.resource = self.resource.replacingOccurrences(of: urlPathParameter.key, with: urlPathParameter.value)
}
}
}
}
50 changes: 6 additions & 44 deletions Source/Examples/Requests/CreateNewPost.swift
Expand Up @@ -8,61 +8,23 @@
import Foundation

/// A simple POST request, creating a new post associated with a given userId.
internal struct CreateNewPost: APIRequest {
/// The endpoint we're querying will return the data we created, so lets make that our `ResponseType`.
typealias ResponseType = Post
internal class CreateNewPost: PostRequest<Post> {

/// This is a POST request, so we mark it accordingly.
var method: HTTPMethod { return .post }

/// Because this is a POST request, we can initialize this to nil, but we'll want to populate it with our dynamic data in our constructor below.
var body: Data? = nil

/// Because we're posting data, we want to tell the server what kind of data it should expect. Add that as a 'Content-Type' header.
var headers: [String : String] = ["Content-Type" : "application/json; charset=UTF-8"]

/// The endpoint resource we're querying. NOTE: The preceeding forward slash is REQUIRED.
var resource: String = "/posts"

/// The possible URL query parameters for this request. NOTE: Instantiate them in the constructor for this request as you can see below.
var urlQueryParameters: [URLQueryItem] = []

/// Constructor for a `CreateNewPost` request, which would look like this: "scheme://host/posts?". You want to initialize any variable parameters here that are dynamic in the URL.
/// - Parameter newPost: The new post to create.
init(newPost: Post) {
// We want to encode our body data. Note the force unwrapping due to the fact that we know our object is encodable.
let encoded = try! JSONEncoder().encode(newPost)
let jsonString = String(data: encoded, encoding: .utf8)!
self.body = Data(base64Encoded: jsonString)
let body = Data(base64Encoded: jsonString)
super.init(resource: "/posts", body: body, headers: ["Content-Type" : "application/json; charset=UTF-8"])
}
}

/// A simple POST request, creating a new post associated with a given userId, and not expecting a response.
internal struct CreateNewPost_NR: APIRequest {
/// Since this is a POST request, we don't really care about the response as long as it's successful.
typealias ResponseType = NoResponse

/// This is a POST request, so we mark it accordingly.
var method: HTTPMethod { return .post }

/// Because this is a POST request, we can initialize this to nil, but we'll want to populate it with our dynamic data in our constructor below.
var body: Data? = nil

/// Because we're posting data, we want to tell the server what kind of data it should expect. Add that as a 'Content-Type' header.
var headers: [String : String] = ["Content-Type" : "application/json; charset=UTF-8"]

/// The endpoint resource we're querying. NOTE: The preceeding forward slash is REQUIRED.
var resource: String = "/posts"

/// The possible URL query parameters for this request. NOTE: Instantiate them in the constructor for this request as you can see below.
var urlQueryParameters: [URLQueryItem] = []
internal class CreateNewPost_NR: PostRequest<NoResponse> {

/// Constructor for a `CreateNewPost_NR` request, which would look like this: "scheme://host/posts?". You want to initialize any variable parameters here that are dynamic in the URL.
/// - Parameter newPost: The new post to create.
init(newPost: Post) {
// We want to encode our body data. Note the force unwrapping due to the fact that we know our object is encodable.
let encoded = try! JSONEncoder().encode(newPost)
let jsonString = String(data: encoded, encoding: .utf8)!
self.body = Data(base64Encoded: jsonString)
let body = Data(base64Encoded: jsonString)
super.init(resource: "/posts", body: body, headers: ["Content-Type" : "application/json; charset=UTF-8"])
}
}
24 changes: 2 additions & 22 deletions Source/Examples/Requests/DeletePost.swift
Expand Up @@ -8,29 +8,9 @@
import Foundation

/// A simple DELETE request, deleting the post associated with an id.
internal struct DeletePost: APIRequest {
/// The API we're querying doesn't return any data upon a DELETE.
typealias ResponseType = NoResponse
internal class DeletePost: DeleteRequest<NoResponse> {

/// This is a DELETE request, so we mark it accordingly.
var method: HTTPMethod { return .delete }

/// Because this is a DELETE request, we don't have any body data to incorporate.
var body: Data? = nil

/// We're not going to include any headers for this request.
var headers: [String : String] = [:]

/// The endpoint resource we're querying. NOTE: The preceeding forward slash is REQUIRED.
var resource: String = "/posts"

/// The possible URL query parameters for this request. NOTE: Instantiate them in the constructor for this request as you can see below.
var urlQueryParameters: [URLQueryItem] = []

/// Constructor for a `DeletePost` request, which would look like this: "scheme://host/posts/<postId>". You want to initialize any variable parameters here that are dynamic in the URL.
/// - Parameter postId: The id of the post to delete.
init(postId: Int) {
// This specific API addresses posts by scheme://host/posts/<postId>
self.resource = [self.resource, String(describing: postId)].joined(separator: "/")
super.init(resource: "/posts/:id", urlPathParameters: [":id" : String(describing: postId)])
}
}
26 changes: 2 additions & 24 deletions Source/Examples/Requests/GetPosts.swift
Expand Up @@ -7,32 +7,10 @@
//
import Foundation


/// A simple GET request, fetching the posts of a specific user from a test API.
internal struct GetPosts: APIRequest {
/// Our expected data to be returned is an Array of `Post` objects. See `Post.swift` for the structure.
typealias ResponseType = [Post]

/// This is a GET request, so we mark it accordingly.
var method: HTTPMethod { return .get }

/// Because this is a GET request, we don't have any body data to incorporate.
var body: Data? = nil

/// We're not going to include any headers for this request.
var headers: [String : String] = [:]

/// The endpoint resource we're querying. NOTE: The preceeding forward slash is REQUIRED.
var resource: String = "/posts"

/// The possible URL query parameters for this request. NOTE: Instantiate them in the constructor for this request as you can see below.
var urlQueryParameters: [URLQueryItem] = []
internal class GetPosts: GetRequest<[Post]> {

/// Constructor for a `GetPosts` request, which would look like this: "scheme://host<resource>?<urlQueryParameters>". You want to initialize any variable parameters here that are dynamic in the URL.
/// - Parameter userId: The userId to search for posts for.
init(userId: Int) {
// Just append each of your dynamic URL query parameters to the list here.
// NOTE: You can either use the provided HTTPParameter class, or just pass in a string representation of the value for your parameter.
self.urlQueryParameters.append(URLQueryItem(name: "userId", value: HTTPParameter.int(userId).description))
super.init(resource: "/posts", urlQueryParameters: [URLQueryItem(name: "userId", value: HTTPParameter.int(userId).description)])
}
}
52 changes: 6 additions & 46 deletions Source/Examples/Requests/UpdatePost.swift
Expand Up @@ -8,63 +8,23 @@
import Foundation

/// A simple PUT request, updating an existing post associated with a given userId.
internal struct UpdatePostPut: APIRequest {
/// The endpoint we're querying will not return any data upon an update.
typealias ResponseType = NoResponse
internal class UpdatePostPut: PutRequest<NoResponse> {

/// This is a PUT request, so we mark it accordingly.
var method: HTTPMethod { return .put }

/// Because this is a PUT request, we can initialize this to nil, but we'll want to populate it with our dynamic data in our constructor below.
var body: Data? = nil

/// Because we're updating data, we want to tell the server what kind of data it should expect. Add that as a 'Content-Type' header.
var headers: [String : String] = ["Content-Type" : "application/json; charset=UTF-8"]

/// The endpoint resource we're querying. NOTE: The preceeding forward slash is REQUIRED.
var resource: String = "/posts"

/// The possible URL query parameters for this request. NOTE: Instantiate them in the constructor for this request as you can see below.
var urlQueryParameters: [URLQueryItem] = []


/// Constructor for a `UpdatePostPut` request, which would look like this: "scheme://host/posts?". You want to initialize any variable parameters here that are dynamic in the URL.
/// - Parameter newPost: The new data to update the post with.
init(newPost: Post) {
// We want to encode our body data. Note the force unwrapping due to the fact that we know our object is encodable.
let encoded = try! JSONEncoder().encode(newPost)
let jsonString = String(data: encoded, encoding: .utf8)!
self.body = Data(base64Encoded: jsonString)
let body = Data(base64Encoded: jsonString)
super.init(resource: "/posts", body: body, headers: ["Content-Type" : "application/json; charset=UTF-8"])
}
}

/// A simple PATCH request, updating an existing post associated with a given userId.
struct UpdatePostPatch: APIRequest {
/// The endpoint we're querying will not return any data upon an update.
typealias ResponseType = NoResponse

/// This is a PATCH request, so we mark it accordingly.
var method: HTTPMethod { return .patch }

/// Because this is a PATCH request, we can initialize this to nil, but we'll want to populate it with our dynamic data in our constructor below.
var body: Data? = nil

/// Because we're updating data, we want to tell the server what kind of data it should expect. Add that as a 'Content-Type' header.
var headers: [String : String] = ["Content-Type" : "application/json; charset=UTF-8"]

/// The endpoint resource we're querying. NOTE: The preceeding forward slash is REQUIRED.
var resource: String = "/posts"

/// The possible URL query parameters for this request. NOTE: Instantiate them in the constructor for this request as you can see below.
var urlQueryParameters: [URLQueryItem] = []

internal class UpdatePostPatch: PatchRequest<NoResponse> {

/// Constructor for a `UpdatePostPatch` request, which would look like this: "scheme://host/posts?". You want to initialize any variable parameters here that are dynamic in the URL.
/// - Parameter newPost: The new data to update the post with.
init(newPost: Post) {
// We want to encode our body data. Note the force unwrapping due to the fact that we know our object is encodable.
let encoded = try! JSONEncoder().encode(newPost)
let jsonString = String(data: encoded, encoding: .utf8)!
self.body = Data(base64Encoded: jsonString)
let body = Data(base64Encoded: jsonString)
super.init(resource: "/posts", body: body, headers: ["Content-Type" : "application/json; charset=UTF-8"])
}
}

0 comments on commit 413d5dc

Please sign in to comment.