Skip to content

Commit

Permalink
Update to Swift 5.0 and support FHIR canonical type
Browse files Browse the repository at this point in the history
  • Loading branch information
drdavec committed May 21, 2019
1 parent f95b737 commit 3628b19
Show file tree
Hide file tree
Showing 360 changed files with 1,456 additions and 782 deletions.
2 changes: 1 addition & 1 deletion .swift-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.2
5.0
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Changelog
=========

## 4.1

- Update to Swift 5.0

## 4.0

- Update to FHIR `R4` (v4.0.0-a53ec6ee1b)
Expand Down
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Contributing
============

For fixes, improvements and ideas, fork the repository and issue a pull request.
To get a pull request accepted, please adhere to these two rules:

- Work from the latest `develop` branch, unless it's a very small fix
- Respect current code style (indent with tabs, compare to existing code for more clues)

Ideally, only work on one feature at a time.
If you've done work on the code (i.e. not just fixes to the README), add yourself to the top of [`CONTRIBUTORS.md`](./CONTRIBUTORS.md).

Happy coding!
8 changes: 8 additions & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Contributors
============

Contributors to the codebase, in reverse chronological order:

- Dave Carlson, @drdavec
- Pascal Pfiffner, @p2

2 changes: 1 addition & 1 deletion FHIR.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

