diff --git a/Source/APIRequest.swift b/Source/APIRequest.swift index 5aabf23..7ed18ee 100644 --- a/Source/APIRequest.swift +++ b/Source/APIRequest.swift @@ -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. @@ -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 : 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 : 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 : 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 : 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 : 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) + } + } + } } diff --git a/Source/Examples/Requests/CreateNewPost.swift b/Source/Examples/Requests/CreateNewPost.swift index 94ca88b..029031d 100644 --- a/Source/Examples/Requests/CreateNewPost.swift +++ b/Source/Examples/Requests/CreateNewPost.swift @@ -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 { - /// 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 { - /// 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"]) } } diff --git a/Source/Examples/Requests/DeletePost.swift b/Source/Examples/Requests/DeletePost.swift index 8048b8c..28419be 100644 --- a/Source/Examples/Requests/DeletePost.swift +++ b/Source/Examples/Requests/DeletePost.swift @@ -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 { - /// 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/". 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/ - self.resource = [self.resource, String(describing: postId)].joined(separator: "/") + super.init(resource: "/posts/:id", urlPathParameters: [":id" : String(describing: postId)]) } } diff --git a/Source/Examples/Requests/GetPosts.swift b/Source/Examples/Requests/GetPosts.swift index ea09397..36145eb 100644 --- a/Source/Examples/Requests/GetPosts.swift +++ b/Source/Examples/Requests/GetPosts.swift @@ -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?". 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)]) } } diff --git a/Source/Examples/Requests/UpdatePost.swift b/Source/Examples/Requests/UpdatePost.swift index 6502caf..2fd977a 100644 --- a/Source/Examples/Requests/UpdatePost.swift +++ b/Source/Examples/Requests/UpdatePost.swift @@ -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 { - /// 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 { - /// 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"]) } }