Skip to content

Commit

Permalink
Update.
Browse files Browse the repository at this point in the history
  • Loading branch information
iq3addLi committed May 16, 2017
1 parent fd7db02 commit c76c30e
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 93 deletions.
55 changes: 39 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,23 @@ It can be used only when the following conditions are satisfied.


```Swift
struct DummyStruct {
// Basic pattern
struct BasicStruct {
let message:String
}

extension DummyStruct : WebInitializable {
extension BasicStruct : WebInitializable {
typealias inputType = TestParam
typealias errorType = ApplicationError

static var path = "http://localhost:8080/dummy"
static var path = "http://localhost:8080/basic"

init (fromJson json:Any) throws{
guard case let dic as [String:Any] = json
else { throw ParseError(code: -1, reason: "not dictionary") }
else { throw ParseError(code: -1, reason: "Return body is not a dictionary.") }

guard case let message as String = dic["message"]
else { throw ParseError(code: -1, reason: "message not found.") }
else { throw ParseError(code: -1, reason: "Message is not found.") }

self.message = message
}
Expand Down Expand Up @@ -86,31 +87,53 @@ extension ApplicationError : WebSerializable{
## Struct Initialize

```Swift
let dummy = try? DummyStruct( TestParam(param: "hoge") )
let basic = try? BasicStruct( TestParam(param: "hoge") )
```

# Customize

You can customize the behavior by implementing timeout and configuration.

## Want to extend the timeout
```Swift
extension CustomStruct : WebInitializable {
typealias inputType = TestParam
typealias errorType = ApplicationError

static var path = "http://localhost:8080/timeout"
...
static var timeout = 10
...
}
```

## Want to custom URLSessionConfiguration
```Swift
extension CustomStruct : WebInitializable {
...
static var configuration:URLSessionConfiguration {
let def = URLSessionConfiguration.default
def.allowsCelluarAccess = false
def.allowsCelluarAccess = false // celluar access disabled
return def
}

init (fromJson json:Any) throws{

}
...
}
```
## Want to change HTTP method
```Swift
extension CustomStruct : WebInitializable {
...
static var method = "OPTIONS"
...
}
```
## Want to add HTTP headers
```Swift
extension CustomStruct : WebInitializable {
...
static var headers = [
"hello" : "world"
]
...
}
```

# known Issues
* There was a problem that a segmentation fault occurred when used with Ubuntu.
* I looked up this problem is solved on DEVELOPMENT-SNAPSHOT-2017-02-09-a.
* I looked up this problem is solved on DEVELOPMENT-SNAPSHOT-2017-02-09-a.
97 changes: 45 additions & 52 deletions Sources/WebStruct.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
// WebStruct.swift
// WebStruct
//
// Created by Arakane Ikumi on 2016/08/24.
// Created by iq3 on 2016/08/24.
// Copyright © 2016年 addli.co.jp. All rights reserved.
//

import Foundation
import Dispatch

/**
WebStruct error type
*/
public indirect enum Error : Swift.Error{
case network(Swift.Error)
case http(Swift.Error)
Expand All @@ -17,58 +20,81 @@ public indirect enum Error : Swift.Error{
case application(WebSerializable)
}

/**
Json serializeable protocol
*/
public protocol WebSerializable{
// Must implement
init (fromJson json:Any) throws
}

/**
Json deserializeable protocol
*/
public protocol WebDeserializable {
// Must implement
func toJsonData() -> Any
}

/**
Json initializable protocol
*/
public protocol WebInitializable : WebSerializable {
associatedtype inputType: WebDeserializable
associatedtype errorType: WebSerializable

// Must implement
static var path:String { get }

// Optional
static var method:String { get }
static var headers:[String:String] { get }
static var timeout:TimeInterval { get }
static var configuration:URLSessionConfiguration { get }
static var urlsessionDelegate:URLSessionDelegate? { get }
}

/**
Default implement for WebInitializable
*/
extension WebInitializable{
public init(_ param:Self.inputType) throws {
self = try WebStruct<Self,Self.errorType>().get( param )
}

// default values
static public var method:String { return "POST" }
static public var headers:[String:String] { return [:] }
static public var timeout:TimeInterval { return 5.0 }
static public var configuration:URLSessionConfiguration { return URLSessionConfiguration.default }
static public var urlsessionDelegate:URLSessionDelegate? { return nil }
}

/**
Default implement for WebInitializable
*/
fileprivate struct WebStruct <T:WebInitializable,ERR:WebSerializable>{

fileprivate init(){}

fileprivate func get<P:WebDeserializable>(_ param:P) throws -> T {

guard let url = URL(string: T.path )
else{ fatalError() }

guard let body = try? JSONSerialization.data(withJSONObject: param.toJsonData(), options: JSONSerialization.WritingOptions())
else{ fatalError() }
// verify for request
guard let url = URL(string: T.path ) else{ fatalError() }
guard let body = try? JSONSerialization.data(withJSONObject: param.toJsonData(), options: JSONSerialization.WritingOptions()) else{ fatalError() }

// setup for request
var request = URLRequest(url:url, cachePolicy:.reloadIgnoringLocalCacheData, timeoutInterval:T.timeout)
request.httpMethod = "POST"
request.httpMethod = T.method
request.addValue("application/json", forHTTPHeaderField:"Content-Type")
for (key,value) in T.headers {
request.addValue( value, forHTTPHeaderField: key )
}
request.httpBody = body

#if os(macOS) || os(iOS)
let session = URLSession(configuration: T.configuration, delegate: URLSessionDelegateClass(), delegateQueue: nil)
#else
let session = URLSession(configuration: T.configuration, delegate:nil, delegateQueue: nil)
#endif

// send request
let session = URLSession(configuration: T.configuration, delegate: T.urlsessionDelegate, delegateQueue: nil)
let semaphore = DispatchSemaphore(value: 0)

var data:Data?,response:URLResponse?,error:Swift.Error?
let subtask = session.dataTask(with: request) { (d, r, e) in
data = d; response = r; error = e;
Expand All @@ -77,16 +103,19 @@ fileprivate struct WebStruct <T:WebInitializable,ERR:WebSerializable>{
subtask.resume()
let _ = semaphore.wait(timeout: DispatchTime.distantFuture)

// verify for response
if let error = error {
throw Error.network(error)
}

if case let httpResponse as HTTPURLResponse = response{
switch httpResponse.statusCode{
case 200...299: break
default: throw Error.http(NSError(domain: "HTTPError", code: httpResponse.statusCode, userInfo: nil))
default: throw Error.http(NSError(domain: "HTTPError", code: httpResponse.statusCode, userInfo: [ "description" : HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode) ]) )
}
}

// parse
guard let someData = data,
let jsonDic = try? JSONSerialization.jsonObject(with: someData, options:JSONSerialization.ReadingOptions())
else { throw Error.ignoreData }
Expand All @@ -102,45 +131,9 @@ fileprivate struct WebStruct <T:WebInitializable,ERR:WebSerializable>{
throw Error.application(err)
}

// complete
return gen
}
}


// Passed self certificate
#if os(macOS) || os(iOS)
fileprivate class URLSessionDelegateClass : NSObject, URLSessionDelegate{
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void){


var disposition: Foundation.URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?

if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
disposition = .useCredential
credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
} else {
if challenge.previousFailureCount > 0 {
disposition = .cancelAuthenticationChallenge
} else {
credential = session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)

if credential != nil {
disposition = .useCredential
}
}
}
completionHandler(disposition, credential)
}
}

#else

extension URLRequest {
static func allowsAnyHTTPSCertificateForHost(host: String) -> Bool {
return true
}
}

#endif

45 changes: 39 additions & 6 deletions Tests/WebStructTests/Structs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,29 @@ import Foundation

@testable import WebStruct

struct DummyStruct {
// Basic pattern
struct BasicStruct {
let message:String
}

extension DummyStruct : WebInitializable {
extension BasicStruct : WebInitializable {
typealias inputType = TestParam
typealias errorType = ApplicationError

static var path = "http://localhost:8080/dummy"
static var path = "http://localhost:8080/basic"

init (fromJson json:Any) throws{
guard case let dic as [String:Any] = json
else { throw ParseError(code: -1, reason: "not dictionary") }
else { throw ParseError(code: -1, reason: "Return body is not a dictionary.") }

guard case let message as String = dic["message"]
else { throw ParseError(code: -1, reason: "message not found.") }
else { throw ParseError(code: -1, reason: "Message is not found.") }

self.message = message
}
}

// Abnormal pattern
struct ErrorStruct {

}
Expand All @@ -48,7 +50,7 @@ extension ErrorStruct : WebInitializable {
}



// Added custom property
struct CustomStruct {

}
Expand All @@ -72,6 +74,35 @@ extension CustomStruct : WebInitializable {
}


// Added custom http header
struct CustomHeadersStruct {
let headers:[String:String]
}

extension CustomHeadersStruct : WebInitializable {
typealias inputType = TestParam
typealias errorType = ApplicationError

static var path = "http://localhost:8080/headers"
static var method = "OPTIONS"
static var headers = [
"hello" : "world"
]

init (fromJson json:Any) throws{

guard case let dic as [String:Any] = json
else { throw ParseError(code: -1, reason: "Return body is not a dictionary.") }

guard case let headers as [String:String] = dic["YourHTTPHeader"]
else { throw ParseError(code: -1, reason: "YourHTTPHeader is not found.") }

self.headers = headers
}
}


// Posting value
struct TestParam {
let param:String
}
Expand All @@ -82,6 +113,8 @@ extension TestParam : WebDeserializable {
}
}


// Error type
struct ParseError : Swift.Error{
let code:Int
let reason:String
Expand Down
Loading

0 comments on commit c76c30e

Please sign in to comment.