diff --git a/.gitignore b/.gitignore index 412279e..07e34ad 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build .swiftpm/ *.xcodeproj *~ +.vscode/ \ No newline at end of file diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index d1b700e..0000000 --- a/Package.resolved +++ /dev/null @@ -1,34 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "OpenAPIKit", - "repositoryURL": "https://github.com/mattpolzin/OpenAPIKit.git", - "state": { - "branch": null, - "revision": "0c668112f9803c1858ed070f9e8ce84922a0b41f", - "version": "3.0.0-alpha.4" - } - }, - { - "package": "SwaggerParser", - "repositoryURL": "https://github.com/tachyonics/SwaggerParser.git", - "state": { - "branch": null, - "revision": "c0eb70485c59a9d7dd6765dcd303b49fdb3040ee", - "version": "0.6.4" - } - }, - { - "package": "Yams", - "repositoryURL": "https://github.com/jpsim/Yams.git", - "state": { - "branch": null, - "revision": "9ff1cc9327586db4e0c8f46f064b6a82ec1566fa", - "version": "4.0.6" - } - } - ] - }, - "version": 1 -} diff --git a/Package.swift b/Package.swift index 1265449..540e67f 100644 --- a/Package.swift +++ b/Package.swift @@ -30,55 +30,29 @@ let package = Package( .library( name: "ServiceModelGenerate", targets: ["ServiceModelGenerate"]), - .library( - name: "SwaggerServiceModel", - targets: ["SwaggerServiceModel"]), - .library( - name: "OpenAPIServiceModel", - targets: ["OpenAPIServiceModel"]), - ], - dependencies: [ - .package(url: "https://github.com/tachyonics/SwaggerParser.git", from: "0.6.4"), - .package(url: "https://github.com/jpsim/Yams.git", from: "4.0.0"), - .package(url: "https://github.com/mattpolzin/OpenAPIKit.git", from: "3.0.0-alpha.4"), ], + dependencies: [], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages which this package depends on. .target( name: "ServiceModelCodeGeneration", dependencies: [ - .target(name: "ServiceModelEntities"), - ] - ), - .target( - name: "ServiceModelEntities", dependencies: [ - ] - ), - .target( - name: "SwaggerServiceModel", dependencies: [ - .target(name: "ServiceModelCodeGeneration"), - .product(name: "Yams", package: "Yams"), - .product(name: "SwaggerParser", package: "SwaggerParser"), - .product(name: "OpenAPIKit30" , package: "OpenAPIKit"), + .target(name: "ServiceModelEntities") ] ), .target( - name: "OpenAPIServiceModel", dependencies: [ - .target(name: "ServiceModelCodeGeneration"), - .product(name: "Yams", package: "Yams"), - .product(name: "SwaggerParser", package: "SwaggerParser"), - .product(name: "OpenAPIKit" , package: "OpenAPIKit"), - ] + name: "ServiceModelEntities", + dependencies: [] ), .target( - name: "ServiceModelGenerate", dependencies: [ - .target(name: "SwaggerServiceModel"), - .target(name: "ServiceModelEntities") + name: "ServiceModelGenerate", + dependencies: [ + .target(name: "ServiceModelEntities"), + .target(name: "ServiceModelCodeGeneration") ] ), .testTarget( - name: "ServiceModelEntitiesTests", dependencies: [ - .target(name: "ServiceModelEntities"), + name: "ServiceModelEntitiesTests", + dependencies: [ + .target(name: "ServiceModelEntities") ] ), ], diff --git a/README.md b/README.md index 33c8e06..938e839 100644 --- a/README.md +++ b/README.md @@ -189,8 +189,7 @@ public struct MyCodeGeneration { ## The ServiceModel Protocol The `ServiceModel` protocol represents the parsed service model and provides access to descriptions of -the operations, fields and errors. This library provides `SwaggerServiceModel` that conforms to this protocol -and will parse a Swagger 2.0 specification file. +the operations, fields and errors. ## The ModelClientDelegate protocol diff --git a/Sources/OpenAPIServiceModel/CreateOpenAPIServiceModel.swift b/Sources/OpenAPIServiceModel/CreateOpenAPIServiceModel.swift deleted file mode 100644 index ae950c7..0000000 --- a/Sources/OpenAPIServiceModel/CreateOpenAPIServiceModel.swift +++ /dev/null @@ -1,412 +0,0 @@ -// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). -// You may not use this file except in compliance with the License. -// A copy of the License is located at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. -// -// CreateOpenAPIServiceModel.swift -// OpenAPIServiceModel -// - -import Foundation -import OpenAPIKit30 -import ServiceModelEntities -import ServiceModelCodeGeneration -import Yams - -let nonErrorCodeRange = 200...299 - -internal extension OpenAPIServiceModel { - struct OperationInputMembers { - var queryMembers: [String: Member] = [:] - var additionalHeaderMembers: [String: Member] = [:] - var pathMembers: [String: Member] = [:] - } - - static func filterOperations(operations: [OpenAPI.HttpMethod: OpenAPI.Operation], - modelOverride: ModelOverride?) -> [OpenAPI.HttpMethod: OpenAPI.Operation] { - - guard let ignoreOperations = modelOverride?.ignoreOperations else { - return operations - } - - var filteredOperations: [OpenAPI.HttpMethod: OpenAPI.Operation] = [:] - - operations.forEach { (key, value) in - if ignoreOperations.contains("*.*") { - return - } - - if ignoreOperations.contains("*.\(key.rawValue)") { - return - } - - if let identifier = value.operationId { - if ignoreOperations.contains("\(identifier).\(key.rawValue)") { - return - } - - if ignoreOperations.contains("\(identifier).*") { - return - } - } - - filteredOperations[key] = value - } - - return filteredOperations - } - - static func createOpenAPIModel(definition: OpenAPI.Document, modelOverride: ModelOverride?) -> OpenAPIServiceModel { - var model = OpenAPIServiceModel() - - model.serviceInformation = ServiceInformation( - title: definition.info.title, - description: definition.info.description, - version: definition.info.version) - - for (name, schema) in definition.components.schemas { - var enclosingEntityName = name.rawValue - parseDefinitionSchemas(model: &model, enclosingEntityName: &enclosingEntityName, - schema: schema, modelOverride: modelOverride, document: definition) - } - - for (path, pathDefinition) in definition.paths { - var operations:[OpenAPI.HttpMethod: OpenAPI.Operation] = [:] - - pathDefinition.endpoints.forEach { endpoint in - operations[endpoint.method] = endpoint.operation - } - - let filteredOperations = filterOperations(operations: operations, - modelOverride: modelOverride) - - for (type, operation) in filteredOperations { - guard let operationId = operation.operationId else { - continue - } - - // if there is more than one operation for this path - let operationName: String - if filteredOperations.count > 1 { - operationName = operationId + type.rawValue.lowercased().startingWithUppercase - } else { - operationName = operationId - } - - let inputDescription = - OperationInputDescription(defaultInputLocation: .body) - - var operationDescription = OperationDescription( - inputDescription: inputDescription, - outputDescription: OperationOutputDescription()) - operationDescription.httpUrl = path.rawValue - operationDescription.httpVerb = type.rawValue.uppercased() - - parseOperation(description: &operationDescription, - operationName: operationName, - model: &model, operation: operation, - modelOverride: modelOverride, document: definition) - - model.operationDescriptions[operationName] = operationDescription - } - } - - return model - } - - static func parseOperation(description: inout OperationDescription, - operationName: String, - model: inout OpenAPIServiceModel, - operation: OpenAPI.Operation, - modelOverride: ModelOverride?, - document: OpenAPI.Document) { - let (members, bodyStructureName) = getOperationMembersAndBodyStructureName( - operation: operation, - operationName: operationName, - model: &model, - modelOverride: modelOverride, document: document) - - setOperationInput(bodyStructureName: bodyStructureName, operationInputMembers: members, - description: &description, model: &model, operationName: operationName) - - setOperationOutput(operation: operation, operationName: operationName, model: &model, - modelOverride: modelOverride, description: &description, document: document) - } - - static func getOperationMembersAndBodyStructureName( - operation: OpenAPI.Operation, - operationName: String, - model: inout OpenAPIServiceModel, - modelOverride: ModelOverride?, document: OpenAPI.Document) -> (members: OperationInputMembers, bodyStructureName: String?) { - var members = OperationInputMembers() - var bodyStructureName: String? - - if let requestBody = operation.requestBody { - switch requestBody { - case .a: - fatalError("Unsupported request body reference.") - case .b(let request): - getBodyOperationMembers(request, bodyStructureName: &bodyStructureName, - operationName: operationName, model: &model, modelOverride: modelOverride, document: document) - } - } - - for (index, parameter) in operation.parameters.enumerated() { - switch parameter { - case .b(let parameterValue): - if let schemaValue = parameterValue.schemaOrContent.schemaValue { - getFixedFieldsOperationMembers(fixedFields: parameterValue, operationName: operationName, - index: index, members: &members, items: schemaValue, - model: &model, modelOverride: modelOverride) - } - case .a(_): - fatalError("Not implemented.") - } - } - - return (members: members, bodyStructureName: bodyStructureName) - } - - static func getBodyOperationMembers(_ request: OpenAPI.Request, bodyStructureName: inout String?, - operationName: String, model: inout OpenAPIServiceModel, - modelOverride: ModelOverride?, document: OpenAPI.Document) { - for (_, content) in request.content { - if let either = content.schema { - switch either { - case .a(let reference): - if let refName = reference.name { - bodyStructureName = refName - } - case .b(let schema): - switch schema.value { - case .object: - var enclosingEntityName = "\(operationName)RequestBody" - var structureDescription = StructureDescription() - guard let objectContext = schema.objectContext else { - continue - } - parseObjectSchema(structureDescription: &structureDescription, enclosingEntityName: &enclosingEntityName, - model: &model, objectContext: objectContext, modelOverride: modelOverride, document: document) - - model.structureDescriptions[enclosingEntityName] = structureDescription - - bodyStructureName = enclosingEntityName - default: - // The schemas object and reference are most widely used in the requestBody field - fatalError("Not implemented.") - } - } - } - } - } - - static func ignoreRequestHeader(operationName: String, headerName: String, - modelOverride: ModelOverride?) -> Bool { - - guard let ignoreRequestHeaders = modelOverride?.ignoreRequestHeaders else { - return false - } - - if ignoreRequestHeaders.contains("*.*") { - return true - } - - if ignoreRequestHeaders.contains("*.\(headerName)") { - return true - } - - if ignoreRequestHeaders.contains("\(operationName).\(headerName)") { - return true - } - - if ignoreRequestHeaders.contains("\(operationName).*") { - return true - } - - return false - } - - static func getFixedFieldsOperationMembers(fixedFields: OpenAPI.Parameter, operationName: String, - index: Int, members: inout OpenAPIServiceModel.OperationInputMembers, - items: JSONSchema, model: inout OpenAPIServiceModel, modelOverride: ModelOverride?) { - let typeName = fixedFields.name.safeModelName().startingWithUppercase - - let fieldName = "\(operationName)Request\(typeName)" - let member = Member(value: fieldName, - position: index, - required: fixedFields.required, - documentation: fixedFields.description) - switch fixedFields.location { - case .query: - members.queryMembers[fixedFields.name] = member - case .path: - members.pathMembers[fixedFields.name] = member - case .header: - guard !ignoreRequestHeader(operationName: operationName, headerName: fixedFields.name, modelOverride: modelOverride) else { - return - } - - members.additionalHeaderMembers[fixedFields.name] = member - default: - break - } - - addField(item: items, fieldName: fieldName, - model: &model, modelOverride: modelOverride) - } - - static func addOperationResponseFromSchema(schema: JSONSchema, operationName: String, forCode code: Int, index: Int?, - description: inout OperationDescription, - model: inout OpenAPIServiceModel, modelOverride: ModelOverride?, document: OpenAPI.Document) { - switch schema.value { - case .one(let subschemas, _): - for (index, subschema) in subschemas.enumerated() { - switch subschema.value { - case .reference(let ref, _): - addOperationResponseFromReference(reference: ref, operationName: operationName, forCode: code, - index: index, description: &description, - model: &model, modelOverride: modelOverride) - default: - addOperationResponseFromSchema(schema: subschema, operationName: operationName, forCode: code, - index: index, description: &description, - model: &model, modelOverride: modelOverride, document: document) - } - } - case .object: - let indexString = index?.description ?? "" - var structureName = "\(operationName)\(code)Response\(indexString)Body" - var structureDescription = StructureDescription() - - if let objectContext = schema.objectContext { - parseObjectSchema(structureDescription: &structureDescription, enclosingEntityName: &structureName, - model: &model, objectContext: objectContext, modelOverride: modelOverride, document: document) - - model.structureDescriptions[structureName] = structureDescription - - if nonErrorCodeRange.contains(code) { - description.output = structureName - } else { - description.errors.append((type: structureName, code: code)) - model.errorTypes.insert(structureName) - } - } - default: - fatalError("Not implemented.") - } - } - - static func addOperationResponseFromReference(reference: JSONReference, operationName: String, forCode code: Int, - index: Int?, description: inout OperationDescription, - model: inout OpenAPIServiceModel, modelOverride: ModelOverride?) { - if let refName = reference.name { - if nonErrorCodeRange.contains(code) { - description.output = refName - } else { - description.errors.append((type: refName, code: code)) - model.errorTypes.insert(refName) - } - } - } - - static func addField(item: JSONSchema, fieldName: String, - model: inout OpenAPIServiceModel, modelOverride: ModelOverride?) { - switch item.value { - case .string(_, let context): - addStringField(metadata: context, - schema: nil, - model: &model, - fieldName: fieldName, - modelOverride: modelOverride) - case .number(_, let context): - model.fieldDescriptions[fieldName] = - Fields.double(rangeConstraint: NumericRangeConstraint( - minimum: context.minimum?.value, - maximum: context.maximum?.value, - exclusiveMinimum: context.minimum?.exclusive ?? false, - exclusiveMaximum: context.maximum?.exclusive ?? false)) - case .integer(let integerFormat, let context): - if integerFormat.format == .int64 { - model.fieldDescriptions[fieldName] = - Fields.long(rangeConstraint: NumericRangeConstraint( - minimum: context.minimum?.value, - maximum: context.maximum?.value, - exclusiveMinimum: context.minimum?.exclusive ?? false, - exclusiveMaximum: context.maximum?.exclusive ?? false)) - } else { - model.fieldDescriptions[fieldName] = - Fields.integer(rangeConstraint: NumericRangeConstraint( - minimum: context.minimum?.value, - maximum: context.maximum?.value, - exclusiveMinimum: context.minimum?.exclusive ?? false, - exclusiveMaximum: context.maximum?.exclusive ?? false)) - } - - - case .boolean: - model.fieldDescriptions[fieldName] = Fields.boolean - default: - fatalError("Field type not supported.") - } - } - - static func getEnumerationValues(metadata: JSONSchemaContext) -> [(name: String, value: String)] { - let enumerationValues: [(name: String, value: String)] - if let allowedValues = metadata.allowedValues { - enumerationValues = allowedValues.filter { allowedValue in allowedValue.value is String } - .compactMap { allowedValue in allowedValue.value as? String } - .map { value in (name: value, value: value) } - } else { - enumerationValues = [] - } - - return enumerationValues - } - - static func addStringField(metadata: JSONSchema.StringContext?, - schema: JSONSchema?, - model: inout OpenAPIServiceModel, - fieldName: String, - modelOverride: ModelOverride?) { - let newValueConstraints: [(name: String, value: String)] - - if modelOverride?.modelStringPatternsAreAlternativeList ?? false, - let regexExpression = metadata?.pattern, - regexExpression.first == "^", - regexExpression.last == "$" { - newValueConstraints = - regexExpression.dropFirst().dropLast().split(separator: "|").map { subString in - let value = String(subString) - return (name: value, value: value) - } - } else { - if let coreContext = schema?.coreContext { - newValueConstraints = getEnumerationValues(metadata: coreContext) - } else { - newValueConstraints = [] - } - } - - // If minLength is 0, the field is optional and does not require validation - if metadata?.minLength == 0 { - model.fieldDescriptions[fieldName] = Fields.string( - regexConstraint: nil, - lengthConstraint: LengthRangeConstraint(minimum: nil, - maximum: metadata?.maxLength ?? nil), - valueConstraints: newValueConstraints) - } else { - model.fieldDescriptions[fieldName] = Fields.string( - regexConstraint: nil, - lengthConstraint: LengthRangeConstraint(minimum: metadata?.minLength ?? nil, - maximum: metadata?.maxLength ?? nil), - valueConstraints: newValueConstraints) - } - } -} diff --git a/Sources/OpenAPIServiceModel/OpenAPIServiceModel.swift b/Sources/OpenAPIServiceModel/OpenAPIServiceModel.swift deleted file mode 100644 index 4d138de..0000000 --- a/Sources/OpenAPIServiceModel/OpenAPIServiceModel.swift +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). -// You may not use this file except in compliance with the License. -// A copy of the License is located at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. -// -// OpenAPIServiceModel.swift -// OpenAPIServiceModel -// - -import Foundation -import OpenAPIKit30 -import ServiceModelEntities -import ServiceModelCodeGeneration -import Yams - -/** - Struct that models the Metadata of the OpenAPI model. - */ -public struct OpenAPIServiceModel: ServiceModel { - public var serviceInformation: ServiceInformation? = nil - public var serviceDescriptions: [String: ServiceDescription] = [:] - public var structureDescriptions: [String: StructureDescription] = [:] - public var operationDescriptions: [String: OperationDescription] = [:] - public var fieldDescriptions: [String: Fields] = [:] - public var errorTypes: Set = [] - public var typeMappings: [String: String] = [:] - public var errorCodeMappings: [String: String] = [:] - - public static func create(data: Data, modelFormat: ModelFormat, - modelOverride: ModelOverride?) throws -> OpenAPIServiceModel { - let definition: OpenAPI.Document - switch modelFormat { - case .yaml: - let decoder = YAMLDecoder() - let dataAsString = String(data: data, encoding: .utf8)! - definition = try decoder.decode(OpenAPI.Document.self, from: dataAsString) - default: - let decoder = JSONDecoder() - definition = try decoder.decode(OpenAPI.Document.self, from: data) - } - - return createOpenAPIModel(definition: definition, modelOverride: modelOverride) - } -} diff --git a/Sources/OpenAPIServiceModel/ParseSchemas.swift b/Sources/OpenAPIServiceModel/ParseSchemas.swift deleted file mode 100644 index 478c657..0000000 --- a/Sources/OpenAPIServiceModel/ParseSchemas.swift +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). -// You may not use this file except in compliance with the License. -// A copy of the License is located at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. -// -// ParseSchemas.swift -// OpenAPIServiceModel -// - -import Foundation -import OpenAPIKit30 -import ServiceModelEntities -import ServiceModelCodeGeneration -import Yams - -internal extension OpenAPIServiceModel { - static func parseDefinitionSchemas(model: inout OpenAPIServiceModel, enclosingEntityName: inout String, - schema: JSONSchema, modelOverride: ModelOverride?, document: OpenAPI.Document) { - switch schema.value { - case .boolean: - model.fieldDescriptions[enclosingEntityName] = .boolean - case .integer(let integerFormat, let integerContext): - if integerFormat.format == .int64 { - model.fieldDescriptions[enclosingEntityName] = Fields.long(rangeConstraint: - NumericRangeConstraint(minimum: integerContext.minimum?.value, - maximum: integerContext.maximum?.value, - exclusiveMinimum: integerContext.minimum?.exclusive ?? false, - exclusiveMaximum: integerContext.maximum?.exclusive ?? false)) - } else { - model.fieldDescriptions[enclosingEntityName] = Fields.integer(rangeConstraint: - NumericRangeConstraint(minimum: integerContext.minimum?.value, - maximum: integerContext.maximum?.value, - exclusiveMinimum: integerContext.minimum?.exclusive ?? false, - exclusiveMaximum: integerContext.maximum?.exclusive ?? false)) - } - - case .object(_ , let objectContext): - if case .b(let mapSchema) = objectContext.additionalProperties { - parseMapDefinitionSchema(mapSchema: mapSchema, - enclosingEntityName: &enclosingEntityName, - model: &model) - } else { - var structureDescription = StructureDescription() - parseObjectSchema(structureDescription: &structureDescription, enclosingEntityName: &enclosingEntityName, - model: &model, objectContext: objectContext, modelOverride: modelOverride, document: document) - - model.structureDescriptions[enclosingEntityName] = structureDescription - } - case .array(_, let arrayContext): - parseArrayDefinitionSchemas(arrayMetadata: arrayContext, enclosingEntityName: &enclosingEntityName, - model: &model, modelOverride: modelOverride, document: document) - case .string(_, let stringContext): - addStringField(metadata: stringContext, schema: schema, - model: &model, fieldName: enclosingEntityName, modelOverride: modelOverride) - case .number(_, let numberContext): - model.fieldDescriptions[enclosingEntityName] = Fields.double(rangeConstraint: - NumericRangeConstraint(minimum: numberContext.minimum?.value, - maximum: numberContext.maximum?.value, - exclusiveMinimum: numberContext.minimum?.exclusive ?? false, - exclusiveMaximum: numberContext.maximum?.exclusive ?? false)) - case .all(let otherSchema, _), .any(let otherSchema, _), .one(let otherSchema, _): - var structureDescription = StructureDescription() - parseOtherSchemas(structureDescription: &structureDescription, enclosingEntityName: &enclosingEntityName, - model: &model, otherSchema: otherSchema, modelOverride: modelOverride, document: document) - case .reference: - break - case .fragment: - fatalError("Schema 'fragment' not implemented") - case .not: - fatalError("Schema 'not' not implemented") - } - } - - static func parseObjectSchema(structureDescription: inout StructureDescription, enclosingEntityName: inout String, - model: inout OpenAPIServiceModel, objectContext: JSONSchema.ObjectContext, - modelOverride: ModelOverride?, document: OpenAPI.Document) { - let sortedKeys = objectContext.properties.keys.sorted(by: <) - - for (index, name) in sortedKeys.enumerated() { - guard let property = objectContext.properties[name] else { - continue - } - switch property.value { - case .reference(let ref, _): - if let referenceName = ref.name { - structureDescription.members[name] = Member(value: referenceName, position: index, - required: objectContext.requiredProperties.contains(name), - documentation: nil) - } - default: - var enclosingEntityNameForProperty = enclosingEntityName + name.startingWithUppercase - parseDefinitionSchemas(model: &model, enclosingEntityName: &enclosingEntityNameForProperty, - schema: property, modelOverride: modelOverride, document: document) - - structureDescription.members[name] = Member(value: enclosingEntityNameForProperty, position: index, - required: objectContext.requiredProperties.contains(name), - documentation: nil) - } - } - } - - static func parseMapDefinitionSchema(mapSchema: JSONSchema, - enclosingEntityName: inout String, - model: inout OpenAPIServiceModel) { - let valueType: String - switch mapSchema.value { - case .reference(let ref, _): - if let valueType = ref.name { - model.fieldDescriptions[enclosingEntityName] = Fields.map( - keyType: "String", valueType: valueType, - lengthConstraint: LengthRangeConstraint()) - } - case .string: - valueType = "String" - model.fieldDescriptions[enclosingEntityName] = Fields.map( - keyType: "String", valueType: valueType, - lengthConstraint: LengthRangeConstraint()) - default: - fatalError("Not implemented") - } - } - - static func parseArrayDefinitionSchemas(arrayMetadata: JSONSchema.ArrayContext, - enclosingEntityName: inout String, - model: inout OpenAPIServiceModel, - modelOverride: ModelOverride?, document: OpenAPI.Document) { - if let items = arrayMetadata.items { - switch items.value { - case .reference(let ref, _): - if let type = ref.name { - let optionalMinItems: Int? - if arrayMetadata.minItems > 0 { - optionalMinItems = arrayMetadata.minItems - } else { - optionalMinItems = nil - } - - let lengthConstraint = LengthRangeConstraint(minimum: optionalMinItems, - maximum: arrayMetadata.maxItems) - model.fieldDescriptions[enclosingEntityName] = Fields.list(type: type, - lengthConstraint: lengthConstraint) - } - default: - var arrayElementEntityName: String - - // If the enclosingEntityName ends in an "s", swap with element name - if enclosingEntityName.suffix(1).lowercased() == "s" { - arrayElementEntityName = String(enclosingEntityName.dropLast()) - } else { - arrayElementEntityName = enclosingEntityName - enclosingEntityName = "\(enclosingEntityName)s" - } - - parseDefinitionSchemas(model: &model, enclosingEntityName: &arrayElementEntityName, - schema: items, modelOverride: modelOverride, document: document) - - let type = arrayElementEntityName - - let optionalMinItems: Int? - if arrayMetadata.minItems > 0 { - optionalMinItems = arrayMetadata.minItems - } else { - optionalMinItems = nil - } - - let lengthConstraint = LengthRangeConstraint(minimum: optionalMinItems, - maximum: arrayMetadata.maxItems) - model.fieldDescriptions[enclosingEntityName] = Fields.list(type: type, - lengthConstraint: lengthConstraint) - } - } - } - - // Parse all, any, one schemas - static func parseOtherSchemas(structureDescription: inout StructureDescription, enclosingEntityName: inout String, - model: inout OpenAPIServiceModel, otherSchema: [JSONSchema], - modelOverride: ModelOverride?, document: OpenAPI.Document) { - for (index, subschema) in otherSchema.enumerated() { - var enclosingEntityNameForProperty = "\(enclosingEntityName)\(index + 1)" - - switch subschema.value { - case .object(_, let objectContext): - parseObjectSchema(structureDescription: &structureDescription, enclosingEntityName: &enclosingEntityNameForProperty, - model: &model, objectContext: objectContext, modelOverride: modelOverride, document: document) - default: - fatalError("Non object/structure allOf schemas are not implemented. \(String(describing: subschema.jsonType))") - } - } - } -} diff --git a/Sources/OpenAPIServiceModel/SetOperationInput.swift b/Sources/OpenAPIServiceModel/SetOperationInput.swift deleted file mode 100644 index 34cedec..0000000 --- a/Sources/OpenAPIServiceModel/SetOperationInput.swift +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). -// You may not use this file except in compliance with the License. -// A copy of the License is located at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. -// -// SetOperationInput.swift -// OpenAPIServiceModel -// - -import Foundation -import OpenAPIKit30 -import ServiceModelEntities -import ServiceModelCodeGeneration -import Yams - -internal extension OpenAPIServiceModel { - struct OperationInputFields { - let allMembers: [String: Member] - let pathFields: [String] - let queryFields: [String] - let bodyFields: [String] - let additionalHeaderFields: [String] - } - - static func setOperationInput(bodyStructureName: String?, - operationInputMembers: OperationInputMembers, - description: inout OperationDescription, - model: inout OpenAPIServiceModel, - operationName: String) { - if let bodyStructureName = bodyStructureName, - operationInputMembers.queryMembers.isEmpty && operationInputMembers.additionalHeaderMembers.isEmpty - && operationInputMembers.pathMembers.isEmpty { - description.input = bodyStructureName - } else { - let operationInputFields = getOperationInputFields(bodyStructureName: bodyStructureName, - operationInputMembers: operationInputMembers, - model: &model) - - let inputModelName = "\(operationName)Request" - let inputModelDescription = "Input model for the \(operationName) operation." - let structureDefinition = StructureDescription(members: operationInputFields.allMembers, - documentation: inputModelDescription) - - model.structureDescriptions[inputModelName] = structureDefinition - description.input = inputModelName - - if !operationInputFields.queryFields.isEmpty { - description.inputDescription = OperationInputDescription( - pathFields: operationInputFields.pathFields, - queryFields: [], - bodyFields: operationInputFields.bodyFields, - additionalHeaderFields: operationInputFields.additionalHeaderFields, - defaultInputLocation: .query, - bodyStructureName: bodyStructureName) - } else { - description.inputDescription = OperationInputDescription( - pathFields: operationInputFields.pathFields, - queryFields: operationInputFields.queryFields, - bodyFields: operationInputFields.bodyFields, - additionalHeaderFields: operationInputFields.additionalHeaderFields, - defaultInputLocation: .body, - bodyStructureName: bodyStructureName) - } - } - } - - static func getOperationInputFields(bodyStructureName: String?, - operationInputMembers: OperationInputMembers, - model: inout OpenAPIServiceModel) -> OperationInputFields { - var allMembers: [String: Member] = [:] - let pathFields: [String] - let queryFields: [String] - let bodyFields: [String] - let additionalHeaderFields: [String] - - if let bodyStructureName = bodyStructureName { - guard let structureDefinition = model.structureDescriptions[bodyStructureName] else { - fatalError("No structure with type \(bodyStructureName)") - } - - allMembers.merge(structureDefinition.members) { (old, _) in old } - bodyFields = [String](structureDefinition.members.keys) - } else { - bodyFields = [] - } - - allMembers.merge(operationInputMembers.queryMembers) { (old, _) in old } - queryFields = [String](operationInputMembers.queryMembers.keys) - - allMembers.merge(operationInputMembers.additionalHeaderMembers) { (old, _) in old } - additionalHeaderFields = [String](operationInputMembers.additionalHeaderMembers.keys) - - allMembers.merge(operationInputMembers.pathMembers) { (old, _) in old } - pathFields = [String](operationInputMembers.pathMembers.keys) - - let sortedMembers = allMembers.sorted { (left, right) in left.key < right.key } - var correctedAllMembers: [String: Member] = [:] - sortedMembers.enumerated().forEach { entry in - let oldMember = entry.element.value - let correctedMember = Member(value: oldMember.value, - position: entry.offset, - locationName: oldMember.locationName, - required: oldMember.required, - documentation: oldMember.documentation) - - correctedAllMembers[entry.element.key] = correctedMember - } - - return OperationInputFields(allMembers: correctedAllMembers, - pathFields: pathFields, - queryFields: queryFields, - bodyFields: bodyFields, - additionalHeaderFields: additionalHeaderFields) - } -} diff --git a/Sources/OpenAPIServiceModel/SetOperationOutput.swift b/Sources/OpenAPIServiceModel/SetOperationOutput.swift deleted file mode 100644 index 5deed58..0000000 --- a/Sources/OpenAPIServiceModel/SetOperationOutput.swift +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). -// You may not use this file except in compliance with the License. -// A copy of the License is located at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. -// -// SetOperationOutput.swift -// OpenAPIServiceModel -// - -import Foundation -import OpenAPIKit30 -import ServiceModelEntities -import ServiceModelCodeGeneration -import Yams - -internal extension OpenAPIServiceModel { - static func filterHeaders(operation: OpenAPI.Operation, code: Int, headers: OpenAPI.Header.Map, - modelOverride: ModelOverride?) -> OpenAPI.Header.Map { - - guard let ignoreResponseHeaders = modelOverride?.ignoreResponseHeaders else { - return headers - } - - var filteredHeaders: OpenAPI.Header.Map = [:] - - headers.forEach { (key, value) in - if ignoreResponseHeaders.contains("*.*.*") { - return - } - - if ignoreResponseHeaders.contains("*.*.\(key)") { - return - } - - if ignoreResponseHeaders.contains("*.\(code).*") { - return - } - - if let identifier = operation.operationId { - if ignoreResponseHeaders.contains("\(identifier).\(code).\(key)") { - return - } - - if ignoreResponseHeaders.contains("\(identifier).*.\(key)") { - return - } - - if ignoreResponseHeaders.contains("\(identifier).\(code).*") { - return - } - } - - filteredHeaders[key] = value - } - - return filteredHeaders - } - - static func setOperationOutput(operation: OpenAPI.Operation, operationName: String, model: inout OpenAPIServiceModel, - modelOverride: ModelOverride?, description: inout OperationDescription, document: OpenAPI.Document) { - for (code, response) in operation.responses { - switch response { - case .b(let value): - switch code.value { - case .status(let code): - var headerMembers: [String: Member] = [:] - - if let headers = value.headers { - let filteredHeaders = filterHeaders(operation: operation, code: code, - headers: headers, modelOverride: modelOverride) - - filteredHeaders.enumerated().forEach { entry in - let typeName = entry.element.key.safeModelName().startingWithUppercase - - let headerName = "\(operationName)\(typeName)Header" - if let header = entry.element.value.b { - if let schema = header.schemaOrContent.schemaValue { - addField(item: schema, fieldName: headerName, model: &model, modelOverride: modelOverride) - let member = Member(value: headerName, - position: entry.offset, - required: false, - documentation: header.description) - headerMembers[entry.element.key] = member - } - } - } - } - - let content = value.content - content.enumerated().forEach { entry in - if let either = entry.element.value.schema { - switch either { - case .a(let ref): - addOperationResponseFromReference(reference: ref, operationName: operationName, forCode: code, - index: nil, description: &description, model: &model, - modelOverride: modelOverride) - case .b(let schema): - addOperationResponseFromSchema(schema: schema, operationName: operationName, forCode: code, - index: nil, description: &description, model: &model, - modelOverride: modelOverride, document: document) - } - } - } - - if !headerMembers.isEmpty { - setOperationOutputWithHeaders(description: &description, model: &model, headerMembers: headerMembers, - operationName: operationName, code: code) - } - - default: - fatalError("Range and Default status code types not implemented.") - } - case .a: - fatalError("Not implemented.") - } - } - } - - private static func setOperationOutputWithHeaders(description: inout OperationDescription, model: inout OpenAPIServiceModel, - headerMembers: [String: Member], operationName: String, code: Int) { - var allMembers: [String: Member] = [:] - let bodyFields: [String] - let headerFields: [String] - let bodyStructureName = description.output - - if let bodyStructureName = bodyStructureName { - guard let structureDefinition = model.structureDescriptions[bodyStructureName] else { - fatalError("No structure with type \(bodyStructureName)") - } - - allMembers.merge(structureDefinition.members) { (old, _) in old } - bodyFields = [String](structureDefinition.members.keys) - } else { - bodyFields = [] - } - - allMembers.merge(headerMembers) { (old, _) in old } - headerFields = [String](headerMembers.keys) - - let sortedMembers = allMembers.sorted { (left, right) in left.key < right.key } - var correctedAllMembers: [String: Member] = [:] - sortedMembers.enumerated().forEach { entry in - let oldMember = entry.element.value - let correctedMember = Member(value: oldMember.value, - position: entry.offset, - locationName: oldMember.locationName, - required: oldMember.required, - documentation: oldMember.documentation) - - correctedAllMembers[entry.element.key] = correctedMember - } - - let outputModelName = "\(operationName)\(code)Response" - let outputModelDescription = "Output model for the \(operationName) operation." - let structureDefinition = StructureDescription(members: correctedAllMembers, - documentation: outputModelDescription) - - model.structureDescriptions[outputModelName] = structureDefinition - description.output = outputModelName - - description.outputDescription = OperationOutputDescription( - bodyFields: bodyFields, - headerFields: headerFields, - bodyStructureName: bodyStructureName, - payloadAsMember: nil) - } -} diff --git a/Sources/ServiceModelGenerate/ServiceModelGenerate.swift b/Sources/ServiceModelGenerate/ServiceModelGenerate.swift index 6e4e355..933da7e 100644 --- a/Sources/ServiceModelGenerate/ServiceModelGenerate.swift +++ b/Sources/ServiceModelGenerate/ServiceModelGenerate.swift @@ -18,8 +18,6 @@ import Foundation import ServiceModelCodeGeneration import ServiceModelEntities -import SwaggerParser -import Yams public struct ServiceModelGenerate { private static func getModelDataForFilePath(modelFilePath: String) -> (data: Data, modelFormat: ModelFormat) { diff --git a/Sources/SwaggerServiceModel/SwaggerServiceModel+createSwaggerModel.swift b/Sources/SwaggerServiceModel/SwaggerServiceModel+createSwaggerModel.swift deleted file mode 100644 index 049a160..0000000 --- a/Sources/SwaggerServiceModel/SwaggerServiceModel+createSwaggerModel.swift +++ /dev/null @@ -1,372 +0,0 @@ -// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). -// You may not use this file except in compliance with the License. -// A copy of the License is located at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. -// -// SwaggerServiceModel+createSwaggerModel.swift -// SwaggerServiceModel -// - -import Foundation -import ServiceModelEntities -import ServiceModelCodeGeneration -import SwaggerParser -import Yams - -internal extension SwaggerServiceModel { - struct OperationInputMembers { - var queryMembers: [String: Member] = [:] - var additionalHeaderMembers: [String: Member] = [:] - var pathMembers: [String: Member] = [:] - } - - static func filterOperations(operations: [OperationType: SwaggerParser.Operation], - modelOverride: ModelOverride?) -> [OperationType: SwaggerParser.Operation] { - - guard let ignoreOperations = modelOverride?.ignoreOperations else { - // no filtering required - return operations - } - - var filteredOperations: [OperationType: SwaggerParser.Operation] = [:] - - operations.forEach { (key, value) in - if ignoreOperations.contains("*.*") { - return - } - - if ignoreOperations.contains("*.\(key.rawValue)") { - return - } - - if let identifier = value.identifier { - if ignoreOperations.contains("\(identifier).\(key.rawValue)") { - return - } - - if ignoreOperations.contains("\(identifier).*") { - return - } - } - - filteredOperations[key] = value - } - - return filteredOperations - } - - static func createSwaggerModel(definition: Swagger, modelOverride: ModelOverride?) -> SwaggerServiceModel { - var model = SwaggerServiceModel() - - model.serviceInformation = ServiceInformation( - title: definition.information.title, - description: definition.information.description, - version: definition.information.version) - - for (name, schema) in definition.definitions { - var enclosingEntityName = name - parseDefinitionSchemas(model: &model, enclosingEntityName: &enclosingEntityName, - schema: schema, modelOverride: modelOverride) - } - - for (path, pathDefinition) in definition.paths { - let filteredOperations = filterOperations(operations: pathDefinition.operations, - modelOverride: modelOverride) - - // iterate through the operations - for (type, operation) in filteredOperations { - guard let identifier = operation.identifier else { - continue - } - - // if there is more than one operation for this path - let operationName: String - if filteredOperations.count > 1 { - operationName = identifier + type.rawValue.startingWithUppercase - } else { - operationName = identifier - } - - let inputDescription = - OperationInputDescription(defaultInputLocation: .body) - - var operationDescription = OperationDescription( - inputDescription: inputDescription, - outputDescription: OperationOutputDescription()) - operationDescription.httpUrl = path - operationDescription.httpVerb = type.rawValue.uppercased() - - parseOperation(description: &operationDescription, - operationName: operationName, - model: &model, operation: operation, - modelOverride: modelOverride) - - model.operationDescriptions[operationName] = operationDescription - } - } - - return model - } - - static func parseOperation(description: inout OperationDescription, - operationName: String, - model: inout SwaggerServiceModel, - operation: SwaggerParser.Operation, - modelOverride: ModelOverride?) { - let (members, bodyStructureName) = getOperationMembersAndBodyStructureName( - operation: operation, - operationName: operationName, - model: &model, - modelOverride: modelOverride) - - setOperationInput(bodyStructureName: bodyStructureName, operationInputMembers: members, - description: &description, model: &model, operationName: operationName) - - setOperationOutput(operation: operation, operationName: operationName, model: &model, - modelOverride: modelOverride, description: &description) - } - - static func getOperationMembersAndBodyStructureName( - operation: SwaggerParser.Operation, - operationName: String, - model: inout SwaggerServiceModel, - modelOverride: ModelOverride?) -> (members: OperationInputMembers, bodyStructureName: String?) { - var members = OperationInputMembers() - var bodyStructureName: String? - - for (index, parameter) in operation.parameters.enumerated() { - switch parameter { - case .a(let value): - switch value { - case .body(fixedFields: _, schema: let schema): - getBodyOperationMembers(schema, bodyStructureName: &bodyStructureName, - operationName: operationName, model: &model, modelOverride: modelOverride) - case .other(let fixedFields, let items): - switch fixedFields.location { - case .query, .path, .header: - getFixedFieldsOperationMembers(fixedFields: fixedFields, operationName: operationName, - index: index, members: &members, items: items, - model: &model, modelOverride: modelOverride) - - default: - fatalError("Location not supported") - } - } - case .b: - fatalError("Not implemented.") - } - } - - return (members: members, bodyStructureName: bodyStructureName) - } - - static func getBodyOperationMembers(_ schema: Schema, bodyStructureName: inout String?, - operationName: String, model: inout SwaggerServiceModel, modelOverride: ModelOverride?) { - switch schema.type { - case .structure(let structure): - bodyStructureName = structure.name - case .object(let objectSchema): - var enclosingEntityName = "\(operationName)RequestBody" - var structureDescription = StructureDescription() - parseObjectSchema(structureDescription: &structureDescription, enclosingEntityName: &enclosingEntityName, - model: &model, objectSchema: objectSchema, modelOverride: modelOverride) - - model.structureDescriptions[enclosingEntityName] = structureDescription - - bodyStructureName = enclosingEntityName - default: - fatalError("Not implemented.") - } - } - - static func ignoreRequestHeader(operationName: String, headerName: String, - modelOverride: ModelOverride?) -> Bool { - - guard let ignoreRequestHeaders = modelOverride?.ignoreRequestHeaders else { - // no filtering required - return false - } - - if ignoreRequestHeaders.contains("*.*") { - return true - } - - if ignoreRequestHeaders.contains("*.\(headerName)") { - return true - } - - if ignoreRequestHeaders.contains("\(operationName).\(headerName)") { - return true - } - - if ignoreRequestHeaders.contains("\(operationName).*") { - return true - } - - return false - } - - static func getFixedFieldsOperationMembers(fixedFields: FixedParameterFields, operationName: String, - index: Int, members: inout SwaggerServiceModel.OperationInputMembers, - items: Items, model: inout SwaggerServiceModel, modelOverride: ModelOverride?) { - let typeName = fixedFields.name.safeModelName().startingWithUppercase - - let fieldName = "\(operationName)Request\(typeName)" - let member = Member(value: fieldName, - position: index, - required: fixedFields.required, - documentation: fixedFields.description) - switch fixedFields.location { - case .query: - members.queryMembers[fixedFields.name] = member - case .path: - members.pathMembers[fixedFields.name] = member - case .header: - guard !ignoreRequestHeader(operationName: operationName, headerName: fixedFields.name, modelOverride: modelOverride) else { - // ignore header - return - } - - members.additionalHeaderMembers[fixedFields.name] = member - default: - // cannot happen - break - } - - addField(type: items.type, fieldName: fieldName, - model: &model, modelOverride: modelOverride) - } - - static func addOperationResponseFromSchema(_ schema: Schema, operationName: String, forCode code: Int, index: Int?, - description: inout OperationDescription, - model: inout SwaggerServiceModel, modelOverride: ModelOverride?) { - switch schema.type { - case .structure(let structure): - if code >= 200 && code < 300 { - description.output = structure.name - } else { - description.errors.append((type: structure.name, code: code)) - model.errorTypes.insert(structure.name) - } - case .oneOf(let schema): - schema.subschemas.enumerated().forEach { entry in - addOperationResponseFromSchema(entry.element, operationName: operationName, forCode: code, - index: entry.offset, description: &description, - model: &model, modelOverride: modelOverride) - } - case .object(let objectSchema): - let indexString = index?.description ?? "" - var structureName = "\(operationName)\(code)Response\(indexString)Body" - var structureDescription = StructureDescription() - parseObjectSchema(structureDescription: &structureDescription, enclosingEntityName: &structureName, - model: &model, objectSchema: objectSchema, modelOverride: modelOverride) - - model.structureDescriptions[structureName] = structureDescription - - if code >= 200 && code < 300 { - description.output = structureName - } else { - description.errors.append((type: structureName, code: code)) - model.errorTypes.insert(structureName) - } - default: - fatalError("Not implemented.") - } - } - - static func addField(type: ItemsType, fieldName: String, - model: inout SwaggerServiceModel, modelOverride: ModelOverride?) { - switch type { - case .string(item: let item): - addStringField(metadata: item.metadata, - schema: nil, - model: &model, - fieldName: fieldName, - modelOverride: modelOverride) - case .number(item: let item): - model.fieldDescriptions[fieldName] = - Fields.double(rangeConstraint: NumericRangeConstraint( - minimum: item.metadata?.minimum, - maximum: item.metadata?.maximum, - exclusiveMinimum: item.metadata?.exclusiveMinimum ?? false, - exclusiveMaximum: item.metadata?.exclusiveMaximum ?? false)) - case .integer(item: let item): - if item.format == IntegerFormat.int64 { - model.fieldDescriptions[fieldName] = - Fields.long(rangeConstraint: NumericRangeConstraint( - minimum: item.metadata?.minimum, - maximum: item.metadata?.maximum, - exclusiveMinimum: item.metadata?.exclusiveMinimum ?? false, - exclusiveMaximum: item.metadata?.exclusiveMaximum ?? false)) - } else { - model.fieldDescriptions[fieldName] = - Fields.integer(rangeConstraint: NumericRangeConstraint( - minimum: item.metadata?.minimum, - maximum: item.metadata?.maximum, - exclusiveMinimum: item.metadata?.exclusiveMinimum ?? false, - exclusiveMaximum: item.metadata?.exclusiveMaximum ?? false)) - } - - - case .boolean: - model.fieldDescriptions[fieldName] = Fields.boolean - default: - fatalError("Field type not supported.") - } - } - - static func getEnumerationValues(metadata: SwaggerParser.Metadata) -> [(name: String, value: String)] { - let enumerationValues: [(name: String, value: String)] - if let values = metadata.enumeratedValues { - enumerationValues = values.filter { value in value is String } - .compactMap { value in value as? String } - .map { value in (name: value, value: value) } - } else { - enumerationValues = [] - } - - return enumerationValues - } - - static func addStringField(metadata: StringMetadata?, - schema: Schema?, - model: inout SwaggerServiceModel, - fieldName: String, - modelOverride: ModelOverride?) { - let pattern: String? - let newValueConstraints: [(name: String, value: String)] - // if the pattern is a list of alternatives - if modelOverride?.modelStringPatternsAreAlternativeList ?? false, - let current = metadata?.pattern, - let first = current.first, first == "^", - let last = current.last, last == "$" { - pattern = nil - newValueConstraints = - current.dropFirst().dropLast().split(separator: "|").map { subString in - let value = String(subString) - return (name: value, value: value) - } - } else { - pattern = nil - if let schema = schema { - newValueConstraints = getEnumerationValues(metadata: schema.metadata) - } else { - newValueConstraints = [] - } - } - - model.fieldDescriptions[fieldName] = Fields.string( - regexConstraint: pattern, - lengthConstraint: LengthRangeConstraint(minimum: metadata?.minLength ?? nil, - maximum: metadata?.maxLength ?? nil), - valueConstraints: newValueConstraints) - } -} diff --git a/Sources/SwaggerServiceModel/SwaggerServiceModel+parseSchemas.swift b/Sources/SwaggerServiceModel/SwaggerServiceModel+parseSchemas.swift deleted file mode 100644 index 25f97ef..0000000 --- a/Sources/SwaggerServiceModel/SwaggerServiceModel+parseSchemas.swift +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). -// You may not use this file except in compliance with the License. -// A copy of the License is located at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. -// -// SwaggerServiceModel+parseSchemas.swift -// SwaggerServiceModel -// - -import Foundation -import ServiceModelEntities -import ServiceModelCodeGeneration -import SwaggerParser -import Yams - -internal extension SwaggerServiceModel { - static func parseDefinitionSchemas(model: inout SwaggerServiceModel, enclosingEntityName: inout String, - schema: SwaggerParser.Schema, modelOverride: ModelOverride?) { - switch schema.type { - case .boolean: - model.fieldDescriptions[enclosingEntityName] = .boolean - case .integer(.int32, let metadata),.integer(.none, let metadata) : - - model.fieldDescriptions[enclosingEntityName] = Fields.integer(rangeConstraint: - NumericRangeConstraint(minimum: metadata.minimum, - maximum: metadata.maximum, - exclusiveMinimum: metadata.exclusiveMinimum ?? false, - exclusiveMaximum: metadata.exclusiveMaximum ?? false)) - case .integer(.int64, let metadata): - model.fieldDescriptions[enclosingEntityName] = Fields.long(rangeConstraint: - NumericRangeConstraint(minimum: metadata.minimum, - maximum: metadata.maximum, - exclusiveMinimum: metadata.exclusiveMinimum ?? false, - exclusiveMaximum: metadata.exclusiveMaximum ?? false)) - - case .structure(let structureSchema): - var structureDescription = StructureDescription() - parseStructureSchema(structureDescription: &structureDescription, enclosingEntityName: &enclosingEntityName, - model: &model, structureSchema: structureSchema, modelOverride: modelOverride) - - model.structureDescriptions[enclosingEntityName] = structureDescription - case .object(let objectSchema): - if case .b(let mapSchema) = objectSchema.additionalProperties { - parseMapDefinitionSchema(mapSchema: mapSchema, - enclosingEntityName: &enclosingEntityName, - model: &model) - } else { - var structureDescription = StructureDescription() - parseObjectSchema(structureDescription: &structureDescription, enclosingEntityName: &enclosingEntityName, - model: &model, objectSchema: objectSchema, modelOverride: modelOverride) - - model.structureDescriptions[enclosingEntityName] = structureDescription - } - case .array(let arrayMetadata): - parseArrayDefinitionSchemas(arrayMetadata: arrayMetadata, enclosingEntityName: &enclosingEntityName, - model: &model, modelOverride: modelOverride) - case .allOf(let allOfSchema): - var structureDescription = StructureDescription() - parseAllOfSchema(structureDescription: &structureDescription, enclosingEntityName: &enclosingEntityName, - model: &model, allOfSchema: allOfSchema, modelOverride: modelOverride) - model.structureDescriptions[enclosingEntityName] = structureDescription - case .string(_, let metadata): - addStringField(metadata: metadata, schema: schema, - model: &model, fieldName: enclosingEntityName, modelOverride: modelOverride) - case .number(_, let metadata): - model.fieldDescriptions[enclosingEntityName] = Fields.double(rangeConstraint: - NumericRangeConstraint(minimum: metadata.minimum, - maximum: metadata.maximum, - exclusiveMinimum: metadata.exclusiveMinimum ?? false, - exclusiveMaximum: metadata.exclusiveMaximum ?? false)) - case .enumeration: - model.fieldDescriptions[enclosingEntityName] = Fields.string(regexConstraint: nil, lengthConstraint: LengthRangeConstraint(), - valueConstraints: getEnumerationValues(metadata: schema.metadata)) - case .file, .any, .null, .oneOf: - fatalError("Not implemented.") - } - } - - static func parseObjectSchema(structureDescription: inout StructureDescription, enclosingEntityName: inout String, - model: inout SwaggerServiceModel, objectSchema: SwaggerParser.ObjectSchema, - modelOverride: ModelOverride?) { - let sortedKeys = objectSchema.properties.keys.sorted(by: <) - - for (index, name) in sortedKeys.enumerated() { - guard let property = objectSchema.properties[name] else { - fatalError() - } - switch property.type { - case .structure(let structure): - structureDescription.members[name] = Member(value: structure.name, position: index, - required: objectSchema.required.contains(name), - documentation: nil) - default: - var enclosingEntityNameForProperty = enclosingEntityName + name.startingWithUppercase - parseDefinitionSchemas(model: &model, enclosingEntityName: &enclosingEntityNameForProperty, - schema: property, modelOverride: modelOverride) - - structureDescription.members[name] = Member(value: enclosingEntityNameForProperty, position: index, - required: objectSchema.required.contains(name), - documentation: nil) - } - } - } - - static func parseMapDefinitionSchema(mapSchema: Schema, - enclosingEntityName: inout String, - model: inout SwaggerServiceModel) { - let valueType: String - switch mapSchema.type { - case .structure(let structureSchema): - valueType = structureSchema.name - case .string: - valueType = "String" - default: - fatalError("Not implemented.") - } - - model.fieldDescriptions[enclosingEntityName] = Fields.map( - keyType: "String", valueType: valueType, - lengthConstraint: LengthRangeConstraint()) - } - - static func parseArrayDefinitionSchemas(arrayMetadata: (ArraySchema), - enclosingEntityName: inout String, - model: inout SwaggerServiceModel, - modelOverride: ModelOverride?) { - let type: String - switch arrayMetadata.items { - case .one(let value): - switch value.type { - case .structure(let structure): - type = structure.name - default: - var arrayElementEntityName: String - - // if the enclosingEntityName ends in an "s" - if enclosingEntityName.suffix(1).lowercased() == "s" { - arrayElementEntityName = String(enclosingEntityName.dropLast()) - } else { - arrayElementEntityName = enclosingEntityName - enclosingEntityName = "\(enclosingEntityName)s" - } - parseDefinitionSchemas(model: &model, enclosingEntityName: &arrayElementEntityName, - schema: value, modelOverride: modelOverride) - type = arrayElementEntityName - } - case .many: - fatalError("Not implemented.") - } - - let lengthConstraint = LengthRangeConstraint(minimum: arrayMetadata.metadata.minItems, - maximum: arrayMetadata.metadata.maxItems) - model.fieldDescriptions[enclosingEntityName] = Fields.list(type: type, - lengthConstraint: lengthConstraint) - } - - /** - * Objects referencing a single structure should be defined as if they were that structure. - */ - static func parseStructureSchema(structureDescription: inout StructureDescription, enclosingEntityName: inout String, - model: inout SwaggerServiceModel, structureSchema: SwaggerParser.Structure, - modelOverride: ModelOverride?) { - switch structureSchema.structure.type { - case .object(let objectSchema): - parseObjectSchema(structureDescription: &structureDescription, enclosingEntityName: &enclosingEntityName, - model: &model, objectSchema: objectSchema, modelOverride: modelOverride) - default: - fatalError("Non-object direct structure references are not implemented: \(structureSchema.structure.type)") - } - } - - /** - * AllOf schemas should merge the inline structures and objects into a single defintion. - */ - static func parseAllOfSchema(structureDescription: inout StructureDescription, enclosingEntityName: inout String, - model: inout SwaggerServiceModel, allOfSchema: SwaggerParser.AllOfSchema, - modelOverride: ModelOverride?) { - for (index, subschema) in allOfSchema.subschemas.enumerated() { - var enclosingEntityNameForProperty = "\(enclosingEntityName)\(index + 1)" - - switch subschema.type { - case .structure(let structureSchema): - parseStructureSchema(structureDescription: &structureDescription, enclosingEntityName: &enclosingEntityNameForProperty, - model: &model, structureSchema: structureSchema, modelOverride: modelOverride) - case .object(let objectSchema): - parseObjectSchema(structureDescription: &structureDescription, enclosingEntityName: &enclosingEntityNameForProperty, - model: &model, objectSchema: objectSchema, modelOverride: modelOverride) - default: - fatalError("Non object/structure allOf schemas are not implemented. \(subschema.type)") - } - } - } -} diff --git a/Sources/SwaggerServiceModel/SwaggerServiceModel+setOperationInput.swift b/Sources/SwaggerServiceModel/SwaggerServiceModel+setOperationInput.swift deleted file mode 100644 index dee9db9..0000000 --- a/Sources/SwaggerServiceModel/SwaggerServiceModel+setOperationInput.swift +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). -// You may not use this file except in compliance with the License. -// A copy of the License is located at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. -// -// SwaggerServiceModel+setOperationInput.swift -// SwaggerServiceModel -// - -import Foundation -import ServiceModelEntities -import ServiceModelCodeGeneration -import SwaggerParser -import Yams - -internal extension SwaggerServiceModel { - struct OperationInputFields { - let allMembers: [String: Member] - let pathFields: [String] - let queryFields: [String] - let bodyFields: [String] - let additionalHeaderFields: [String] - } - - static func setOperationInput(bodyStructureName: String?, - operationInputMembers: OperationInputMembers, - description: inout OperationDescription, - model: inout SwaggerServiceModel, - operationName: String) { - // if this operational only has a body structure - if let bodyStructureName = bodyStructureName, - operationInputMembers.queryMembers.isEmpty && operationInputMembers.additionalHeaderMembers.isEmpty - && operationInputMembers.pathMembers.isEmpty { - description.input = bodyStructureName - } else { - let operationInputFields = getOperationInputFields(bodyStructureName: bodyStructureName, - operationInputMembers: operationInputMembers, - model: &model) - - let inputModelName = "\(operationName)Request" - let inputModelDescription = "Input model for the \(operationName) operation." - let structureDefinition = StructureDescription(members: operationInputFields.allMembers, - documentation: inputModelDescription) - - model.structureDescriptions[inputModelName] = structureDefinition - description.input = inputModelName - - if operationInputFields.pathFields.isEmpty && !operationInputFields.queryFields.isEmpty - && operationInputFields.bodyFields.isEmpty && operationInputFields.additionalHeaderFields.isEmpty { - description.inputDescription = OperationInputDescription( - pathFields: operationInputFields.pathFields, - queryFields: [], - bodyFields: operationInputFields.bodyFields, - additionalHeaderFields: operationInputFields.additionalHeaderFields, - defaultInputLocation: .query, - bodyStructureName: bodyStructureName) - } else { - description.inputDescription = OperationInputDescription( - pathFields: operationInputFields.pathFields, - queryFields: operationInputFields.queryFields, - bodyFields: operationInputFields.bodyFields, - additionalHeaderFields: operationInputFields.additionalHeaderFields, - defaultInputLocation: .body, - bodyStructureName: bodyStructureName) - } - } - } - - static func getOperationInputFields(bodyStructureName: String?, - operationInputMembers: OperationInputMembers, - model: inout SwaggerServiceModel) -> OperationInputFields { - var allMembers: [String: Member] = [:] - let pathFields: [String] - let queryFields: [String] - let bodyFields: [String] - let additionalHeaderFields: [String] - - if let bodyStructureName = bodyStructureName { - guard let structureDefinition = model.structureDescriptions[bodyStructureName] else { - fatalError("No structure with type \(bodyStructureName)") - } - - allMembers.merge(structureDefinition.members) { (old, _) in old } - bodyFields = [String](structureDefinition.members.keys) - } else { - bodyFields = [] - } - - allMembers.merge(operationInputMembers.queryMembers) { (old, _) in old } - queryFields = [String](operationInputMembers.queryMembers.keys) - - allMembers.merge(operationInputMembers.additionalHeaderMembers) { (old, _) in old } - additionalHeaderFields = [String](operationInputMembers.additionalHeaderMembers.keys) - - allMembers.merge(operationInputMembers.pathMembers) { (old, _) in old } - pathFields = [String](operationInputMembers.pathMembers.keys) - - let sortedMembers = allMembers.sorted { (left, right) in left.key < right.key } - var correctedAllMembers: [String: Member] = [:] - sortedMembers.enumerated().forEach { entry in - let oldMember = entry.element.value - let correctedMember = Member(value: oldMember.value, - position: entry.offset, - locationName: oldMember.locationName, - required: oldMember.required, - documentation: oldMember.documentation) - - correctedAllMembers[entry.element.key] = correctedMember - } - - return OperationInputFields(allMembers: correctedAllMembers, - pathFields: pathFields, - queryFields: queryFields, - bodyFields: bodyFields, - additionalHeaderFields: additionalHeaderFields) - } -} diff --git a/Sources/SwaggerServiceModel/SwaggerServiceModel+setOperationOutput.swift b/Sources/SwaggerServiceModel/SwaggerServiceModel+setOperationOutput.swift deleted file mode 100644 index 0a0cae7..0000000 --- a/Sources/SwaggerServiceModel/SwaggerServiceModel+setOperationOutput.swift +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). -// You may not use this file except in compliance with the License. -// A copy of the License is located at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. -// -// SwaggerServiceModel+setOperationOutput.swift -// SwaggerServiceModel -// - -import Foundation -import ServiceModelEntities -import ServiceModelCodeGeneration -import SwaggerParser -import Yams - -internal extension SwaggerServiceModel { - static func filterHeaders(operation: SwaggerParser.Operation, code: Int, headers: [String: Items], - modelOverride: ModelOverride?) -> [String: Items] { - - guard let ignoreResponseHeaders = modelOverride?.ignoreResponseHeaders else { - // no filtering required - return headers - } - - var filteredHeaders: [String: Items] = [:] - - headers.forEach { (key, value) in - if ignoreResponseHeaders.contains("*.*.*") { - return - } - - if ignoreResponseHeaders.contains("*.*.\(key)") { - return - } - - if ignoreResponseHeaders.contains("*.\(code).*") { - return - } - - if let identifier = operation.identifier { - if ignoreResponseHeaders.contains("\(identifier).\(code).\(key)") { - return - } - - if ignoreResponseHeaders.contains("\(identifier).*.\(key)") { - return - } - - if ignoreResponseHeaders.contains("\(identifier).\(code).*") { - return - } - } - - filteredHeaders[key] = value - } - - return filteredHeaders - } - - static func setOperationOutput(operation: SwaggerParser.Operation, operationName: String, model: inout SwaggerServiceModel, - modelOverride: ModelOverride?, description: inout OperationDescription) { - // iterate through the responses - for (code, response) in operation.responses { - switch response { - case .a(let value): - let filteredHeaders = filterHeaders(operation: operation, code: code, - headers: value.headers, modelOverride: modelOverride) - - var headerMembers: [String: Member] = [:] - filteredHeaders.enumerated().forEach { entry in - let typeName = entry.element.key.safeModelName().startingWithUppercase - - let headerName = "\(operationName)\(typeName)Header" - - let header = entry.element.value - addField(type: header.type, fieldName: headerName, model: &model, modelOverride: modelOverride) - let member = Member(value: headerName, - position: entry.offset, - required: false, - documentation: header.metadata.description) - headerMembers[entry.element.key] = member - } - - if let schema = value.schema { - addOperationResponseFromSchema(schema, operationName: operationName, forCode: code, index: nil, - description: &description, model: &model, modelOverride: modelOverride) - } - - // if there are headers - if !headerMembers.isEmpty { - setOperationOutputWithHeaders(description: &description, model: &model, headerMembers: headerMembers, - operationName: operationName, code: code) - } - case .b: - fatalError("Not implemented.") - } - } - } - - private static func setOperationOutputWithHeaders(description: inout OperationDescription, model: inout SwaggerServiceModel, - headerMembers: [String: Member], operationName: String, code: Int) { - var allMembers: [String: Member] = [:] - let bodyFields: [String] - let headerFields: [String] - let bodyStructureName = description.output - - if let bodyStructureName = bodyStructureName { - guard let structureDefinition = model.structureDescriptions[bodyStructureName] else { - fatalError("No structure with type \(bodyStructureName)") - } - - allMembers.merge(structureDefinition.members) { (old, _) in old } - bodyFields = [String](structureDefinition.members.keys) - } else { - bodyFields = [] - } - - allMembers.merge(headerMembers) { (old, _) in old } - headerFields = [String](headerMembers.keys) - - let sortedMembers = allMembers.sorted { (left, right) in left.key < right.key } - var correctedAllMembers: [String: Member] = [:] - sortedMembers.enumerated().forEach { entry in - let oldMember = entry.element.value - let correctedMember = Member(value: oldMember.value, - position: entry.offset, - locationName: oldMember.locationName, - required: oldMember.required, - documentation: oldMember.documentation) - - correctedAllMembers[entry.element.key] = correctedMember - } - - let outputModelName = "\(operationName)\(code)Response" - let outputModelDescription = "Output model for the \(operationName) operation." - let structureDefinition = StructureDescription(members: correctedAllMembers, - documentation: outputModelDescription) - - model.structureDescriptions[outputModelName] = structureDefinition - description.output = outputModelName - - description.outputDescription = OperationOutputDescription( - bodyFields: bodyFields, - headerFields: headerFields, - bodyStructureName: bodyStructureName, - payloadAsMember: nil) - } -} diff --git a/Sources/SwaggerServiceModel/SwaggerServiceModel.swift b/Sources/SwaggerServiceModel/SwaggerServiceModel.swift deleted file mode 100644 index 3b1a715..0000000 --- a/Sources/SwaggerServiceModel/SwaggerServiceModel.swift +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). -// You may not use this file except in compliance with the License. -// A copy of the License is located at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. -// -// SwaggerServiceModel.swift -// SwaggerServiceModel -// - -import Foundation -import ServiceModelEntities -import ServiceModelCodeGeneration -import SwaggerParser -import Yams - -/** - Struct that models the Metadata of the Swagger model. - */ -public struct SwaggerServiceModel: ServiceModel { - var documentationDescriptions: [String: String] = [:] - public var serviceInformation: ServiceInformation? = nil - public var serviceDescriptions: [String: ServiceDescription] = [:] - public var structureDescriptions: [String: StructureDescription] = [:] - public var operationDescriptions: [String: OperationDescription] = [:] - public var fieldDescriptions: [String: Fields] = [:] - public var errorTypes: Set = [] - public var typeMappings: [String: String] = [:] - public var errorCodeMappings: [String: String] = [:] - - public static func create(data: Data, modelFormat: ModelFormat, - modelOverride: ModelOverride?) throws -> SwaggerServiceModel { - let definition: Swagger - switch modelFormat { - case .yaml: - let decoder = YAMLDecoder() - let dataAsString = String(data: data, encoding: .utf8)! - let builder = try decoder.decode(SwaggerBuilder.self, from: dataAsString) - definition = try builder.build(builder) - default: - let decoder = JSONDecoder() - let builder = try decoder.decode(SwaggerBuilder.self, from: data) - definition = try builder.build(builder) - } - - return createSwaggerModel(definition: definition, modelOverride: modelOverride) - } -}