diff --git a/Sources/Client/FHIRSearch.swift b/Sources/Client/FHIRSearch.swift index 6f23ff20..3c098e05 100644 --- a/Sources/Client/FHIRSearch.swift +++ b/Sources/Client/FHIRSearch.swift @@ -150,7 +150,11 @@ open class FHIRSearch else { let jsonres = response as! FHIRServerJSONResponse do { + #if !NO_MODEL_IMPORT + let bundle = try Models.Bundle(json: jsonres.json ?? FHIRJSON()) + #else let bundle = try SwiftFHIR.Bundle(json: jsonres.json ?? FHIRJSON()) + #endif bundle._server = server if let entries = bundle.entry { for entry in entries { diff --git a/Sources/Client/FHIRServerDataResponse.swift b/Sources/Client/FHIRServerDataResponse.swift index f4d4a50a..bd31e7d2 100644 --- a/Sources/Client/FHIRServerDataResponse.swift +++ b/Sources/Client/FHIRServerDataResponse.swift @@ -132,7 +132,7 @@ open class FHIRServerDataResponse: FHIRServerResponse { } // was there an error? - if let error = error as? NSError, NSURLErrorDomain == error.domain { + if let error = error, NSURLErrorDomain == error._domain { self.error = FHIRError.requestError(status, error.humanized) } else if let error = error as? FHIRError { @@ -150,8 +150,8 @@ open class FHIRServerDataResponse: FHIRServerResponse { public required init(error: Error) { self.status = 0 self.headers = [String: String]() - if NSURLErrorDomain == (error as NSError).domain { - self.error = FHIRError.requestError(status, (error as NSError).humanized) + if NSURLErrorDomain == error._domain { + self.error = FHIRError.requestError(status, error.humanized) } else if let error = error as? FHIRError { self.error = error @@ -276,14 +276,19 @@ open class FHIRServerJSONResponse: FHIRServerDataResponse { // MARK: - -extension NSError { +extension Error { /** - Return a human-readable, localized string for error codes of the NSURLErrorDomain (!!). + Return a human-readable, localized string for error codes of the NSURLErrorDomain. Will simply return `localizedDescription` for if the + receiver is not of that domain. + + The list of errors that are "humanized" is not necessarily exhaustive. All strings are returned `fhir_localized`. */ public var humanized: String { - assert(NSURLErrorDomain == domain, "Can only use this function with errors in the NSURLErrorDomain") - switch code { + guard NSURLErrorDomain == _domain else { + return localizedDescription + } + switch _code { case NSURLErrorBadURL: return "The URL was malformed".fhir_localized case NSURLErrorTimedOut: return "The connection timed out".fhir_localized case NSURLErrorUnsupportedURL: return "The URL scheme is not supported".fhir_localized diff --git a/Sources/Client/Resource+REST.swift b/Sources/Client/Resource+REST.swift index f5873128..2e1ae0cb 100644 --- a/Sources/Client/Resource+REST.swift +++ b/Sources/Client/Resource+REST.swift @@ -117,8 +117,8 @@ public extension Resource { catch let error { fhir_warn("Error applying response headers after `read` call: \(error)") } - if nil == resource.id { - resource.id = (path as NSString).lastPathComponent + if nil == resource.id, let lpc = URL(string: path) { + resource.id = lpc.lastPathComponent } callback(resource, nil) } diff --git a/Sources/Models/DateAndTime.swift b/Sources/Models/DateAndTime.swift index fa4cf5f4..381e201d 100644 --- a/Sources/Models/DateAndTime.swift +++ b/Sources/Models/DateAndTime.swift @@ -258,12 +258,22 @@ public struct FHIRTime: DateAndTime { return DateNSDateConverter.sharedConverter.create(fromTime: self) } + // TODO: this implementation uses a workaround using string coercion instead of format: "%02d:%02d:%@" because %@ with String is not + // supported on Linux (SR-957) public var description: String { if let secStr = tookSecondsFromString { + #if os(Linux) + return String(format: "%02d:%02d:", hour, minute) + secStr + #else return String(format: "%02d:%02d:%@", hour, minute, secStr) + #endif } if let s = second { + #if os(Linux) + return String(format: "%02d:%02d:", hour, minute) + ((s < 10) ? "0" : "") + String(format: "%g", s) + #else return String(format: "%02d:%02d:%@%g", hour, minute, (s < 10) ? "0" : "", s) + #endif } return String(format: "%02d:%02d", hour, minute) } @@ -381,7 +391,7 @@ public struct DateTime: DateAndTime { public var description: String { if let tm = time { if let tz = timeZoneString ?? timeZone?.offset() { - return String(format: "%@T%@%@", date.description, tm.description, tz) + return "\(date.description)T\(tm.description)\(tz)" } } return date.description @@ -493,7 +503,7 @@ public struct Instant: DateAndTime { public var description: String { let tz = timeZoneString ?? timeZone.offset() - return String(format: "%@T%@%@", date.description, time.description, tz) + return "\(date.description)T\(time.description)\(tz)" } public static func <(lhs: Instant, rhs: Instant) -> Bool { @@ -579,7 +589,7 @@ class DateNSDateConverter { let comp = calendar.dateComponents(flags, from: inDate) let date = FHIRDate(year: comp.year!, month: UInt8(comp.month!), day: UInt8(comp.day!)) - let zone = (comp as NSDateComponents).timeZone ?? utc + let zone = comp.timeZone ?? utc let secs = Double(comp.second!) + (Double(comp.nanosecond!) / 1000000000) let time = FHIRTime(hour: UInt8(comp.hour!), minute: UInt8(comp.minute!), second: secs) @@ -658,37 +668,31 @@ class DateAndTimeParser { // scan date (must have at least the year) if !isTimeOnly { - var year = 0 - if scanner.scanInt(&year) && year < 10000 { // dates above 9999 are considered special cases - var month = 0 - if scanner.scanString("-", into: nil) && scanner.scanInt(&month) && month <= 12 { - var day = 0 - if scanner.scanString("-", into: nil) && scanner.scanInt(&day) && day <= 31 { - date = FHIRDate(year: year, month: UInt8(month), day: UInt8(day)) + if let year = scanner.fhir_scanInt(), year < 10000 { // dates above 9999 are considered special cases + if nil != scanner.fhir_scanString("-"), let month = scanner.fhir_scanInt(), month <= 12 { + if nil != scanner.fhir_scanString("-"), let day = scanner.fhir_scanInt(), day <= 31 { + date = FHIRDate(year: Int(year), month: UInt8(month), day: UInt8(day)) } else { - date = FHIRDate(year: year, month: UInt8(month), day: nil) + date = FHIRDate(year: Int(year), month: UInt8(month), day: nil) } } else { - date = FHIRDate(year: year, month: nil, day: nil) + date = FHIRDate(year: Int(year), month: nil, day: nil) } } } // scan time - if isTimeOnly || scanner.scanString("T", into: nil) { - var hour = 0 - var minute = 0 - if scanner.scanInt(&hour) && hour >= 0 && hour < 24 && scanner.scanString(":", into: nil) - && scanner.scanInt(&minute) && minute >= 0 && minute < 60 { + if isTimeOnly || nil != scanner.fhir_scanString("T") { + if let hour = scanner.fhir_scanInt(), hour >= 0 && hour < 24 && nil != scanner.fhir_scanString(":"), + let minute = scanner.fhir_scanInt(), minute >= 0 && minute < 60 { let digitSet = CharacterSet.decimalDigits var decimalSet = NSMutableCharacterSet.decimalDigits decimalSet.insert(".") - var secStr: NSString? - if scanner.scanString(":", into: nil) && scanner.scanCharacters(from: decimalSet as CharacterSet, into: &secStr), let secStr = secStr as? String, let second = Double(secStr), second < 60.0 { + if nil != scanner.fhir_scanString(":"), let secStr = scanner.fhir_scanCharacters(from: decimalSet as CharacterSet), let second = Double(secStr), second < 60.0 { time = FHIRTime(hour: UInt8(hour), minute: UInt8(minute), second: second, secondsFromString: secStr) } else { @@ -696,35 +700,33 @@ class DateAndTimeParser { } // scan zone - if !scanner.isAtEnd { - var negStr: NSString? - if scanner.scanString("Z", into: nil) { + if !scanner.fhir_isAtEnd { + if nil != scanner.fhir_scanString("Z") { tz = TimeZone(abbreviation: "UTC") tzString = "Z" } - else if scanner.scanString("-", into: &negStr) || scanner.scanString("+", into: nil) { - tzString = (nil == negStr) ? "+" : "-" - var hourStr: NSString? - if scanner.scanCharacters(from: digitSet, into: &hourStr) { - tzString! += hourStr! as String + else if var tzStr = (scanner.fhir_scanString("-") ?? scanner.fhir_scanString("+")) { + if let hourStr = scanner.fhir_scanCharacters(from: digitSet) { + tzStr += hourStr var tzhour = 0 var tzmin = 0 - if 2 == hourStr?.length { - tzhour = hourStr!.integerValue - if scanner.scanString(":", into: nil) && scanner.scanInt(&tzmin) { - tzString! += (tzmin < 10) ? ":0\(tzmin)" : ":\(tzmin)" - if tzmin >= 60 { - tzmin = 0 + if 2 == hourStr.characters.count { + tzhour = Int(hourStr) ?? 0 + if nil != scanner.fhir_scanString(":"), let tzm = scanner.fhir_scanInt() { + tzStr += (tzm < 10) ? ":0\(tzm)" : ":\(tzm)" + if tzm < 60 { + tzmin = tzm } } } - else if 4 == hourStr?.length { - tzhour = Int(hourStr!.substring(to: 2))! - tzmin = Int(hourStr!.substring(from: 2))! + else if 4 == hourStr.characters.count { + tzhour = Int(hourStr.substring(to: hourStr.index(hourStr.startIndex, offsetBy: 2)))! + tzmin = Int(hourStr.substring(from: hourStr.index(hourStr.startIndex, offsetBy: 2)))! } let offset = tzhour * 3600 + tzmin * 60 - tz = TimeZone(secondsFromGMT: nil == negStr ? offset : -1 * offset) + tz = TimeZone(secondsFromGMT: "+" == tzStr ? offset : -1 * offset) + tzString = tzStr } } } @@ -786,7 +788,56 @@ extension TimeZone { let hr = abs((secsFromGMT / 3600) - (secsFromGMT % 3600)) let min = abs((secsFromGMT % 3600) / 60) - return String(format: "%@%02d:%02d", secsFromGMT >= 0 ? "+" : "-", hr, min) + return (secsFromGMT >= 0 ? "+" : "-") + String(format: "%02d:%02d", hr, min) + } +} + + +/** +Extend Scanner to account for interface differences between macOS and Linux (as of November 2016) +*/ +extension Scanner { + + public var fhir_isAtEnd: Bool { + #if os(Linux) + return atEnd + #else + return isAtEnd + #endif + } + + public func fhir_scanString(_ searchString: String) -> String? { + #if os(Linux) + return scanString(string: searchString) + #else + var str: NSString? + if scanString(searchString, into: &str) { + return str as? String + } + return nil + #endif + } + + public func fhir_scanCharacters(from set: CharacterSet) -> String? { + #if os(Linux) + return scanCharactersFromSet(set) + #else + var str: NSString? + if scanCharacters(from: set, into: &str) { + return str as? String + } + return nil + #endif + } + + public func fhir_scanInt() -> Int? { + var int = 0 + #if os(Linux) + let flag = scanInteger(&int) + #else + let flag = scanInt(&int) + #endif + return flag ? int : nil } } diff --git a/Sources/Models/FHIRTypes.swift b/Sources/Models/FHIRTypes.swift index 1ac5069b..488c5153 100644 --- a/Sources/Models/FHIRTypes.swift +++ b/Sources/Models/FHIRTypes.swift @@ -71,9 +71,15 @@ public struct Base64Binary: ExpressibleByStringLiteral, CustomStringConvertible, extension String { /** Convenience getter using `NSLocalizedString()` with no comment. + + TODO: On Linux this currently simply returns self */ public var fhir_localized: String { + #if os(Linux) + return self + #else return NSLocalizedString(self, comment: "") + #endif } } @@ -82,7 +88,7 @@ Execute a `print()`, prepending filename, line and function/method name, if `DEB */ public func fhir_logIfDebug(_ message: @autoclosure () -> String, function: String = #function, file: String = #file, line: Int = #line) { #if DEBUG - print("SwiftFHIR [\((file as NSString).lastPathComponent):\(line)] \(function) \(message())") + print("SwiftFHIR [\(URL(fileURLWithPath: file).lastPathComponent):\(line)] \(function) \(message())") #endif } @@ -90,6 +96,6 @@ public func fhir_logIfDebug(_ message: @autoclosure () -> String, function: Stri Execute a `print()`, prepending filename, line and function/method name and "WARNING" prepended. */ public func fhir_warn(_ message: @autoclosure () -> String, function: String = #function, file: String = #file, line: Int = #line) { - print("SwiftFHIR [\((file as NSString).lastPathComponent):\(line)] \(function) WARNING: \(message())") + print("SwiftFHIR [\(URL(fileURLWithPath: file).lastPathComponent):\(line)] \(function) WARNING: \(message())") } diff --git a/Sources/Models/JSON-extensions.swift b/Sources/Models/JSON-extensions.swift index d60f8716..b2876169 100644 --- a/Sources/Models/JSON-extensions.swift +++ b/Sources/Models/JSON-extensions.swift @@ -66,7 +66,7 @@ extension NSDecimalNumber { */ public convenience init(json: NSNumber) { if let _ = json.stringValue.characters.index(of: ".") { - self.init(string: NSString(format: "%.15g", json.doubleValue) as String) + self.init(string: String(format: "%.15g", json.doubleValue)) } else { self.init(string: "\(json)") diff --git a/SwiftFHIR.xcodeproj/project.pbxproj b/SwiftFHIR.xcodeproj/project.pbxproj index 603b8885..9511a554 100644 --- a/SwiftFHIR.xcodeproj/project.pbxproj +++ b/SwiftFHIR.xcodeproj/project.pbxproj @@ -525,6 +525,7 @@ EE723FF21DC14AD300C398BA /* RelatedArtifact.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE723F6D1DC148C200C398BA /* RelatedArtifact.swift */; }; EE723FF31DC14AD300C398BA /* ServiceDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE723F6E1DC148C200C398BA /* ServiceDefinition.swift */; }; EE723FF41DC14AD300C398BA /* TestReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE723F6F1DC148C200C398BA /* TestReport.swift */; }; + EE82503F1DD61DE20097A737 /* ResourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE01F9721C58FC51003AEA7E /* ResourceTests.swift */; }; EE8901231B7E07D700F1EDBF /* Element+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8901221B7E07D700F1EDBF /* Element+Extensions.swift */; }; EE8901241B7E07D700F1EDBF /* Element+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8901221B7E07D700F1EDBF /* Element+Extensions.swift */; }; EE9ABA2B1D803D8400BA8B54 /* Patient+SMART.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9ABA2A1D803D8400BA8B54 /* Patient+SMART.swift */; }; @@ -971,12 +972,7 @@ isa = PBXGroup; children = ( EE684C2C19A789BA00B5A2C0 /* Info.plist */, - EE684CCE19A7CEF000B5A2C0 /* ExtensionsTest.swift */, - EEE5DF361A5D862B002AFF53 /* FHIRSearchTests.swift */, - EE1F49D41C0D14F60095BF0F /* ReferenceTests.swift */, - EE01F9721C58FC51003AEA7E /* ResourceTests.swift */, - EE39069E1CD3E4F6008FECEA /* RequestTests.swift */, - EEA97F921DCA19C300C3F016 /* ValidationTests.swift */, + EE8250321DD61DBD0097A737 /* ClientTests */, EEEB096E19AD248700C324FC /* ModelTests */, EE1F49DE1C0D1BB40095BF0F /* TestResources */, ); @@ -1152,6 +1148,19 @@ path = Sources/Models; sourceTree = ""; }; + EE8250321DD61DBD0097A737 /* ClientTests */ = { + isa = PBXGroup; + children = ( + EE684CCE19A7CEF000B5A2C0 /* ExtensionsTest.swift */, + EEE5DF361A5D862B002AFF53 /* FHIRSearchTests.swift */, + EE1F49D41C0D14F60095BF0F /* ReferenceTests.swift */, + EE01F9721C58FC51003AEA7E /* ResourceTests.swift */, + EE39069E1CD3E4F6008FECEA /* RequestTests.swift */, + EEA97F921DCA19C300C3F016 /* ValidationTests.swift */, + ); + path = ClientTests; + sourceTree = ""; + }; EE9B31F61ACAD94800980AA9 /* Client */ = { isa = PBXGroup; children = ( @@ -2013,6 +2022,7 @@ EE02F7591ACF259B00179969 /* BundleTests.swift in Sources */, EEA97F971DCA1C3C00C3F016 /* RequestGroupTests.swift in Sources */, EE02F7731ACF259B00179969 /* CoverageTests.swift in Sources */, + EE82503F1DD61DE20097A737 /* ResourceTests.swift in Sources */, EE1F49D61C0D14F60095BF0F /* ReferenceTests.swift in Sources */, EE02F7BB1ACF259B00179969 /* MessageHeaderTests.swift in Sources */, EE31DC521D64AC3600B04BEA /* ActivityDefinitionTests.swift in Sources */, diff --git a/Tests/ExtensionsTest.swift b/Tests/ClientTests/ExtensionsTest.swift similarity index 100% rename from Tests/ExtensionsTest.swift rename to Tests/ClientTests/ExtensionsTest.swift diff --git a/Tests/FHIRSearchTests.swift b/Tests/ClientTests/FHIRSearchTests.swift similarity index 100% rename from Tests/FHIRSearchTests.swift rename to Tests/ClientTests/FHIRSearchTests.swift diff --git a/Tests/ReferenceTests.swift b/Tests/ClientTests/ReferenceTests.swift similarity index 100% rename from Tests/ReferenceTests.swift rename to Tests/ClientTests/ReferenceTests.swift diff --git a/Tests/RequestTests.swift b/Tests/ClientTests/RequestTests.swift similarity index 100% rename from Tests/RequestTests.swift rename to Tests/ClientTests/RequestTests.swift diff --git a/Tests/ResourceTests.swift b/Tests/ClientTests/ResourceTests.swift similarity index 100% rename from Tests/ResourceTests.swift rename to Tests/ClientTests/ResourceTests.swift diff --git a/Tests/ValidationTests.swift b/Tests/ClientTests/ValidationTests.swift similarity index 99% rename from Tests/ValidationTests.swift rename to Tests/ClientTests/ValidationTests.swift index c2574cb4..6f2eff00 100644 --- a/Tests/ValidationTests.swift +++ b/Tests/ClientTests/ValidationTests.swift @@ -99,7 +99,7 @@ class ValidationTests: XCTestCase { XCTAssertNil(error) } - let element3 = QuestionnaireItemOption(value: FHIRDate(string: "2016-03-30")) + let element3 = QuestionnaireItemOption(value: FHIRDate(string: "2016-03-30")!) XCTAssertEqual(2016, element3.valueDate?.year) do { let js = try element3.asJSON() diff --git a/fhir-parser b/fhir-parser index 9bba8156..6171fd31 160000 --- a/fhir-parser +++ b/fhir-parser @@ -1 +1 @@ -Subproject commit 9bba81564d57d473b3da77b85c4c7508ec3ee83f +Subproject commit 6171fd318a459a07cc729c7d6b68545bca965ec8