From 19d6fd7bef8df5c35d3f58aa9727411670e4b1ab Mon Sep 17 00:00:00 2001 From: Alex Skorulis Date: Thu, 14 Nov 2024 18:41:12 +1100 Subject: [PATCH 1/2] Create OptionalAbstractRegistration type --- .../Container+AbstractRegistration.swift | 34 +++++++++++++++++++ .../KnitTests/AbstractRegistrationTests.swift | 18 ++++++++++ 2 files changed, 52 insertions(+) diff --git a/Sources/Knit/Module/Container+AbstractRegistration.swift b/Sources/Knit/Module/Container+AbstractRegistration.swift index 0a22e0d..06781f1 100644 --- a/Sources/Knit/Module/Container+AbstractRegistration.swift +++ b/Sources/Knit/Module/Container+AbstractRegistration.swift @@ -18,6 +18,17 @@ extension Container { abstractRegistrations().abstractRegistrations.append(registration) } + /// Register that a service is expected to exist but no implementation is currently available + /// If no concrete registration is available then nil will be resolved + public func registerAbstract( + _ serviceType: Optional.Type, + name: String? = nil, + file: String = #fileID + ) { + let registration = OptionalAbstractRegistration(name: name, file: file) + abstractRegistrations().abstractRegistrations.append(registration) + } + // Must be called before using `registerAbstract` func registerAbstractContainer() -> AbstractRegistrationContainer { let registrations = AbstractRegistrationContainer() @@ -91,6 +102,29 @@ fileprivate struct RealAbstractRegistration: AbstractRegistration { } } +/// An abstract registration for an optional service +fileprivate struct OptionalAbstractRegistration: AbstractRegistration { + let name: String? + // Source file used for debugging. Not included in hash calculation or equality + let file: String + + var serviceType: ServiceType.Type { ServiceType.self } + + var key: RegistrationKey { + return .init(typeIdentifier: ObjectIdentifier(ServiceType.self), name: name) + } + + func registerPlaceholder( + container: Container, + errorFormatter: ModuleAssemblerErrorFormatter, + dependencyTree: DependencyTree + ) { + container.register(Optional.self, name: name) { _ in + return nil + } + } +} + // MARK: - Inner types extension Container { diff --git a/Tests/KnitTests/AbstractRegistrationTests.swift b/Tests/KnitTests/AbstractRegistrationTests.swift index bfa59d8..4b09a2f 100644 --- a/Tests/KnitTests/AbstractRegistrationTests.swift +++ b/Tests/KnitTests/AbstractRegistrationTests.swift @@ -73,6 +73,16 @@ final class AbstractRegistrationTests: XCTestCase { ) } + @MainActor + func testOptionalAbstractRegistrations() { + let assembler = ModuleAssembler([Assembly3()]) + let string = assembler.resolver.resolve(String?.self) ?? nil + XCTAssertNil(string) + + let int = assembler.resolver.resolve(Optional.self) ?? nil + XCTAssertNil(int) + } + } private struct Assembly1: AutoInitModuleAssembly { @@ -86,3 +96,11 @@ private struct Assembly2: AutoInitModuleAssembly { container.registerAbstract(String.self) } } + +private struct Assembly3: AutoInitModuleAssembly { + static var dependencies: [any ModuleAssembly.Type] { [] } + func assemble(container: Container) { + container.registerAbstract(Optional.self) + container.registerAbstract(Int?.self) + } +} From b00dc4ff97286678dc9144e1a42b4175f364ce97 Mon Sep 17 00:00:00 2001 From: Alex Skorulis <77594448+skorulis-ap@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:52:28 +1100 Subject: [PATCH 2/2] Improve documentation Co-authored-by: Brad Fol --- Sources/Knit/Module/Container+AbstractRegistration.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/Knit/Module/Container+AbstractRegistration.swift b/Sources/Knit/Module/Container+AbstractRegistration.swift index 06781f1..a8238e7 100644 --- a/Sources/Knit/Module/Container+AbstractRegistration.swift +++ b/Sources/Knit/Module/Container+AbstractRegistration.swift @@ -19,7 +19,10 @@ extension Container { } /// Register that a service is expected to exist but no implementation is currently available - /// If no concrete registration is available then nil will be resolved + /// The concrete implementation must be registered or the dependency graph is considered invalid + /// - NOTE: We don't currently support abstract registrations with arguments + /// As this is an `Optional` Service type this allows special handling of the abstract registration for test environments: + /// If during testing and no concrete registration is available, then `nil` will be resolved automatically. public func registerAbstract( _ serviceType: Optional.Type, name: String? = nil,