Pod::Spec.new do |s|
s.name = "FHIR"
s.version = "4.0.0"
s.version = "4.1.0"
s.summary = "Swift 🔥FHIR data model classes, with some goodies."
s.description = <<-DESC
Swift 🔥FHIR data model classes, generated from spec. Uses custom Date/Time structs to facilitate
Expand Down
4 changes: 2 additions & 2 deletions Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>4.0</string>
<string>4.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>4.0.0.0</string>
<string>4.1.0.0</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2014 CHIP. All rights reserved.</string>
<key>NSPrincipalClass</key>
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ See [tags/releases](https://github.com/smart-on-fhir/Swift-FHIR/releases).

Version | Swift | FHIR | &nbsp;
---------|-----------|---------------|-----------------------------
**4.1**| 5.0 | `4.0.0-a53ec6ee1b` | R4
**4.0**| 4.2 | `4.0.0-a53ec6ee1b` | R4
**3.1**| 3.2 | `3.0.0.11832` | STU 3
**3.0** | 3.0 | `3.0.0.11832` | STU 3
Expand Down
8 changes: 4 additions & 4 deletions Sources/Client/Element+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public extension FHIRPrimitive {

- parameter forURI: The URI defining the extension on the receiver
*/
public func extensions(forURI uri: String) -> [Extension]? {
func extensions(forURI uri: String) -> [Extension]? {
return extension_fhir?.filter() { return $0.url?.string == uri }
}
}
Expand All @@ -32,7 +32,7 @@ public extension Element {

- parameter forURI: The URI defining the extension on the receiver
*/
public final func extensions(forURI uri: String) -> [Extension]? {
final func extensions(forURI uri: String) -> [Extension]? {
return extension_fhir?.filter() { return $0.url?.string == uri }
}
}
Expand All @@ -45,7 +45,7 @@ public extension DomainResource {

- parameter forURI: The URI defining the extension on the receiver
*/
public final func extensions(forURI uri: String) -> [Extension]? {
final func extensions(forURI uri: String) -> [Extension]? {
return extension_fhir?.filter() { return $0.url?.string == uri }
}

Expand All @@ -54,7 +54,7 @@ public extension DomainResource {

- parameter forURI: The URI defining the modifier extension on the receiver
*/
public final func modifierExtensions(forURI uri: String) -> [Extension]? {
final func modifierExtensions(forURI uri: String) -> [Extension]? {
return modifierExtension?.filter() { return $0.url?.string == uri }
}
}
Expand Down
160 changes: 160 additions & 0 deletions Sources/Client/FHIRCanonical+Resolving.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//
// FHIRCanonical+Resolving.swift
// SwiftFHIR
//
// Created by Dave Carlson, May 2019
//

import Foundation
#if !NO_MODEL_IMPORT
import Models
#endif


/**
An extension to FHIRCanonical. This allows reference resolving while keeping the superclass'es attributes in place.

Systems resolving references to canonical URLs SHOULD first try to resolve the reference using the canonical reference (e.g. search on a known registry of terminology, conformance, or knowledge resources as appropriate), and then fall back to direct resolution using the URL as a literal reference if a local version of the canonical resource cannot be found. This approach is safe because the approaches must refer to the same artifact, though implementations will need to make appropriate arrangements regarding the version and/or currency of their local copy of the artifact.

This is a version specific reference to a value set. Note that this refers to the ValueSet.version not the ValueSet.meta.versionId. Searching for this on a FHIR server would look like this:

GET fhir/ValueSet?url=http://hl7.org/fhir/ValueSet/my-valueset&version=0.8
*/
extension FHIRCanonical {

/**
Determines if a reference has already been resolved, if it is a contained or a bundled resource which can be returned immediately.

If this method returns nil, it's possible that the referenced resource must be fetched from a server. Use the `resolve(type:callback:)`
method to achive that feat. That method will initially call this method and hence may return immediately if a reference has already been
resolved (or is contained/bundled).

- parameter type: The resource type that should be dereferenced
- returns: An instance of the desired type, nil if it cannot immediately be resolved OR if it is of a different type
*/
public func resolved<T: Resource>(_ type: T.Type) -> T? {
if let refid = self.fragment {
if let resolved = _owner?.resolvedReference(refid) {
if let res = resolved as? T {
return res
}
fhir_warn("reference “\(refid)” was dereferenced to «\(resolved)», which is not of the expected type “\(T.self)")
}

// not yet resolved, let's look at contained resources
if let contained = _owner?._owningResource?.containedResource(refid) {
if let contained = contained as? T {
return contained
}
fhir_warn("reference “\(refid)” was contained as «\(contained)», which is not of the expected type “\(T.self)")
return nil
}
}

// not contained, are we in a bundle and the resource is bundled?
if let refUrl = self.url?.absoluteString {
var bundle = _owner?._owningBundle
while nil != bundle {
if let entries = bundle?.entry {
for entry in entries {
if let resource = entry.resource, resource.hasURI(self) {
if let found = entry.resource as? T {
return found
}
fhir_warn("reference “\(refUrl)” was bundled as «\(String(describing: entry.resource))», which is not of the expected type “\(T.self)")
return nil
}
}
}
bundle = bundle?._owningBundle
}
}

// TODO attempt to retrieve resource from repository, database, or cache of Definition type resources.
// Define a repostory protocol with default implementation within the framework.

return nil
}

/**
Resolves the canonical URI by attempting to fetch from a server.

Checks if a reference can be resolved immediately by calling `resolved(type:)` first, if not proceeds to request the referenced resource
from the respective location.

- parameter type: The type of the resource to expect
- parameter callback: The callback to call upon success or failure, with the resolved resource or nil
*/
public func resolve<T: Resource>(_ type: T.Type, callback: @escaping ((T?) -> Void)) {
if let resolved = resolved(T.self) {
callback(resolved)
}
else if let ref = self.url?.absoluteFHIRString {
var server: FHIRServer? = nil
var path = ref.string

// absolute URL
if let _ = ref.string.range(of: "://") {
if let url = URL(string: ref.string) {
let base = url.deletingLastPathComponent().deletingLastPathComponent()
path = (url.absoluteString.replacingOccurrences(of: base.absoluteString, with: ""))
server = FHIRMinimalServer(baseURL: base, auth: nil) // TODO: what if it's protected?
}
else {
fhir_warn("Unable to construct NSURL from absolute reference «\(ref)»")
}
}

if let server = server {
T.readFrom(path, server: server) { resource, error in
if let res = resource, res.hasURI(self) {
self._owner?._owningResource?.didResolveReference(ref.string, resolved: res)
callback(res as? T) // `readFrom()` will always instantiate its own type, so this should never turn into nil
}
else {
if let err = error {
fhir_warn("error resolving reference «\(ref)»: \(err)")
}
callback(nil)
}
}
}
else {
fhir_warn("resource \(self) does not have a server instance nor does it contain «\(ref)», cannot resolve")
callback(nil)
}
}
}
}


extension Resource {

/**
Check if Resource has 'url' and 'version' elements (i.e. a FHIR Definition type resource),
return true if resource url and version match this canonical URI.
*/
func hasURI(_ canonical: FHIRCanonical) -> Bool {
var match = false

// No class reflection in Swift, serialize to JSON and look for key value.
var json = FHIRJSON()
var errors = [FHIRValidationError]()
self.decorate(json: &json, withKey: "resource", errors: &errors)

if let resourceJSON = json["resource"] as? FHIRJSON {
if let url = resourceJSON["url"] as? String, url == canonical.url?.absoluteString {
// if canonical URI specifies a version, then resource version must match
if let version = canonical.version {
match = version == resourceJSON["version"] as? String
}
else {
match = true
}
}
}

return match
}

}
6 changes: 3 additions & 3 deletions Sources/Client/Resource+Instantiation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public extension Foundation.Bundle {
- parameter type: The type the resource is expected to be; must be a subclass of `Resource`
- returns: A Resource subclass corresponding to the "resourceType" entry, as specified under `type`
*/
public func fhir_bundledResource<T: Resource>(_ name: String, type: T.Type) throws -> T {
func fhir_bundledResource<T: Resource>(_ name: String, type: T.Type) throws -> T {
return try fhir_bundledResource(name, subdirectory: nil, type: type)
}

Expand All @@ -51,7 +51,7 @@ public extension Foundation.Bundle {
- parameter type: The type the resource is expected to be; must be a subclass of `Resource`
- returns: A Resource subclass corresponding to the "resourceType" entry, as specified under `type`
*/
public func fhir_bundledResource<T: Resource>(_ name: String, subdirectory: String?, type: T.Type) throws -> T {
func fhir_bundledResource<T: Resource>(_ name: String, subdirectory: String?, type: T.Type) throws -> T {
let json = try fhir_json(from: name, subdirectory: subdirectory)
var context = FHIRInstantiationContext()
let resource = T.instantiate(from: json, owner: nil, context: &context)
Expand All @@ -68,7 +68,7 @@ public extension Foundation.Bundle {
- parameter type: The type the resource is expected to be; must be a subclass of `Resource`
- returns: A Resource subclass corresponding to the "resourceType" entry, as specified under `type`
*/
public func fhir_json(from name: String, subdirectory: String?) throws -> FHIRJSON {
func fhir_json(from name: String, subdirectory: String?) throws -> FHIRJSON {
if let url = url(forResource: name, withExtension: "json", subdirectory: subdirectory) {
let data = try Data(contentsOf: url)
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? FHIRJSON {
Expand Down
8 changes: 4 additions & 4 deletions Sources/Client/Resource+Operation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public extension Resource {

UNFINISHED.
*/
public func search(_ query: Any) -> FHIRSearch {
func search(_ query: Any) -> FHIRSearch {
if let _ = self.id {
NSLog("UNFINISHED, must add '_id' reference to search expression")
//return FHIRSearch(subject: "_id", reference: myID, type: type(of: self))
Expand All @@ -36,7 +36,7 @@ public extension Resource {
/**
Perform a search, wich the given query construct, against the receiver's compartment.
*/
public class func search(_ query: Any) -> FHIRSearch {
class func search(_ query: Any) -> FHIRSearch {
return FHIRSearch(type: self, query: query)
}

Expand All @@ -46,7 +46,7 @@ public extension Resource {
/**
Perform a given operation on the receiver.
*/
public func perform(operation: FHIROperation, callback: @escaping FHIRResourceErrorCallback) {
func perform(operation: FHIROperation, callback: @escaping FHIRResourceErrorCallback) {
if let server = _server {
if let server = server as? FHIROpenServer {
operation.instance = self
Expand All @@ -64,7 +64,7 @@ public extension Resource {
/**
Perform a given operation on the receiving type.
*/
public class func perform(operation: FHIROperation, server: FHIROpenServer, callback: @escaping FHIRResourceErrorCallback) {
class func perform(operation: FHIROperation, server: FHIROpenServer, callback: @escaping FHIRResourceErrorCallback) {
operation.type = self
_perform(operation: operation, server: server, callback: callback)
}
Expand Down
Loading

0 comments on commit 3628b19

Please sign in to comment.