serval-dna/swift-client-api/package/Sources/restful_client.swift

143 lines
5.6 KiB
Swift
Raw Normal View History

/*
Serval DNA Swift API
Copyright (C) 2016-2018 Flinders University
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
}
public func createRequest(verb: String,
path: String,
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)
request.httpMethod = verb
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)
}
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
}
}