From 8faca9cffdd0b88950a01ecdbd7010683127852a Mon Sep 17 00:00:00 2001 From: Dan Woodel Date: Mon, 7 Aug 2017 16:26:06 +0100 Subject: [PATCH] [Swift3] Download file fix --- .../swift3/AlamofireImplementations.mustache | 111 ++++++++++++++++- .../src/main/resources/swift3/Models.mustache | 9 +- .../swift/.swagger-codegen/VERSION | 1 + .../swift/SwaggerClient.podspec | 4 +- .../Classes/Swaggers/Extensions.swift | 93 ++++++++++++++ .../Classes/Swaggers/Models.swift | 11 +- .../Swaggers/AlamofireImplementations.swift | 117 +++++++++++++++++- 7 files changed, 338 insertions(+), 8 deletions(-) create mode 100644 samples/client/petstore-security-test/swift/.swagger-codegen/VERSION diff --git a/modules/swagger-codegen/src/main/resources/swift3/AlamofireImplementations.mustache b/modules/swagger-codegen/src/main/resources/swift3/AlamofireImplementations.mustache index fd981a05b05..e1c2af2218c 100644 --- a/modules/swagger-codegen/src/main/resources/swift3/AlamofireImplementations.mustache +++ b/modules/swagger-codegen/src/main/resources/swift3/AlamofireImplementations.mustache @@ -207,6 +207,56 @@ open class AlamofireRequestBuilder: RequestBuilder { nil ) }) + case is URL.Type: + validatedRequest.responseData(completionHandler: { (dataResponse) in + cleanupRequest() + + do { + + guard !dataResponse.result.isFailure else { + throw DownloadException.responseFailed + } + + guard let data = dataResponse.data else { + throw DownloadException.responseDataMissing + } + + guard let request = request.request else { + throw DownloadException.requestMissing + } + + let fileManager = FileManager.default + let urlRequest = try request.asURLRequest() + let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] + let requestURL = try self.getURL(from: urlRequest) + + var requestPath = try self.getPath(from: requestURL) + + if let headerFileName = self.getFileName(fromContentDisposition: dataResponse.response?.allHeaderFields["Content-Disposition"] as? String) { + requestPath = requestPath.appending("/\(headerFileName)") + } + + let filePath = documentsDirectory.appendingPathComponent(requestPath) + let directoryPath = filePath.deletingLastPathComponent().path + + try fileManager.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil) + try data.write(to: filePath, options: .atomic) + + completion( + Response( + response: dataResponse.response!, + body: (filePath as! T) + ), + nil + ) + + } catch let requestParserError as DownloadException { + completion(nil, ErrorResponse.HttpError(statusCode: 400, data: dataResponse.data, error: requestParserError)) + } catch let error { + completion(nil, ErrorResponse.HttpError(statusCode: 400, data: dataResponse.data, error: error)) + } + return + }) default: validatedRequest.responseJSON(options: .allowFragments) { response in cleanupRequest() @@ -253,4 +303,63 @@ open class AlamofireRequestBuilder: RequestBuilder { } return httpHeaders } -} + + fileprivate func getFileName(fromContentDisposition contentDisposition : String?) -> String? { + + guard let contentDisposition = contentDisposition else { + return nil + } + + let items = contentDisposition.components(separatedBy: ";") + + var filename : String? = nil + + for contentItem in items { + + let filenameKey = "filename=" + guard let range = contentItem.range(of: filenameKey) else { + break + } + + filename = contentItem + return filename? + .replacingCharacters(in: range, with:"") + .replacingOccurrences(of: "\"", with: "") + .trimmingCharacters(in: .whitespacesAndNewlines) + } + + return filename + + } + + fileprivate func getPath(from url : URL) throws -> String { + + guard var path = NSURLComponents(url: url, resolvingAgainstBaseURL: true)?.path else { + throw DownloadException.requestMissingPath + } + + if path.hasPrefix("/") { + path.remove(at: path.startIndex) + } + + return path + + } + + fileprivate func getURL(from urlRequest : URLRequest) throws -> URL { + + guard let url = urlRequest.url else { + throw DownloadException.requestMissingURL + } + + return url + } + + fileprivate enum DownloadException : Error { + case responseDataMissing + case responseFailed + case requestMissing + case requestMissingPath + case requestMissingURL + } +} \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/swift3/Models.mustache b/modules/swagger-codegen/src/main/resources/swift3/Models.mustache index f8c312452e0..4b190fb8dfd 100644 --- a/modules/swagger-codegen/src/main/resources/swift3/Models.mustache +++ b/modules/swagger-codegen/src/main/resources/swift3/Models.mustache @@ -277,7 +277,7 @@ class Decoders { {{/isEnum}} {{^isEnum}} {{#allVars.isEmpty}} - if let source = source as? {{dataType}} { + if let source = source as? {{classname}} { return .success(source) } else { return .failure(.typeMismatch(expected: "Typealias {{classname}}", actual: "\(source)")) @@ -335,8 +335,11 @@ class Decoders { } for key in propsDictionary.keys { - if let decodedValue = Decoders.decodeOptional(clazz: String.self, source: propsDictionary[key] as AnyObject?) { - result[key] = decodedValue + switch Decoders.decodeOptional(clazz: String.self, source: propsDictionary[key] as AnyObject?) { + + case let .success(value): result[key] = value + default: continue + } } {{/additionalPropertiesType}} diff --git a/samples/client/petstore-security-test/swift/.swagger-codegen/VERSION b/samples/client/petstore-security-test/swift/.swagger-codegen/VERSION new file mode 100644 index 00000000000..f9f7450d135 --- /dev/null +++ b/samples/client/petstore-security-test/swift/.swagger-codegen/VERSION @@ -0,0 +1 @@ +2.3.0-SNAPSHOT \ No newline at end of file diff --git a/samples/client/petstore-security-test/swift/SwaggerClient.podspec b/samples/client/petstore-security-test/swift/SwaggerClient.podspec index e3b018276fd..f6faf03a57a 100644 --- a/samples/client/petstore-security-test/swift/SwaggerClient.podspec +++ b/samples/client/petstore-security-test/swift/SwaggerClient.podspec @@ -8,6 +8,6 @@ Pod::Spec.new do |s| s.license = 'Proprietary' s.homepage = 'https://github.com/swagger-api/swagger-codegen' s.summary = 'SwaggerClient Swift SDK' - s.source_files = 'SwaggerClient/Classes/Swaggers/**/*.swift' - s.dependency 'Alamofire', '~> 3.4.1' + s.source_files = 'SwaggerClient/Classes/**/*.swift' + s.dependency 'Alamofire', '~> 3.5.1' end diff --git a/samples/client/petstore-security-test/swift/SwaggerClient/Classes/Swaggers/Extensions.swift b/samples/client/petstore-security-test/swift/SwaggerClient/Classes/Swaggers/Extensions.swift index c974cc40b36..d14c7fc808c 100644 --- a/samples/client/petstore-security-test/swift/SwaggerClient/Classes/Swaggers/Extensions.swift +++ b/samples/client/petstore-security-test/swift/SwaggerClient/Classes/Swaggers/Extensions.swift @@ -83,4 +83,97 @@ extension NSUUID: JSONEncodable { } } +/// Represents an ISO-8601 full-date (RFC-3339). +/// ex: 12-31-1999 +/// https://xml2rfc.tools.ietf.org/public/rfc/html/rfc3339.html#anchor14 +public final class ISOFullDate: CustomStringConvertible { + + public let year: Int + public let month: Int + public let day: Int + + public init(year year: Int, month: Int, day: Int) { + self.year = year + self.month = month + self.day = day + } + + /** + Converts an NSDate to an ISOFullDate. Only interested in the year, month, day components. + + - parameter date: The date to convert. + + - returns: An ISOFullDate constructed from the year, month, day of the date. + */ + public static func from(date date: NSDate) -> ISOFullDate? { + guard let calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian) else { + return nil + } + + let components = calendar.components( + [ + .Year, + .Month, + .Day, + ], + fromDate: date + ) + return ISOFullDate( + year: components.year, + month: components.month, + day: components.day + ) + } + + /** + Converts a ISO-8601 full-date string to an ISOFullDate. + + - parameter string: The ISO-8601 full-date format string to convert. + + - returns: An ISOFullDate constructed from the string. + */ + public static func from(string string: String) -> ISOFullDate? { + let components = string + .characters + .split("-") + .map(String.init) + .flatMap { Int($0) } + guard components.count == 3 else { return nil } + + return ISOFullDate( + year: components[0], + month: components[1], + day: components[2] + ) + } + + /** + Converts the receiver to an NSDate, in the default time zone. + + - returns: An NSDate from the components of the receiver, in the default time zone. + */ + public func toDate() -> NSDate? { + let components = NSDateComponents() + components.year = year + components.month = month + components.day = day + components.timeZone = NSTimeZone.defaultTimeZone() + let calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian) + return calendar?.dateFromComponents(components) + } + + // MARK: CustomStringConvertible + + public var description: String { + return "\(year)-\(month)-\(day)" + } + +} + +extension ISOFullDate: JSONEncodable { + public func encodeToJSON() -> AnyObject { + return "\(year)-\(month)-\(day)" + } +} + diff --git a/samples/client/petstore-security-test/swift/SwaggerClient/Classes/Swaggers/Models.swift b/samples/client/petstore-security-test/swift/SwaggerClient/Classes/Swaggers/Models.swift index 47aec6ef918..538fa082c8d 100644 --- a/samples/client/petstore-security-test/swift/SwaggerClient/Classes/Swaggers/Models.swift +++ b/samples/client/petstore-security-test/swift/SwaggerClient/Classes/Swaggers/Models.swift @@ -140,7 +140,16 @@ class Decoders { return NSDate(timeIntervalSince1970: Double(sourceInt / 1000) ) } fatalError("formatter failed to parse \(source)") - } + } + + // Decoder for ISOFullDate + Decoders.addDecoder(clazz: ISOFullDate.self, decoder: { (source: AnyObject) -> ISOFullDate in + if let string = source as? String, + let isoDate = ISOFullDate.from(string: string) { + return isoDate + } + fatalError("formatter failed to parse \(source)") + }) // Decoder for [Return] Decoders.addDecoder(clazz: [Return].self) { (source: AnyObject) -> [Return] in diff --git a/samples/client/petstore/swift3/default/PetstoreClient/Classes/Swaggers/AlamofireImplementations.swift b/samples/client/petstore/swift3/default/PetstoreClient/Classes/Swaggers/AlamofireImplementations.swift index fd981a05b05..7e92408b0e3 100644 --- a/samples/client/petstore/swift3/default/PetstoreClient/Classes/Swaggers/AlamofireImplementations.swift +++ b/samples/client/petstore/swift3/default/PetstoreClient/Classes/Swaggers/AlamofireImplementations.swift @@ -207,6 +207,62 @@ open class AlamofireRequestBuilder: RequestBuilder { nil ) }) + case is URL.Type: + validatedRequest.responseData(completionHandler: { (dataResponse) in + cleanupRequest() + + do { + + guard !dataResponse.result.isFailure else { + throw DownloadException.responseFailed + } + + guard let data = dataResponse.data else { + throw DownloadException.responseDataMissing + } + + guard let request = request.request else { + throw DownloadException.requestMissing + } + + let fileManager = FileManager.default + let urlRequest = try request.asURLRequest() + let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] + let requestURL = try self.getURL(from: urlRequest) + + var requestPath = try self.getPath(from: requestURL) + + if let headerFileName = self.getFileName(fromContentDisposition: dataResponse.response?.allHeaderFields["Content-Disposition"] as? String) { + requestPath = requestPath.appending("/\(headerFileName)") + } + + let filePath = documentsDirectory.appendingPathComponent(requestPath) + let directoryPath = filePath.deletingLastPathComponent().path + + try fileManager.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil) + try data.write(to: filePath, options: .atomic) + + completion( + Response( + response: dataResponse.response!, + body: (filePath as! T) + ), + nil + ) + + } catch let requestParserError as DownloadException { + completion( + nil, + ErrorResponse.Error(400, dataResponse.data, requestParserError) + ) + } catch let error { + completion( + nil, + ErrorResponse.Error(400, dataResponse.data, error) + ) + } + return + }) default: validatedRequest.responseJSON(options: .allowFragments) { response in cleanupRequest() @@ -253,4 +309,63 @@ open class AlamofireRequestBuilder: RequestBuilder { } return httpHeaders } -} + + fileprivate func getFileName(fromContentDisposition contentDisposition : String?) -> String? { + + guard let contentDisposition = contentDisposition else { + return nil + } + + let items = contentDisposition.components(separatedBy: ";") + + var filename : String? = nil + + for contentItem in items { + + let filenameKey = "filename=" + guard let range = contentItem.range(of: filenameKey) else { + break + } + + filename = contentItem + return filename? + .replacingCharacters(in: range, with:"") + .replacingOccurrences(of: "\"", with: "") + .trimmingCharacters(in: .whitespacesAndNewlines) + } + + return filename + + } + + fileprivate func getPath(from url : URL) throws -> String { + + guard var path = NSURLComponents(url: url, resolvingAgainstBaseURL: true)?.path else { + throw DownloadException.requestMissingPath + } + + if path.hasPrefix("/") { + path.remove(at: path.startIndex) + } + + return path + + } + + fileprivate func getURL(from urlRequest : URLRequest) throws -> URL { + + guard let url = urlRequest.url else { + throw DownloadException.requestMissingURL + } + + return url + } + + fileprivate enum DownloadException : Error { + case responseDataMissing + case responseFailed + case requestMissing + case requestMissingPath + case requestMissingURL + } +} \ No newline at end of file