-
Notifications
You must be signed in to change notification settings - Fork 205
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
1,022 additions
and
219 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,115 @@ | ||
import Foundation | ||
import LlamaKit | ||
|
||
public let APIKitErrorDomain = "APIKitErrorDomain" | ||
|
||
public protocol Request { | ||
typealias Response: Any | ||
|
||
var URLRequest: NSURLRequest { get } | ||
var URLRequest: NSURLRequest? { get } | ||
|
||
func responseFromObject(object: AnyObject) -> Response? | ||
} | ||
|
||
public protocol API { | ||
class func sendRequest<T: Request>(request: T, handler: (Result<T.Response, NSError>) -> Void) | ||
public enum Method: String { | ||
case GET = "GET" | ||
case POST = "POST" | ||
case PUT = "PUT" | ||
case HEAD = "HEAD" | ||
case DELETE = "DELETE" | ||
case PATCH = "PATCH" | ||
case TRACE = "TRACE" | ||
case OPTIONS = "OPTIONS" | ||
case CONNECT = "CONNECT" | ||
} | ||
|
||
public class API { | ||
// configurations | ||
public class func baseURL() -> NSURL { | ||
return NSURL() | ||
} | ||
|
||
public class func URLSession() -> NSURLSession { | ||
return NSURLSession.sharedSession() | ||
} | ||
|
||
public class func requestBodyBuilder() -> RequestBodyBuilder { | ||
return .JSON(nil) | ||
} | ||
|
||
public class func responseBodyParser() -> ResponseBodyParser { | ||
return .JSON(nil) | ||
} | ||
|
||
// build NSURLRequest | ||
public class func URLRequest(method: Method, _ path: String, _ parameters: [String: AnyObject] = [:]) -> NSURLRequest? { | ||
if let components = NSURLComponents(URL: baseURL(), resolvingAgainstBaseURL: true) { | ||
let request = NSMutableURLRequest() | ||
|
||
switch method { | ||
case .GET, .HEAD, .DELETE: | ||
components.query = URLEncodedSerialization.stringFromObject(parameters, encoding: NSUTF8StringEncoding) | ||
|
||
default: | ||
switch requestBodyBuilder().buildBodyFromObject(parameters) { | ||
case .Success(let box): | ||
request.HTTPBody = box.unbox | ||
|
||
case .Failure(let box): | ||
return nil | ||
} | ||
} | ||
|
||
components.path = (components.path ?? "").stringByAppendingPathComponent(path) | ||
request.URL = components.URL | ||
request.HTTPMethod = method.rawValue | ||
|
||
return request | ||
} else { | ||
return nil | ||
} | ||
} | ||
|
||
// send request and build response object | ||
public class func sendRequest<T: Request>(request: T, handler: (Result<T.Response, NSError>) -> Void = {r in}) { | ||
let session = URLSession() | ||
let mainQueue = dispatch_get_main_queue() | ||
|
||
if let URLRequest = request.URLRequest { | ||
let task = session.dataTaskWithRequest(URLRequest) { data, URLResponse, connectionError in | ||
if let error = connectionError { | ||
dispatch_async(mainQueue, { handler(.Failure(Box(error))) }) | ||
return | ||
} | ||
|
||
let statusCode = (URLResponse as? NSHTTPURLResponse)?.statusCode ?? 0 | ||
if !contains(200..<300, statusCode) { | ||
let userInfo = [NSLocalizedDescriptionKey: "received status code that represents error"] | ||
let error = NSError(domain: APIKitErrorDomain, code: statusCode, userInfo: userInfo) | ||
dispatch_async(mainQueue, { handler(.Failure(Box(error))) }) | ||
return | ||
} | ||
|
||
switch self.responseBodyParser().parseData(data) { | ||
case .Failure(let box): | ||
dispatch_async(mainQueue, { handler(.Failure(Box(box.unbox))) }) | ||
|
||
case .Success(let box): | ||
if let response = request.responseFromObject(box.unbox) { | ||
dispatch_async(mainQueue, { handler(.Success(Box(response))) }) | ||
} else { | ||
let userInfo = [NSLocalizedDescriptionKey: "failed to create model object from raw object."] | ||
let error = NSError(domain: APIKitErrorDomain, code: 0, userInfo: userInfo) | ||
dispatch_async(mainQueue, { handler(.Failure(Box(error))) }) | ||
} | ||
} | ||
} | ||
|
||
task.resume() | ||
} else { | ||
let userInfo = [NSLocalizedDescriptionKey: "failed to build request."] | ||
let error = NSError(domain: APIKitErrorDomain, code: 0, userInfo: userInfo) | ||
dispatch_async(mainQueue, { handler(.Failure(Box(error))) }) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import Foundation | ||
import LlamaKit | ||
|
||
public let APIKitRequestBodyBuidlerErrorDomain = "APIKitRequestBodyBuidlerErrorDomain" | ||
|
||
public enum RequestBodyBuilder { | ||
case JSON(NSJSONWritingOptions) | ||
case URL(NSStringEncoding) | ||
case Custom(AnyObject -> Result<NSData, NSError>) | ||
|
||
public func buildBodyFromObject(object: AnyObject) -> Result<NSData, NSError> { | ||
var result: Result<NSData, NSError> | ||
|
||
switch self { | ||
case .JSON(let writingOptions): | ||
if !NSJSONSerialization.isValidJSONObject(object) { | ||
let userInfo = [NSLocalizedDescriptionKey: "invalidate object for JSON passed."] | ||
let error = NSError(domain: APIKitRequestBodyBuidlerErrorDomain, code: 0, userInfo: userInfo) | ||
result = Result.Failure(Box(error)) | ||
break | ||
} | ||
|
||
var error: NSError? | ||
if let data = NSJSONSerialization.dataWithJSONObject(object, options: writingOptions, error: &error) { | ||
result = Result.Success(Box(data)) | ||
} else { | ||
result = Result.Failure(Box(error!)) | ||
} | ||
|
||
case .URL(let encoding): | ||
var error: NSError? | ||
if let data = URLEncodedSerialization.dataFromObject(object, encoding: encoding, error: &error) { | ||
result = Result.Success(Box(data)) | ||
} else { | ||
result = Result.Failure(Box(error!)) | ||
} | ||
|
||
case .Custom(let buildBodyFromObject): | ||
result = buildBodyFromObject(object) | ||
} | ||
|
||
return result | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import Foundation | ||
import LlamaKit | ||
|
||
public enum ResponseBodyParser { | ||
case JSON(NSJSONReadingOptions) | ||
case URL(NSStringEncoding) | ||
case Custom(NSData -> Result<AnyObject, NSError>) | ||
|
||
public func parseData(data: NSData) -> Result<AnyObject, NSError> { | ||
var result: Result<AnyObject, NSError> | ||
|
||
switch self { | ||
case .JSON(let readingOptions): | ||
var error: NSError? | ||
if let object: AnyObject = NSJSONSerialization.JSONObjectWithData(data, options: readingOptions, error: &error) { | ||
result = Result.Success(Box(object)) | ||
} else { | ||
result = Result.Failure(Box(error!)) | ||
} | ||
|
||
case .URL(let encoding): | ||
var error: NSError? | ||
if let object: AnyObject = URLEncodedSerialization.objectFromData(data, encoding: encoding, error: &error) { | ||
result = Result.Success(Box(object)) | ||
} else { | ||
result = Result.Failure(Box(error!)) | ||
} | ||
|
||
case .Custom(let parseData): | ||
result = parseData(data) | ||
} | ||
|
||
return result | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import Foundation | ||
|
||
private func escape(string: String) -> String { | ||
return CFURLCreateStringByAddingPercentEscapes(nil, string, nil, "!*'();:@&=+$,/?%#[]", CFStringBuiltInEncodings.UTF8.rawValue) | ||
} | ||
|
||
private func unescape(string: String) -> String { | ||
return CFURLCreateStringByReplacingPercentEscapes(nil, string, nil) | ||
} | ||
|
||
public class URLEncodedSerialization { | ||
public class func objectFromData(data: NSData, encoding: NSStringEncoding, inout error: NSError?) -> AnyObject? { | ||
var dictionary: [String: AnyObject]? | ||
|
||
if let string = NSString(data: data, encoding: encoding) as? String { | ||
dictionary = [String: AnyObject]() | ||
|
||
for pair in split(string, { $0 == "&" }) { | ||
let contents = split(pair, { $0 == "=" }) | ||
|
||
if contents.count == 2 { | ||
dictionary?[contents[0]] = unescape(contents[1]) | ||
} | ||
} | ||
} | ||
|
||
if dictionary == nil { | ||
let userInfo = [NSLocalizedDescriptionKey: "failed to decode urlencoded string."] | ||
error = NSError(domain: APIKitErrorDomain, code: 0, userInfo: userInfo) | ||
} | ||
|
||
return dictionary | ||
} | ||
|
||
public class func dataFromObject(object: AnyObject, encoding: NSStringEncoding, inout error: NSError?) -> NSData? { | ||
let string = stringFromObject(object, encoding: encoding) | ||
let data = string.dataUsingEncoding(encoding, allowLossyConversion: false) | ||
|
||
if data == nil { | ||
let userInfo = [NSLocalizedDescriptionKey: "failed to decode urlencoded string."] | ||
error = NSError(domain: APIKitErrorDomain, code: 0, userInfo: userInfo) | ||
} | ||
|
||
return data | ||
} | ||
|
||
public class func stringFromObject(object: AnyObject, encoding: NSStringEncoding) -> String { | ||
var pairs = [String]() | ||
|
||
if let dictionary = object as? [String: AnyObject] { | ||
for (key, value) in dictionary { | ||
let string = (value as? String) ?? "\(value)" | ||
let pair = "\(key)=\(escape(string))" | ||
pairs.append(pair) | ||
} | ||
} | ||
|
||
return join("&", pairs) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// | ||
// APIKitTests.swift | ||
// APIKitTests | ||
// | ||
// Created by Yosuke Ishikawa on 2/21/15. | ||
// Copyright (c) 2015 Yosuke Ishikawa. All rights reserved. | ||
// | ||
|
||
import Cocoa | ||
import XCTest | ||
|
||
class APIKitTests: XCTestCase { | ||
|
||
override func setUp() { | ||
super.setUp() | ||
// Put setup code here. This method is called before the invocation of each test method in the class. | ||
} | ||
|
||
override func tearDown() { | ||
// Put teardown code here. This method is called after the invocation of each test method in the class. | ||
super.tearDown() | ||
} | ||
|
||
func testExample() { | ||
// This is an example of a functional test case. | ||
XCTAssert(true, "Pass") | ||
} | ||
|
||
func testPerformanceExample() { | ||
// This is an example of a performance test case. | ||
self.measureBlock() { | ||
// Put the code you want to measure the time of here. | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>CFBundleDevelopmentRegion</key> | ||
<string>en</string> | ||
<key>CFBundleExecutable</key> | ||
<string>$(EXECUTABLE_NAME)</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>-.$(PRODUCT_NAME:rfc1034identifier)</string> | ||
<key>CFBundleInfoDictionaryVersion</key> | ||
<string>6.0</string> | ||
<key>CFBundleName</key> | ||
<string>$(PRODUCT_NAME)</string> | ||
<key>CFBundlePackageType</key> | ||
<string>BNDL</string> | ||
<key>CFBundleShortVersionString</key> | ||
<string>1.0</string> | ||
<key>CFBundleSignature</key> | ||
<string>????</string> | ||
<key>CFBundleVersion</key> | ||
<string>1</string> | ||
</dict> | ||
</plist> |
Oops, something went wrong.