Skip to content

Commit

Permalink
Do not issue fatal error when developer mode enabled
Browse files Browse the repository at this point in the history
  • Loading branch information
drhaynes authored and dhardiman committed Jan 22, 2024
1 parent 3eec3a0 commit ec430f1
Show file tree
Hide file tree
Showing 15 changed files with 84 additions and 43 deletions.
2 changes: 1 addition & 1 deletion Sources/Config/ConfigGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class ConfigGenerator {
throw ConfigError.badJSON
}
guard let template = templates.first(where: { $0.canHandle(config: config) == true }) else { throw ConfigError.noTemplate }
let configurationFile = try template.init(config: config, name: url.deletingPathExtension().lastPathComponent, scheme: arguments.scheme, source: url.deletingLastPathComponent())
let configurationFile = try template.init(config: config, name: url.deletingPathExtension().lastPathComponent, scheme: arguments.scheme, source: url.deletingLastPathComponent(), generationBehaviour: GenerationBehaviour(developerMode: arguments.developerMode))
var swiftOutput: URL
if let filename = configurationFile.filename {
swiftOutput = url.deletingLastPathComponent().appendingPathComponent(filename)
Expand Down
14 changes: 9 additions & 5 deletions Sources/Config/ConfigurationFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ struct Configuration {
}

// swiftlint:disable:next identifier_name IV is a well understood abbreviation
func stringRepresentation(scheme: String, iv: IV, encryptionKey: String?, requiresNonObjcDeclarations: Bool, publicProperties: Bool, instanceProperties: Bool, indentWidth: Int = 0) -> String {
func stringRepresentation(scheme: String, iv: IV, encryptionKey: String?, requiresNonObjcDeclarations: Bool, publicProperties: Bool, instanceProperties: Bool, indentWidth: Int = 0, generationBehaviour: GenerationBehaviour) -> String {
let separator = "\n\n"
let propertiesString = properties.values.map({ $0.propertyDeclaration(for: keyForProperty($0, in: scheme), iv: iv, encryptionKey: encryptionKey, requiresNonObjCDeclarations: requiresNonObjcDeclarations, isPublic: publicProperties, instanceProperty: instanceProperties, indentWidth: indentWidth) })
let propertiesString = properties.values.map({ $0.propertyDeclaration(for: keyForProperty($0, in: scheme), iv: iv, encryptionKey: encryptionKey, requiresNonObjCDeclarations: requiresNonObjcDeclarations, isPublic: publicProperties, instanceProperty: instanceProperties, indentWidth: indentWidth, generationBehaviour: generationBehaviour) })
.sorted()
.joined(separator: separator)

Expand All @@ -67,7 +67,7 @@ struct Configuration {
className = className.replacingCharacters(in: startIndex...startIndex, with: firstLetter.description.uppercased())
return """
\(String.indent(for: indentWidth))public enum \(className) {
\(config.value.stringRepresentation(scheme: scheme, iv: iv, encryptionKey: encryptionKey, requiresNonObjcDeclarations: requiresNonObjcDeclarations, publicProperties: publicProperties, instanceProperties: instanceProperties, indentWidth: indentWidth + 1))
\(config.value.stringRepresentation(scheme: scheme, iv: iv, encryptionKey: encryptionKey, requiresNonObjcDeclarations: requiresNonObjcDeclarations, publicProperties: publicProperties, instanceProperties: instanceProperties, indentWidth: indentWidth + 1, generationBehaviour: generationBehaviour))
\(String.indent(for: indentWidth))}
"""
}
Expand Down Expand Up @@ -186,8 +186,10 @@ struct ConfigurationFile: Template {
let entityType: String?

let outputAsInstanceVariables: Bool

let generationBehaviour: GenerationBehaviour

init(config: [String: Any], name: String, scheme: String, source: URL) throws {
init(config: [String: Any], name: String, scheme: String, source: URL, generationBehaviour: GenerationBehaviour = GenerationBehaviour()) throws {
self.scheme = scheme
self.name = name

Expand All @@ -197,6 +199,8 @@ struct ConfigurationFile: Template {

self.customTypes = CustomType.typeArray(from: self.template)
self.commonPatterns = OverridePattern.patterns(from: self.template)

self.generationBehaviour = generationBehaviour

if let defaultType = template?["defaultType"] as? String {
self.defaultType = PropertyType(rawValue: defaultType)
Expand Down Expand Up @@ -244,7 +248,7 @@ struct ConfigurationFile: Template {
var description: String {
let extendedClass = template?["extensionOn"] as? String
let requiresNonObjcDeclarations = template?["requiresNonObjC"] as? Bool ?? false
let values = rootConfiguration.stringRepresentation(scheme: scheme, iv: iv, encryptionKey: encryptionKey, requiresNonObjcDeclarations: requiresNonObjcDeclarations, publicProperties: extendedClass == nil, instanceProperties: outputAsInstanceVariables)
let values = rootConfiguration.stringRepresentation(scheme: scheme, iv: iv, encryptionKey: encryptionKey, requiresNonObjcDeclarations: requiresNonObjcDeclarations, publicProperties: extendedClass == nil, instanceProperties: outputAsInstanceVariables, generationBehaviour: generationBehaviour)

let entityType = extendedClass != nil ? "extension" : (self.entityType ?? "enum")
let importsString = imports.sorted().map { "import \($0)" }.joined(separator: "\n")
Expand Down
4 changes: 2 additions & 2 deletions Sources/Config/ConfigurationProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ struct ConfigurationProperty<T>: Property, AssociatedPropertyKeyProviding {
return defaultValue
}

func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String {
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int, generationBehaviour: GenerationBehaviour) -> String {
var template: String = ""
if let description = description {
template += "\(String.indent(for: indentWidth))/// \(description)\n"
Expand All @@ -107,7 +107,7 @@ struct ConfigurationProperty<T>: Property, AssociatedPropertyKeyProviding {
let propertyValue = value(for: scheme)
let outputValue: String
if let type = type {
outputValue = type.valueDeclaration(for: propertyValue, iv: iv, key: encryptionKey)
outputValue = type.valueDeclaration(for: propertyValue, iv: iv, key: encryptionKey, generationBehaviour: generationBehaviour)
} else {
outputValue = "\(propertyValue)"
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/Config/CustomType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ struct CustomProperty: Property {
return customType.typeName
}

func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String {
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int, generationBehaviour: GenerationBehaviour) -> String {
return template(for: description, isPublic: isPublic, instanceProperty: instanceProperty, indentWidth: indentWidth).replacingOccurrences(of: "{key}", with: key)
.replacingOccurrences(of: "{typeName}", with: typeName)
.replacingOccurrences(of: "{value}", with: outputValue(for: scheme, type: customType))
Expand Down Expand Up @@ -94,7 +94,7 @@ struct CustomPropertyArray: Property {
return "[\(customType.typeName)]"
}

func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String {
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int, generationBehaviour: GenerationBehaviour) -> String {
return template(for: description, isPublic: isPublic, instanceProperty: instanceProperty, indentWidth: indentWidth).replacingOccurrences(of: "{key}", with: key)
.replacingOccurrences(of: "{typeName}", with: typeName)
.replacingOccurrences(of: "{value}", with: outputValue(for: scheme, type: customType))
Expand Down Expand Up @@ -125,7 +125,7 @@ private func valueString(for placeholder: Placeholder, from dictionary: [String:
private func valueString(for placeholder: Placeholder, from value: Any) -> String {
guard let unusedIV = try? IV(dict: [:]) else { return "" }
if let type = placeholder.type {
return type.valueDeclaration(for: value, iv: unusedIV, key: nil)
return type.valueDeclaration(for: value, iv: unusedIV, key: nil, generationBehaviour: GenerationBehaviour())
} else {
return "\(value)"
}
Expand Down
4 changes: 3 additions & 1 deletion Sources/Config/EnumConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ struct EnumConfiguration: Template {
let type: String

let properties: [String: Property]
let generationBehaviour: GenerationBehaviour

init(config: [String: Any], name: String, scheme: String, source: URL) throws {
init(config: [String: Any], name: String, scheme: String, source: URL, generationBehaviour: GenerationBehaviour = GenerationBehaviour()) throws {
self.name = name
self.scheme = scheme
self.generationBehaviour = generationBehaviour
guard let template = config["template"] as? [String: String],
let type = template["rawType"] else { throw EnumError.noType }
self.type = type
Expand Down
18 changes: 18 additions & 0 deletions Sources/Config/GenerationBehaviour.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// GenerationBehaviour.swift
//
//
// Created by drh on 22/01/2024.
//

import Foundation

/// Struct for configuring configuration generation behaviour.
struct GenerationBehaviour {
/// When enabled, will create warnings on generation failures rather than fatal errors.
let developerMode: Bool

init(developerMode: Bool = false) {
self.developerMode = developerMode
}
}
2 changes: 1 addition & 1 deletion Sources/Config/IV.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct IV: Property {
hash = try dict.hashRepresentation()
}

func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String {
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int, generationBehaviour: GenerationBehaviour) -> String {
return "\(String.indent(for: indentWidth))\(isPublic ? "public " : "")\(instanceProperty ? "" : "static ")let \(key): \(typeName) = \(byteArrayOutput(from: Array(hash.utf8)))"
}
}
30 changes: 20 additions & 10 deletions Sources/Config/Property.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ protocol Property {
var key: String { get }
var typeName: String { get }
var associatedProperty: String? { get }
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int, generationBehaviour: GenerationBehaviour) -> String
}

private func dictionaryValue(_ dict: [String: Any]) -> String {
Expand Down Expand Up @@ -110,7 +110,7 @@ enum PropertyType: String {
}
}

func valueDeclaration(for value: Any, iv: IV, key: String?) -> String {
func valueDeclaration(for value: Any, iv: IV, key: String?, generationBehaviour: GenerationBehaviour) -> String {
let stringValueAllowingOptional = { (optional: Bool) -> String in
if let string = value as? String {
return string.isEmpty ? "\"\"" : "#\"\(string)\"#"
Expand Down Expand Up @@ -155,24 +155,34 @@ enum PropertyType: String {
case .dynamicColourReference:
return dynamicColourReferenceValue(for: value as? [String: String])
case .environmentVariable:
return "#\"\(environmentVariable(for: value as? String))\"#"
let valueString = produceEnvironmentVariable(for: value as? String, generationBehaviour: generationBehaviour)
return "#\"\(valueString)\"#"
case .encryptedEnvironmentVariable:
let valueString = environmentVariable(for: value as? String)
guard let key = key else { fatalError("No encryption key present to encrypt value") }
guard let encryptedString = valueString.encrypt(key: Array(key.utf8), iv: Array(iv.hash.utf8)) else {
fatalError("Unable to encrypt \(value) with key")
}
return byteArrayOutput(from: encryptedString)
let valueString = produceEnvironmentVariable(for: value as? String, generationBehaviour: generationBehaviour)
guard let encryptedString = valueString.encrypt(key: Array(key.utf8), iv: Array(iv.hash.utf8)) else {
fatalError("Unable to encrypt \(value) with key")
}
return byteArrayOutput(from: encryptedString)
default:
return "\(value)"
}
}

private enum EnvResult {
case defined(String)
case undefined
}

private func environmentVariable(for value: String?) -> String {
private func produceEnvironmentVariable(for value: String?, generationBehaviour: GenerationBehaviour) -> String {
guard let environmentVariableName = value,
let rawValue = getenv(environmentVariableName),
let stringValue = String(utf8String: rawValue) else {
fatalError("Missing environment variable \(value ?? "")")
if generationBehaviour.developerMode {
return "Environment variable \(value ?? "") was not defined at runtime"
} else {
fatalError("Missing environment variable \(value ?? "")")
}
}
return stringValue
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Config/ReferenceProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct ReferenceProperty: Property {
return defaultValue
}

func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String {
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int, generationBehaviour: GenerationBehaviour) -> String {
var template: String = ""
if let description = description {
template += "\(String.indent(for: indentWidth))/// \(description)\n"
Expand Down
2 changes: 1 addition & 1 deletion Sources/Config/Template.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation

protocol Template: CustomStringConvertible {
init(config: [String: Any], name: String, scheme: String, source: URL) throws
init(config: [String: Any], name: String, scheme: String, source: URL, generationBehaviour: GenerationBehaviour) throws

static func canHandle(config: [String: Any]) -> Bool

Expand Down
11 changes: 9 additions & 2 deletions Tests/ConfigTests/ConfigurationPropertyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ class ConfigurationPropertyTests: XCTestCase {
])
}

func whenTheDeclarationIsWritten<T>(for configurationProperty: ConfigurationProperty<T>?, scheme: String = "any", encryptionKey: String? = nil, isPublic: Bool = false, instanceProperty: Bool = false, requiresNonObjC: Bool = false, indentWidth: Int = 0) throws -> String? {
func whenTheDeclarationIsWritten<T>(for configurationProperty: ConfigurationProperty<T>?, scheme: String = "any", encryptionKey: String? = nil, isPublic: Bool = false, instanceProperty: Bool = false, requiresNonObjC: Bool = false, indentWidth: Int = 0, generationBehaviour: GenerationBehaviour = GenerationBehaviour()) throws -> String? {
let iv = try IV(dict: ["initialise": "me"])
print("\(iv.hash)")
return configurationProperty?.propertyDeclaration(for: scheme, iv: iv, encryptionKey: encryptionKey, requiresNonObjCDeclarations: requiresNonObjC, isPublic: isPublic, instanceProperty: instanceProperty, indentWidth: indentWidth)
return configurationProperty?.propertyDeclaration(for: scheme, iv: iv, encryptionKey: encryptionKey, requiresNonObjCDeclarations: requiresNonObjC, isPublic: isPublic, instanceProperty: instanceProperty, indentWidth: indentWidth, generationBehaviour: generationBehaviour)
}

func testItCanWriteADeclarationForAStringPropertyUsingTheDefaultValue() throws {
Expand Down Expand Up @@ -354,6 +354,13 @@ class ConfigurationPropertyTests: XCTestCase {
let actualValue = try whenTheDeclarationIsWritten(for: property)
expect(actualValue).to(equal(expectedValue))
}

func testItCreatesAWarningWhenAnEnvironmentVariablePropertyDoesNotExistAndDeveloperModeIsEnabled() throws {
let property = ConfigurationProperty<String>(key: "test", typeHint: "EnvironmentVariable", dict: ["defaultValue": "DOESNT_EXIST"])
let expectedValue = ##" static let test: String = #"Environment variable DOESNT_EXIST was not defined at runtime"#"##
let actualValue = try whenTheDeclarationIsWritten(for: property, generationBehaviour: GenerationBehaviour(developerMode: true))
expect(actualValue).to(equal(expectedValue))
}

func testItCanWriteAnEncryptedEnvironmentVariableProperty() throws {
// Note: this test doesn't test the encryption itself, that test will be written elsewhere
Expand Down
Loading

0 comments on commit ec430f1

Please sign in to comment.