From 438117e3c72ada302b67daf05e27a69408d1f4e6 Mon Sep 17 00:00:00 2001 From: Simon Pilkington Date: Tue, 2 Apr 2019 10:38:21 -0700 Subject: [PATCH] Detect errors that will normalize to the same name and keep them distinct. --- .../ModelErrorsDelegate.swift | 18 +++--- ...odeGenerator+generateErrorDefinition.swift | 41 +++++++------ ...delCodeGenerator+generateModelErrors.swift | 60 +++++++++++++++---- 3 files changed, 80 insertions(+), 39 deletions(-) diff --git a/Sources/ServiceModelCodeGeneration/ModelErrorsDelegate.swift b/Sources/ServiceModelCodeGeneration/ModelErrorsDelegate.swift index 7b03623..725d451 100644 --- a/Sources/ServiceModelCodeGeneration/ModelErrorsDelegate.swift +++ b/Sources/ServiceModelCodeGeneration/ModelErrorsDelegate.swift @@ -17,6 +17,8 @@ import Foundation +public typealias ErrorType = (normalizedName: String, identity: String) + /** Delegate protocol that can customize the generation of errors from the Service Model. @@ -39,7 +41,7 @@ public protocol ModelErrorsDelegate { - errorTypes: The sorted list of error types. */ func errorTypeAdditionalImportsGenerator(fileBuilder: FileBuilder, - errorTypes: [String]) + errorTypes: [ErrorType]) /** Generator for the error type additional error identities. @@ -49,7 +51,7 @@ public protocol ModelErrorsDelegate { - errorTypes: The sorted list of error types. */ func errorTypeAdditionalErrorIdentitiesGenerator(fileBuilder: FileBuilder, - errorTypes: [String]) + errorTypes: [ErrorType]) /** Indicates the number of additional error cases that will be added. @@ -59,7 +61,7 @@ public protocol ModelErrorsDelegate { - errorTypes: The sorted list of error types. */ func errorTypeWillAddAdditionalCases(fileBuilder: FileBuilder, - errorTypes: [String]) -> Int + errorTypes: [ErrorType]) -> Int /** Generator for the error type additional error cases. @@ -69,7 +71,7 @@ public protocol ModelErrorsDelegate { - errorTypes: The sorted list of error types. */ func errorTypeAdditionalErrorCasesGenerator(fileBuilder: FileBuilder, - errorTypes: [String]) + errorTypes: [ErrorType]) /** Generator for the error type CodingKeys. @@ -79,16 +81,18 @@ public protocol ModelErrorsDelegate { - errorTypes: The sorted list of error types. */ func errorTypeCodingKeysGenerator(fileBuilder: FileBuilder, - errorTypes: [String]) + errorTypes: [ErrorType]) /** Generator for the error type identity. - Parameters: - fileBuilder: The FileBuilder to output to. + - codingErrorUnknownError: the error that can be thrown for an unknown error. - Returns: the variable name used to store the identity. */ - func errorTypeIdentityGenerator(fileBuilder: FileBuilder) -> String + func errorTypeIdentityGenerator(fileBuilder: FileBuilder, + codingErrorUnknownError: String) -> String /** Generator for the error type additional decode cases using the error identity. @@ -98,7 +102,7 @@ public protocol ModelErrorsDelegate { - errorTypes: The sorted list of error types. */ func errorTypeAdditionalErrorDecodeStatementsGenerator(fileBuilder: FileBuilder, - errorTypes: [String]) + errorTypes: [ErrorType]) } diff --git a/Sources/ServiceModelGenerate/ServiceModelCodeGenerator+generateErrorDefinition.swift b/Sources/ServiceModelGenerate/ServiceModelCodeGenerator+generateErrorDefinition.swift index 1c47fbd..d30d824 100644 --- a/Sources/ServiceModelGenerate/ServiceModelCodeGenerator+generateErrorDefinition.swift +++ b/Sources/ServiceModelGenerate/ServiceModelCodeGenerator+generateErrorDefinition.swift @@ -22,7 +22,7 @@ import ServiceModelEntities public extension ServiceModelCodeGenerator { internal func generateErrorDefinition(fileBuilder: FileBuilder, - sortedErrors: [String], + sortedErrors: [ErrorType], delegate: ModelErrorsDelegate) { let baseName = applicationDescription.baseName addErrorIdentities(fileBuilder: fileBuilder, sortedErrors: sortedErrors, @@ -59,7 +59,9 @@ public extension ServiceModelCodeGenerator { fileBuilder.appendLine("public init(from decoder: Decoder) throws {", postInc: true) // add code to get the identity variable from the delegate - let identityVariable = delegate.errorTypeIdentityGenerator(fileBuilder: fileBuilder) + let identityVariable = delegate.errorTypeIdentityGenerator( + fileBuilder: fileBuilder, + codingErrorUnknownError: "\(baseName)CodingError.unknownError") fileBuilder.appendEmptyLine() fileBuilder.appendLine("switch \(identityVariable) {") @@ -85,7 +87,7 @@ public extension ServiceModelCodeGenerator { } private func generateErrorPayloadType(fileBuilder: FileBuilder, - sortedErrors: [String], + sortedErrors: [ErrorType], delegate: ModelErrorsDelegate) -> String { // if there are additional errors, create a payload type for them let errorPayloadTypeName = "\(applicationDescription.baseName)ErrorPayload" @@ -111,19 +113,19 @@ public extension ServiceModelCodeGenerator { return errorPayloadTypeName } - private func addErrorCases(fileBuilder: FileBuilder, sortedErrors: [String], + private func addErrorCases(fileBuilder: FileBuilder, sortedErrors: [ErrorType], errorPayloadTypeName: String) { // for each of the errors - for name in sortedErrors { + for error in sortedErrors { let enumName = getNormalizedEnumCaseName( - modelTypeName: name.normalizedErrorName, + modelTypeName: error.normalizedName, inStructure: "\(applicationDescription.baseName)Error", usingUpperCamelCase: true) let payload: String // if this is an error from the model - if model.errorTypes.contains(name) { - payload = name + if model.errorTypes.contains(error.identity) { + payload = error.identity } else { payload = errorPayloadTypeName } @@ -133,26 +135,26 @@ public extension ServiceModelCodeGenerator { } private func addErrorDecodeStatements(fileBuilder: FileBuilder, - sortedErrors: [String], + sortedErrors: [ErrorType], delegate: ModelErrorsDelegate, errorPayloadTypeName: String) { let baseName = applicationDescription.baseName // for each of the errors - for name in sortedErrors { + for error in sortedErrors { let identityName = getNormalizedVariableName( - modelTypeName: name.normalizedErrorName, + modelTypeName: error.normalizedName, inStructure: nil, reservedWordsAllowed: true) let parameterName = getNormalizedVariableName( - modelTypeName: name.normalizedErrorName, + modelTypeName: error.normalizedName, inStructure: "\(baseName)Error", reservedWordsAllowed: true) let payload: String - if model.errorTypes.contains(name) { - payload = name + if model.errorTypes.contains(error.identity) { + payload = error.identity } else { payload = errorPayloadTypeName } @@ -180,7 +182,7 @@ public extension ServiceModelCodeGenerator { } private func addErrorIdentities(fileBuilder: FileBuilder, - sortedErrors: [String], + sortedErrors: [ErrorType], delegate: ModelErrorsDelegate) { if delegate.canExpectValidationError { fileBuilder.appendLine(""" @@ -190,13 +192,14 @@ public extension ServiceModelCodeGenerator { } // for each of the errors - for name in sortedErrors { + for error in sortedErrors { let identityName = getNormalizedVariableName( - modelTypeName: name.normalizedErrorName, + modelTypeName: error.normalizedName, inStructure: nil, reservedWordsAllowed: true) - let identity = model.errorCodeMappings[name] ?? name + let rawIdentity = error.identity + let identity = model.errorCodeMappings[rawIdentity] ?? rawIdentity fileBuilder.appendLine(""" private let \(identityName)Identity = "\(identity)" @@ -207,7 +210,7 @@ public extension ServiceModelCodeGenerator { } private func getEntityType(fileBuilder: FileBuilder, - sortedErrors: [String], + sortedErrors: [ErrorType], delegate: ModelErrorsDelegate) -> String { // add any additional error cases from the delegate let additionalCases = delegate.errorTypeWillAddAdditionalCases(fileBuilder: fileBuilder, errorTypes: sortedErrors) diff --git a/Sources/ServiceModelGenerate/ServiceModelCodeGenerator+generateModelErrors.swift b/Sources/ServiceModelGenerate/ServiceModelCodeGenerator+generateModelErrors.swift index d7531e0..858fe97 100644 --- a/Sources/ServiceModelGenerate/ServiceModelCodeGenerator+generateModelErrors.swift +++ b/Sources/ServiceModelGenerate/ServiceModelCodeGenerator+generateModelErrors.swift @@ -54,9 +54,7 @@ public extension ServiceModelCodeGenerator { allErrorTypes = model.errorTypes } - let sortedErrors = allErrorTypes.sorted { entry1, entry2 in - return entry1 < entry2 - } + let sortedErrors = getSortedErrors(allErrorTypes: allErrorTypes) delegate.errorTypeAdditionalImportsGenerator(fileBuilder: fileBuilder, errorTypes: sortedErrors) @@ -85,9 +83,45 @@ public extension ServiceModelCodeGenerator { let fileName = "\(baseName)ModelErrors.swift" fileBuilder.write(toFile: fileName, atFilePath: "\(applicationDescription.baseFilePath)/Sources/\(baseName)Model") } + + private func getSortedErrors(allErrorTypes: Set) -> [ErrorType] { + // determine if any errors will normalize to the same name + var errorNameCount: [String: Int] = [:] + allErrorTypes.forEach { errorIdentity in + let normalizedErrorName = errorIdentity.normalizedErrorName + + if let existingCount = errorNameCount[normalizedErrorName] { + errorNameCount[normalizedErrorName] = existingCount + 1 + } else { + errorNameCount[normalizedErrorName] = 1 + } + } + + let rawSortedErrors = allErrorTypes.sorted { entry1, entry2 in + return entry1 < entry2 + } + + let sortedErrors: [ErrorType] = rawSortedErrors.map { errorIdentity in + let normalizedErrorName = errorIdentity.normalizedErrorName + + let errorNameCount = errorNameCount[normalizedErrorName] ?? 1 + + if errorNameCount > 1 { + // don't normalize the name as there will be a clash + return (normalizedName: errorIdentity.upperToLowerCamelCase, + identity: errorIdentity) + } else { + // use the normalized name + return (normalizedName: normalizedErrorName, + identity: errorIdentity) + } + } + + return sortedErrors + } private func generateProtocolExtension(fileBuilder: FileBuilder, - sortedErrors: [String], + sortedErrors: [ErrorType], delegate: ModelErrorsDelegate) { let baseName = applicationDescription.baseName guard !sortedErrors.isEmpty else { @@ -107,8 +141,8 @@ public extension ServiceModelCodeGenerator { fileBuilder.incIndent() fileBuilder.incIndent() // for each of the errors - for name in sortedErrors { - let internalName = name.normalizedErrorName + for error in sortedErrors { + let internalName = error.normalizedName fileBuilder.appendLine(""" case .\(internalName): return \(internalName)Identity @@ -139,8 +173,8 @@ public extension ServiceModelCodeGenerator { fileBuilder.incIndent() fileBuilder.incIndent() // for each of the errors - for name in sortedErrors { - let internalName = name.normalizedErrorName + for error in sortedErrors { + let internalName = error.normalizedName fileBuilder.appendLine(""" case .\(internalName)(let details): try details.encode(to: encoder) @@ -159,7 +193,7 @@ public extension ServiceModelCodeGenerator { } private func generateErrorOptionSet(fileBuilder: FileBuilder, - sortedErrors: [String], + sortedErrors: [ErrorType], delegate: ModelErrorsDelegate) { let baseName = applicationDescription.baseName @@ -174,8 +208,8 @@ public extension ServiceModelCodeGenerator { """) // for each of the errors - for (index, name) in sortedErrors.enumerated() { - let internalName = name.normalizedErrorName + for (index, error) in sortedErrors.enumerated() { + let internalName = error.normalizedName fileBuilder.appendLine(" public static let \(internalName) = \(baseName)ErrorTypes(rawValue: \(index + 1))") } @@ -190,8 +224,8 @@ public extension ServiceModelCodeGenerator { fileBuilder.incIndent() fileBuilder.incIndent() // for each of the errors - for (index, name) in sortedErrors.enumerated() { - let internalName = name.normalizedErrorName + for (index, error) in sortedErrors.enumerated() { + let internalName = error.normalizedName fileBuilder.appendLine(""" case \(index + 1): return \(internalName)Identity