2016-10-25 05:46:08 +00:00
|
|
|
/*
|
|
|
|
Serval DNA Swift API
|
2018-04-05 23:19:01 +00:00
|
|
|
Copyright (C) 2016-2018 Flinders University
|
2016-10-25 05:46:08 +00:00
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU General Public License
|
|
|
|
as published by the Free Software Foundation; either version 2
|
|
|
|
of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
|
|
|
public class ServalRestfulClient {
|
|
|
|
|
|
|
|
public struct Configuration {
|
|
|
|
public let host : String
|
|
|
|
public let port : Int16
|
|
|
|
public let username : String
|
|
|
|
public let password : String
|
|
|
|
|
|
|
|
public func with(host: String? = nil, port: Int16? = nil, username: String? = nil, password: String? = nil) -> Configuration {
|
|
|
|
return Configuration(host: host ?? type(of:self).default.host,
|
|
|
|
port: port ?? type(of:self).default.port,
|
|
|
|
username: username ?? type(of:self).default.username,
|
|
|
|
password: password ?? type(of:self).default.password)
|
|
|
|
}
|
|
|
|
|
|
|
|
public static let `default` = Configuration(
|
|
|
|
host: "127.0.0.1",
|
|
|
|
port : 4110,
|
|
|
|
username: "",
|
|
|
|
password: ""
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
public let configuration : Configuration
|
|
|
|
|
|
|
|
public enum Exception : Error {
|
|
|
|
case requestFailed(statusCode: Int)
|
|
|
|
case missingContentType
|
|
|
|
case invalidContentType(mimeType: String)
|
|
|
|
case malformedJson
|
|
|
|
case invalidJson(reason: String)
|
|
|
|
}
|
|
|
|
|
|
|
|
public struct Request {
|
|
|
|
fileprivate let urlSession : URLSession
|
|
|
|
fileprivate let dataTask : URLSessionDataTask
|
|
|
|
public func close() {
|
|
|
|
print("TODO: close", to: &debugStream) // TODO
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public init(configuration: Configuration = Configuration.default) {
|
|
|
|
self.configuration = configuration
|
|
|
|
}
|
|
|
|
|
2018-04-05 23:19:01 +00:00
|
|
|
public func createRequest(verb: String,
|
|
|
|
path: String,
|
2016-10-25 05:46:08 +00:00
|
|
|
query: [String: String] = [:],
|
|
|
|
completionHandler: @escaping (Int?, Any?, Error?) -> Void)
|
|
|
|
-> Request?
|
|
|
|
{
|
|
|
|
var urlComponents = URLComponents()
|
|
|
|
urlComponents.scheme = "http"
|
|
|
|
urlComponents.host = self.configuration.host
|
|
|
|
urlComponents.port = Int(self.configuration.port)
|
|
|
|
urlComponents.path = "/\(path)"
|
|
|
|
var items : [URLQueryItem] = []
|
|
|
|
for (name, value) in query {
|
|
|
|
items.append(URLQueryItem(name: name, value: value))
|
|
|
|
}
|
|
|
|
urlComponents.queryItems = items
|
|
|
|
if !query.isEmpty {
|
|
|
|
precondition(urlComponents.percentEncodedQuery != nil)
|
|
|
|
}
|
|
|
|
let url = urlComponents.url!
|
|
|
|
let session = URLSession(configuration: URLSessionConfiguration.default)
|
|
|
|
debugPrint(url, to: &debugStream)
|
|
|
|
var request = URLRequest(url: url)
|
2018-04-05 23:19:01 +00:00
|
|
|
request.httpMethod = verb
|
2016-10-25 05:46:08 +00:00
|
|
|
if !self.configuration.username.isEmpty {
|
|
|
|
let data = (self.configuration.username + ":" + self.configuration.password).data(using: String.Encoding.utf8)
|
|
|
|
if let base64 = data?.base64EncodedString() {
|
|
|
|
request.setValue("Basic \(base64)", forHTTPHeaderField: "Authorization")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let dataTask = session.dataTask(with: request) { (data, response, error) in
|
|
|
|
if let error = error {
|
|
|
|
completionHandler(nil, nil, error)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let httpResponse = response as! HTTPURLResponse
|
|
|
|
guard let mimeType = httpResponse.mimeType else {
|
|
|
|
completionHandler(nil, nil, ServalRestfulClient.Exception.missingContentType)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
guard mimeType == "application/json" else {
|
|
|
|
completionHandler(nil, nil, ServalRestfulClient.Exception.invalidContentType(mimeType: mimeType))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
guard let json = try? JSONSerialization.jsonObject(with: data!, options: []) else {
|
|
|
|
completionHandler(nil, nil, ServalRestfulClient.Exception.malformedJson)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
completionHandler(httpResponse.statusCode, json, nil)
|
|
|
|
}
|
|
|
|
dataTask.resume()
|
|
|
|
return Request(urlSession: session, dataTask: dataTask)
|
|
|
|
}
|
2018-04-06 04:43:07 +00:00
|
|
|
|
|
|
|
public static func transformJsonTable(json: Any?) throws -> [[String: Any?]]
|
|
|
|
{
|
|
|
|
guard let json_top = json as? [String: Any] else {
|
|
|
|
throw ServalRestfulClient.Exception.invalidJson(reason: "root is not JSON object")
|
|
|
|
}
|
|
|
|
guard let header = json_top["header"] as? [String] else {
|
|
|
|
throw ServalRestfulClient.Exception.invalidJson(reason: "missing 'header' element")
|
|
|
|
}
|
|
|
|
guard let rows = json_top["rows"] as? [[Any]] else {
|
|
|
|
throw ServalRestfulClient.Exception.invalidJson(reason: "missing 'rows' element")
|
|
|
|
}
|
|
|
|
var ret : [[String: Any?]] = []
|
|
|
|
for row in rows {
|
|
|
|
guard row.count == header.count else {
|
|
|
|
throw ServalRestfulClient.Exception.invalidJson(reason: "row has \(row.count) elements; should be \(header.count)")
|
|
|
|
}
|
|
|
|
ret.append(Dictionary(uniqueKeysWithValues: zip(header, row.map { $0 as? NSNull == nil ? $0 : nil })))
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2016-10-25 05:46:08 +00:00
|
|
|
}
|