diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index e4f2303..c0b9e31 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -7,23 +7,23 @@ on: jobs: build: - runs-on: macos-13 + runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: Set Xcode - run: xcodes select 15.2 + run: xcodes select 16.1 - name: Build run: swift build -v test: - runs-on: macos-13 + runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: Set Xcode - run: xcodes select 15.2 + run: xcodes select 16.1 - name: Run tests run: swift test -v --parallel --xunit-output test.xml diff --git a/Package.resolved b/Package.resolved index c6b8437..1755eae 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/lunarway/SwaggerSwiftML", "state" : { - "revision" : "a973b5151c225defbe13663108d4b11c07cd3064", - "version" : "1.0.19" + "revision" : "08d62c3c8bb32638299222ab5c6acf67aba98aad", + "version" : "2.0.0" } }, { diff --git a/Package.swift b/Package.swift index b185ce5..2459ad6 100644 --- a/Package.swift +++ b/Package.swift @@ -8,8 +8,8 @@ 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: "2.0.0") ], targets: [ .executableTarget( diff --git a/Sources/SwaggerSwiftCore/API Factory/APIFactory.swift b/Sources/SwaggerSwiftCore/API Factory/APIFactory.swift index 6a64d46..82b5653 100644 --- a/Sources/SwaggerSwiftCore/API Factory/APIFactory.swift +++ b/Sources/SwaggerSwiftCore/API Factory/APIFactory.swift @@ -7,8 +7,8 @@ struct APIFactory { func generate(for swagger: Swagger, withSwaggerFile swaggerFile: SwaggerFile) throws -> (APIDefinition, [ModelDefinition]) { let (apiFunctions, inlineModelDefinitions) = try getApiList(fromSwagger: swagger, swaggerFile: swaggerFile) - let modelDefinitions = getModelDefinitions(fromSwagger: swagger) - let responseModelDefinitions = getResponseModelDefinitions(fromSwagger: swagger) + let modelDefinitions = try getModelDefinitions(fromSwagger: swagger) + let responseModelDefinitions = try getResponseModelDefinitions(fromSwagger: swagger) // Model Definitions can be a lot of things - when resolving the inheritance tree for // the model definitions we just need the actual models, and a model can only inherit @@ -63,9 +63,7 @@ struct APIFactory { var apis = [APIRequest]() var modelDefinitions = [ModelDefinition]() - let parameters: [Parameter] = (path.parameters ?? []).map { - return swagger.findParameter(node: $0) - } + let parameters: [Parameter] = try (path.parameters ?? []).map(swagger.findParameter(node:)) for httpMethod in HTTPMethod.allCases { guard let operation = path.operationForMethod(httpMethod) @@ -90,14 +88,14 @@ struct APIFactory { /// Get the global set of model definitions. This is the normal specified list, and not the inline definitions that are defined in e.g. path definitions and other places /// - Parameter swagger: the swagger /// - Returns: model definitions - private func getModelDefinitions(fromSwagger swagger: Swagger) -> [ModelDefinition] { + private func getModelDefinitions(fromSwagger swagger: Swagger) throws -> [ModelDefinition] { guard let definitions = swagger.definitions else { return [] } var allDefinitions = [ModelDefinition]() for (typeName, schema) in definitions { - let resolved = modelTypeResolver.resolve(forSchema: schema, + let resolved = try modelTypeResolver.resolve(forSchema: schema, typeNamePrefix: typeName, namespace: swagger.serviceName, swagger: swagger) @@ -139,7 +137,7 @@ struct APIFactory { /// Get the global set of response model definitions /// - Parameter swagger: the swagger /// - Returns: the set of global response model definitions - private func getResponseModelDefinitions(fromSwagger swagger: Swagger) -> [ModelDefinition] { + private func getResponseModelDefinitions(fromSwagger swagger: Swagger) throws -> [ModelDefinition] { var modelDefinitions = [ModelDefinition]() for (typeName, response) in swagger.responses ?? [:] { guard let schema = response.schema else { @@ -161,7 +159,7 @@ struct APIFactory { case .reference(let reference): if let (_, typeSchema) = swagger.definitions?.first(where: { reference == "#/definitions/\($0.key)" }) { // we dont need the type part as it just represents the primary model definition returned from this function - let resolvedModel = modelTypeResolver.resolve(forSchema: typeSchema, + let resolvedModel = try modelTypeResolver.resolve(forSchema: typeSchema, typeNamePrefix: typeName, namespace: swagger.serviceName, swagger: swagger) @@ -172,7 +170,7 @@ struct APIFactory { } case .node(let schema): // we dont need the type part as it just represents the primary model definition returned from this function - let resolvedModel = modelTypeResolver.resolve(forSchema: schema, + let resolvedModel = try modelTypeResolver.resolve(forSchema: schema, typeNamePrefix: typeName, namespace: swagger.serviceName, swagger: swagger) diff --git a/Sources/SwaggerSwiftCore/API Request Factory/APIRequestFactory.swift b/Sources/SwaggerSwiftCore/API Request Factory/APIRequestFactory.swift index a82c969..364c84a 100644 --- a/Sources/SwaggerSwiftCore/API Request Factory/APIRequestFactory.swift +++ b/Sources/SwaggerSwiftCore/API Request Factory/APIRequestFactory.swift @@ -26,7 +26,7 @@ public struct APIRequestFactory { var inlineResponseModels = [ModelDefinition]() - let responses: [Response] = operation.responses.compactMap { + let responses: [Response] = try operation.responses.compactMap { let statusCodeString = $0.key guard let statusCode = HTTPStatusCode(rawValue: statusCodeString) else { fatalError("Unknown status code received: \(statusCodeString)") @@ -34,12 +34,12 @@ public struct APIRequestFactory { guard let requestResponse = $0.value else { return nil } - if let (responseType, embeddedDefinitions) = parse(request: requestResponse, - httpMethod: httpMethod, - servicePath: servicePath, - statusCode: statusCodeString, - swagger: swagger, - modelTypeResolver: modelTypeResolver) { + if let (responseType, embeddedDefinitions) = try parse(request: requestResponse, + httpMethod: httpMethod, + servicePath: servicePath, + statusCode: statusCodeString, + swagger: swagger, + modelTypeResolver: modelTypeResolver) { return .init(statusCode: statusCode, responseType: responseType, inlineModels: embeddedDefinitions) @@ -64,8 +64,8 @@ public struct APIRequestFactory { inlineResponseModels.append(contentsOf: inlineModels) - let allParameters: [Parameter] = (operation.parameters ?? []).map { - swagger.findParameter(node: $0) + let allParameters: [Parameter] = try (operation.parameters ?? []).map { + try swagger.findParameter(node: $0) } + pathParameters let requestSpecificHeaders = allParameters @@ -90,7 +90,7 @@ public struct APIRequestFactory { let headers = requestSpecificHeaders - let queryItems = resolveQueries(parameters: allParameters, swagger: swagger) + let queryItems = try resolveQueries(parameters: allParameters, swagger: swagger) let apiResponseTypes = apiResponseTypeFactory.make( forResponses: responses, @@ -132,7 +132,7 @@ public struct APIRequestFactory { /// - parameters: the total set of parameters available to the api request /// - swagger: the swagger spec /// - Returns: the list of query elements that should be set in the request - private func resolveQueries(parameters: [Parameter], swagger: Swagger) -> [QueryElement] { + private func resolveQueries(parameters: [Parameter], swagger: Swagger) throws -> [QueryElement] { let queryParameters = parameters.filter { if case ParameterLocation.query = $0.location { return true @@ -141,51 +141,56 @@ public struct APIRequestFactory { } } - let queries: [QueryElement] = queryParameters.compactMap { parameter in - switch parameter.location { - case .query(let type, _): - switch type { - case .string(let format, let enumValues, _, _, _): - let isEnum = (enumValues?.count ?? 0) > 0 - let valueType: QueryElement.ValueType = isEnum ? .enum : .default - - if let format = format { - switch format { - case .int32: fallthrough - case .long: fallthrough - case .float: fallthrough - case .double: fallthrough - case .string: fallthrough - case .byte: fallthrough - case .binary: fallthrough - case .boolean: fallthrough - case .password: fallthrough - case .email: fallthrough - case .unsupported: - return QueryElement( - fieldName: parameter.name, - fieldValue: parameter.name.camelized, - isOptional: parameter.required == false, - valueType: valueType - ) - case .date: fallthrough - case .dateTime: - return QueryElement( - fieldName: parameter.name, - fieldValue: parameter.name.camelized, - isOptional: parameter.required == false, - valueType: .date - ) - } - } else { + return try queryParameters.map { try queryParameterToQueryElement(parameter: $0, swagger: swagger) } + } + + private func queryParameterToQueryElement(parameter: Parameter, swagger: Swagger) throws -> QueryElement { + switch parameter.location { + case .query(let type, _): + switch type { + case .string(let format, let enumValues, _, _, _): + let isEnum = (enumValues?.count ?? 0) > 0 + let valueType: QueryElement.ValueType = isEnum ? .enum : .default + + if let format = format { + switch format { + case .int32: fallthrough + case .long: fallthrough + case .float: fallthrough + case .double: fallthrough + case .string: fallthrough + case .byte: fallthrough + case .binary: fallthrough + case .boolean: fallthrough + case .password: fallthrough + case .email: fallthrough + case .unsupported: return QueryElement( fieldName: parameter.name, fieldValue: parameter.name.camelized, isOptional: parameter.required == false, valueType: valueType ) + case .date: fallthrough + case .dateTime: + return QueryElement( + fieldName: parameter.name, + fieldValue: parameter.name.camelized, + isOptional: parameter.required == false, + valueType: .date + ) } - case .array(let items, let collectionFormat, maxItems: _, minItems: _, uniqueItems: _): + } else { + return QueryElement( + fieldName: parameter.name, + fieldValue: parameter.name.camelized, + isOptional: parameter.required == false, + valueType: valueType + ) + } + case .array(let items, let collectionFormat, _, _, _): + switch items { + case .node(let items): if case .string(_, let enumValues, _, _, _) = items.type { if enumValues != nil { return QueryElement( @@ -196,30 +201,48 @@ public struct APIRequestFactory { ) } } - + return QueryElement( fieldName: parameter.name, fieldValue: parameter.name.camelized, isOptional: parameter.required == false, valueType: .array(isEnum: false, collectionFormat: collectionFormat) ) - case .number: fallthrough - case .integer: fallthrough - case .boolean: fallthrough - case .file: + case .reference(let reference): + let schema = try swagger.findSchema(reference: reference) + + if case .string(_, let enumValues, _, _, _, _) = schema.type { + if enumValues != nil { + return QueryElement( + fieldName: parameter.name, + fieldValue: parameter.name.camelized, + isOptional: parameter.required == false, + valueType: .array(isEnum: true, collectionFormat: collectionFormat) + ) + } + } + return QueryElement( fieldName: parameter.name, fieldValue: parameter.name.camelized, isOptional: parameter.required == false, - valueType: .default + valueType: .array(isEnum: false, collectionFormat: collectionFormat) ) } - default: - fatalError("This should not happen") + case .number: fallthrough + case .integer: fallthrough + case .boolean: fallthrough + case .file: + return QueryElement( + fieldName: parameter.name, + fieldValue: parameter.name.camelized, + isOptional: parameter.required == false, + valueType: .default + ) } + default: + fatalError("This should not happen") } - - return queries } private func consumeMimeType(forOperation operation: SwaggerSwiftML.Operation, swagger: Swagger, httpMethod: String, servicePath: String) throws -> APIRequestConsumes { diff --git a/Sources/SwaggerSwiftCore/API Request Factory/RequestParameterFactory.swift b/Sources/SwaggerSwiftCore/API Request Factory/RequestParameterFactory.swift index e6a8cfb..27e7d96 100644 --- a/Sources/SwaggerSwiftCore/API Request Factory/RequestParameterFactory.swift +++ b/Sources/SwaggerSwiftCore/API Request Factory/RequestParameterFactory.swift @@ -19,9 +19,7 @@ public struct RequestParameterFactory { /// - swaggerFile: the swagger file /// - Returns: the list of all parameters to the API request func make(forOperation operation: SwaggerSwiftML.Operation, functionName: String, responseTypes: [ResponseTypeMap], pathParameters: [Parameter], swagger: Swagger, swaggerFile: SwaggerFile) throws -> ([FunctionParameter], [ModelDefinition], ReturnType) { - let parameters: [Parameter] = (operation.parameters ?? []).map { - swagger.findParameter(node: $0) - } + pathParameters + let parameters: [Parameter] = try (operation.parameters ?? []).map(swagger.findParameter(node:)) + pathParameters var resolvedParameters = [FunctionParameter]() var resolvedModelDefinitions = [ModelDefinition]() @@ -60,7 +58,7 @@ public struct RequestParameterFactory { resolvedModelDefinitions.append(contentsOf: queryModels) // Body - if let (bodyParameter, bodyModels) = resolveBodyParameters(parameters: parameters, + if let (bodyParameter, bodyModels) = try resolveBodyParameters(parameters: parameters, typePrefix: typeName, namespace: swagger.serviceName, swagger: swagger) { @@ -279,7 +277,7 @@ public struct RequestParameterFactory { return (functionParameters, modelDefinitions) } - private func resolveBodyParameters(parameters: [SwaggerSwiftML.Parameter], typePrefix: String, namespace: String, swagger: Swagger) -> (FunctionParameter, [ModelDefinition])? { + private func resolveBodyParameters(parameters: [SwaggerSwiftML.Parameter], typePrefix: String, namespace: String, swagger: Swagger) throws -> (FunctionParameter, [ModelDefinition])? { var schemaNode: Node? var parameter: Parameter? for param in parameters { @@ -296,7 +294,7 @@ public struct RequestParameterFactory { switch schemaNode { case .node(let schema): - let resolvedType = modelTypeResolver.resolve(forSchema: schema, + let resolvedType = try modelTypeResolver.resolve(forSchema: schema, typeNamePrefix: typePrefix, namespace: namespace, swagger: swagger) @@ -310,13 +308,8 @@ public struct RequestParameterFactory { return (param, resolvedType.inlineModelDefinitions) case .reference(let reference): - guard let schema = swagger.findSchema(reference: reference) else { - return nil - } - - guard let modelDefinition = ModelReference(rawValue: reference) else { - return nil - } + let schema = try swagger.findSchema(reference: reference) + let modelDefinition = try ModelReference(rawValue: reference) let type = schema.type(named: modelDefinition.typeName) diff --git a/Sources/SwaggerSwiftCore/Extensions/DataFormat.swift b/Sources/SwaggerSwiftCore/Extensions/DataFormat.swift index 045cc30..6457ae9 100644 --- a/Sources/SwaggerSwiftCore/Extensions/DataFormat.swift +++ b/Sources/SwaggerSwiftCore/Extensions/DataFormat.swift @@ -1,6 +1,6 @@ import SwaggerSwiftML -extension DataFormat: CustomStringConvertible { +extension DataFormat: @retroactive CustomStringConvertible { public var description: String { switch self { case .int32: diff --git a/Sources/SwaggerSwiftCore/Extensions/Schema.swift b/Sources/SwaggerSwiftCore/Extensions/Schema.swift index 514ecb8..309e082 100644 --- a/Sources/SwaggerSwiftCore/Extensions/Schema.swift +++ b/Sources/SwaggerSwiftCore/Extensions/Schema.swift @@ -34,7 +34,7 @@ extension Schema { case .boolean(let defaultValue): return .boolean(defaultValue: defaultValue) case .array: - return .array(typeName: .object(typeName: name)) + return .array(type: .object(typeName: name)) case .object: return .object(typeName: name) case .freeform: diff --git a/Sources/SwaggerSwiftCore/Extensions/Swagger.swift b/Sources/SwaggerSwiftCore/Extensions/Swagger.swift index 5195319..7cee2df 100644 --- a/Sources/SwaggerSwiftCore/Extensions/Swagger.swift +++ b/Sources/SwaggerSwiftCore/Extensions/Swagger.swift @@ -1,6 +1,10 @@ import Foundation import SwaggerSwiftML +struct NotFound: Error { + let reference: String +} + extension Swagger { /// The name of the service the Swagger file exposes var serviceName: String { @@ -10,7 +14,7 @@ extension Swagger { .joined() } - func findParameter(node: Node) -> Parameter { + func findParameter(node: Node) throws -> Parameter { switch node { case .reference(let reference): for (key, value) in self.parameters ?? [:] { @@ -20,13 +24,13 @@ extension Swagger { } } - fatalError("Failed to find parameter named: \(reference)") + throw NotFound(reference: reference) case .node(let node): return node } } - func findSchema(reference: String) -> Schema? { + func findSchema(reference: String) throws -> Schema { for (key, value) in self.definitions ?? [:] { let searchName = "#/definitions/\(key)" if reference == searchName { @@ -39,13 +43,13 @@ extension Swagger { if reference == searchName, let schemaNode = value.schema { switch schemaNode { case .reference(let reference): - return findSchema(reference: reference) + return try findSchema(reference: reference) case .node(let schema): return schema } } } - return nil + throw NotFound(reference: reference) } } diff --git a/Sources/SwaggerSwiftCore/Functions/parseRequest.swift b/Sources/SwaggerSwiftCore/Functions/parseRequest.swift index d75bee6..e680d96 100644 --- a/Sources/SwaggerSwiftCore/Functions/parseRequest.swift +++ b/Sources/SwaggerSwiftCore/Functions/parseRequest.swift @@ -1,6 +1,6 @@ import SwaggerSwiftML -func parse(request requestNode: Node, httpMethod: HTTPMethod, servicePath: String, statusCode: Int, swagger: Swagger, modelTypeResolver: ModelTypeResolver) -> (TypeType, [ModelDefinition])? { +func parse(request requestNode: Node, httpMethod: HTTPMethod, servicePath: String, statusCode: Int, swagger: Swagger, modelTypeResolver: ModelTypeResolver) throws -> (TypeType, [ModelDefinition])? { let requestName = servicePath .replacingOccurrences(of: "{", with: "") .replacingOccurrences(of: "}", with: "") @@ -16,10 +16,7 @@ func parse(request requestNode: Node, httpMethod: HTTPM let request: SwaggerSwiftML.Response switch requestNode { case .reference(let reference): - guard let modelReference = ModelReference(rawValue: reference) else { - log("[\(swagger.serviceName) \(httpMethod) \(servicePath)]: Failed to parse reference: \(reference)") - return nil - } + let modelReference = try ModelReference(rawValue: reference) switch modelReference { case .definitions: @@ -44,20 +41,14 @@ func parse(request requestNode: Node, httpMethod: HTTPM if let schemaNode = request.schema { switch schemaNode { case .node(let schema): - let resolvedType = modelTypeResolver.resolve(forSchema: schema, + let resolvedType = try modelTypeResolver.resolve(forSchema: schema, typeNamePrefix: prefix, namespace: swagger.serviceName, swagger: swagger) return (resolvedType.propertyType, resolvedType.inlineModelDefinitions) case .reference(let ref): - guard let schema = swagger.findSchema(reference: ref) else { - log("[\(swagger.serviceName) \(httpMethod) \(servicePath)] Failed to find definition named: \(ref)", error: true) - return nil - } - - guard let modelReference = ModelReference(rawValue: ref) else { - return nil - } + let schema = try swagger.findSchema(reference: ref) + let modelReference = try ModelReference(rawValue: ref) let resolvedType = schema.type(named: modelReference.typeName) diff --git a/Sources/SwaggerSwiftCore/Generator/Generator.swift b/Sources/SwaggerSwiftCore/Generator/Generator.swift index 13cbc6a..c8bbadb 100644 --- a/Sources/SwaggerSwiftCore/Generator/Generator.swift +++ b/Sources/SwaggerSwiftCore/Generator/Generator.swift @@ -217,12 +217,14 @@ public struct Generator { } 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) + 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) diff --git a/Sources/SwaggerSwiftCore/Model Type Resolver/ModelTypeResolver.swift b/Sources/SwaggerSwiftCore/Model Type Resolver/ModelTypeResolver.swift index 4af7a47..54db60b 100644 --- a/Sources/SwaggerSwiftCore/Model Type Resolver/ModelTypeResolver.swift +++ b/Sources/SwaggerSwiftCore/Model Type Resolver/ModelTypeResolver.swift @@ -23,7 +23,7 @@ public struct ModelTypeResolver { /// - namespace: the namespace of any inline type /// - swagger: the swagger spec /// - Returns:the resolved models - func resolve(forSchema schema: SwaggerSwiftML.Schema, typeNamePrefix: String, namespace: String, swagger: Swagger) -> ResolvedModel { + func resolve(forSchema schema: SwaggerSwiftML.Schema, typeNamePrefix: String, namespace: String, swagger: Swagger) throws -> ResolvedModel { switch schema.type { case .string(let format, let enumValues, _, _, _, let defaultValue): let type = StringResolver.resolve(format: format, @@ -56,15 +56,15 @@ public struct ModelTypeResolver { let type = BooleanResolver.resolve(with: defaultValue) return .init(type) case .array(let items, _, _, _, _): - let (type, inlineModels) = typeOfItems(schema: schema, + let (type, inlineModels) = try typeOfItems(schema: schema, items: items, typeNamePrefix: "\(typeNamePrefix)Item", namespace: namespace, swagger: swagger) - return .init(.array(typeName: type), inlineModels) + return .init(.array(type: type), inlineModels) case .object(let properties, let allOf): - let resolvedType = objectModelFactory.make(properties: properties, + let resolvedType = try objectModelFactory.make(properties: properties, requiredProperties: [], allOf: allOf, swagger: swagger, @@ -78,13 +78,10 @@ public struct ModelTypeResolver { case .any: return .init(.object(typeName: "[String: AdditionalProperty]")) case .reference(let reference): - guard let modelReference = ModelReference(rawValue: reference) else { - return .init(.void) - } - + let modelReference = try ModelReference(rawValue: reference) return .init(.object(typeName: "[String: " + modelReference.typeName + "]")) case .schema(let schema): - let resolvedType = self.resolve(forSchema: schema, + let resolvedType = try self.resolve(forSchema: schema, typeNamePrefix: typeNamePrefix, namespace: namespace, swagger: swagger) @@ -98,24 +95,22 @@ public struct ModelTypeResolver { } } - private func typeOfItems(schema: Schema, items: Node, typeNamePrefix: String, namespace: String, swagger: Swagger) -> (TypeType, [ModelDefinition]) { + private func typeOfItems(schema: Schema, items: Node, typeNamePrefix: String, namespace: String, swagger: Swagger) throws -> (TypeType, [ModelDefinition]) { switch items { case .reference(let reference): - guard let schema = swagger.findSchema(reference: reference) else { - log("[\(swagger.serviceName)] Could not resolve reference: \(reference) - are you sure it exists?", error: true) - return (.void, []) - } + let schema = try swagger.findSchema(reference: reference) switch schema.type { case .object: - if let typeName = ModelReference(rawValue: reference)?.typeName { - return (.object(typeName: swagger.serviceName + "." + typeName), []) - } else { - log("[\(swagger.serviceName)] Invalid reference found: \(reference)", error: true) - return (.void, []) - } + let typeName = try ModelReference(rawValue: reference).typeName + return (.object(typeName: swagger.serviceName + "." + typeName), []) case .string: - let resolved = resolve(forSchema: schema, typeNamePrefix: typeNamePrefix, namespace: namespace, swagger: swagger) + let resolved = try resolve( + forSchema: schema, + typeNamePrefix: typeNamePrefix, + namespace: namespace, + swagger: swagger + ) return (resolved.propertyType, resolved.inlineModelDefinitions) default: log("[\(swagger.serviceName)] Unsupported schema type: \(schema.type)") @@ -177,13 +172,13 @@ public struct ModelTypeResolver { case .boolean: return (.boolean(defaultValue: nil), []) case .array(let items, collectionFormat: _, maxItems: _, minItems: _, uniqueItems: _): - return typeOfItems(schema: schema, + return try typeOfItems(schema: schema, items: Node.node(items), typeNamePrefix: typeNamePrefix, namespace: namespace, swagger: swagger) case .object(let required, let properties, let allOf): - return objectModelFactory.make( + return try objectModelFactory.make( properties: properties, requiredProperties: required, allOf: allOf, diff --git a/Sources/SwaggerSwiftCore/Model Type Resolver/ObjectModelFactory.swift b/Sources/SwaggerSwiftCore/Model Type Resolver/ObjectModelFactory.swift index a450cfa..882bd82 100644 --- a/Sources/SwaggerSwiftCore/Model Type Resolver/ObjectModelFactory.swift +++ b/Sources/SwaggerSwiftCore/Model Type Resolver/ObjectModelFactory.swift @@ -8,27 +8,27 @@ private struct AllOfPart { public class ObjectModelFactory { public var modelTypeResolver: ModelTypeResolver! - + public init() { } - - func make(properties: [String: Node], requiredProperties: [String], allOf: [Node]?, swagger: Swagger, typeNamePrefix: String, schema: Schema, namespace: String, customFields: [String: String]) -> (TypeType, [ModelDefinition]) { + + func make(properties: [String: Node], requiredProperties: [String], allOf: [Node]?, swagger: Swagger, typeNamePrefix: String, schema: Schema, namespace: String, customFields: [String: String]) throws -> (TypeType, [ModelDefinition]) { let typeName = (customFields["x-override-name"] ?? schema.overridesName ?? typeNamePrefix) .modelNamed .split(separator: ".").map { String($0).uppercasingFirst }.joined() - + let propertyTypeName = "\(namespace).\(typeName)" let newNamespace = namespace + "." + typeName - + if let allOf = allOf, allOf.count > 0 { - let allOfParts: [AllOfPart] = parseAllOf(allOf: allOf, - typeName: typeName, - namespace: namespace, - swagger: swagger) - + let allOfParts: [AllOfPart] = try parseAllOf(allOf: allOf, + typeName: typeName, + namespace: namespace, + swagger: swagger) + let embedddedDefinitions = allOfParts.flatMap { $0.embedddedDefinitions } let inherits = allOfParts.compactMap { $0.typeName } - + let model = Model(description: schema.description, typeName: typeName, fields: allOfParts.flatMap { $0.fields }, @@ -36,15 +36,15 @@ public class ObjectModelFactory { isInternalOnly: schema.isInternalOnly, embeddedDefinitions: embedddedDefinitions, isCodable: true) - + return (.object(typeName: propertyTypeName), [.object(model)]) } else { // when parsing the properties (fields) of a schema, there can be embedded models associated with the field - let (resolvedProperties, inlineModels) = resolveProperties(properties: properties, - withRequiredProperties: requiredProperties + schema.required, - namespace: newNamespace, - swagger: swagger) - + let (resolvedProperties, inlineModels) = try resolveProperties(properties: properties, + withRequiredProperties: requiredProperties + schema.required, + namespace: newNamespace, + swagger: swagger) + let model = Model(description: schema.description, typeName: typeName, fields: resolvedProperties.sorted(by: { $0.safePropertyName < $1.safePropertyName }), @@ -52,42 +52,37 @@ public class ObjectModelFactory { isInternalOnly: schema.isInternalOnly, embeddedDefinitions: inlineModels, isCodable: true) - + return (.object(typeName: propertyTypeName), [.object(model)]) } } - - private func resolveProperties(properties: [String: Node], withRequiredProperties requiredProperties: [String], namespace: String, swagger: Swagger) -> ([ModelField], [ModelDefinition]) { + + private func resolveProperties(properties: [String: Node], withRequiredProperties requiredProperties: [String], namespace: String, swagger: Swagger) throws -> ([ModelField], [ModelDefinition]) { var totalFields = [ModelField]() var totalModels = [ModelDefinition]() - + for (name, schemaNode) in properties { - if let (field, models) = resolveProperty(named: name, - namespace: namespace, - schemaNode: schemaNode, - withRequiredProperties: requiredProperties, - swagger: swagger) { + if let (field, models) = try resolveProperty(named: name, + namespace: namespace, + schemaNode: schemaNode, + withRequiredProperties: requiredProperties, + swagger: swagger) { totalFields.append(field) totalModels.append(contentsOf: models) } } - + return (totalFields, totalModels) } - - private func resolveProperty(named: String, namespace: String, schemaNode: Node, withRequiredProperties requiredProperties: [String], swagger: Swagger) -> (ModelField, [ModelDefinition])? { + + private func resolveProperty(named: String, namespace: String, schemaNode: Node, withRequiredProperties requiredProperties: [String], swagger: Swagger) throws -> (ModelField, [ModelDefinition])? { let isRequired = requiredProperties.contains(named) - + switch schemaNode { case .reference(let reference): - guard let schema = swagger.findSchema(reference: reference) else { - return nil - } - - guard let modelReference = ModelReference(rawValue: reference) else { - return nil - } - + let schema = try swagger.findSchema(reference: reference) + let modelReference = try ModelReference(rawValue: reference) + var typeName: String if modelReference.typeName == "Type" { typeName = "Type\(named.uppercasingFirst)" @@ -96,10 +91,10 @@ public class ObjectModelFactory { } else { typeName = modelReference.typeName } - + // since this is a referenced object is must be something that is available from the top level nested types typeName = "\(swagger.serviceName).\(typeName)" - + // since this is a referenced object that is globally defined, // then the global reference will always be a typealias (or an object), and // can therefore be refered to as an object type @@ -107,130 +102,124 @@ public class ObjectModelFactory { type: .object(typeName: typeName), name: named, isRequired: isRequired) - + return (field, []) case .node(let schema): var fieldTypeName = "\(named.uppercasingFirst)" if fieldTypeName == "Type" { fieldTypeName = "\(fieldTypeName)\(named.uppercasingFirst)" } - + fieldTypeName = fieldTypeName.modelNamed - - let resolvedType = modelTypeResolver.resolve(forSchema: schema, - typeNamePrefix: fieldTypeName, - namespace: namespace, - swagger: swagger) - + + let resolvedType = try modelTypeResolver.resolve(forSchema: schema, + typeNamePrefix: fieldTypeName, + namespace: namespace, + swagger: swagger) + let modelField = ModelField(description: schema.description, type: resolvedType.propertyType, name: named, isRequired: isRequired || schema.required.contains(named)) - + return (modelField, resolvedType.inlineModelDefinitions) } } - - private func parseAllOf(allOf: [Node], typeName: String, namespace: String, swagger: Swagger) -> [AllOfPart] { + + private func parseAllOf(allOf: [Node], typeName: String, namespace: String, swagger: Swagger) throws -> [AllOfPart] { var allOfParts = [AllOfPart]() - + for allOf in allOf { switch allOf { case .reference(let reference): - guard let schema = swagger.findSchema(reference: reference) else { - continue - } - + let schema = try swagger.findSchema(reference: reference) + guard case SchemaType.object = schema.type else { log("[\(typeName)] Non object type received as a part of an all of statement", error: true) continue } - - guard let modelReference = ModelReference(rawValue: reference) else { - continue - } - + + let modelReference = try ModelReference(rawValue: reference) + let part = AllOfPart(typeName: modelReference.typeName, fields: [], embedddedDefinitions: []) - + allOfParts.append(part) case .node(let schema): guard case let SchemaType.object(properties, allOfItems) = schema.type else { fatalError("Not implemented") } - + if let allOfItems = allOfItems, allOfItems.count > 0 { log("There is allOf items present but it is not currently supported") } - + var allFields = [ModelField]() var allInlineModels = [ModelDefinition]() - + for property in properties { - if let (field, inlineModels) = parseAllOfPartProperty(named: property.key, - propertySchema: property.value, - requiredProperties: schema.required, - namespace: namespace, - swagger: swagger) { - + if let (field, inlineModels) = try parseAllOfPartProperty(named: property.key, + propertySchema: property.value, + requiredProperties: schema.required, + namespace: namespace, + swagger: swagger) { + allFields.append(field) allInlineModels.append(contentsOf: inlineModels) } } - + let allOfPart = AllOfPart(typeName: nil, fields: allFields, embedddedDefinitions: allInlineModels) - + allOfParts.append(allOfPart) } } - + return allOfParts } - - private func parseAllOfPartProperty(named name: String, propertySchema: Node, requiredProperties: [String], namespace: String, swagger: Swagger) -> (ModelField, [ModelDefinition])? { + + private func parseAllOfPartProperty(named name: String, propertySchema: Node, requiredProperties: [String], namespace: String, swagger: Swagger) throws -> (ModelField, [ModelDefinition])? { let isRequired = requiredProperties.contains(name) - + switch propertySchema { case .reference(let reference): - guard let schema = swagger.findSchema(reference: reference) else { - return nil - } - + let schema = try swagger.findSchema(reference: reference) + let typeName = reference.components(separatedBy: "/").last ?? "" if case SchemaType.object = schema.type { let field = ModelField(description: schema.description, type: .object(typeName: typeName), name: name, isRequired: isRequired) - + return (field, []) } else { - let resolvedType = modelTypeResolver.resolve(forSchema: schema, - typeNamePrefix: typeName, - namespace: namespace, - swagger: swagger) - + let resolvedType = try modelTypeResolver.resolve(forSchema: schema, + typeNamePrefix: typeName, + namespace: namespace, + swagger: swagger) + let field = ModelField(description: schema.description, type: resolvedType.propertyType, name: name, isRequired: isRequired) - + return (field, resolvedType.inlineModelDefinitions) } case .node(let schema): - let resolvedType = modelTypeResolver.resolve(forSchema: schema, - typeNamePrefix: name.modelNamed, - namespace: namespace, - swagger: swagger) - + let resolvedType = try modelTypeResolver.resolve(forSchema: schema, + typeNamePrefix: name.modelNamed, + namespace: namespace, + swagger: swagger) + let field = ModelField(description: schema.description, type: resolvedType.propertyType, name: name, isRequired: isRequired) - + return (field, resolvedType.inlineModelDefinitions) } } diff --git a/Sources/SwaggerSwiftCore/Models/ModelReference.swift b/Sources/SwaggerSwiftCore/Models/ModelReference.swift index d414eb0..5bf4e12 100644 --- a/Sources/SwaggerSwiftCore/Models/ModelReference.swift +++ b/Sources/SwaggerSwiftCore/Models/ModelReference.swift @@ -2,9 +2,9 @@ enum ModelReference { case responses(typeName: String) case definitions(typeName: String) - init?(rawValue: String) { + init(rawValue: String) throws { guard rawValue.hasPrefix("#/") else { - return nil + throw InvalidReference() } let reference = rawValue @@ -12,7 +12,7 @@ enum ModelReference { .split(separator: "/") guard reference.count == 2 else { - return nil + throw InvalidReference() } let type = String(reference[0]) @@ -26,7 +26,7 @@ enum ModelReference { case "responses": self = .responses(typeName: typeName) default: - return nil + throw InvalidReference() } } @@ -37,3 +37,5 @@ enum ModelReference { } } } + +struct InvalidReference: Error { } diff --git a/Sources/SwaggerSwiftCore/Models/TypeType.swift b/Sources/SwaggerSwiftCore/Models/TypeType.swift index 0e3aabd..dd40d2d 100644 --- a/Sources/SwaggerSwiftCore/Models/TypeType.swift +++ b/Sources/SwaggerSwiftCore/Models/TypeType.swift @@ -9,7 +9,7 @@ indirect enum TypeType { case float(defaultValue: Float?) case boolean(defaultValue: Bool?) case int64(defaultValue: Int64?) - case array(typeName: TypeType) + case array(type: TypeType) case object(typeName: String) case enumeration(typeName: String) case date diff --git a/Sources/SwaggerSwiftCore/Static Files/DateDecodingStrategyFile.swift b/Sources/SwaggerSwiftCore/Static Files/DateDecodingStrategyFile.swift index 763f3ca..398f4ad 100644 --- a/Sources/SwaggerSwiftCore/Static Files/DateDecodingStrategyFile.swift +++ b/Sources/SwaggerSwiftCore/Static Files/DateDecodingStrategyFile.swift @@ -1,7 +1,7 @@ let dateDecodingStrategy = """ import Foundation -@Sendable internal func dateDecodingStrategy(_ decoder: Decoder) throws -> Date { +@Sendable package func dateDecodingStrategy(_ decoder: Decoder) throws -> Date { let container = try decoder.singleValueContainer() let stringValue = try container.decode(String.self) diff --git a/Sources/SwaggerSwiftCore/Utilities/ParameterType+ToString.swift b/Sources/SwaggerSwiftCore/Utilities/ParameterType+ToString.swift index a945f8e..4810a62 100644 --- a/Sources/SwaggerSwiftCore/Utilities/ParameterType+ToString.swift +++ b/Sources/SwaggerSwiftCore/Utilities/ParameterType+ToString.swift @@ -59,22 +59,36 @@ extension ParameterType { } case .boolean: return (.boolean(defaultValue: nil), []) - case .array(let items, let collectionFormat, maxItems: _, minItems: _, uniqueItems: _): - let (type, embedddedDefinitions) = try typeOfItems( - items.type, - collectionFormat: collectionFormat, - typePrefix: typePrefix, - swagger: swagger - ) + case .array(let items, let collectionFormat, _, _, _): + switch items { + case .node(let items): + let (type, embedddedDefinitions) = try typeOfItems( + items.type, + collectionFormat: collectionFormat, + typePrefix: typePrefix, + swagger: swagger + ) + + return (.array(type: type), embedddedDefinitions) + case .reference(let reference): + let schema = try swagger.findSchema(reference: reference) + let modelDefinition = try ModelReference(rawValue: reference) + let typeType = schema.type(named: modelDefinition.typeName) + return (.array(type: typeType), []) + } - return (.array(typeName: type), embedddedDefinitions) case .file: return (.object(typeName: "FormData"), []) } } } -private func typeOfItems(_ itemsType: ItemsType, collectionFormat: CollectionFormat, typePrefix: String, swagger: Swagger) throws -> (TypeType, [ModelDefinition]) { +private func typeOfItems( + _ itemsType: ItemsType, + collectionFormat: CollectionFormat, + typePrefix: String, + swagger: Swagger +) throws -> (TypeType, [ModelDefinition]) { switch itemsType { case .string(format: let format, let enumValues, _, _, _): let modelDefinitions: [ModelDefinition]