Skip to content

Commit 5844b29

Browse files
committed
[Swift3] Download file fix
1 parent 5d263e1 commit 5844b29

File tree

8 files changed

+335
-11
lines changed

8 files changed

+335
-11
lines changed

modules/swagger-codegen/src/main/resources/swift3/AlamofireImplementations.mustache

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,56 @@ open class AlamofireRequestBuilder<T>: RequestBuilder<T> {
207207
nil
208208
)
209209
})
210+
case is URL.Type:
211+
validatedRequest.responseData(completionHandler: { (dataResponse) in
212+
cleanupRequest()
213+
214+
do {
215+
216+
guard !dataResponse.result.isFailure else {
217+
throw DownloadException.responseFailed
218+
}
219+
220+
guard let data = dataResponse.data else {
221+
throw DownloadException.responseDataMissing
222+
}
223+
224+
guard let request = request.request else {
225+
throw DownloadException.requestMissing
226+
}
227+
228+
let fileManager = FileManager.default
229+
let urlRequest = try request.asURLRequest()
230+
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
231+
let requestURL = try self.getURL(from: urlRequest)
232+
233+
var requestPath = try self.getPath(from: requestURL)
234+
235+
if let headerFileName = self.getFileName(fromContentDisposition: dataResponse.response?.allHeaderFields["Content-Disposition"] as? String) {
236+
requestPath = requestPath.appending("/\(headerFileName)")
237+
}
238+
239+
let filePath = documentsDirectory.appendingPathComponent(requestPath)
240+
let directoryPath = filePath.deletingLastPathComponent().path
241+
242+
try fileManager.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil)
243+
try data.write(to: filePath, options: .atomic)
244+
245+
completion(
246+
Response(
247+
response: dataResponse.response!,
248+
body: (filePath as! T)
249+
),
250+
nil
251+
)
252+
253+
} catch let requestParserError as DownloadException {
254+
completion(nil, ErrorResponse.HttpError(statusCode: 400, data: dataResponse.data, error: requestParserError))
255+
} catch let error {
256+
completion(nil, ErrorResponse.HttpError(statusCode: 400, data: dataResponse.data, error: error))
257+
}
258+
return
259+
})
210260
default:
211261
validatedRequest.responseJSON(options: .allowFragments) { response in
212262
cleanupRequest()
@@ -253,4 +303,63 @@ open class AlamofireRequestBuilder<T>: RequestBuilder<T> {
253303
}
254304
return httpHeaders
255305
}
256-
}
306+
307+
fileprivate func getFileName(fromContentDisposition contentDisposition : String?) -> String? {
308+
309+
guard let contentDisposition = contentDisposition else {
310+
return nil
311+
}
312+
313+
let items = contentDisposition.components(separatedBy: ";")
314+
315+
var filename : String? = nil
316+
317+
for contentItem in items {
318+
319+
let filenameKey = "filename="
320+
guard let range = contentItem.range(of: filenameKey) else {
321+
break
322+
}
323+
324+
filename = contentItem
325+
return filename?
326+
.replacingCharacters(in: range, with:"")
327+
.replacingOccurrences(of: "\"", with: "")
328+
.trimmingCharacters(in: .whitespacesAndNewlines)
329+
}
330+
331+
return filename
332+
333+
}
334+
335+
fileprivate func getPath(from url : URL) throws -> String {
336+
337+
guard var path = NSURLComponents(url: url, resolvingAgainstBaseURL: true)?.path else {
338+
throw DownloadException.requestMissingPath
339+
}
340+
341+
if path.hasPrefix("/") {
342+
path.remove(at: path.startIndex)
343+
}
344+
345+
return path
346+
347+
}
348+
349+
fileprivate func getURL(from urlRequest : URLRequest) throws -> URL {
350+
351+
guard let url = urlRequest.url else {
352+
throw DownloadException.requestMissingURL
353+
}
354+
355+
return url
356+
}
357+
358+
fileprivate enum DownloadException : Error {
359+
case responseDataMissing
360+
case responseFailed
361+
case requestMissing
362+
case requestMissingPath
363+
case requestMissingURL
364+
}
365+
}

