Skip to content

Commit

Permalink
move some APIKit.Request implementations to common protocols
Browse files Browse the repository at this point in the history
  • Loading branch information
banjun committed Apr 13, 2017
1 parent 1f68c37 commit 482c5bd
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 261 deletions.
68 changes: 44 additions & 24 deletions Examples/01. Simplest API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extension URITemplateContextConvertible {
}

enum RequestError: Error {
case encode
case encode
}

enum ResponseError: Error {
Expand All @@ -40,34 +40,25 @@ struct RawDataParser: DataParser {
}

struct TextBodyParameters: BodyParameters {
let contentType: String
let content: String
func buildEntity() throws -> RequestBodyEntity {
guard let r = content.data(using: .utf8) else { throw RequestError.encode }
return .data(r)
}
let contentType: String
let content: String
func buildEntity() throws -> RequestBodyEntity {
guard let r = content.data(using: .utf8) else { throw RequestError.encode }
return .data(r)
}
}


// MARK: - Transitions


struct GET__message: Request {
typealias Response = Responses
let baseURL: URL
var method: HTTPMethod {return .get}

var path: String {return "/message"}
protocol APIBlueprintRequest: Request {}
extension APIBlueprintRequest {
var dataParser: DataParser {return RawDataParser()}

enum Responses {
case http200_text_plain(String)
func contentMIMEType(in urlResponse: HTTPURLResponse) -> String? {
return (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
}


// conver object (Data) to expected type
// convert object (Data) to expected type
func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any {
let contentType = (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
let contentType = contentMIMEType(in: urlResponse)
switch (object, contentType) {
case let (data as Data, "application/json"?): return try JSONSerialization.jsonObject(with: data, options: [])
case let (data as Data, "text/plain"?):
Expand All @@ -80,9 +71,38 @@ struct GET__message: Request {
default: return object
}
}
}

protocol URITemplateRequest: Request {
static var pathTemplate: URITemplate { get }
associatedtype PathVars: URITemplateContextConvertible
var pathVars: PathVars { get }
}
extension URITemplateRequest {
// reconstruct URL to use URITemplate.expand. NOTE: APIKit does not support URITemplate format other than `path + query`
func intercept(urlRequest: URLRequest) throws -> URLRequest {
var req = urlRequest
req.url = URL(string: baseURL.absoluteString + type(of: self).pathTemplate.expand(pathVars.context))!
return req
}
}


// MARK: - Transitions


struct GET__message: APIBlueprintRequest {
let baseURL: URL
var method: HTTPMethod {return .get}

var path: String {return "/message"}

enum Responses {
case http200_text_plain(String)
}

func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
let contentType = (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Responses {
let contentType = contentMIMEType(in: urlResponse)
switch (urlResponse.statusCode, contentType) {
case (200, "text/plain"?):
return .http200_text_plain(try String.decodeValue(object))
Expand Down
103 changes: 52 additions & 51 deletions Examples/02. Resource and Actions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extension URITemplateContextConvertible {
}

enum RequestError: Error {
case encode
case encode
}

enum ResponseError: Error {
Expand All @@ -40,12 +40,51 @@ struct RawDataParser: DataParser {
}

struct TextBodyParameters: BodyParameters {
let contentType: String
let content: String
func buildEntity() throws -> RequestBodyEntity {
guard let r = content.data(using: .utf8) else { throw RequestError.encode }
return .data(r)
}
let contentType: String
let content: String
func buildEntity() throws -> RequestBodyEntity {
guard let r = content.data(using: .utf8) else { throw RequestError.encode }
return .data(r)
}
}

protocol APIBlueprintRequest: Request {}
extension APIBlueprintRequest {
var dataParser: DataParser {return RawDataParser()}

func contentMIMEType(in urlResponse: HTTPURLResponse) -> String? {
return (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
}

// convert object (Data) to expected type
func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any {
let contentType = contentMIMEType(in: urlResponse)
switch (object, contentType) {
case let (data as Data, "application/json"?): return try JSONSerialization.jsonObject(with: data, options: [])
case let (data as Data, "text/plain"?):
guard let s = String(data: data, encoding: .utf8) else { throw ResponseError.invalidData(urlResponse.statusCode, contentType) }
return s
case let (data as Data, "text/html"?):
guard let s = String(data: data, encoding: .utf8) else { throw ResponseError.invalidData(urlResponse.statusCode, contentType) }
return s
case let (data as Data, _): return data
default: return object
}
}
}

protocol URITemplateRequest: Request {
static var pathTemplate: URITemplate { get }
associatedtype PathVars: URITemplateContextConvertible
var pathVars: PathVars { get }
}
extension URITemplateRequest {
// reconstruct URL to use URITemplate.expand. NOTE: APIKit does not support URITemplate format other than `path + query`
func intercept(urlRequest: URLRequest) throws -> URLRequest {
var req = urlRequest
req.url = URL(string: baseURL.absoluteString + type(of: self).pathTemplate.expand(pathVars.context))!
return req
}
}


Expand All @@ -58,37 +97,18 @@ struct TextBodyParameters: BodyParameters {
/// bears a status code. Code 200 is great as it means all is green. Responding
/// with some data can be a great idea as well so let's add a plain text message to
/// our response.
struct GET__message: Request {
typealias Response = Responses
struct GET__message: APIBlueprintRequest {
let baseURL: URL
var method: HTTPMethod {return .get}

var path: String {return "/message"}
var dataParser: DataParser {return RawDataParser()}

enum Responses {
case http200_text_plain(String)
}


// conver object (Data) to expected type
func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any {
let contentType = (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
switch (object, contentType) {
case let (data as Data, "application/json"?): return try JSONSerialization.jsonObject(with: data, options: [])
case let (data as Data, "text/plain"?):
guard let s = String(data: data, encoding: .utf8) else { throw ResponseError.invalidData(urlResponse.statusCode, contentType) }
return s
case let (data as Data, "text/html"?):
guard let s = String(data: data, encoding: .utf8) else { throw ResponseError.invalidData(urlResponse.statusCode, contentType) }
return s
case let (data as Data, _): return data
default: return object
}
}

func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
let contentType = (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Responses {
let contentType = contentMIMEType(in: urlResponse)
switch (urlResponse.statusCode, contentType) {
case (200, "text/plain"?):
return .http200_text_plain(try String.decodeValue(object))
Expand All @@ -103,39 +123,20 @@ struct GET__message: Request {
/// [request](http://www.w3.org/TR/di-gloss/#def-http-request) and then send a
/// response back confirming the posting was a success (_HTTP Status Code 204 ~
/// Resource updated successfully, no content is returned_).
struct PUT__message: Request {
typealias Response = Responses
struct PUT__message: APIBlueprintRequest {
let baseURL: URL
var method: HTTPMethod {return .put}

var path: String {return "/message"}
var dataParser: DataParser {return RawDataParser()}

let param: String
var bodyParameters: BodyParameters? {return TextBodyParameters(contentType: "text/plain", content: param)}
enum Responses {
case http204_(Void)
}


// conver object (Data) to expected type
func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any {
let contentType = (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
switch (object, contentType) {
case let (data as Data, "application/json"?): return try JSONSerialization.jsonObject(with: data, options: [])
case let (data as Data, "text/plain"?):
guard let s = String(data: data, encoding: .utf8) else { throw ResponseError.invalidData(urlResponse.statusCode, contentType) }
return s
case let (data as Data, "text/html"?):
guard let s = String(data: data, encoding: .utf8) else { throw ResponseError.invalidData(urlResponse.statusCode, contentType) }
return s
case let (data as Data, _): return data
default: return object
}
}

func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
let contentType = (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Responses {
let contentType = contentMIMEType(in: urlResponse)
switch (urlResponse.statusCode, contentType) {
case (204, _):
return .http204_()
Expand Down
95 changes: 48 additions & 47 deletions Examples/03. Named Resource and Actions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extension URITemplateContextConvertible {
}

enum RequestError: Error {
case encode
case encode
}

enum ResponseError: Error {
Expand All @@ -40,35 +40,25 @@ struct RawDataParser: DataParser {
}

struct TextBodyParameters: BodyParameters {
let contentType: String
let content: String
func buildEntity() throws -> RequestBodyEntity {
guard let r = content.data(using: .utf8) else { throw RequestError.encode }
return .data(r)
}
let contentType: String
let content: String
func buildEntity() throws -> RequestBodyEntity {
guard let r = content.data(using: .utf8) else { throw RequestError.encode }
return .data(r)
}
}


// MARK: - Transitions

/// Now this is informative! No extra explanation needed here. This action clearly
/// retrieves the message.
struct Retrieve_a_Message: Request {
typealias Response = Responses
let baseURL: URL
var method: HTTPMethod {return .get}

var path: String {return "/message"}
protocol APIBlueprintRequest: Request {}
extension APIBlueprintRequest {
var dataParser: DataParser {return RawDataParser()}

enum Responses {
case http200_text_plain(String)
func contentMIMEType(in urlResponse: HTTPURLResponse) -> String? {
return (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
}


// conver object (Data) to expected type
// convert object (Data) to expected type
func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any {
let contentType = (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
let contentType = contentMIMEType(in: urlResponse)
switch (object, contentType) {
case let (data as Data, "application/json"?): return try JSONSerialization.jsonObject(with: data, options: [])
case let (data as Data, "text/plain"?):
Expand All @@ -81,9 +71,39 @@ struct Retrieve_a_Message: Request {
default: return object
}
}
}

protocol URITemplateRequest: Request {
static var pathTemplate: URITemplate { get }
associatedtype PathVars: URITemplateContextConvertible
var pathVars: PathVars { get }
}
extension URITemplateRequest {
// reconstruct URL to use URITemplate.expand. NOTE: APIKit does not support URITemplate format other than `path + query`
func intercept(urlRequest: URLRequest) throws -> URLRequest {
var req = urlRequest
req.url = URL(string: baseURL.absoluteString + type(of: self).pathTemplate.expand(pathVars.context))!
return req
}
}


// MARK: - Transitions

/// Now this is informative! No extra explanation needed here. This action clearly
/// retrieves the message.
struct Retrieve_a_Message: APIBlueprintRequest {
let baseURL: URL
var method: HTTPMethod {return .get}

var path: String {return "/message"}

enum Responses {
case http200_text_plain(String)
}

func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
let contentType = (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Responses {
let contentType = contentMIMEType(in: urlResponse)
switch (urlResponse.statusCode, contentType) {
case (200, "text/plain"?):
return .http200_text_plain(try String.decodeValue(object))
Expand All @@ -94,39 +114,20 @@ struct Retrieve_a_Message: Request {
}

/// `Update a message` - nice and simple naming is the best way to go.
struct Update_a_Message: Request {
typealias Response = Responses
struct Update_a_Message: APIBlueprintRequest {
let baseURL: URL
var method: HTTPMethod {return .put}

var path: String {return "/message"}
var dataParser: DataParser {return RawDataParser()}

let param: String
var bodyParameters: BodyParameters? {return TextBodyParameters(contentType: "text/plain", content: param)}
enum Responses {
case http204_(Void)
}


// conver object (Data) to expected type
func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any {
let contentType = (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
switch (object, contentType) {
case let (data as Data, "application/json"?): return try JSONSerialization.jsonObject(with: data, options: [])
case let (data as Data, "text/plain"?):
guard let s = String(data: data, encoding: .utf8) else { throw ResponseError.invalidData(urlResponse.statusCode, contentType) }
return s
case let (data as Data, "text/html"?):
guard let s = String(data: data, encoding: .utf8) else { throw ResponseError.invalidData(urlResponse.statusCode, contentType) }
return s
case let (data as Data, _): return data
default: return object
}
}

func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
let contentType = (urlResponse.allHeaderFields["Content-Type"] as? String)?.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces)
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Responses {
let contentType = contentMIMEType(in: urlResponse)
switch (urlResponse.statusCode, contentType) {
case (204, _):
return .http204_()
Expand Down

0 comments on commit 482c5bd

Please sign in to comment.