Skip to content

Commit

Permalink
[Swift] Improve server protocol to prepare for C(R)UD
Browse files Browse the repository at this point in the history
  • Loading branch information
p2 committed Jan 24, 2015
1 parent 16c94a2 commit 12604e1
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 27 deletions.
34 changes: 9 additions & 25 deletions Swift/FHIRResource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

import Foundation

/// The block signature for most server interaction callbacks
public typealias FHIRResourceErrorCallback = ((resource: FHIRResource?, error: NSError?) -> Void)


/**
* Abstract superclass for all FHIR resource models.
Expand Down Expand Up @@ -95,7 +98,7 @@ public class FHIRResource: FHIRElement
/**
Reads the resource with the given id from the given server.
*/
public class func read(id: String, server: FHIRServer, callback: ((resource: FHIRResource?, error: NSError?) -> ())) {
public class func read(id: String, server: FHIRServer, callback: FHIRResourceErrorCallback) {
let path = "\(resourceName)/\(id)"
readFrom(path, server: server) { resource, error in
if let res = resource {
Expand All @@ -108,13 +111,13 @@ public class FHIRResource: FHIRElement
/**
Reads the resource from the given path on the given server.
*/
public class func readFrom(path: String, server: FHIRServer, callback: ((resource: FHIRResource?, error: NSError?) -> ())) {
server.requestJSON(path) { json, error in
if nil != error {
callback(resource: nil, error: error)
public class func readFrom(path: String, server: FHIRServer, callback: FHIRResourceErrorCallback) {
server.getJSON(path) { response, error in
if nil != error || nil != response?.error {
callback(resource: nil, error: error ?? response!.error!)
}
else {
let resource = self(json: json)
let resource = self(json: response?.body)
resource._server = server
callback(resource: resource, error: nil)
}
Expand Down Expand Up @@ -180,22 +183,3 @@ public class FHIRResourceMeta: FHIRElement
}
}


/**
* Protocol for server objects to be used by `FHIRResource` and subclasses.
*/
public protocol FHIRServer
{
/** A server object must always have a base URL. */
var baseURL: NSURL { get }

/**
Instance method that takes a path, which is relative to `baseURL`, retrieves data from that URL and returns a
decoded JSONDictionary - or an error - in the callback.

:param: path The REST path to request, relative to the server's base URL
:param: callback The callback to call when the request ends (success or failure)
*/
func requestJSON(path: String, callback: ((json: JSONDictionary?, error: NSError?) -> Void))
}

4 changes: 2 additions & 2 deletions Swift/FHIRSearch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ public class FHIRSearch
return
}

server.requestJSON(construct()) { json, error in
server.getJSON(construct()) { response, error in
if nil != error {
callback(bundle: nil, error: error)
}
else {
callback(bundle: Bundle(json: json), error: nil)
callback(bundle: Bundle(json: response?.body), error: nil)
}
}
}
Expand Down
131 changes: 131 additions & 0 deletions Swift/FHIRServer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//
// FHIRReference.swift
// SMART-on-FHIR
//
// Created by Pascal Pfiffner on 01/24/15.
// 2015, SMART Platforms.
//

import Foundation


/// Callback from server methods
public typealias FHIRServerJSONCallback = ((response: FHIRServerJSONResponse?, error: NSError?) -> Void)

/// The FHIR server error domain
public let FHIRServerErrorDomain = "FHIRServerError"


/**
Encapsulates a server response.
*/
public class FHIRServerResponse
{
/// The HTTP status code
public let status: Int = 0

/// Response headers
public let headers: [String: String]

/// An NSError, generated from status code unless it was explicitly assigned.
public var error: NSError?

public required init(status: Int, headers: [String: String]) {
self.status = status
self.headers = headers

if status >= 400 {
let errstr = NSHTTPURLResponse.localizedStringForStatusCode(status)
error = NSError(domain: FHIRServerErrorDomain, code: status, userInfo: [NSLocalizedDescriptionKey: errstr])
}
}


/** Instantiate a FHIRServerJSONResponse from an NS(HTTP)URLResponse. */
public class func from(# response: NSURLResponse) -> Self {
var status = 0
var headers = [String: String]()

if let http = response as? NSHTTPURLResponse {
status = http.statusCode
for (key, val) in http.allHeaderFields {
if let keystr = key as? String {
if let valstr = val as? String {
headers[keystr] = valstr
}
else {
println("DEBUG: Not a string in location headers: \(val) (for \(keystr))")
}
}
}
}

return self(status: status, headers: headers)
}
}


/**
Encapsulates a server response with JSON response body, if any.
*/
public class FHIRServerJSONResponse: FHIRServerResponse
{
/// The response body, decoded into a JSONDictionary
public var body: JSONDictionary?

public required init(status: Int, headers: [String: String]) {
super.init(status: status, headers: headers)
}

/** Instantiate a FHIRServerJSONResponse from an NS(HTTP)URLResponse and NSData. */
public class func from(# response: NSURLResponse, data inData: NSData?) -> Self {
let sup = super.from(response: response)
let res = self(status: sup.status, headers: sup.headers) // TODO: figure out how to make super work with "Self"

if let data = inData {
var error: NSError? = nil
if let json = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as? JSONDictionary {
res.body = json
}
else {
let errstr = "Failed to deserialize JSON into a dictionary: \(error?.localizedDescription)\n"
"\(NSString(data: data, encoding: NSUTF8StringEncoding))"
res.error = NSError(domain: FHIRServerErrorDomain, code: res.status, userInfo: [NSLocalizedDescriptionKey: errstr])
}
}

return res
}
}


/**
Protocol for server objects to be used by `FHIRResource` and subclasses.
*/
public protocol FHIRServer
{
/** A server object must always have a base URL. */
var baseURL: NSURL { get }

/**
Instance method that takes a path, which is relative to `baseURL`, executes a GET request from that URL and
returns a decoded JSONDictionary - or an error - in the callback.

:param: path The REST path to request, relative to the server's base URL
:param: callback The callback to call when the request ends (success or failure)
*/
func getJSON(path: String, callback: FHIRServerJSONCallback)

/**
Instance method that takes a path, which is relative to `baseURL`, executes a PUT request at that URL and
returns a decoded JSONDictionary - or an error - in the callback.

:param: path The REST path to request, relative to the server's base URL
:param: body The request body data as JSONDictionary
:param: callback The callback to call when the request ends (success or failure)
*/
func putJSON(path: String, body: JSONDictionary, callback: FHIRServerJSONCallback)

func postJSON(path: String, body: JSONDictionary, callback: FHIRServerJSONCallback)
}

1 change: 1 addition & 0 deletions Swift/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
'date', 'dateTime', 'time', 'instant',
]),
('Swift/JSON-extensions.swift', None, []),
('Swift/FHIRServer.swift', None, []),
]

# factory methods
Expand Down

0 comments on commit 12604e1

Please sign in to comment.