diff --git a/Sources/SwaggerSwiftCore/API Request Factory/Models/APIRequest+Swiftable.swift b/Sources/SwaggerSwiftCore/API Request Factory/Models/APIRequest+Swiftable.swift index 5f5d102..82eb142 100644 --- a/Sources/SwaggerSwiftCore/API Request Factory/Models/APIRequest+Swiftable.swift +++ b/Sources/SwaggerSwiftCore/API Request Factory/Models/APIRequest+Swiftable.swift @@ -19,10 +19,6 @@ extension APIRequest { }.joined(separator: ", ") } - private var functionReturnType: String { - returnType.typeName.toString(required: true) - } - private func makeRequestFunction(serviceName: String?, swaggerFile: SwaggerFile) -> String { let servicePath = self.servicePath.split(separator: "/") .map { @@ -155,11 +151,11 @@ if let \(($0.swiftyName)) = \(headersName).\($0.swiftyName) { .addNewlinesIfNonEmpty() let responseTypes = self.responseTypes - .map { $0.print(apiName: serviceName ?? "") } + .map { $0.print(apiName: serviceName ?? "", errorType: returnType.failureType.toString(required: true)) } .joined(separator: "\n") return """ -private func _\(functionName)(\(functionArguments)) async -> \(functionReturnType) { +private func _\(functionName)(\(functionArguments)) async throws(\(returnType.failureType.toString(required: true))) -> \(returnType.successType.toString(required: true)) { let endpointUrl = baseUrlProvider().appendingPathComponent("\(servicePath)") \(queries.count > 0 ? "var" : "let") urlComponents = URLComponents(url: endpointUrl, resolvingAgainstBaseURL: true)! @@ -175,20 +171,24 @@ private func _\(functionName)(\(functionArguments)) async -> \(functionReturnTyp do { (data, response) = try await urlSession().\(urlSessionMethodName) } catch { - return .failure(.requestFailed(error: error)) + throw .requestFailed(error: error) } if let interceptor { do { - try await interceptor.networkDidPerformRequest(urlRequest: request, urlResponse: response, data: data, error: nil) + try await interceptor.networkDidPerformRequest( + urlRequest: request, + urlResponse: response, + data: data, + error: nil + ) } catch { - return .failure(.requestFailed(error: error)) + throw .requestFailed(error: error) } } guard let httpResponse = response as? HTTPURLResponse else { - let error = NSError(domain: "\(serviceName ?? "Generic")", code: 0, userInfo: [NSLocalizedDescriptionKey: "Returned response object wasnt a HTTP URL Response as expected, but was instead a \\(String(describing: response))"]) - return .failure(.requestFailed(error: error)) + fatalError("The response must be a URL response") } let decoder = JSONDecoder() @@ -199,7 +199,7 @@ private func _\(functionName)(\(functionArguments)) async -> \(functionReturnTyp default: let result = String(data: data, encoding: .utf8) ?? "" let error = NSError(domain: "\(serviceName ?? "Generic")", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: result]) - return .failure(.requestFailed(error: error)) + throw .requestFailed(error: error) } } @@ -238,10 +238,31 @@ private func _\(functionName)(\(functionArguments)) async -> \(functionReturnTyp body += "\n" body += """ - \(accessControl) func \(functionName)(\(functionArguments)\(functionArguments.isEmpty ? "" : ", ")completion: @Sendable @escaping (\(functionReturnType)) -> Void = { _ in }) { + \(accessControl) func \(functionName)(\(functionArguments)\(functionArguments.isEmpty ? "" : ", ")completion: @Sendable @escaping (Result<\(returnType.successType.toString(required: true)), \(returnType.failureType.toString(required: true))>) -> Void = { _ in }) { _Concurrency.Task { - let result = await _\(functionName)(\(parameters.map { "\($0.name.variableNameFormatted): \($0.name.variableNameFormatted)" }.joined(separator: ", "))) - completion(result) + do { + +""" + + if returnType.successType.toString(required: true) == "Void" { + body += """ + try await _\(functionName)(\(parameters.map { "\($0.name.variableNameFormatted): \($0.name.variableNameFormatted)" }.joined(separator: ", "))) + completion(.success(())) + + """ + } else { + body += """ + let result = try await _\(functionName)(\(parameters.map { "\($0.name.variableNameFormatted): \($0.name.variableNameFormatted)" }.joined(separator: ", "))) + completion(.success(result)) + + """ + } + + body += """ + } catch let error { + let error = error as! \(returnType.failureType.toString(required: true)) + completion(.failure(error)) + } } } @@ -256,11 +277,14 @@ private func _\(functionName)(\(functionArguments)) async -> \(functionReturnTyp body += "\n" + if returnType.successType.toString(required: true) != "Void" { + body += "@discardableResult\n" + } + body += """ - @discardableResult - \(accessControl) func \(functionName)(\(functionArguments)) async -> \(functionReturnType) { - await _\(functionName)(\(parameters.map { "\($0.name.variableNameFormatted): \($0.name.variableNameFormatted)" }.joined(separator: ", "))) + \(accessControl) func \(functionName)(\(functionArguments)) async throws(\(returnType.failureType.toString(required: true))) -> \(returnType.successType.toString(required: true)) { + try await _\(functionName)(\(parameters.map { "\($0.name.variableNameFormatted): \($0.name.variableNameFormatted)" }.joined(separator: ", "))) } """ diff --git a/Sources/SwaggerSwiftCore/API Request Factory/Models/APIRequestResponseType.swift b/Sources/SwaggerSwiftCore/API Request Factory/Models/APIRequestResponseType.swift index 33a1a60..e154ec9 100644 --- a/Sources/SwaggerSwiftCore/API Request Factory/Models/APIRequestResponseType.swift +++ b/Sources/SwaggerSwiftCore/API Request Factory/Models/APIRequestResponseType.swift @@ -36,17 +36,16 @@ enum APIRequestResponseType { } } - func print(apiName: String) -> String { + func print(apiName: String, errorType: String) -> String { let failed = !statusCode.isSuccess - let swiftResult = failed ? "failure" : "success" let resultType: (String, Bool) -> String = { resultType, enumBased -> String in let resultBlock = resultType.count == 0 ? "" : "(\(resultType))" if failed { if enumBased { - return ".backendError(error: .\(statusCode.name)\(resultBlock))" + return "\(errorType).backendError(error: .\(statusCode.name)\(resultBlock))" } else { - return ".backendError(error: \(resultType))" + return "\(errorType).backendError(error: \(resultType))" } } else { return enumBased ? ".\(statusCode.name)\(resultBlock)" : resultType @@ -58,27 +57,28 @@ enum APIRequestResponseType { return """ case \(statusCode.rawValue): let result = String(data: data, encoding: .utf8) ?? "" - return .\(swiftResult)(\(resultType("result", resultIsEnum))) + \(failed ? "throw" : "return") \(resultType("result", resultIsEnum)) """ case .object(let statusCode, let resultIsEnum, let responseType): if responseType == "Data" { return """ case \(statusCode.rawValue): - return .\(swiftResult)(data) + \(failed ? "throw" : "return") data """ } else { return """ case \(statusCode.rawValue): do { let result = try decoder.decode(\(responseType.modelNamed).self, from: data) - - return .\(swiftResult)(\(resultType("result", resultIsEnum))) + \(failed ? "throw" : "return") \(resultType("result", resultIsEnum)) } catch let error { - interceptor?.networkFailedToParseObject(urlRequest: request, - urlResponse: response, - data: data, - error: error) - return .failure(.requestFailed(error: error)) + interceptor?.networkFailedToParseObject( + urlRequest: request, + urlResponse: response, + data: data, + error: error + ) + throw \(errorType).requestFailed(error: error) } """ } @@ -86,12 +86,12 @@ case \(statusCode.rawValue): if resultIsEnum { return """ case \(statusCode.rawValue): - return .\(swiftResult)(\(resultType("", resultIsEnum))) + \(failed ? "throw" : "return") \(resultType("", resultIsEnum)) """ } else { return """ case \(statusCode.rawValue): - return .\(swiftResult)(\(resultType("()", resultIsEnum))) + \(failed ? "throw" : "return") \(resultType("()", resultIsEnum)) """ } case .int(let statusCode, _): @@ -107,14 +107,14 @@ case \(statusCode.rawValue): ] ) - return .failure(.requestFailed(error: error)) + throw \(errorType).requestFailed(error: error) } """ case .double(let statusCode, _): return """ case \(statusCode.rawValue): if let stringValue = String(data: data, encoding: .utf8), let value = Double(stringValue) { - return .success(value) + return value } else { let error = NSError(domain: "\(apiName)", code: 0, @@ -123,14 +123,14 @@ case \(statusCode.rawValue): ] ) - return .failure(.requestFailed(error: error)) + throw \(errorType).requestFailed(error: error) } """ case .float(let statusCode, _): return """ case \(statusCode.rawValue): if let stringValue = String(data: data, encoding: .utf8), let value = Float(stringValue) { - return .success(value) + return value } else { let error = NSError(domain: "\(apiName)", code: 0, @@ -139,14 +139,14 @@ case \(statusCode.rawValue): ] ) - return .failure(.requestFailed(error: error)) + throw \(errorType).requestFailed(error: error) } """ case .boolean(let statusCode, _): return """ case \(statusCode.rawValue): if let stringValue = String(data: data, encoding: .utf8), let value = Bool(stringValue) { - return .success(value) + return value } else { let error = NSError(domain: "\(apiName)", code: 0, @@ -155,14 +155,14 @@ case \(statusCode.rawValue): ] ) - return .failure(.requestFailed(error: error)) + throw \(errorType).requestFailed(error: error) } """ case .int64(let statusCode, _): return """ case \(statusCode.rawValue): if let stringValue = String(data: data, encoding: .utf8), let value = Int64(stringValue) { - return .success(value) + return value } else { let error = NSError(domain: "\(apiName)", code: 0, @@ -171,7 +171,7 @@ case \(statusCode.rawValue): ] ) - return .failure(.requestFailed(error: error)) + throw \(errorType).requestFailed(error: error) } """ case .array(let statusCode, let resultIsEnum, let innerType): @@ -179,10 +179,9 @@ case \(statusCode.rawValue): case \(statusCode.rawValue): do { let result = try decoder.decode([\(innerType)].self, from: data) - - return .\(swiftResult)(\(resultType("result", resultIsEnum))) + \(failed ? "throw" : "return") \(resultType("result", resultIsEnum)) } catch let error { - return .failure(.requestFailed(error: error)) + throw \(errorType).requestFailed(error: error)) } """ case .enumeration(let statusCode, let resultIsEnum, let responseType): @@ -197,7 +196,7 @@ case \(statusCode.rawValue): .trimmingCharacters(in: CharacterSet(charactersIn: "\\"")) let enumValue = \(responseType)(rawValue: cleanedStringValue) - return .\(swiftResult)(\(resultType("enumValue", resultIsEnum))) + \(failed ? "throw" : "return") \(resultType("enumValue", resultIsEnum)) } else { let error = NSError(domain: "\(apiName)", code: 0, @@ -206,7 +205,7 @@ case \(statusCode.rawValue): ] ) - return .failure(.requestFailed(error: error)) + throw \(errorType).requestFailed(error: error) } """ } diff --git a/Sources/SwaggerSwiftCore/API Request Factory/RequestParameterFactory.swift b/Sources/SwaggerSwiftCore/API Request Factory/RequestParameterFactory.swift index 27e7d96..b4bc9a4 100644 --- a/Sources/SwaggerSwiftCore/API Request Factory/RequestParameterFactory.swift +++ b/Sources/SwaggerSwiftCore/API Request Factory/RequestParameterFactory.swift @@ -89,7 +89,8 @@ public struct RequestParameterFactory { let returnType = ReturnType( description: "The completion handler of the function returns as soon as the request completes", - typeName: .object(typeName: "Result<\(successTypeName), ServiceError<\(failureTypeName)>>") + successType: .object(typeName: successTypeName), + failureType: .object(typeName: "ServiceError<\(failureTypeName)>") ) return (resolvedParameters, resolvedModelDefinitions, returnType) diff --git a/Sources/SwaggerSwiftCore/Generator/Generator.swift b/Sources/SwaggerSwiftCore/Generator/Generator.swift index c8bbadb..965e819 100644 --- a/Sources/SwaggerSwiftCore/Generator/Generator.swift +++ b/Sources/SwaggerSwiftCore/Generator/Generator.swift @@ -46,34 +46,24 @@ public struct Generator { typealias APISpec = (APIDefinition, [ModelDefinition]) - let apiSpecs: [APISpec] = try await withThrowingTaskGroup(of: APISpec?.self) { group in - var apiSpecs = [APISpec]() - - for service in services { - group.addTask { - async let swagger = try await downloadSwagger( - githubToken: githubToken, - organisation: swaggerFile.organisation, - serviceName: service.key, - branch: service.value.branch ?? "master", - swaggerPath: service.value.path ?? swaggerFile.path - ) - - let apiSpec = try await APIFactory( - apiRequestFactory: apiRequestFactory, - modelTypeResolver: modelTypeResolver - ).generate(for: swagger, withSwaggerFile: swaggerFile) - - return apiSpec - } - } + var apiSpecs = [APISpec]() + for service in services { do { - for try await spec in group { - if let spec = spec { - apiSpecs.append(spec) - } - } + async let swagger = try await downloadSwagger( + githubToken: githubToken, + organisation: swaggerFile.organisation, + serviceName: service.key, + branch: service.value.branch ?? "master", + swaggerPath: service.value.path ?? swaggerFile.path + ) + + let apiSpec = try await APIFactory( + apiRequestFactory: apiRequestFactory, + modelTypeResolver: modelTypeResolver + ).generate(for: swagger, withSwaggerFile: swaggerFile) + + apiSpecs.append(apiSpec) } catch { if let error = error as? FetchSwaggerError { error.logError() @@ -87,11 +77,7 @@ public struct Generator { } else { log("Failed to download Swagger: \(error.localizedDescription)", error: true) } - - throw NSError(domain: "", code: 0) } - - return apiSpecs } let destination = URL(fileURLWithPath: swaggerFilePath).deletingLastPathComponent().appendingPathComponent(swaggerFile.destination).path @@ -216,7 +202,14 @@ public struct Generator { return data } - private func downloadSwagger(githubToken: String, organisation: String, serviceName: String, branch: String, swaggerPath: String, urlSession: URLSession = .shared) async throws -> Swagger { + private func downloadSwagger( + githubToken: String, + organisation: String, + serviceName: String, + branch: String, + swaggerPath: String, + urlSession: URLSession = .shared + ) async throws -> Swagger { let data = try await download( githubToken: githubToken, organisation: organisation, @@ -369,7 +362,7 @@ public struct Generator { extension String { func write(toFile path: String, addHeader: Bool = true) throws { if addHeader { - let file = "// Autogenerated with ❤️ by SwaggerSwift\n// Do not modify this file manually 🙅\n// swiftlint:disable all\n\n" + self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + "\n\n// swiftlint:enable all\n" + let file = "// Autogenerated with ❤️ by SwaggerSwift\n// Do not modify this file manually 🙅\n\n" + self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + "\n" try file.write(toFile: path, atomically: true, encoding: .utf8) } else { try self.write(toFile: path, atomically: true, encoding: .utf8) diff --git a/Sources/SwaggerSwiftCore/Models/ReturnType.swift b/Sources/SwaggerSwiftCore/Models/ReturnType.swift index 7d31653..0d0c883 100644 --- a/Sources/SwaggerSwiftCore/Models/ReturnType.swift +++ b/Sources/SwaggerSwiftCore/Models/ReturnType.swift @@ -2,5 +2,6 @@ import Foundation struct ReturnType { let description: String - let typeName: TypeType + let successType: TypeType + let failureType: TypeType }