diff --git a/Specs/BinaryUpload/generated/Swift/BinaryUpload.podspec b/Specs/BinaryUpload/generated/Swift/BinaryUpload.podspec new file mode 100644 index 000000000..180cab20d --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/BinaryUpload.podspec @@ -0,0 +1,14 @@ +Pod::Spec.new do |s| + s.source_files = '*.swift' + s.name = 'BinaryUpload' + s.authors = 'Yonas Kolb' + s.summary = 'A generated API' + s.version = '1.0' + s.homepage = 'https://github.com/yonaskolb/SwagGen' + s.source = { :git => 'git@github.com:https://github.com/yonaskolb/SwagGen.git' } + s.ios.deployment_target = '10.0' + s.tvos.deployment_target = '10.0' + s.osx.deployment_target = '10.12' + s.source_files = 'Sources/**/*.swift' + s.dependency 'Alamofire', '~> 5.4.4' +end diff --git a/Specs/BinaryUpload/generated/Swift/Cartfile b/Specs/BinaryUpload/generated/Swift/Cartfile new file mode 100644 index 000000000..e60e02fd8 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Cartfile @@ -0,0 +1,2 @@ + +github "Alamofire/Alamofire" ~> 5.4.4 diff --git a/Specs/BinaryUpload/generated/Swift/Info.plist b/Specs/BinaryUpload/generated/Swift/Info.plist new file mode 100644 index 000000000..1007fd9dd --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/Specs/BinaryUpload/generated/Swift/Package.swift b/Specs/BinaryUpload/generated/Swift/Package.swift new file mode 100644 index 000000000..1ee04ac95 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version:5.2 + +import PackageDescription + +let package = Package( + name: "BinaryUpload", + platforms: [ + .macOS(.v10_12), + .iOS(.v10), + .tvOS(.v10), + .watchOS(.v3) + ], + products: [ + .library(name: "BinaryUpload", targets: ["BinaryUpload"]) + ], + dependencies: [ + .package(url: "https://github.com/Alamofire/Alamofire.git", .exact("5.4.4")), + ], + targets: [ + .target(name: "BinaryUpload", dependencies: [ + "Alamofire", + ], path: "Sources") + ] +) diff --git a/Specs/BinaryUpload/generated/Swift/README.md b/Specs/BinaryUpload/generated/Swift/README.md new file mode 100644 index 000000000..cde823532 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/README.md @@ -0,0 +1,158 @@ +# BinaryUpload + +This is an api generated from a OpenAPI 3.0 spec with [SwagGen](https://github.com/yonaskolb/SwagGen) + +## Operation + +Each operation lives under the `BinaryUpload` namespace and within an optional tag: `BinaryUpload(.tagName).operationId`. If an operation doesn't have an operationId one will be generated from the path and method. + +Each operation has a nested `Request` and a `Response`, as well as a static `service` property + +#### Service + +This is the struct that contains the static information about an operation including it's id, tag, method, pre-modified path, and authorization requirements. It has a generic `ResponseType` type which maps to the `Response` type. +You shouldn't really need to interact with this service type. + +#### Request + +Each request is a subclass of `APIRequest` and has an `init` with a body param if it has a body, and a `options` struct for other url and path parameters. There is also a convenience init for passing parameters directly. +The `options` and `body` structs are both mutable so they can be modified before actually sending the request. + +#### Response + +The response is an enum of all the possible responses the request can return. it also contains getters for the `statusCode`, whether it was `successful`, and the actual decoded optional `success` response. If the operation only has one type of failure type there is also an optional `failure` type. + +## Model +Models that are sent and returned from the API are mutable classes. Each model is `Equatable` and `Codable`. + +`Required` properties are non optional and non-required are optional + +All properties can be passed into the initializer, with `required` properties being mandatory. + +If a model has `additionalProperties` it will have a subscript to access these by string + +## APIClient +The `APIClient` is used to encode, authorize, send, monitor, and decode the requests. There is a `APIClient.default` that uses the default `baseURL` otherwise a custom one can be initialized: + +```swift +public init(baseURL: String, sessionManager: SessionManager = .default, defaultHeaders: [String: String] = [:], behaviours: [RequestBehaviour] = []) +``` + +#### APIClient properties + +- `baseURL`: The base url that every request `path` will be appended to +- `behaviours`: A list of [Request Behaviours](#requestbehaviour) to add to every request +- `sessionManager`: An `Alamofire.SessionManager` that can be customized +- `defaultHeaders`: Headers that will be applied to every request +- `decodingQueue`: The `DispatchQueue` to decode responses on + +#### Making a request +To make a request first initialize a [Request](#request) and then pass it to `makeRequest`. The `complete` closure will be called with an `APIResponse` + +```swift +func makeRequest(_ request: APIRequest, behaviours: [RequestBehaviour] = [], queue: DispatchQueue = DispatchQueue.main, complete: @escaping (APIResponse) -> Void) -> Request? { +``` + +Example request (that is not neccessarily in this api): + +```swift + +let getUserRequest = BinaryUpload.User.GetUser.Request(id: 123) +let apiClient = APIClient.default + +apiClient.makeRequest(getUserRequest) { apiResponse in + switch apiResponse { + case .result(let apiResponseValue): + if let user = apiResponseValue.success { + print("GetUser returned user \(user)") + } else { + print("GetUser returned \(apiResponseValue)") + } + case .error(let apiError): + print("GetUser failed with \(apiError)") + } +} +``` + +Each [Request](#request) also has a `makeRequest` convenience function that uses `BinaryUpload.shared`. + +#### APIResponse +The `APIResponse` that gets passed to the completion closure contains the following properties: + +- `request`: The original request +- `result`: A `Result` type either containing an `APIClientError` or the [Response](#response) of the request +- `urlRequest`: The `URLRequest` used to send the request +- `urlResponse`: The `HTTPURLResponse` that was returned by the request +- `data`: The `Data` returned by the request. +- `timeline`: The `Alamofire.Timeline` of the request which contains timing information. + +#### Encoding and Decoding +Only JSON requests and responses are supported. These are encoded and decoded by `JSONEncoder` and `JSONDecoder` respectively, using Swift's `Codable` apis. +There are some options to control how invalid JSON is handled when decoding and these are available as static properties on `BinaryUpload`: + +- `safeOptionalDecoding`: Whether to discard any errors when decoding optional properties. Defaults to `true`. +- `safeArrayDecoding`: Whether to remove invalid elements instead of throwing when decoding arrays. Defaults to `true`. + +Dates are encoded and decoded differently according to the swagger date format. They use different `DateFormatter`'s that you can set. +- `date-time` + - `DateTime.dateEncodingFormatter`: defaults to `yyyy-MM-dd'T'HH:mm:ss.Z` + - `DateTime.dateDecodingFormatters`: an array of date formatters. The first one to decode successfully will be used +- `date` + - `DateDay.dateFormatter`: defaults to `yyyy-MM-dd` + +#### APIClientError +This is error enum that `APIResponse.result` may contain: + +```swift +public enum APIClientError: Error { + case unexpectedStatusCode(statusCode: Int, data: Data) + case decodingError(DecodingError) + case requestEncodingError(String) + case validationError(String) + case networkError(Error) + case unknownError(Error) +} +``` + +#### RequestBehaviour +Request behaviours are used to modify, authorize, monitor or respond to requests. They can be added to the `APIClient.behaviours` for all requests, or they can passed into `makeRequest` for just that single request. + +`RequestBehaviour` is a protocol you can conform to with each function being optional. As the behaviours must work across multiple different request types, they only have access to a typed erased `AnyRequest`. + +```swift +public protocol RequestBehaviour { + + /// runs first and allows the requests to be modified. If modifying asynchronously use validate + func modifyRequest(request: AnyRequest, urlRequest: URLRequest) -> URLRequest + + /// validates and modifies the request. complete must be called with either .success or .fail + func validate(request: AnyRequest, urlRequest: URLRequest, complete: @escaping (RequestValidationResult) -> Void) + + /// called before request is sent + func beforeSend(request: AnyRequest) + + /// called when request successfuly returns a 200 range response + func onSuccess(request: AnyRequest, result: Any) + + /// called when request fails with an error. This will not be called if the request returns a known response even if the a status code is out of the 200 range + func onFailure(request: AnyRequest, error: APIClientError) + + /// called if the request recieves a network response. This is not called if request fails validation or encoding + func onResponse(request: AnyRequest, response: AnyResponse) +} +``` + +### Authorization +Each request has an optional `securityRequirement`. You can create a `RequestBehaviour` that checks this requirement and adds some form of authorization (usually via headers) in `validate` or `modifyRequest`. An alternative way is to set the `APIClient.defaultHeaders` which applies to all requests. + +#### Reactive and Promises +To add support for a specific asynchronous library, just add an extension on `APIClient` and add a function that wraps the `makeRequest` function and converts from a closure based syntax to returning the object of choice (stream, future...ect) + +## Models + +- **UploadModel** + +## Requests + +- **BinaryUpload.PostUser**: POST `/withoutmodel` +- **BinaryUpload.PostWithType**: POST `/withmodel` diff --git a/Specs/BinaryUpload/generated/Swift/Sources/API.swift b/Specs/BinaryUpload/generated/Swift/Sources/API.swift new file mode 100644 index 000000000..fd532317a --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/API.swift @@ -0,0 +1,28 @@ +// +// Generated by SwagGen +// https://github.com/yonaskolb/SwagGen +// + +import Foundation + +public struct BinaryUpload { + + /// Whether to discard any errors when decoding optional properties + public static var safeOptionalDecoding = false + + /// Whether to remove invalid elements instead of throwing when decoding arrays + public static var safeArrayDecoding = false + + /// Used to encode Dates when uses as string params + public static var dateEncodingFormatter = DateFormatter(formatString: "yyyy-MM-dd'T'HH:mm:ssZZZZZ", + locale: Locale(identifier: "en_US_POSIX"), + calendar: Calendar(identifier: .gregorian)) + + public static let version = "1.0" + + + public enum Server { + + public static let main = "http://localhost:3000" + } +} diff --git a/Specs/BinaryUpload/generated/Swift/Sources/APIClient.swift b/Specs/BinaryUpload/generated/Swift/Sources/APIClient.swift new file mode 100644 index 000000000..b2135c1b5 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/APIClient.swift @@ -0,0 +1,253 @@ +// +// Generated by SwagGen +// https://github.com/yonaskolb/SwagGen +// + +import Foundation +import Alamofire + +/// Manages and sends APIRequests +public class APIClient { + + public static var `default` = APIClient(baseURL: BinaryUpload.Server.main) + + /// A list of RequestBehaviours that can be used to monitor and alter all requests + public var behaviours: [RequestBehaviour] = [] + + /// The base url prepended before every request path + public var baseURL: String + + /// The Alamofire SessionManager used for each request + public var sessionManager: Session + + /// These headers will get added to every request + public var defaultHeaders: [String: String] + + public var jsonDecoder = JSONDecoder() + public var jsonEncoder = JSONEncoder() + + public var decodingQueue = DispatchQueue(label: "apiClient", qos: .utility, attributes: .concurrent) + + public init(baseURL: String, sessionManager: Session = .default, defaultHeaders: [String: String] = [:], behaviours: [RequestBehaviour] = []) { + self.baseURL = baseURL + self.sessionManager = sessionManager + self.behaviours = behaviours + self.defaultHeaders = defaultHeaders + jsonDecoder.dateDecodingStrategy = .custom(dateDecoder) + jsonEncoder.dateEncodingStrategy = .formatted(BinaryUpload.dateEncodingFormatter) + } + + /// Makes a network request + /// + /// - Parameters: + /// - request: The API request to make + /// - behaviours: A list of behaviours that will be run for this request. Merged with APIClient.behaviours + /// - completionQueue: The queue that complete will be called on + /// - complete: A closure that gets passed the APIResponse + /// - Returns: A cancellable request. Not that cancellation will only work after any validation RequestBehaviours have run + @discardableResult + public func makeRequest(_ request: APIRequest, behaviours: [RequestBehaviour] = [], completionQueue: DispatchQueue = DispatchQueue.main, complete: @escaping (APIResponse) -> Void) -> CancellableRequest? { + // create composite behaviour to make it easy to call functions on array of behaviours + let requestBehaviour = RequestBehaviourGroup(request: request, behaviours: self.behaviours + behaviours) + + // create the url request from the request + var urlRequest: URLRequest + do { + guard let safeURL = URL(string: baseURL) else { + throw InternalError.malformedURL + } + + urlRequest = try request.createURLRequest(baseURL: safeURL, encoder: jsonEncoder) + } catch { + let error = APIClientError.requestEncodingError(error) + requestBehaviour.onFailure(error: error) + let response = APIResponse(request: request, result: .failure(error)) + complete(response) + return nil + } + + // add the default headers + if urlRequest.allHTTPHeaderFields == nil { + urlRequest.allHTTPHeaderFields = [:] + } + for (key, value) in defaultHeaders { + urlRequest.allHTTPHeaderFields?[key] = value + } + + urlRequest = requestBehaviour.modifyRequest(urlRequest) + + let cancellableRequest = CancellableRequest(request: request.asAny()) + + requestBehaviour.validate(urlRequest) { result in + switch result { + case .success(let urlRequest): + let networkRequest = self.makeNetworkRequest(request: request, urlRequest: urlRequest, requestBehaviour: requestBehaviour, completionQueue: completionQueue, complete: complete) + cancellableRequest.networkRequest = networkRequest + case .failure(let error): + let error = APIClientError.validationError(error) + let response = APIResponse(request: request, result: .failure(error), urlRequest: urlRequest) + requestBehaviour.onFailure(error: error) + complete(response) + } + } + return cancellableRequest + } + + private func makeNetworkRequest(request: APIRequest, urlRequest: URLRequest, requestBehaviour: RequestBehaviourGroup, completionQueue: DispatchQueue, complete: @escaping (APIResponse) -> Void) -> Request { + requestBehaviour.beforeSend() + + if request.service.isUpload { + return sessionManager.upload( + multipartFormData: { multipartFormData in + for (name, value) in request.formParameters { + if let file = value as? UploadFile { + switch file.type { + case let .url(url): + if let fileName = file.fileName, let mimeType = file.mimeType { + multipartFormData.append(url, withName: name, fileName: fileName, mimeType: mimeType) + } else { + multipartFormData.append(url, withName: name) + } + case let .data(data): + if let fileName = file.fileName, let mimeType = file.mimeType { + multipartFormData.append(data, withName: name, fileName: fileName, mimeType: mimeType) + } else { + multipartFormData.append(data, withName: name) + } + } + } else if let url = value as? URL { + multipartFormData.append(url, withName: name) + } else if let data = value as? Data { + multipartFormData.append(data, withName: name) + } else if let string = value as? String { + multipartFormData.append(Data(string.utf8), withName: name) + } + } + }, + with: urlRequest + ) + .responseData(queue: decodingQueue) { dataResponse in + self.handleResponse(request: request, requestBehaviour: requestBehaviour, dataResponse: dataResponse, completionQueue: completionQueue, complete: complete) + } + } else { + return sessionManager.request(urlRequest) + .responseData(queue: decodingQueue) { dataResponse in + self.handleResponse(request: request, requestBehaviour: requestBehaviour, dataResponse: dataResponse, completionQueue: completionQueue, complete: complete) + + } + } + } + + private func handleResponse(request: APIRequest, requestBehaviour: RequestBehaviourGroup, dataResponse: AFDataResponse, completionQueue: DispatchQueue, complete: @escaping (APIResponse) -> Void) { + + let result: APIResult + + switch dataResponse.result { + case .success(let value): + do { + guard let statusCode = dataResponse.response?.statusCode else { + throw InternalError.emptyResponse + } + + let decoded = try T(statusCode: statusCode, data: value, decoder: jsonDecoder) + result = .success(decoded) + if decoded.successful { + requestBehaviour.onSuccess(result: decoded.response as Any) + } + } catch let error { + let apiError: APIClientError + if let error = error as? DecodingError { + apiError = APIClientError.decodingError(error) + } else if let error = error as? APIClientError { + apiError = error + } else { + apiError = APIClientError.unknownError(error) + } + + result = .failure(apiError) + requestBehaviour.onFailure(error: apiError) + } + case .failure(let error): + let apiError = APIClientError.networkError(error) + result = .failure(apiError) + requestBehaviour.onFailure(error: apiError) + } + let response = APIResponse(request: request, result: result, urlRequest: dataResponse.request, urlResponse: dataResponse.response, data: dataResponse.data, metrics: dataResponse.metrics) + requestBehaviour.onResponse(response: response.asAny()) + + completionQueue.async { + complete(response) + } + } +} + +private extension APIClient { + enum InternalError: Error { + case malformedURL + case emptyResponse + } +} + +public class CancellableRequest { + /// The request used to make the actual network request + public let request: AnyRequest + + init(request: AnyRequest) { + self.request = request + } + + var networkRequest: Request? + + /// cancels the request + public func cancel() { + networkRequest?.cancel() + } +} + +// Helper extension for sending requests +extension APIRequest { + + /// makes a request using the default APIClient. Change your baseURL in APIClient.default.baseURL + public func makeRequest(complete: @escaping (APIResponse) -> Void) { + APIClient.default.makeRequest(self, complete: complete) + } +} + +// Create URLRequest +extension APIRequest { + + public func createURLRequest(baseURL: URL, encoder: RequestEncoder = JSONEncoder()) throws -> URLRequest { + var urlRequest = URLRequest(url: baseURL.appendingPathComponent(path)) + urlRequest.httpMethod = service.method + urlRequest.allHTTPHeaderFields = headers + + // filter out parameters with empty string value + var queryParams: [String: Any] = [:] + for (key, value) in queryParameters { + if !String(describing: value).isEmpty { + queryParams[key] = value + } + } + + if !queryParams.isEmpty { + urlRequest = try URLEncoding.queryString.encode(urlRequest, with: queryParams) + } + + var formParams: [String: Any] = [:] + for (key, value) in formParameters { + if !String(describing: value).isEmpty { + formParams[key] = value + } + } + + if !formParams.isEmpty { + urlRequest = try URLEncoding.httpBody.encode(urlRequest, with: formParams) + } + + if let encodeBody = encodeBody { + urlRequest.httpBody = try encodeBody(encoder) + urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") + } + return urlRequest + } +} diff --git a/Specs/BinaryUpload/generated/Swift/Sources/APIClientError.swift b/Specs/BinaryUpload/generated/Swift/Sources/APIClientError.swift new file mode 100644 index 000000000..82fb6db07 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/APIClientError.swift @@ -0,0 +1,40 @@ +// +// Generated by SwagGen +// https://github.com/yonaskolb/SwagGen +// + +import Foundation + +public enum APIClientError: Error { + case unexpectedStatusCode(statusCode: Int, data: Data) + case decodingError(DecodingError) + case requestEncodingError(Error) + case validationError(Error) + case networkError(Error) + case unknownError(Error) + + public var name:String { + switch self { + case .unexpectedStatusCode: return "Unexpected status code" + case .decodingError: return "Decoding error" + case .validationError: return "Request validation failed" + case .requestEncodingError: return "Request encoding failed" + case .networkError: return "Network error" + case .unknownError: return "Unknown error" + } + } +} + +extension APIClientError: CustomStringConvertible { + + public var description:String { + switch self { + case .unexpectedStatusCode(let statusCode, _): return "\(name): \(statusCode)" + case .decodingError(let error): return "\(name): \(error.localizedDescription)\n\(error)" + case .validationError(let error): return "\(name): \(error.localizedDescription)" + case .requestEncodingError(let error): return "\(name): \(error)" + case .networkError(let error): return "\(name): \(error.localizedDescription)" + case .unknownError(let error): return "\(name): \(error.localizedDescription)" + } + } +} diff --git a/Specs/BinaryUpload/generated/Swift/Sources/APIRequest.swift b/Specs/BinaryUpload/generated/Swift/Sources/APIRequest.swift new file mode 100644 index 000000000..49097b6c1 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/APIRequest.swift @@ -0,0 +1,131 @@ +// +// Generated by SwagGen +// https://github.com/yonaskolb/SwagGen +// + +import Foundation + +public class APIRequest { + + public let service: APIService + public private(set) var queryParameters: [String: Any] + public private(set) var formParameters: [String: Any] + public let encodeBody: ((RequestEncoder) throws -> Data)? + private(set) var headerParameters: [String: String] + public var customHeaders: [String: String] = [:] + + public var headers: [String: String] { + return headerParameters.merging(customHeaders) { param, custom in return custom } + } + + public var path: String { + return service.path + } + + public init(service: APIService, + queryParameters: [String: Any] = [:], + formParameters: [String: Any] = [:], + headers: [String: String] = [:], + encodeBody: ((RequestEncoder) throws -> Data)? = nil) { + self.service = service + self.queryParameters = queryParameters + self.formParameters = formParameters + self.headerParameters = headers + self.encodeBody = encodeBody + } +} + +extension APIRequest: CustomStringConvertible { + + public var description: String { + var string = "\(service.name): \(service.method) \(path)" + if !queryParameters.isEmpty { + string += "?" + queryParameters.map {"\($0)=\($1)"}.joined(separator: "&") + } + return string + } +} + +extension APIRequest: CustomDebugStringConvertible { + + public var debugDescription: String { + var string = description + if let encodeBody = encodeBody, + let data = try? encodeBody(JSONEncoder()), + let bodyString = String(data: data, encoding: .utf8) { + string += "\nbody: \(bodyString)" + } + return string + } +} + +/// A file upload +public struct UploadFile: Equatable, Codable { + + public let type: FileType + public let fileName: String? + public let mimeType: String? + + public init(type: FileType) { + self.type = type + self.fileName = nil + self.mimeType = nil + } + + public init(type: FileType, fileName: String, mimeType: String) { + self.type = type + self.fileName = fileName + self.mimeType = mimeType + } + + public enum FileType: Equatable, Codable { + case data(Data) + case url(URL) + + enum CodingKeys: CodingKey { + case data + case url + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .data(value): + try container.encode(value, forKey: .data) + case let .url(value): + try container.encode(value, forKey: .url) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let key = container.allKeys.first + + switch key { + case .data: + let value = try container.decode( + Data.self, + forKey: .data + ) + self = .data(value) + case .url: + let value = try container.decode( + URL.self, + forKey: .url + ) + self = .url(value) + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Unabled to decode FileType." + ) + ) + } + } + } + + func encode() -> Any { + return self + } +} diff --git a/Specs/BinaryUpload/generated/Swift/Sources/APIResponse.swift b/Specs/BinaryUpload/generated/Swift/Sources/APIResponse.swift new file mode 100644 index 000000000..7fdc7bb41 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/APIResponse.swift @@ -0,0 +1,101 @@ +// +// Generated by SwagGen +// https://github.com/yonaskolb/SwagGen +// + +import Foundation +import Alamofire + +public protocol APIResponseValue: CustomDebugStringConvertible, CustomStringConvertible { + associatedtype SuccessType + var statusCode: Int { get } + var successful: Bool { get } + var response: Any { get } + init(statusCode: Int, data: Data, decoder: ResponseDecoder) throws + var success: SuccessType? { get } +} + +public enum APIResponseResult: CustomStringConvertible, CustomDebugStringConvertible { + case success(SuccessType) + case failure(FailureType) + + public var value: Any { + switch self { + case .success(let value): return value + case .failure(let value): return value + } + } + + public var successful: Bool { + switch self { + case .success: return true + case .failure: return false + } + } + + public var description: String { + return "\(successful ? "success" : "failure")" + } + + public var debugDescription: String { + return "\(description):\n\(value)" + } +} + +public struct APIResponse { + + /// The APIRequest used for this response + public let request: APIRequest + + /// The result of the response . + public let result: APIResult + + /// The URL request sent to the server. + public let urlRequest: URLRequest? + + /// The server's response to the URL request. + public let urlResponse: HTTPURLResponse? + + /// The data returned by the server. + public let data: Data? + + /// The timeline of the complete lifecycle of the request. + public let metrics: URLSessionTaskMetrics? + + init(request: APIRequest, result: APIResult, urlRequest: URLRequest? = nil, urlResponse: HTTPURLResponse? = nil, data: Data? = nil, metrics: URLSessionTaskMetrics? = nil) { + self.request = request + self.result = result + self.urlRequest = urlRequest + self.urlResponse = urlResponse + self.data = data + self.metrics = metrics + } +} + +extension APIResponse: CustomStringConvertible, CustomDebugStringConvertible { + + public var description:String { + var string = "\(request)" + + switch result { + case .success(let value): + string += " returned \(value.statusCode)" + let responseString = "\(type(of: value.response))" + if responseString != "()" { + string += ": \(responseString)" + } + case .failure(let error): string += " failed: \(error)" + } + return string + } + + public var debugDescription: String { + var string = description + if let response = try? result.get().response { + if let debugStringConvertible = response as? CustomDebugStringConvertible { + string += "\n\(debugStringConvertible.debugDescription)" + } + } + return string + } +} diff --git a/Specs/BinaryUpload/generated/Swift/Sources/APIResult.swift b/Specs/BinaryUpload/generated/Swift/Sources/APIResult.swift new file mode 100644 index 000000000..718c5ceb8 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/APIResult.swift @@ -0,0 +1,6 @@ +// +// Generated by SwagGen +// https://github.com/yonaskolb/SwagGen +// + +public typealias APIResult = Result diff --git a/Specs/BinaryUpload/generated/Swift/Sources/APIService.swift b/Specs/BinaryUpload/generated/Swift/Sources/APIService.swift new file mode 100644 index 000000000..4cf2264f2 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/APIService.swift @@ -0,0 +1,47 @@ +// +// Generated by SwagGen +// https://github.com/yonaskolb/SwagGen +// + +public struct APIService { + + public let id: String + public let tag: String + public let method: String + public let path: String + public let hasBody: Bool + public let isUpload: Bool + public let securityRequirements: [SecurityRequirement] + + public init(id: String, tag: String = "", method:String, path:String, hasBody: Bool, isUpload: Bool = false, securityRequirements: [SecurityRequirement] = []) { + self.id = id + self.tag = tag + self.method = method + self.path = path + self.hasBody = hasBody + self.isUpload = isUpload + self.securityRequirements = securityRequirements + } +} + +extension APIService: CustomStringConvertible { + + public var name: String { + return "\(tag.isEmpty ? "" : "\(tag).")\(id)" + } + + public var description: String { + return "\(name): \(method) \(path)" + } +} + +public struct SecurityRequirement { + public let type: String + public let scopes: [String] + + public init(type: String, scopes: [String]) { + self.type = type + self.scopes = scopes + } +} + diff --git a/Specs/BinaryUpload/generated/Swift/Sources/AnyCodable.swift b/Specs/BinaryUpload/generated/Swift/Sources/AnyCodable.swift new file mode 100644 index 000000000..e263e9005 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/AnyCodable.swift @@ -0,0 +1,188 @@ +import Foundation + +public struct AnyCodable { + let value: Any + + init(_ value: T?) { + self.value = value ?? () + } +} + +extension AnyCodable: Codable { + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + if container.decodeNil() { + self.init(()) + } else if let bool = try? container.decode(Bool.self) { + self.init(bool) + } else if let int = try? container.decode(Int.self) { + self.init(int) + } else if let uint = try? container.decode(UInt.self) { + self.init(uint) + } else if let double = try? container.decode(Double.self) { + self.init(double) + } else if let string = try? container.decode(String.self) { + self.init(string) + } else if let array = try? container.decode([AnyCodable].self) { + self.init(array.map { $0.value }) + } else if let dictionary = try? container.decode([String: AnyCodable].self) { + self.init(dictionary.mapValues { $0.value }) + } else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyCodable value cannot be decoded") + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + switch self.value { + case is Void: + try container.encodeNil() + case let bool as Bool: + try container.encode(bool) + case let int as Int: + try container.encode(int) + case let int8 as Int8: + try container.encode(int8) + case let int16 as Int16: + try container.encode(int16) + case let int32 as Int32: + try container.encode(int32) + case let int64 as Int64: + try container.encode(int64) + case let uint as UInt: + try container.encode(uint) + case let uint8 as UInt8: + try container.encode(uint8) + case let uint16 as UInt16: + try container.encode(uint16) + case let uint32 as UInt32: + try container.encode(uint32) + case let uint64 as UInt64: + try container.encode(uint64) + case let float as Float: + try container.encode(float) + case let double as Double: + try container.encode(double) + case let string as String: + try container.encode(string) + case let date as Date: + try container.encode(date) + case let url as URL: + try container.encode(url) + case let array as [Any?]: + try container.encode(array.map { AnyCodable($0) }) + case let dictionary as [String: Any?]: + try container.encode(dictionary.mapValues { AnyCodable($0) }) + case let object as Encodable: + try object.encode(to: encoder) + default: + let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "AnyCodable value cannot be encoded") + throw EncodingError.invalidValue(self.value, context) + } + } +} + +extension AnyCodable: Equatable { + static public func ==(lhs: AnyCodable, rhs: AnyCodable) -> Bool { + switch (lhs.value, rhs.value) { + case is (Void, Void): + return true + case let (lhs as Bool, rhs as Bool): + return lhs == rhs + case let (lhs as Int, rhs as Int): + return lhs == rhs + case let (lhs as Int8, rhs as Int8): + return lhs == rhs + case let (lhs as Int16, rhs as Int16): + return lhs == rhs + case let (lhs as Int32, rhs as Int32): + return lhs == rhs + case let (lhs as Int64, rhs as Int64): + return lhs == rhs + case let (lhs as UInt, rhs as UInt): + return lhs == rhs + case let (lhs as UInt8, rhs as UInt8): + return lhs == rhs + case let (lhs as UInt16, rhs as UInt16): + return lhs == rhs + case let (lhs as UInt32, rhs as UInt32): + return lhs == rhs + case let (lhs as UInt64, rhs as UInt64): + return lhs == rhs + case let (lhs as Float, rhs as Float): + return lhs == rhs + case let (lhs as Double, rhs as Double): + return lhs == rhs + case let (lhs as String, rhs as String): + return lhs == rhs + case (let lhs as [String: AnyCodable], let rhs as [String: AnyCodable]): + return lhs == rhs + case (let lhs as [AnyCodable], let rhs as [AnyCodable]): + return lhs == rhs + default: + return false + } + } +} + +extension AnyCodable: CustomStringConvertible { + public var description: String { + switch value { + case is Void: + return String(describing: nil as Any?) + case let value as CustomStringConvertible: + return value.description + default: + return String(describing: value) + } + } +} + +extension AnyCodable: CustomDebugStringConvertible { + public var debugDescription: String { + switch value { + case let value as CustomDebugStringConvertible: + return "AnyCodable(\(value.debugDescription))" + default: + return "AnyCodable(\(self.description))" + } + } +} + +extension AnyCodable: ExpressibleByNilLiteral, ExpressibleByBooleanLiteral, ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByStringLiteral, ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral { + + public init(nilLiteral: ()) { + self.init(nil as Any?) + } + + public init(booleanLiteral value: Bool) { + self.init(value) + } + + public init(integerLiteral value: Int) { + self.init(value) + } + + public init(floatLiteral value: Double) { + self.init(value) + } + + public init(extendedGraphemeClusterLiteral value: String) { + self.init(value) + } + + public init(stringLiteral value: String) { + self.init(value) + } + + public init(arrayLiteral elements: Any...) { + self.init(elements) + } + + public init(dictionaryLiteral elements: (AnyHashable, Any)...) { + self.init(Dictionary(elements, uniquingKeysWith: { (first, _) in first })) + } +} diff --git a/Specs/BinaryUpload/generated/Swift/Sources/Coding.swift b/Specs/BinaryUpload/generated/Swift/Sources/Coding.swift new file mode 100644 index 000000000..7aa59e4c8 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/Coding.swift @@ -0,0 +1,367 @@ +// +// Generated by SwagGen +// https://github.com/yonaskolb/SwagGen +// + +import Foundation + +public protocol APIModel: Codable, Equatable { } + +public typealias DateTime = Date +public typealias File = UploadFile +public typealias ID = UUID + +public protocol ResponseDecoder { + + func decode(_ type: T.Type, from: Data) throws -> T +} + +extension JSONDecoder: ResponseDecoder {} + +public protocol RequestEncoder { + + func encode(_ value: T) throws -> Data +} + +extension JSONEncoder: RequestEncoder {} + +extension APIModel { + func encode() -> [String: Any] { + guard + let jsonData = try? JSONEncoder().encode(self), + let jsonValue = try? JSONSerialization.jsonObject(with: jsonData), + let jsonDictionary = jsonValue as? [String: Any] else { + return [:] + } + return jsonDictionary + } +} + +struct StringCodingKey: CodingKey, ExpressibleByStringLiteral { + + private let string: String + private let int: Int? + + var stringValue: String { return string } + + init(string: String) { + self.string = string + int = nil + } + init?(stringValue: String) { + string = stringValue + int = nil + } + + var intValue: Int? { return int } + init?(intValue: Int) { + string = String(describing: intValue) + int = intValue + } + + init(stringLiteral value: String) { + string = value + int = nil + } +} + +// any json decoding +extension ResponseDecoder { + + func decodeAny(_ type: T.Type, from data: Data) throws -> T { + guard let decoded = try decode(AnyCodable.self, from: data) as? T else { + throw DecodingError.typeMismatch(T.self, DecodingError.Context(codingPath: [StringCodingKey(string: "")], debugDescription: "Decoding of \(T.self) failed")) + } + return decoded + } +} + +// any decoding +extension KeyedDecodingContainer { + + func decodeAny(_ type: T.Type, forKey key: K) throws -> T { + guard let value = try decode(AnyCodable.self, forKey: key).value as? T else { + throw DecodingError.typeMismatch(T.self, DecodingError.Context(codingPath: codingPath, debugDescription: "Decoding of \(T.self) failed")) + } + return value + } + + func decodeAnyIfPresent(_ type: T.Type, forKey key: K) throws -> T? { + return try decodeOptional { + guard let value = try decodeIfPresent(AnyCodable.self, forKey: key)?.value else { return nil } + if let typedValue = value as? T { + return typedValue + } else { + throw DecodingError.typeMismatch(T.self, DecodingError.Context(codingPath: codingPath, debugDescription: "Decoding of \(T.self) failed")) + } + } + } + + func toDictionary() throws -> [String: Any] { + var dictionary: [String: Any] = [:] + for key in allKeys { + dictionary[key.stringValue] = try decodeAny(key) + } + return dictionary + } + + func decode(_ key: KeyedDecodingContainer.Key) throws -> T where T: Decodable { + return try decode(T.self, forKey: key) + } + + func decodeIfPresent(_ key: KeyedDecodingContainer.Key) throws -> T? where T: Decodable { + return try decodeOptional { + try decodeIfPresent(T.self, forKey: key) + } + } + + func decodeAny(_ key: K) throws -> T { + return try decodeAny(T.self, forKey: key) + } + + func decodeAnyIfPresent(_ key: K) throws -> T? { + return try decodeAnyIfPresent(T.self, forKey: key) + } + + public func decodeArray(_ key: K) throws -> [T] { + var container: UnkeyedDecodingContainer + var array: [T] = [] + + do { + container = try nestedUnkeyedContainer(forKey: key) + } catch { + if BinaryUpload.safeArrayDecoding { + return array + } else { + throw error + } + } + + while !container.isAtEnd { + do { + let element = try container.decode(T.self) + array.append(element) + } catch { + if BinaryUpload.safeArrayDecoding { + // hack to advance the current index + _ = try? container.decode(AnyCodable.self) + } else { + throw error + } + } + } + return array + } + + public func decodeArrayIfPresent(_ key: K) throws -> [T]? { + return try decodeOptional { + if contains(key) { + return try decodeArray(key) + } else { + return nil + } + } + } + + fileprivate func decodeOptional(_ closure: () throws -> T? ) throws -> T? { + if BinaryUpload.safeOptionalDecoding { + do { + return try closure() + } catch { + return nil + } + } else { + return try closure() + } + } +} + +// any encoding +extension KeyedEncodingContainer { + + mutating func encodeAnyIfPresent(_ value: T?, forKey key: K) throws { + guard let value = value else { return } + try encodeIfPresent(AnyCodable(value), forKey: key) + } + + mutating func encodeAny(_ value: T, forKey key: K) throws { + try encode(AnyCodable(value), forKey: key) + } +} + +// Date structs for date and date-time formats + +extension DateFormatter { + + convenience init(formatString: String, locale: Locale? = nil, timeZone: TimeZone? = nil, calendar: Calendar? = nil) { + self.init() + dateFormat = formatString + if let locale = locale { + self.locale = locale + } + if let timeZone = timeZone { + self.timeZone = timeZone + } + if let calendar = calendar { + self.calendar = calendar + } + } + + convenience init(dateStyle: DateFormatter.Style, timeStyle: DateFormatter.Style) { + self.init() + self.dateStyle = dateStyle + self.timeStyle = timeStyle + } +} + +let dateDecoder: (Decoder) throws -> Date = { decoder in + let container = try decoder.singleValueContainer() + let string = try container.decode(String.self) + + let formatterWithMilliseconds = DateFormatter() + formatterWithMilliseconds.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" + formatterWithMilliseconds.locale = Locale(identifier: "en_US_POSIX") + formatterWithMilliseconds.timeZone = TimeZone(identifier: "UTC") + formatterWithMilliseconds.calendar = Calendar(identifier: .gregorian) + + let formatterWithoutMilliseconds = DateFormatter() + formatterWithoutMilliseconds.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + formatterWithoutMilliseconds.locale = Locale(identifier: "en_US_POSIX") + formatterWithoutMilliseconds.timeZone = TimeZone(identifier: "UTC") + formatterWithoutMilliseconds.calendar = Calendar(identifier: .gregorian) + + guard let date = formatterWithMilliseconds.date(from: string) ?? + formatterWithoutMilliseconds.date(from: string) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Could not decode date") + } + return date + } + +public struct DateDay: Codable, Comparable { + + /// The date formatter used for encoding and decoding + public static let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + formatter.calendar = .current + return formatter + }() + + public let date: Date + public let year: Int + public let month: Int + public let day: Int + + public init(date: Date = Date()) { + self.date = date + let dateComponents = Calendar.current.dateComponents([.day, .month, .year], from: date) + guard let year = dateComponents.year, + let month = dateComponents.month, + let day = dateComponents.day else { + fatalError("Date does not contain correct components") + } + self.year = year + self.month = month + self.day = day + } + + public init(year: Int, month: Int, day: Int) { + let dateComponents = DateComponents(calendar: .current, year: year, month: month, day: day) + guard let date = dateComponents.date else { + fatalError("Could not create date in current calendar") + } + self.date = date + self.year = year + self.month = month + self.day = day + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let string = try container.decode(String.self) + guard let date = DateDay.dateFormatter.date(from: string) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Date not in correct format of \(DateDay.dateFormatter.dateFormat ?? "")") + } + self.init(date: date) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + let string = DateDay.dateFormatter.string(from: date) + try container.encode(string) + } + + public static func == (lhs: DateDay, rhs: DateDay) -> Bool { + return lhs.year == rhs.year && + lhs.month == rhs.month && + lhs.day == rhs.day + } + + public static func < (lhs: DateDay, rhs: DateDay) -> Bool { + return lhs.date < rhs.date + } +} + +extension DateFormatter { + + public func string(from dateDay: DateDay) -> String { + return string(from: dateDay.date) + } +} + +// for parameter encoding + +extension DateDay { + func encode() -> Any { + return DateDay.dateFormatter.string(from: date) + } +} + +extension Date { + func encode() -> Any { + return BinaryUpload.dateEncodingFormatter.string(from: self) + } +} + +extension URL { + func encode() -> Any { + return absoluteString + } +} + +extension RawRepresentable { + func encode() -> Any { + return rawValue + } +} + +extension Array where Element: RawRepresentable { + func encode() -> [Any] { + return map { $0.rawValue } + } +} + +extension Dictionary where Key == String, Value: RawRepresentable { + func encode() -> [String: Any] { + return mapValues { $0.rawValue } + } +} + +extension UUID { + func encode() -> Any { + return uuidString + } +} + +extension String { + func encode() -> Any { + return self + } +} + +extension Data { + + func encode() -> Any { + return self + } +} diff --git a/Specs/BinaryUpload/generated/Swift/Sources/Models/UploadModel.swift b/Specs/BinaryUpload/generated/Swift/Sources/Models/UploadModel.swift new file mode 100644 index 000000000..ce44bbdc7 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/Models/UploadModel.swift @@ -0,0 +1,43 @@ +// +// Generated by SwagGen +// https://github.com/yonaskolb/SwagGen +// + +import Foundation + +public class UploadModel: APIModel { + + public var binary: File? + + public var id: String? + + public init(binary: File? = nil, id: String? = nil) { + self.binary = binary + self.id = id + } + + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + binary = try container.decodeIfPresent("binary") + id = try container.decodeIfPresent("id") + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encodeIfPresent(binary, forKey: "binary") + try container.encodeIfPresent(id, forKey: "id") + } + + public func isEqual(to object: Any?) -> Bool { + guard let object = object as? UploadModel else { return false } + guard self.binary == object.binary else { return false } + guard self.id == object.id else { return false } + return true + } + + public static func == (lhs: UploadModel, rhs: UploadModel) -> Bool { + return lhs.isEqual(to: rhs) + } +} diff --git a/Specs/BinaryUpload/generated/Swift/Sources/RequestBehaviour.swift b/Specs/BinaryUpload/generated/Swift/Sources/RequestBehaviour.swift new file mode 100644 index 000000000..ed2d38a31 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/RequestBehaviour.swift @@ -0,0 +1,192 @@ +// +// Generated by SwagGen +// https://github.com/yonaskolb/SwagGen +// + +import Foundation + +public protocol RequestBehaviour { + + /// runs first and allows the requests to be modified. If modifying asynchronously use validate + func modifyRequest(request: AnyRequest, urlRequest: URLRequest) -> URLRequest + + /// validates and modifies the request. complete must be called with either .success or .fail + func validate(request: AnyRequest, urlRequest: URLRequest, complete: @escaping (RequestValidationResult) -> Void) + + /// called before request is sent + func beforeSend(request: AnyRequest) + + /// called when request successfuly returns a 200 range response + func onSuccess(request: AnyRequest, result: Any) + + /// called when request fails with an error. This will not be called if the request returns a known response even if the a status code is out of the 200 range + func onFailure(request: AnyRequest, error: APIClientError) + + /// called if the request recieves a network response. This is not called if request fails validation or encoding + func onResponse(request: AnyRequest, response: AnyResponse) +} + +public enum RequestValidationResult { + case success(URLRequest) + case failure(Error) +} + +// Provides empty defaults so that each function becomes optional +public extension RequestBehaviour { + func modifyRequest(request: AnyRequest, urlRequest: URLRequest) -> URLRequest { return urlRequest } + func validate(request: AnyRequest, urlRequest: URLRequest, complete: @escaping (RequestValidationResult) -> Void) { + complete(.success(urlRequest)) + } + func beforeSend(request: AnyRequest) {} + func onSuccess(request: AnyRequest, result: Any) {} + func onFailure(request: AnyRequest, error: APIClientError) {} + func onResponse(request: AnyRequest, response: AnyResponse) {} +} + +// Group different RequestBehaviours together +struct RequestBehaviourGroup { + + let request: AnyRequest + let behaviours: [RequestBehaviour] + + init(request: APIRequest, behaviours: [RequestBehaviour]) { + self.request = request.asAny() + self.behaviours = behaviours + } + + func beforeSend() { + behaviours.forEach { + $0.beforeSend(request: request) + } + } + + func validate(_ urlRequest: URLRequest, complete: @escaping (RequestValidationResult) -> Void) { + if behaviours.isEmpty { + complete(.success(urlRequest)) + return + } + + var count = 0 + var modifiedRequest = urlRequest + func validateNext() { + let behaviour = behaviours[count] + behaviour.validate(request: request, urlRequest: modifiedRequest) { result in + count += 1 + switch result { + case .success(let urlRequest): + modifiedRequest = urlRequest + if count == self.behaviours.count { + complete(.success(modifiedRequest)) + } else { + validateNext() + } + case .failure(let error): + complete(.failure(error)) + } + } + } + validateNext() + } + + func onSuccess(result: Any) { + behaviours.forEach { + $0.onSuccess(request: request, result: result) + } + } + + func onFailure(error: APIClientError) { + behaviours.forEach { + $0.onFailure(request: request, error: error) + } + } + + func onResponse(response: AnyResponse) { + behaviours.forEach { + $0.onResponse(request: request, response: response) + } + } + + func modifyRequest(_ urlRequest: URLRequest) -> URLRequest { + var urlRequest = urlRequest + behaviours.forEach { + urlRequest = $0.modifyRequest(request: request, urlRequest: urlRequest) + } + return urlRequest + } +} + +//MARK: Type erased Requests and Responses + +public typealias AnyResponse = APIResponse + +public class AnyRequest: APIRequest { + private let requestPath: String + + override public var path: String { + return requestPath + } + + init(request: APIRequest) { + requestPath = request.path + super.init(service: request.service.asAny(), queryParameters: request.queryParameters, formParameters: request.formParameters, headers: request.headers, encodeBody: request.encodeBody) + } +} + +public struct AnyResponseValue: APIResponseValue, CustomDebugStringConvertible, CustomStringConvertible { + + public typealias SuccessType = Any + + public let statusCode: Int + public let successful: Bool + public let response: Any + public let responseEnum: Any + public let success: Any? + + public init(statusCode: Int, successful: Bool, response: Any, responseEnum: Any, success: Any?) { + self.statusCode = statusCode + self.successful = successful + self.response = response + self.responseEnum = responseEnum + self.success = success + } + + public init(statusCode: Int, data: Data, decoder: ResponseDecoder) throws { + fatalError() + } + + public var description:String { + return "\(responseEnum)" + } + + public var debugDescription: String { + if let debugDescription = responseEnum as? CustomDebugStringConvertible { + return debugDescription.debugDescription + } else { + return "\(responseEnum)" + } + } +} + +extension APIResponseValue { + public func asAny() -> AnyResponseValue { + return AnyResponseValue(statusCode: statusCode, successful: successful, response: response, responseEnum: self, success: success) + } +} + +extension APIResponse { + public func asAny() -> APIResponse { + return APIResponse(request: request.asAny(), result: result.map{ $0.asAny() }, urlRequest: urlRequest, urlResponse: urlResponse, data: data, metrics: metrics) + } +} + +extension APIRequest { + public func asAny() -> AnyRequest { + return AnyRequest(request: self) + } +} + +extension APIService { + public func asAny() -> APIService { + return APIService(id: id, tag: tag, method: method, path: path, hasBody: hasBody, securityRequirements: securityRequirements) + } +} diff --git a/Specs/BinaryUpload/generated/Swift/Sources/Requests/PostUser.swift b/Specs/BinaryUpload/generated/Swift/Sources/Requests/PostUser.swift new file mode 100644 index 000000000..54eaf0992 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/Requests/PostUser.swift @@ -0,0 +1,120 @@ +// +// Generated by SwagGen +// https://github.com/yonaskolb/SwagGen +// + +import Foundation + +extension BinaryUpload { + + /** + Create New User + + Create a new user. + */ + public enum PostUser { + + public static let service = APIService(id: "post-user", tag: "", method: "POST", path: "/withoutModel", hasBody: true, securityRequirements: []) + + public final class Request: APIRequest { + + /** Create a new user. */ + public class Body: APIModel { + + public var binary: File + + public init(binary: File) { + self.binary = binary + } + + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + binary = try container.decode("binary") + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encode(binary, forKey: "binary") + } + + public func isEqual(to object: Any?) -> Bool { + guard let object = object as? Body else { return false } + guard self.binary == object.binary else { return false } + return true + } + + public static func == (lhs: Body, rhs: Body) -> Bool { + return lhs.isEqual(to: rhs) + } + } + + public var body: Body? + + public init(body: Body?, encoder: RequestEncoder? = nil) { + self.body = body + super.init(service: PostUser.service) { defaultEncoder in + return try (encoder ?? defaultEncoder).encode(body) + } + } + } + + public enum Response: APIResponseValue, CustomStringConvertible, CustomDebugStringConvertible { + public typealias SuccessType = Void + + /** Missing Required Information */ + case status400 + + /** Email Already Taken */ + case status409 + + public var success: Void? { + switch self { + default: return nil + } + } + + public var response: Any { + switch self { + default: return () + } + } + + public var statusCode: Int { + switch self { + case .status400: return 400 + case .status409: return 409 + } + } + + public var successful: Bool { + switch self { + case .status400: return false + case .status409: return false + } + } + + public init(statusCode: Int, data: Data, decoder: ResponseDecoder) throws { + switch statusCode { + case 400: self = .status400 + case 409: self = .status409 + default: throw APIClientError.unexpectedStatusCode(statusCode: statusCode, data: data) + } + } + + public var description: String { + return "\(statusCode) \(successful ? "success" : "failure")" + } + + public var debugDescription: String { + var string = description + let responseString = "\(response)" + if responseString != "()" { + string += "\n\(responseString)" + } + return string + } + } + } +} diff --git a/Specs/BinaryUpload/generated/Swift/Sources/Requests/PostWithType.swift b/Specs/BinaryUpload/generated/Swift/Sources/Requests/PostWithType.swift new file mode 100644 index 000000000..4928f2de3 --- /dev/null +++ b/Specs/BinaryUpload/generated/Swift/Sources/Requests/PostWithType.swift @@ -0,0 +1,108 @@ +// +// Generated by SwagGen +// https://github.com/yonaskolb/SwagGen +// + +import Foundation + +extension BinaryUpload { + + public enum PostWithType { + + public static let service = APIService(id: "post-withType", tag: "", method: "POST", path: "/withModel", hasBody: true, securityRequirements: []) + + public final class Request: APIRequest { + + public class Body: APIModel { + + public var uploadData: UploadModel? + + public init(uploadData: UploadModel? = nil) { + self.uploadData = uploadData + } + + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + uploadData = try container.decodeIfPresent("uploadData") + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encodeIfPresent(uploadData, forKey: "uploadData") + } + + public func isEqual(to object: Any?) -> Bool { + guard let object = object as? Body else { return false } + guard self.uploadData == object.uploadData else { return false } + return true + } + + public static func == (lhs: Body, rhs: Body) -> Bool { + return lhs.isEqual(to: rhs) + } + } + + public var body: Body? + + public init(body: Body?, encoder: RequestEncoder? = nil) { + self.body = body + super.init(service: PostWithType.service) { defaultEncoder in + return try (encoder ?? defaultEncoder).encode(body) + } + } + } + + public enum Response: APIResponseValue, CustomStringConvertible, CustomDebugStringConvertible { + public typealias SuccessType = Void + + /** OK */ + case status200 + + public var success: Void? { + switch self { + case .status200: return () + } + } + + public var response: Any { + switch self { + default: return () + } + } + + public var statusCode: Int { + switch self { + case .status200: return 200 + } + } + + public var successful: Bool { + switch self { + case .status200: return true + } + } + + public init(statusCode: Int, data: Data, decoder: ResponseDecoder) throws { + switch statusCode { + case 200: self = .status200 + default: throw APIClientError.unexpectedStatusCode(statusCode: statusCode, data: data) + } + } + + public var description: String { + return "\(statusCode) \(successful ? "success" : "failure")" + } + + public var debugDescription: String { + var string = description + let responseString = "\(response)" + if responseString != "()" { + string += "\n\(responseString)" + } + return string + } + } + } +} diff --git a/Specs/BinaryUpload/spec.yml b/Specs/BinaryUpload/spec.yml new file mode 100644 index 000000000..37c508dd3 --- /dev/null +++ b/Specs/BinaryUpload/spec.yml @@ -0,0 +1,64 @@ +openapi: 3.1.0 +info: + title: test + version: '1.0' +servers: + - url: 'http://localhost:3000' +paths: + /withoutModel: + post: + summary: Create New User + operationId: post-user + responses: + '400': + description: Missing Required Information + '409': + description: Email Already Taken + requestBody: + content: + application/json: + schema: + type: object + properties: + binary: + type: string + format: binary + required: + - binary + examples: + Create User Bob Fellow: + value: + firstName: Bob + lastName: Fellow + email: bob.fellow@gmail.com + dateOfBirth: '1996-08-24' + description: Post the necessary fields for the API to create a new user. + description: Create a new user. + parameters: [] + /withModel: + post: + summary: '' + operationId: post-withType + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + type: object + properties: + uploadData: + $ref: '#/components/schemas/UploadModel' + parameters: [] +components: + schemas: + UploadModel: + title: UploadModel + type: object + properties: + id: + type: string + binary: + type: string + format: binary diff --git a/Specs/Petstore/generated/Swift/Sources/APIRequest.swift b/Specs/Petstore/generated/Swift/Sources/APIRequest.swift index e529d1cb0..49097b6c1 100644 --- a/Specs/Petstore/generated/Swift/Sources/APIRequest.swift +++ b/Specs/Petstore/generated/Swift/Sources/APIRequest.swift @@ -60,7 +60,7 @@ extension APIRequest: CustomDebugStringConvertible { } /// A file upload -public struct UploadFile: Equatable { +public struct UploadFile: Equatable, Codable { public let type: FileType public let fileName: String? @@ -78,9 +78,51 @@ public struct UploadFile: Equatable { self.mimeType = mimeType } - public enum FileType: Equatable { + public enum FileType: Equatable, Codable { case data(Data) case url(URL) + + enum CodingKeys: CodingKey { + case data + case url + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .data(value): + try container.encode(value, forKey: .data) + case let .url(value): + try container.encode(value, forKey: .url) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let key = container.allKeys.first + + switch key { + case .data: + let value = try container.decode( + Data.self, + forKey: .data + ) + self = .data(value) + case .url: + let value = try container.decode( + URL.self, + forKey: .url + ) + self = .url(value) + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Unabled to decode FileType." + ) + ) + } + } } func encode() -> Any { diff --git a/Specs/PetstoreTest/generated/Swift/Sources/APIRequest.swift b/Specs/PetstoreTest/generated/Swift/Sources/APIRequest.swift index e529d1cb0..49097b6c1 100644 --- a/Specs/PetstoreTest/generated/Swift/Sources/APIRequest.swift +++ b/Specs/PetstoreTest/generated/Swift/Sources/APIRequest.swift @@ -60,7 +60,7 @@ extension APIRequest: CustomDebugStringConvertible { } /// A file upload -public struct UploadFile: Equatable { +public struct UploadFile: Equatable, Codable { public let type: FileType public let fileName: String? @@ -78,9 +78,51 @@ public struct UploadFile: Equatable { self.mimeType = mimeType } - public enum FileType: Equatable { + public enum FileType: Equatable, Codable { case data(Data) case url(URL) + + enum CodingKeys: CodingKey { + case data + case url + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .data(value): + try container.encode(value, forKey: .data) + case let .url(value): + try container.encode(value, forKey: .url) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let key = container.allKeys.first + + switch key { + case .data: + let value = try container.decode( + Data.self, + forKey: .data + ) + self = .data(value) + case .url: + let value = try container.decode( + URL.self, + forKey: .url + ) + self = .url(value) + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Unabled to decode FileType." + ) + ) + } + } } func encode() -> Any { diff --git a/Specs/Rocket/generated/Swift/Sources/APIRequest.swift b/Specs/Rocket/generated/Swift/Sources/APIRequest.swift index e529d1cb0..49097b6c1 100644 --- a/Specs/Rocket/generated/Swift/Sources/APIRequest.swift +++ b/Specs/Rocket/generated/Swift/Sources/APIRequest.swift @@ -60,7 +60,7 @@ extension APIRequest: CustomDebugStringConvertible { } /// A file upload -public struct UploadFile: Equatable { +public struct UploadFile: Equatable, Codable { public let type: FileType public let fileName: String? @@ -78,9 +78,51 @@ public struct UploadFile: Equatable { self.mimeType = mimeType } - public enum FileType: Equatable { + public enum FileType: Equatable, Codable { case data(Data) case url(URL) + + enum CodingKeys: CodingKey { + case data + case url + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .data(value): + try container.encode(value, forKey: .data) + case let .url(value): + try container.encode(value, forKey: .url) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let key = container.allKeys.first + + switch key { + case .data: + let value = try container.decode( + Data.self, + forKey: .data + ) + self = .data(value) + case .url: + let value = try container.decode( + URL.self, + forKey: .url + ) + self = .url(value) + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Unabled to decode FileType." + ) + ) + } + } } func encode() -> Any { diff --git a/Specs/TBX/generated/Swift/Sources/APIRequest.swift b/Specs/TBX/generated/Swift/Sources/APIRequest.swift index e529d1cb0..49097b6c1 100644 --- a/Specs/TBX/generated/Swift/Sources/APIRequest.swift +++ b/Specs/TBX/generated/Swift/Sources/APIRequest.swift @@ -60,7 +60,7 @@ extension APIRequest: CustomDebugStringConvertible { } /// A file upload -public struct UploadFile: Equatable { +public struct UploadFile: Equatable, Codable { public let type: FileType public let fileName: String? @@ -78,9 +78,51 @@ public struct UploadFile: Equatable { self.mimeType = mimeType } - public enum FileType: Equatable { + public enum FileType: Equatable, Codable { case data(Data) case url(URL) + + enum CodingKeys: CodingKey { + case data + case url + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .data(value): + try container.encode(value, forKey: .data) + case let .url(value): + try container.encode(value, forKey: .url) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let key = container.allKeys.first + + switch key { + case .data: + let value = try container.decode( + Data.self, + forKey: .data + ) + self = .data(value) + case .url: + let value = try container.decode( + URL.self, + forKey: .url + ) + self = .url(value) + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Unabled to decode FileType." + ) + ) + } + } } func encode() -> Any { diff --git a/Specs/TFL/generated/Swift/Sources/APIRequest.swift b/Specs/TFL/generated/Swift/Sources/APIRequest.swift index e529d1cb0..49097b6c1 100644 --- a/Specs/TFL/generated/Swift/Sources/APIRequest.swift +++ b/Specs/TFL/generated/Swift/Sources/APIRequest.swift @@ -60,7 +60,7 @@ extension APIRequest: CustomDebugStringConvertible { } /// A file upload -public struct UploadFile: Equatable { +public struct UploadFile: Equatable, Codable { public let type: FileType public let fileName: String? @@ -78,9 +78,51 @@ public struct UploadFile: Equatable { self.mimeType = mimeType } - public enum FileType: Equatable { + public enum FileType: Equatable, Codable { case data(Data) case url(URL) + + enum CodingKeys: CodingKey { + case data + case url + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .data(value): + try container.encode(value, forKey: .data) + case let .url(value): + try container.encode(value, forKey: .url) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let key = container.allKeys.first + + switch key { + case .data: + let value = try container.decode( + Data.self, + forKey: .data + ) + self = .data(value) + case .url: + let value = try container.decode( + URL.self, + forKey: .url + ) + self = .url(value) + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Unabled to decode FileType." + ) + ) + } + } } func encode() -> Any { diff --git a/Specs/TestSpec/generated/Swift/Sources/APIRequest.swift b/Specs/TestSpec/generated/Swift/Sources/APIRequest.swift index e529d1cb0..49097b6c1 100644 --- a/Specs/TestSpec/generated/Swift/Sources/APIRequest.swift +++ b/Specs/TestSpec/generated/Swift/Sources/APIRequest.swift @@ -60,7 +60,7 @@ extension APIRequest: CustomDebugStringConvertible { } /// A file upload -public struct UploadFile: Equatable { +public struct UploadFile: Equatable, Codable { public let type: FileType public let fileName: String? @@ -78,9 +78,51 @@ public struct UploadFile: Equatable { self.mimeType = mimeType } - public enum FileType: Equatable { + public enum FileType: Equatable, Codable { case data(Data) case url(URL) + + enum CodingKeys: CodingKey { + case data + case url + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .data(value): + try container.encode(value, forKey: .data) + case let .url(value): + try container.encode(value, forKey: .url) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let key = container.allKeys.first + + switch key { + case .data: + let value = try container.decode( + Data.self, + forKey: .data + ) + self = .data(value) + case .url: + let value = try container.decode( + URL.self, + forKey: .url + ) + self = .url(value) + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Unabled to decode FileType." + ) + ) + } + } } func encode() -> Any { diff --git a/Templates/Swift/Sources/APIRequest.swift b/Templates/Swift/Sources/APIRequest.swift index 9795e84b5..ed0a0a049 100644 --- a/Templates/Swift/Sources/APIRequest.swift +++ b/Templates/Swift/Sources/APIRequest.swift @@ -57,7 +57,7 @@ extension APIRequest: CustomDebugStringConvertible { } /// A file upload -public struct UploadFile: Equatable { +public struct UploadFile: Equatable, Codable { public let type: FileType public let fileName: String? @@ -75,9 +75,51 @@ public struct UploadFile: Equatable { self.mimeType = mimeType } - public enum FileType: Equatable { + public enum FileType: Equatable, Codable { case data(Data) case url(URL) + + enum CodingKeys: CodingKey { + case data + case url + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .data(value): + try container.encode(value, forKey: .data) + case let .url(value): + try container.encode(value, forKey: .url) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let key = container.allKeys.first + + switch key { + case .data: + let value = try container.decode( + Data.self, + forKey: .data + ) + self = .data(value) + case .url: + let value = try container.decode( + URL.self, + forKey: .url + ) + self = .url(value) + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Unabled to decode FileType." + ) + ) + } + } } func encode() -> Any {