From d26204645a3805d15124eb3b3b03a9c0e88e3430 Mon Sep 17 00:00:00 2001 From: Brad Fol Date: Thu, 11 Jan 2024 15:49:28 -0800 Subject: [PATCH] Clarify Default Override Behavior API A terminology update to clarify what the different behaviors are for default overrides. --- Sources/Knit/Module/DependencyBuilder.swift | 12 ++++---- Sources/Knit/Module/ModuleAssembler.swift | 14 ++++++---- Sources/Knit/Module/ModuleAssembly.swift | 24 ++++++++++++---- .../Knit/Module/ScopedModuleAssembler.swift | 8 +++--- .../ModuleAssemblyOverrideTests.swift | 28 +++++++++++-------- 5 files changed, 53 insertions(+), 33 deletions(-) diff --git a/Sources/Knit/Module/DependencyBuilder.swift b/Sources/Knit/Module/DependencyBuilder.swift index 56e3704..4b4e0a1 100644 --- a/Sources/Knit/Module/DependencyBuilder.swift +++ b/Sources/Knit/Module/DependencyBuilder.swift @@ -11,17 +11,17 @@ final class DependencyBuilder { private var inputModules: [any ModuleAssembly] = [] var assemblies: [any ModuleAssembly] = [] let isRegisteredInParent: (any ModuleAssembly.Type) -> Bool - private let defaultOverrides: DefaultOverrideState + private let overrideBehavior: OverrideBehavior private var moduleSources: [String: any ModuleAssembly.Type] = [:] init( modules: [any ModuleAssembly], assemblyValidation: ((any ModuleAssembly.Type) throws -> Void)? = nil, - defaultOverrides: DefaultOverrideState = .whenTesting, + overrideBehavior: OverrideBehavior = .defaultOverridesWhenTesting, isRegisteredInParent: ((any ModuleAssembly.Type) -> Bool)? = nil ) throws { self.assemblyValidation = assemblyValidation - self.defaultOverrides = defaultOverrides + self.overrideBehavior = overrideBehavior inputModules = modules self.isRegisteredInParent = isRegisteredInParent ?? {_ in false } @@ -134,7 +134,7 @@ final class DependencyBuilder { _ moduleType: any ModuleAssembly.Type, fromInput: Bool ) throws -> (any ModuleAssembly.Type)? { - guard defaultOverrides.allow, + guard overrideBehavior.allowDefaultOverrides, !fromInput, let defaultType = (moduleType as? any DefaultModuleAssemblyOverride.Type) else { @@ -199,7 +199,7 @@ extension DependencyBuilder { switch self { case let .moduleNotProvided(moduleType, sourcePath): var testAdvice = "" - if DefaultOverrideState.isRunningTests { + if OverrideBehavior.isRunningTests { testAdvice += "Adding a dependency on the testing module for \(moduleType) should fix this issue" } return """ @@ -240,7 +240,7 @@ extension ModuleAssembly { /// All original dependencies are registered along with any additional dependencies that the override requires static var combinedDependencies: [any ModuleAssembly.Type] { - if DefaultOverrideState.isRunningTests { + if OverrideBehavior.isRunningTests { // For tests only take this modules dependencies return Self.dependencies } else { diff --git a/Sources/Knit/Module/ModuleAssembler.swift b/Sources/Knit/Module/ModuleAssembler.swift index 10cc513..f7822c5 100644 --- a/Sources/Knit/Module/ModuleAssembler.swift +++ b/Sources/Knit/Module/ModuleAssembler.swift @@ -25,14 +25,16 @@ public final class ModuleAssembler { - Parameters: - parent: A ModuleAssembler that has already been setup with some dependencies. - modules: Array of modules to register - - defaultOverrides: Array of override types to use when resolving modules + - overrideBehavior: Behavior of default override usage. + - assemblyValidation: An optional closure to perform custom validation on module assemblies for this assembler. + The Assembler will invoke this closure with each ModuleAssembly.Type as it performs its initialization. + If the closure throws an error for any of the assemblies then a fatal error will occur. - postAssemble: Hook after all assemblies are registered to make changes to the container. - */ public convenience init( parent: ModuleAssembler? = nil, _ modules: [any Assembly], - defaultOverrides: DefaultOverrideState = .whenTesting, + overrideBehavior: OverrideBehavior = .defaultOverridesWhenTesting, assemblyValidation: ((any ModuleAssembly.Type) throws -> Void)? = nil, postAssemble: ((Container) -> Void)? = nil, file: StaticString = #file, @@ -44,7 +46,7 @@ public final class ModuleAssembler { try self.init( parent: parent, _modules: modules, - defaultOverrides: defaultOverrides, + overrideBehavior: overrideBehavior, assemblyValidation: assemblyValidation, postAssemble: postAssemble ) @@ -63,7 +65,7 @@ public final class ModuleAssembler { required init( parent: ModuleAssembler? = nil, _modules modules: [any Assembly], - defaultOverrides: DefaultOverrideState = .whenTesting, + overrideBehavior: OverrideBehavior = .defaultOverridesWhenTesting, assemblyValidation: ((any ModuleAssembly.Type) throws -> Void)? = nil, postAssemble: ((Container) -> Void)? = nil ) throws { @@ -73,7 +75,7 @@ public final class ModuleAssembler { self.builder = try DependencyBuilder( modules: moduleAssemblies, assemblyValidation: assemblyValidation, - defaultOverrides: defaultOverrides, + overrideBehavior: overrideBehavior, isRegisteredInParent: { type in return parent?.isRegistered(type) ?? false } diff --git a/Sources/Knit/Module/ModuleAssembly.swift b/Sources/Knit/Module/ModuleAssembly.swift index 3743bae..8551e38 100644 --- a/Sources/Knit/Module/ModuleAssembly.swift +++ b/Sources/Knit/Module/ModuleAssembly.swift @@ -62,14 +62,26 @@ extension ModuleAssembly where Self: GeneratedModuleAssembly { public static var dependencies: [any ModuleAssembly.Type] { scoped(generatedDependencies) } } -public enum DefaultOverrideState { - case on, off, whenTesting +/// Control the behavior of Assembly Overrides. +public enum OverrideBehavior { - var allow: Bool { + /// Use any default overrides that are available. + case useDefaultOverrides + + /// Disable and ignore any default overrides. + /// Overrides that are _explicitly provided_ will still be used. + case disableDefaultOverrides + + /// Use default overrides *based on the runtime context*. + /// If the ModuleAssembler is running in a unit test environment, the default overrides will be used. + /// Otherwise disable and ignore default overrides. + case defaultOverridesWhenTesting + + var allowDefaultOverrides: Bool { switch self { - case .on: return true - case .off: return false - case .whenTesting: return Self.isRunningTests + case .useDefaultOverrides: return true + case .disableDefaultOverrides: return false + case .defaultOverridesWhenTesting: return Self.isRunningTests } } diff --git a/Sources/Knit/Module/ScopedModuleAssembler.swift b/Sources/Knit/Module/ScopedModuleAssembler.swift index c2b9e2e..6e99c38 100644 --- a/Sources/Knit/Module/ScopedModuleAssembler.swift +++ b/Sources/Knit/Module/ScopedModuleAssembler.swift @@ -17,7 +17,7 @@ public final class ScopedModuleAssembler { public convenience init( parent: ModuleAssembler? = nil, _ modules: [any Assembly], - defaultOverrides: DefaultOverrideState = .whenTesting, + overrideBehavior: OverrideBehavior = .defaultOverridesWhenTesting, postAssemble: ((Container) -> Void)? = nil, file: StaticString = #file, line: UInt = #line @@ -26,7 +26,7 @@ public final class ScopedModuleAssembler { try self.init( parent: parent, _modules: modules, - defaultOverrides: defaultOverrides, + overrideBehavior: overrideBehavior, postAssemble: postAssemble ) } catch { @@ -42,13 +42,13 @@ public final class ScopedModuleAssembler { required init( parent: ModuleAssembler? = nil, _modules modules: [any Assembly], - defaultOverrides: DefaultOverrideState = .whenTesting, + overrideBehavior: OverrideBehavior = .defaultOverridesWhenTesting, postAssemble: ((Container) -> Void)? = nil ) throws { self.internalAssembler = try ModuleAssembler( parent: parent, _modules: modules, - defaultOverrides: defaultOverrides, + overrideBehavior: overrideBehavior, assemblyValidation: { moduleAssemblyType in guard moduleAssemblyType.resolverType == ScopedResolver.self else { throw ScopedModuleAssemblerError.incorrectTargetResolver( diff --git a/Tests/KnitTests/ModuleAssemblyOverrideTests.swift b/Tests/KnitTests/ModuleAssemblyOverrideTests.swift index f593e98..337ca1e 100644 --- a/Tests/KnitTests/ModuleAssemblyOverrideTests.swift +++ b/Tests/KnitTests/ModuleAssemblyOverrideTests.swift @@ -8,14 +8,17 @@ import XCTest final class ModuleAssemblyOverrideTests: XCTestCase { func test_registrationWithoutFakes() throws { - let builder = try DependencyBuilder(modules: [Assembly2()], defaultOverrides: .off) + let builder = try DependencyBuilder(modules: [Assembly2()], overrideBehavior: .disableDefaultOverrides) XCTAssertEqual(builder.assemblies.count, 2) XCTAssertTrue(builder.assemblies[0] is Assembly1) XCTAssertTrue(builder.assemblies[1] is Assembly2) } func test_registrationWithFakes() throws { - let builder = try DependencyBuilder(modules: [Assembly2(), Assembly2Fake()], defaultOverrides: .off) + let builder = try DependencyBuilder( + modules: [Assembly2(), Assembly2Fake()], + overrideBehavior: .disableDefaultOverrides + ) XCTAssertEqual(builder.assemblies.count, 3) XCTAssertTrue(builder.assemblies[0] is Assembly1) XCTAssertTrue(builder.assemblies[1] is FakeAssembly3) @@ -33,7 +36,7 @@ final class ModuleAssemblyOverrideTests: XCTestCase { } func test_assemblerWithDefaultOverrides() { - let assembler = ModuleAssembler([Assembly2()], defaultOverrides: .on) + let assembler = ModuleAssembler([Assembly2()], overrideBehavior: .useDefaultOverrides) XCTAssertTrue(assembler.registeredModules.contains(where: {$0 == Assembly1Fake.self})) XCTAssertTrue(assembler.isRegistered(Assembly1Fake.self)) // Treat Assembly1 as being registered because the mock is @@ -41,20 +44,20 @@ final class ModuleAssemblyOverrideTests: XCTestCase { } func test_noDefaultOverrideForInputModules() { - let assembler = ModuleAssembler([Assembly1()], defaultOverrides: .on) + let assembler = ModuleAssembler([Assembly1()], overrideBehavior: .useDefaultOverrides) XCTAssertTrue(assembler.isRegistered(Assembly1.self)) // The fake is not automatically registered XCTAssertFalse(assembler.isRegistered(Assembly1Fake.self)) } func test_explicitInputOverride() { - let assembler = ModuleAssembler([Assembly1(), Assembly1Fake()], defaultOverrides: .on) + let assembler = ModuleAssembler([Assembly1(), Assembly1Fake()], overrideBehavior: .useDefaultOverrides) XCTAssertTrue(assembler.isRegistered(Assembly1.self)) XCTAssertTrue(assembler.isRegistered(Assembly1Fake.self)) } func test_assemblerWithoutDefaultOverrides() { - let assembler = ModuleAssembler([Assembly2()], defaultOverrides: .off) + let assembler = ModuleAssembler([Assembly2()], overrideBehavior: .disableDefaultOverrides) XCTAssertTrue(assembler.isRegistered(Assembly1.self)) XCTAssertFalse(assembler.isRegistered(Assembly1Fake.self)) } @@ -83,7 +86,7 @@ final class ModuleAssemblyOverrideTests: XCTestCase { func test_overrideDefaultOverride() { let assembler = ModuleAssembler( [Assembly4(), Assembly4Fake2()], - defaultOverrides: .on + overrideBehavior: .useDefaultOverrides ) XCTAssertTrue(assembler.isRegistered(Assembly4.self)) XCTAssertTrue(assembler.isRegistered(Assembly4Fake2.self)) @@ -91,13 +94,16 @@ final class ModuleAssemblyOverrideTests: XCTestCase { } func test_nonAutoOverride() throws { - let builder = try DependencyBuilder(modules: [Assembly1(), NonAutoOverride()], defaultOverrides: .off) + let builder = try DependencyBuilder( + modules: [Assembly1(), NonAutoOverride()], + overrideBehavior: .disableDefaultOverrides + ) XCTAssertTrue(builder.assemblies.first is NonAutoOverride) } func test_parentNonAutoOverride() { let parent = ModuleAssembler([NonAutoOverride()]) - let child = ModuleAssembler(parent: parent, [Assembly1()], defaultOverrides: .off) + let child = ModuleAssembler(parent: parent, [Assembly1()], overrideBehavior: .disableDefaultOverrides) XCTAssertTrue(child.isRegistered(Assembly1.self)) XCTAssertTrue(child.registeredModules.isEmpty) } @@ -105,7 +111,7 @@ final class ModuleAssemblyOverrideTests: XCTestCase { func test_multipleOverrides() { let assembler = ModuleAssembler( [MultipleDependencyAssembly(), MultipleOverrideAssembly()], - defaultOverrides: .off + overrideBehavior: .disableDefaultOverrides ) XCTAssertTrue(assembler.isRegistered(Assembly1.self)) @@ -135,7 +141,7 @@ private struct Assembly2: ModuleAssembly { } } -// Mock implementaiton of Assembly2. Adds an extra dependency on Assembly3 +// Mock implementation of Assembly2. Adds an extra dependency on Assembly3 private struct Assembly2Fake: AutoInitModuleAssembly { func assemble(container: Container) {