modules/swagger-codegen/src/main/resources/swift3/Models.mustache

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ class Decoders {
277277
{{/isEnum}}
278278
{{^isEnum}}
279279
{{#allVars.isEmpty}}
280-
if let source = source as? {{dataType}} {
280+
if let source = source as? {{classname}} {
281281
return .success(source)
282282
} else {
283283
return .failure(.typeMismatch(expected: "Typealias {{classname}}", actual: "\(source)"))
@@ -335,8 +335,11 @@ class Decoders {
335335
}
336336

337337
for key in propsDictionary.keys {
338-
if let decodedValue = Decoders.decodeOptional(clazz: String.self, source: propsDictionary[key] as AnyObject?) {
339-
result[key] = decodedValue
338+
switch Decoders.decodeOptional(clazz: String.self, source: propsDictionary[key] as AnyObject?) {
339+
340+
case let .success(value): result[key] = value
341+
default: continue
342+
340343
}
341344
}
342345
{{/additionalPropertiesType}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2.3.0-SNAPSHOT

samples/client/petstore-security-test/swift/SwaggerClient.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ Pod::Spec.new do |s|
88
s.license = 'Proprietary'
99
s.homepage = 'https://github.com/swagger-api/swagger-codegen'
1010
s.summary = 'SwaggerClient Swift SDK'
11-
s.source_files = 'SwaggerClient/Classes/Swaggers/**/*.swift'
12-
s.dependency 'Alamofire', '~> 3.4.1'
11+
s.source_files = 'SwaggerClient/Classes/**/*.swift'
12+
s.dependency 'Alamofire', '~> 3.5.1'
1313
end

samples/client/petstore-security-test/swift/SwaggerClient/Classes/Swaggers/Extensions.swift

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,97 @@ extension NSUUID: JSONEncodable {
8383
}
8484
}
8585

86+
/// Represents an ISO-8601 full-date (RFC-3339).
87+
/// ex: 12-31-1999
88+
/// https://xml2rfc.tools.ietf.org/public/rfc/html/rfc3339.html#anchor14
89+
public final class ISOFullDate: CustomStringConvertible {
90+
91+
public let year: Int
92+
public let month: Int
93+
public let day: Int
94+
95+
public init(year year: Int, month: Int, day: Int) {
96+
self.year = year
97+
self.month = month
98+
self.day = day
99+
}
100+
101+
/**
102+
Converts an NSDate to an ISOFullDate. Only interested in the year, month, day components.
103+
104+
- parameter date: The date to convert.
105+
106+
- returns: An ISOFullDate constructed from the year, month, day of the date.
107+
*/
108+
public static func from(date date: NSDate) -> ISOFullDate? {
109+
guard let calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian) else {
110+
return nil
111+
}
112+
113+
let components = calendar.components(
114+
[
115+
.Year,
116+
.Month,
117+
.Day,
118+
],
119+
fromDate: date
120+
)
121+
return ISOFullDate(
122+
year: components.year,
123+
month: components.month,
124+
day: components.day
125+
)
126+
}
127+
128+
/**
129+
Converts a ISO-8601 full-date string to an ISOFullDate.
130+
131+
- parameter string: The ISO-8601 full-date format string to convert.
132+
133+
- returns: An ISOFullDate constructed from the string.
134+
*/
135+
public static func from(string string: String) -> ISOFullDate? {
136+
let components = string
137+
.characters
138+
.split("-")
139+
.map(String.init)
140+
.flatMap { Int($0) }
141+
guard components.count == 3 else { return nil }
142+
143+
return ISOFullDate(
144+
year: components[0],
145+
month: components[1],
146+
day: components[2]
147+
)
148+
}
149+
150+
/**
151+
Converts the receiver to an NSDate, in the default time zone.
152+
153+
- returns: An NSDate from the components of the receiver, in the default time zone.
154+
*/
155+
public func toDate() -> NSDate? {
156+
let components = NSDateComponents()
157+
components.year = year
158+
components.month = month
159+
components.day = day
160+
components.timeZone = NSTimeZone.defaultTimeZone()
161+
let calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian)
162+
return calendar?.dateFromComponents(components)
163+
}
164+
165+
// MARK: CustomStringConvertible
166+
167+
public var description: String {
168+
return "\(year)-\(month)-\(day)"
169+
}
170+
171+
}
172+
173+
extension ISOFullDate: JSONEncodable {
174+
public func encodeToJSON() -> AnyObject {
175+
return "\(year)-\(month)-\(day)"
176+
}
177+
}
178+
86179

samples/client/petstore-security-test/swift/SwaggerClient/Classes/Swaggers/Models.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,16 @@ class Decoders {
140140
return NSDate(timeIntervalSince1970: Double(sourceInt / 1000) )
141141
}
142142
fatalError("formatter failed to parse \(source)")
143-
}
143+
}
144+
145+
// Decoder for ISOFullDate
146+
Decoders.addDecoder(clazz: ISOFullDate.self, decoder: { (source: AnyObject) -> ISOFullDate in
147+
if let string = source as? String,
148+
let isoDate = ISOFullDate.from(string: string) {
149+
return isoDate
150+
}
151+
fatalError("formatter failed to parse \(source)")
152+
})
144153

145154
// Decoder for [Return]
146155
Decoders.addDecoder(clazz: [Return].self) { (source: AnyObject) -> [Return] in

samples/client/petstore/swift3/default/PetstoreClient/Classes/Swaggers/AlamofireImplementations.swift

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,56 @@ open class AlamofireRequestBuilder<T>: RequestBuilder<T> {
207207
nil
208208
)
209209
})
210+
case is URL.Type:
211+
validatedRequest.responseData(completionHandler: { (dataResponse) in
212+
cleanupRequest()
213+
214+
do {
215+
216+
guard !dataResponse.result.isFailure else {
217+
throw DownloadException.responseFailed
218+
}
219+
220+
guard let data = dataResponse.data else {
221+
throw DownloadException.responseDataMissing
222+
}
223+
224+
guard let request = request.request else {
225+
throw DownloadException.requestMissing
226+
}
227+
228+
let fileManager = FileManager.default
229+
let urlRequest = try request.asURLRequest()
230+
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
231+
let requestURL = try self.getURL(from: urlRequest)
232+
233+
var requestPath = try self.getPath(from: requestURL)
234+
235+
if let headerFileName = self.getFileName(fromContentDisposition: dataResponse.response?.allHeaderFields["Content-Disposition"] as? String) {
236+
requestPath = requestPath.appending("/\(headerFileName)")
237+
}
238+
239+
let filePath = documentsDirectory.appendingPathComponent(requestPath)
240+
let directoryPath = filePath.deletingLastPathComponent().path
241+
242+
try fileManager.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil)
243+
try data.write(to: filePath, options: .atomic)
244+
245+
completion(
246+
Response(
247+
response: dataResponse.response!,
248+
body: (filePath as! T)
249+
),
250+
nil
251+
)
252+
253+
} catch let requestParserError as DownloadException {
254+
completion(nil, ErrorResponse.HttpError(statusCode: 400, data: dataResponse.data, error: requestParserError))
255+
} catch let error {
256+
completion(nil, ErrorResponse.HttpError(statusCode: 400, data: dataResponse.data, error: error))
257+
}
258+
return
259+
})
210260
default:
211261
validatedRequest.responseJSON(options: .allowFragments) { response in
212262
cleanupRequest()
@@ -253,4 +303,63 @@ open class AlamofireRequestBuilder<T>: RequestBuilder<T> {
253303
}
254304
return httpHeaders
255305
}
256-
}
306+
307+
fileprivate func getFileName(fromContentDisposition contentDisposition : String?) -> String? {
308+
309+
guard let contentDisposition = contentDisposition else {
310+
return nil
311+
}
312+
313+
let items = contentDisposition.components(separatedBy: ";")
314+
315+
var filename : String? = nil
316+
317+
for contentItem in items {
318+
319+
let filenameKey = "filename="
320+
guard let range = contentItem.range(of: filenameKey) else {
321+
break
322+
}
323+
324+
filename = contentItem
325+
return filename?
326+
.replacingCharacters(in: range, with:"")
327+
.replacingOccurrences(of: "\"", with: "")
328+
.trimmingCharacters(in: .whitespacesAndNewlines)
329+
}
330+
331+
return filename
332+
333+
}
334+
335+
fileprivate func getPath(from url : URL) throws -> String {
336+
337+
guard var path = NSURLComponents(url: url, resolvingAgainstBaseURL: true)?.path else {
338+
throw DownloadException.requestMissingPath
339+
}
340+
341+
if path.hasPrefix("/") {
342+
path.remove(at: path.startIndex)
343+
}
344+
345+
return path
346+
347+
}
348+
349+
fileprivate func getURL(from urlRequest : URLRequest) throws -> URL {
350+
351+
guard let url = urlRequest.url else {
352+
throw DownloadException.requestMissingURL
353+
}
354+
355+
return url
356+
}
357+
358+
fileprivate enum DownloadException : Error {
359+
case responseDataMissing
360+
case responseFailed
361+
case requestMissing
362+
case requestMissingPath
363+
case requestMissingURL
364+
}
365+
}

0 commit comments

Comments
 (0)