Skip to content

Commit

Permalink
✨ Typed Throws
Browse files Browse the repository at this point in the history
  • Loading branch information
MadsBogeskov committed Nov 21, 2024
1 parent 3da4e37 commit 56ea2c4
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 72 deletions.
9 changes: 0 additions & 9 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
{
"pins" : [
{
"identity" : "swaggerswiftml",
"kind" : "remoteSourceControl",
"location" : "https://github.com/lunarway/SwaggerSwiftML",
"state" : {
"revision" : "a973b5151c225defbe13663108d4b11c07cd3064",
"version" : "1.0.19"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
Expand Down
5 changes: 3 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ let package = Package(
platforms: [.macOS(.v12)],
products: [.executable(name: "swaggerswift", targets: ["SwaggerSwift"])],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", .upToNextMajor(from: "1.1.1")),
.package(url: "https://github.com/lunarway/SwaggerSwiftML", from: "1.0.19")
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.1.1"),
// .package(url: "https://github.com/lunarway/SwaggerSwiftML", from: "1.0.19")
.package(path: "../swaggerswiftml")
],
targets: [
.executableTarget(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)!
Expand All @@ -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()
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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))
}
}
}
Expand All @@ -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: ", ")))
}
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -58,40 +57,41 @@ 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)
}
"""
}
case .void(let statusCode, let resultIsEnum):
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, _):
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -171,18 +171,17 @@ case \(statusCode.rawValue):
]
)
return .failure(.requestFailed(error: error))
throw \(errorType).requestFailed(error: error)
}
"""
case .array(let statusCode, let resultIsEnum, let innerType):
return """
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):
Expand All @@ -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,
Expand All @@ -206,7 +205,7 @@ case \(statusCode.rawValue):
]
)
return .failure(.requestFailed(error: error))
throw \(errorType).requestFailed(error: error)
}
"""
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,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)
Expand Down
29 changes: 18 additions & 11 deletions Sources/SwaggerSwiftCore/Generator/Generator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public struct Generator {

typealias APISpec = (APIDefinition, [ModelDefinition])

let apiSpecs: [APISpec] = try await withThrowingTaskGroup(of: APISpec?.self) { group in
let apiSpecs: [APISpec] = await withThrowingTaskGroup(of: APISpec?.self) { group in
var apiSpecs = [APISpec]()

for service in services {
Expand Down Expand Up @@ -87,8 +87,6 @@ public struct Generator {
} else {
log("Failed to download Swagger: \(error.localizedDescription)", error: true)
}

throw NSError(domain: "", code: 0)
}

return apiSpecs
Expand Down Expand Up @@ -216,13 +214,22 @@ public struct Generator {
return data
}

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,
serviceName: serviceName,
branch: branch,
swaggerPath: swaggerPath,
urlSession: urlSession)
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,
serviceName: serviceName,
branch: branch,
swaggerPath: swaggerPath,
urlSession: urlSession
)

guard let stringValue = String(data: data, encoding: .utf8) else {
throw FetchSwaggerError.invalidResponse(serviceName: serviceName)
Expand Down Expand Up @@ -367,7 +374,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)
Expand Down
Loading

0 comments on commit 56ea2c4

Please sign in to comment.