From f25736c0a012ce1b69815361f74caab31a2791a4 Mon Sep 17 00:00:00 2001 From: Pascal Pfiffner Date: Mon, 30 Nov 2015 21:13:55 -0500 Subject: [PATCH] Update FHIR submodule to use new FHIROpenServer as base class --- Classes/Client.swift | 3 +- Classes/Server.swift | 317 +++------------------------ Swift-FHIR | 2 +- SwiftSMART.xcodeproj/project.pbxproj | 4 + Tests/ClientTests.swift | 13 +- Tests/ServerTests.swift | 6 +- 6 files changed, 44 insertions(+), 301 deletions(-) diff --git a/Classes/Client.swift b/Classes/Client.swift index 9d9cd268..57315316 100644 --- a/Classes/Client.swift +++ b/Classes/Client.swift @@ -130,8 +130,7 @@ public class Client /** Stops any request currently in progress. */ public func abort() { - server.abortAuthorization() - server.abortSession() + server.abort() } /** Resets state and authorization data. */ diff --git a/Classes/Server.swift b/Classes/Server.swift index d59326c3..751c8305 100644 --- a/Classes/Server.swift +++ b/Classes/Server.swift @@ -21,14 +21,11 @@ import Foundation This implementation manages its own NSURLSession, either with an optional delegate provided via `sessionDelegate` or simply the shared session. Subclasses can change this behavior by overriding `createDefaultSession` or any of the other request-related methods. */ -public class Server: FHIRServer +public class Server: FHIROpenServer { /// The service URL as a string, as specified during initalization to be used as `aud` parameter. final let aud: String - /// The server's base URL. - public final let baseURL: NSURL - /// An optional name of the server; will be read from conformance statement unless manually assigned. public final var name: String? @@ -48,13 +45,6 @@ public class Server: FHIRServer } } - /// The operations the server supports, as specified in the conformance statement. - var operations: [String: OperationDefinition]? - var conformanceOperations: [ConformanceRestOperation]? - - /// The active URL session. - var session: NSURLSession? - var mustAbortAuthorization = false /// An optional NSURLSessionDelegate. @@ -71,15 +61,10 @@ public class Server: FHIRServer /** Main initializer. Makes sure the base URL ends with a "/" to facilitate URL generation later on. */ - public init(baseURL base: NSURL, auth: OAuth2JSON? = nil) { + public required init(baseURL base: NSURL, auth: OAuth2JSON? = nil) { aud = base.absoluteString - if let last = base.absoluteString.characters.last where last != "/" { - baseURL = base.URLByAppendingPathComponent("/") - } - else { - baseURL = base - } authSettings = auth + super.init(baseURL: base, auth: auth) didSetAuthSettings() } @@ -110,73 +95,24 @@ public class Server: FHIRServer // MARK: - Server Conformance - /// The server's conformance statement. Must be implicitly fetched using `getConformance()` - public internal(set) var conformance: Conformance? { - didSet { - if nil == name && nil != conformance?.name { - name = conformance!.name - } - - // look at ConformanceRest entries for security and operation information - if let rests = conformance?.rest { - var best: ConformanceRest? - for rest in rests { - if nil == best { - best = rest - } - else if "client" == rest.mode { - best = rest - break - } - } - - // use the "best" matching rest entry to extract the information we want - if let rest = best { - if let security = rest.security { - auth = Auth.fromConformanceSecurity(security, server: self, settings: authSettings) - } - - // if we have not yet initialized an Auth object we'll use one for "no auth" - if nil == auth { - auth = Auth(type: .None, server: self, settings: authSettings) - logIfDebug("Server seems to be open, proceeding with none-type auth") - } - - if let operations = rest.operation { - conformanceOperations = operations - } - } - } + public override func didSetConformance(conformance: Conformance) { + if nil == name && nil != conformance.name { + name = conformance.name } + super.didSetConformance(conformance) } - /** - Executes a `read` action against the server's "metadata" path, as returned from `conformancePath()`, which should return the Conformance - statement. - */ - final func getConformance(callback: (error: FHIRError?) -> ()) { - if nil != conformance { - callback(error: nil) - return - } + public override func didFindConformanceRestStatement(rest: ConformanceRest) { + super.didFindConformanceRestStatement(rest) - // not yet fetched, fetch it - Conformance.readFrom(conformancePath(), server: self) { resource, error in - if let conf = resource as? Conformance { - self.conformance = conf - callback(error: nil) - } - else { - callback(error: error ?? FHIRError.Error("Conformance.readFrom() did not return a Conformance instance but \(resource)")) - } + // initialize Auth; if we can't find a suitable Auth we'll use one for "no auth" + if let security = rest.security { + auth = Auth.fromConformanceSecurity(security, server: self, settings: authSettings) + } + if nil == auth { + auth = Auth(type: .None, server: self, settings: authSettings) + logIfDebug("Server seems to be open, proceeding with none-type auth") } - } - - /** Return the relative path to the Conformance statement. This should be "metadata", we're also adding "_summary=true" to only request - the summary, not the entire statement. - */ - public func conformancePath() -> String { - return "metadata?_summary=true" } @@ -251,11 +187,24 @@ public class Server: FHIRServer } } + public func abort() { + abortAuthorization() + abortSession() + } + + func abortAuthorization() { + logIfDebug("Aborting authorization") + mustAbortAuthorization = true + if nil != auth { + auth!.abort() + } + } + /** Resets authorization state - including deletion of any known access and refresh tokens. */ func reset() { - abortSession() + abort() auth?.reset() } @@ -302,209 +251,7 @@ public class Server: FHIRServer public func register(dynreg: OAuth2DynReg, callback: ((json: OAuth2JSON?, error: ErrorType?) -> Void)) { registerIfNeeded(dynreg, onlyIfNeeded: false, callback: callback) } - - - // MARK: - Requests - - /** - The server can return the appropriate request handler for the type and resource combination. - - Request handlers are responsible for constructing an NSURLRequest that correctly performs the desired REST interaction. - - - parameter type: The type of the request (GET, PUT, POST or DELETE) - - parameter resource: The resource to be involved in the request, if any - - returns: An appropriate `FHIRServerRequestHandler`, for example a _FHIRServerJSONRequestHandler_ if sending and receiving JSON - */ - public func handlerForRequestOfType(type: FHIRRequestType, resource: FHIRResource?) -> FHIRServerRequestHandler? { - return FHIRServerJSONRequestHandler(type, resource: resource) - } - - /** - This method simply creates an absolute URL from the receiver's `baseURL` and the given path. - - A chance for subclasses to mess with URL generation if needed. - */ - public func absoluteURLForPath(path: String, handler: FHIRServerRequestHandler) -> NSURL? { - return NSURL(string: path, relativeToURL: baseURL) - } - - /** - This method should first execute `handlerForRequestOfType()` to obtain an appropriate request handler, then execute the prepared - request against the server. - - - parameter type: The type of the request (GET, PUT, POST or DELETE) - - parameter path: The relative path on the server to be interacting against - - parameter resource: The resource to be involved in the request, if any - - parameter callback: A callback, likely called asynchronously, returning a response instance - */ - public func performRequestOfType(type: FHIRRequestType, path: String, resource: FHIRResource?, callback: ((response: FHIRServerResponse) -> Void)) { - if let handler = handlerForRequestOfType(type, resource: resource) { - performRequestAgainst(path, handler: handler, callback: callback) - } - else { - let res = FHIRServerRequestHandler.noneAvailableForType(type) - callback(response: res) - } - } - - /** - Method to execute a given request with a given request/response handler. - - - parameter path: The path, relative to the server's base; may include URL query and URL fragment (!) - - parameter handler: The RequestHandler that prepares the request and processes the response - - parameter callback: The callback to execute; NOT guaranteed to be performed on the main thread! - */ - public func performRequestAgainst(path: String, handler: R, callback: ((response: FHIRServerResponse) -> Void)) { - if let url = absoluteURLForPath(path, handler: handler) { - let request = auth?.signedRequest(url) ?? NSMutableURLRequest(URL: url) - do { - try handler.prepareRequest(request) - self.performPreparedRequest(request, handler: handler, callback: callback) - } - catch let error { - let err = (error as NSError).localizedDescription ?? "if only I knew why (\(__FILE__):\(__LINE__))" - callback(response: handler.notSent("Failed to prepare request against \(url): \(err)")) - } - } - else { - let res = handler.notSent("Failed to parse path «\(path)» relative to server base URL") - callback(response: res) - } - } - - /** - Method to execute an already prepared request and use the given request/response handler. - - This implementation uses the instance's NSURLSession to execute data tasks with the requests. Subclasses can override to supply - different NSURLSessions based on the request, if so desired. - - - parameter request: The URL request to perform - - parameter handler: The RequestHandler that prepares the request and processes the response - - parameter callback: The callback to execute; NOT guaranteed to be performed on the main thread! - */ - public func performPreparedRequest(request: NSMutableURLRequest, handler: R, callback: ((response: FHIRServerResponse) -> Void)) { - performPreparedRequest(request, withSession: URLSession(), handler: handler, callback: callback) - } - - /** - Method to execute an already prepared request with a given session and use the given request/response handler. - - - parameter request: The URL request to perform - - parameter withSession: The NSURLSession instance to use - - parameter handler: The RequestHandler that prepares the request and processes the response - - parameter callback: The callback to execute; NOT guaranteed to be performed on the main thread! - */ - public func performPreparedRequest(request: NSMutableURLRequest, withSession session: NSURLSession, handler: R, callback: ((response: FHIRServerResponse) -> Void)) { - let task = session.dataTaskWithRequest(request) { data, response, error in - let res = handler.response(response: response, data: data, error: error) - logIfDebug("Server responded with status \(res.status)") - //let str = NSString(data: data!, encoding: NSUTF8StringEncoding) - //logIfDebug("\(str)") - callback(response: res) - } - - logIfDebug("Performing \(handler.type.rawValue) request against \(request.URL!)") - task.resume() - } - - - // MARK: - Operations - - func conformanceOperation(name: String) -> ConformanceRestOperation? { - if let defs = conformanceOperations { - for def in defs { - if name == def.name { - return def - } - } - } - return nil - } - - /** - Retrieve the operation definition with the given name, either from cache or load the resource. - - Once an OperationDefinition has been retrieved, it is cached into the instance's `operations` dictionary. Must be used after the - conformance statement has been fetched, i.e. after using `ready` or `getConformance`. - */ - public func operation(name: String, callback: (OperationDefinition? -> Void)) { - if let op = operations?[name] { - callback(op) - } - else if let def = conformanceOperation(name) { - def.definition?.resolve(OperationDefinition.self) { optop in - if let op = optop { - if nil != self.operations { - self.operations![name] = op - } - else { - self.operations = [name: op] - } - } - callback(optop) - } - } - else { - callback(nil) - } - } - - /** - Performs the given Operation. - - `Resource` has extensions to facilitate working with operations, be sure to take a look. - - - parameter operation: The operation instance to perform - - parameter callback: The callback to call when the request ends (success or failure) - */ - public func performOperation(operation: FHIROperation, callback: ((response: FHIRServerResponse) -> Void)) { - self.operation(operation.name) { definition in - if let def = definition { - do { - try operation.validateWith(def) - try operation.perform(self, callback: callback) - } - catch let error { - callback(response: FHIRServerJSONResponse(error: error)) - } - } - else { - callback(response: FHIRServerJSONResponse(error: FHIRError.OperationNotSupported(operation.name))) - } - } - } - - - // MARK: - Session Management - - final public func URLSession() -> NSURLSession { - if nil == session { - session = createDefaultSession() - } - return session! - } - - /** Create the server's default session. Override in subclasses to customize NSURLSession behavior. */ - public func createDefaultSession() -> NSURLSession { - if let delegate = sessionDelegate { - return NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: delegate, delegateQueue: nil) - } - return NSURLSession.sharedSession() - } - - func abortAuthorization() { - logIfDebug("Aborting authorization") - mustAbortAuthorization = true - if nil != auth { - auth!.abort() - } - } - - func abortSession() { - if nil != session { - session!.invalidateAndCancel() - session = nil - } - } } +public typealias FHIRBaseServer = Server + diff --git a/Swift-FHIR b/Swift-FHIR index 76bf140d..58589d5d 160000 --- a/Swift-FHIR +++ b/Swift-FHIR @@ -1 +1 @@ -Subproject commit 76bf140ddc1d5907e3e3a770b9797e0994e11d2a +Subproject commit 58589d5d51cf1b87ce544a1c11a611d315b2acae diff --git a/SwiftSMART.xcodeproj/project.pbxproj b/SwiftSMART.xcodeproj/project.pbxproj index 548a3f7c..2f73df17 100644 --- a/SwiftSMART.xcodeproj/project.pbxproj +++ b/SwiftSMART.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ EE1F49B81C0CC5780095BF0F /* OAuth2AuthConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1F498B1C04D57A0095BF0F /* OAuth2AuthConfig.swift */; }; EE1F49B91C0CC57B0095BF0F /* OAuth2ClientConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1F498C1C04D57A0095BF0F /* OAuth2ClientConfig.swift */; }; EE1F49BA1C0CC57E0095BF0F /* OAuth2Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1F498D1C04D57A0095BF0F /* OAuth2Error.swift */; }; + EE1F49E21C0D366A0095BF0F /* FHIROpenServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1F49E11C0D366A0095BF0F /* FHIROpenServer.swift */; }; EE29E702195838DD008882C8 /* ServerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE29E701195838DD008882C8 /* ServerTests.swift */; }; EE29E70419583A04008882C8 /* metadata in Resources */ = {isa = PBXBuildFile; fileRef = EE29E70319583A04008882C8 /* metadata */; }; EE43B1B419546D880017679A /* SMART.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE43B1A919546D880017679A /* SMART.framework */; }; @@ -415,6 +416,7 @@ EE1F498D1C04D57A0095BF0F /* OAuth2Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuth2Error.swift; sourceTree = ""; }; EE1F49981C051DDA0095BF0F /* FHIRError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FHIRError.swift; sourceTree = ""; }; EE1F49B61C0672810095BF0F /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = SOURCE_ROOT; }; + EE1F49E11C0D366A0095BF0F /* FHIROpenServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FHIROpenServer.swift; sourceTree = ""; }; EE29E701195838DD008882C8 /* ServerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerTests.swift; sourceTree = ""; }; EE29E70319583A04008882C8 /* metadata */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = metadata; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.javascript; }; EE43B1A919546D880017679A /* SMART.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SMART.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -964,6 +966,7 @@ EE9B32021ACADE9900980AA9 /* Reference+Resolving.swift */, EE8901251B7E085B00F1EDBF /* FHIRElement+Utilities.swift */, EE56A49D1ABB0B7900FA99EB /* FHIRServer.swift */, + EE1F49E11C0D366A0095BF0F /* FHIROpenServer.swift */, EE9B32051ACAE6A900980AA9 /* FHIRServerRequestHandler.swift */, EE9B32081ACAE6D300980AA9 /* FHIRServerResponse.swift */, EE02F8091ACF2FDB00179969 /* FHIRSearch.swift */, @@ -1255,6 +1258,7 @@ EEEE30D91BB1FD53008866E2 /* ImplementationGuide.swift in Sources */, EEEE30DA1BB1FD53008866E2 /* MedicationOrder.swift in Sources */, EE56A57C1ABB0B7900FA99EB /* List.swift in Sources */, + EE1F49E21C0D366A0095BF0F /* FHIROpenServer.swift in Sources */, EE56A5DE1ABB0B7A00FA99EB /* SearchParameter.swift in Sources */, EE56A5A41ABB0B7A00FA99EB /* Organization.swift in Sources */, EE9B31F91ACADAF700980AA9 /* Resource+REST.swift in Sources */, diff --git a/Tests/ClientTests.swift b/Tests/ClientTests.swift index 8514feb0..3c8c742e 100644 --- a/Tests/ClientTests.swift +++ b/Tests/ClientTests.swift @@ -15,18 +15,11 @@ class ClientTests: XCTestCase { func testInit() { let client = Client(baseURL: "https://api.io", settings: ["cliend_id": "client", "redirect": "oauth://callback"]) XCTAssertTrue(client.server.baseURL.absoluteString == "https://api.io/") - - //XCTAssertNil(client.auth.clientId, "clientId will only be queryable once we have an OAuth2 instance") + +// //XCTAssertNil(client.auth.clientId, "clientId will only be queryable once we have an OAuth2 instance") client.ready { error in - print(error?.localizedDescription) + XCTAssertNil(error) } } - -// func testPerformanceExample() { -// // This is an example of a performance test case. -// self.measureBlock() { -// // Put the code you want to measure the time of here. -// } -// } } diff --git a/Tests/ServerTests.swift b/Tests/ServerTests.swift index b529b71a..e314fa5e 100644 --- a/Tests/ServerTests.swift +++ b/Tests/ServerTests.swift @@ -16,10 +16,10 @@ class ServerTests: XCTestCase { func testMetadataParsing() throws { let server = Server(base: "https://api.io") - XCTAssertTrue("https://api.io" == server.baseURL.absoluteString) + XCTAssertEqual("https://api.io", server.baseURL.absoluteString) + XCTAssertEqual("https://api.io", server.aud) - // TODO: How to use NSBundle(forClass)? - let metaURL = NSBundle(path: __FILE__.smart_stringByDeletingLastPathComponent)!.URLForResource("metadata", withExtension: "") + let metaURL = NSBundle(forClass: self.dynamicType).URLForResource("metadata", withExtension: "") XCTAssertNotNil(metaURL, "Need file `metadata` for unit tests") let metaData = NSData(contentsOfURL: metaURL!) let meta = try NSJSONSerialization.JSONObjectWithData(metaData!, options: []) as! FHIRJSON