Skip to content

Commit

Permalink
Unify @dependency property and introduce optional dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
Supereg committed Nov 8, 2023
1 parent cf93e19 commit ee34612
Show file tree
Hide file tree
Showing 20 changed files with 450 additions and 280 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ extension Module {
}


extension _CollectPropertyWrapper: _StorageValueCollector {
extension _CollectPropertyWrapper: StorageValueCollector {
public func retrieve<Repository: SharedRepository<SpeziAnchor>>(from repository: Repository) {
injectedValues = repository[CollectedModuleValue<Value>.self] ?? []
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ extension Module {
}


extension _ProvidePropertyWrapper: _StorageValueProvider {
extension _ProvidePropertyWrapper: StorageValueProvider {
public func collect<Repository: SharedRepository<SpeziAnchor>>(into repository: inout Repository) {
if let wrapperWithOptional = self as? OptionalBasedProvideProperty {
wrapperWithOptional.collectOptional(into: &repository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,16 @@ import SpeziFoundation
/// data provided by other ``Module``s.
///
/// Data requested through a Storage Value Collector might be provided through a ``_StorageValueProvider``.
public protocol _StorageValueCollector {
// swiftlint:disable:previous type_name
// to be hidden from documentation

protocol StorageValueCollector {
/// This method is called to retrieve all the requested values from the given ``SpeziStorage`` repository.
/// - Parameter repository: Provides access to the ``SpeziStorage`` repository for read access.
func retrieve<Repository: SharedRepository<SpeziAnchor>>(from repository: Repository)
}


extension Module {
var storageValueCollectors: [_StorageValueCollector] {
retrieveProperties(ofType: _StorageValueCollector.self)
var storageValueCollectors: [StorageValueCollector] {
retrieveProperties(ofType: StorageValueCollector.self)
}

func injectModuleValues<Repository: SharedRepository<SpeziAnchor>>(from repository: Repository) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,16 @@ import SpeziFoundation
/// data with other ``Module``s.
///
/// Data provided through a Storage Value Provider can be retrieved through a ``_StorageValueCollector``.
public protocol _StorageValueProvider {
// swiftlint:disable:previous type_name
// to be hidden from documentation

protocol StorageValueProvider {
/// This method is called to collect all provided values into the given ``SpeziStorage`` repository.
/// - Parameter repository: Provides access to the ``SpeziStorage`` repository.
func collect<Repository: SharedRepository<SpeziAnchor>>(into repository: inout Repository)
}


extension Module {
var storageValueProviders: [_StorageValueProvider] {
retrieveProperties(ofType: _StorageValueProvider.self)
var storageValueProviders: [StorageValueProvider] {
retrieveProperties(ofType: StorageValueProvider.self)
}

func collectModuleValues<Repository: SharedRepository<SpeziAnchor>>(into repository: inout Repository) {
Expand Down
49 changes: 49 additions & 0 deletions Sources/Spezi/Dependencies/DependencyBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//


/// A result builder to build a ``DependencyCollection``.
@resultBuilder
public enum DependencyBuilder {
/// An auto-closure expression, providing the default dependency value, building the ``DependencyCollection``.
public static func buildExpression<M: Module>(_ expression: @escaping @autoclosure () -> M) -> DependencyCollection {
DependencyCollection(DependencyContext(defaultValue: expression))
}

/// Build a block of ``DependencyCollection``s.
public static func buildBlock(_ components: DependencyCollection...) -> DependencyCollection {
buildArray(components)
}

/// Build the first block of an conditional ``DependencyCollection`` component.
public static func buildEither(first component: DependencyCollection) -> DependencyCollection {
component
}

Check warning on line 26 in Sources/Spezi/Dependencies/DependencyBuilder.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Spezi/Dependencies/DependencyBuilder.swift#L24-L26

Added lines #L24 - L26 were not covered by tests

/// Build the second block of an conditional ``DependencyCollection`` component.
public static func buildEither(second component: DependencyCollection) -> DependencyCollection {
component
}

Check warning on line 31 in Sources/Spezi/Dependencies/DependencyBuilder.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Spezi/Dependencies/DependencyBuilder.swift#L29-L31

Added lines #L29 - L31 were not covered by tests

/// Build an optional ``DependencyCollection`` component.
public static func buildOptional(_ component: DependencyCollection?) -> DependencyCollection {
component ?? DependencyCollection()
}

Check warning on line 36 in Sources/Spezi/Dependencies/DependencyBuilder.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Spezi/Dependencies/DependencyBuilder.swift#L34-L36

Added lines #L34 - L36 were not covered by tests

/// Build an ``DependencyCollection`` component with limited availability.
public static func buildLimitedAvailability(_ component: DependencyCollection) -> DependencyCollection {
component
}

Check warning on line 41 in Sources/Spezi/Dependencies/DependencyBuilder.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Spezi/Dependencies/DependencyBuilder.swift#L39-L41

Added lines #L39 - L41 were not covered by tests

/// Build an array of ``DependencyCollection`` components.
public static func buildArray(_ components: [DependencyCollection]) -> DependencyCollection {
DependencyCollection(components.reduce(into: []) { result, component in
result.append(contentsOf: component.entries)
})
}
}
57 changes: 57 additions & 0 deletions Sources/Spezi/Dependencies/DependencyCollection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//


/// A collection of dependency declarations.
public struct DependencyCollection: DependencyDeclaration {
let entries: [AnyDependencyContext]


init(_ entries: [AnyDependencyContext]) {
self.entries = entries
}

init(_ entries: AnyDependencyContext...) {
self.init(entries)
}


func collect(into dependencyManager: DependencyManager) {
for entry in entries {
entry.collect(into: dependencyManager)
}
}

func inject(from dependencyManager: DependencyManager) {
for entry in entries {
entry.inject(from: dependencyManager)
}
}

private func singleDependencyContext() -> AnyDependencyContext {
guard let dependency = entries.first else {
preconditionFailure("DependencyCollection unexpectedly empty!")

Check warning on line 38 in Sources/Spezi/Dependencies/DependencyCollection.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Spezi/Dependencies/DependencyCollection.swift#L38

Added line #L38 was not covered by tests
}
precondition(entries.count == 1, "Expected exactly one element in the dependency collection!")
return dependency
}

func singleDependencyRetrieval<M>(for module: M.Type = M.self) -> M {
singleDependencyContext().retrieve(dependency: M.self)
}

func singleOptionalDependencyRetrieval<M>(for module: M.Type = M.self) -> M? {
singleDependencyContext().retrieveOptional(dependency: M.self)
}

func retrieveModules() -> [any Module] {
entries.map { dependency in
dependency.retrieve(dependency: (any Module).self)
}
}
}
57 changes: 57 additions & 0 deletions Sources/Spezi/Dependencies/DependencyContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import XCTRuntimeAssertions


protocol AnyDependencyContext: DependencyDeclaration {
func retrieve<M>(dependency: M.Type) -> M

func retrieveOptional<M>(dependency: M.Type) -> M?
}


class DependencyContext<Dependency: Module>: AnyDependencyContext {
let defaultValue: (() -> Dependency)?
private var injectedDependency: Dependency?

init(for type: Dependency.Type = Dependency.self, defaultValue: (() -> Dependency)? = nil) {
self.defaultValue = defaultValue
}

func collect(into dependencyManager: DependencyManager) {
dependencyManager.require(Dependency.self, defaultValue: defaultValue)
}

func inject(from dependencyManager: DependencyManager) {
precondition(injectedDependency == nil, "Dependency of type \(Dependency.self) is already injected!")
injectedDependency = dependencyManager.retrieve(optional: defaultValue == nil)
}

func retrieve<M>(dependency: M.Type) -> M {
guard let injectedDependency else {
preconditionFailure(
"""
A `@Dependency` was accessed before the dependency was activated. \
Only access dependencies once the module has been configured and the Spezi initialization is complete.
"""
)
}
guard let dependency = injectedDependency as? M else {
preconditionFailure("A injected dependency of type \(type(of: injectedDependency)) didn't match the expected type \(M.self)!")

Check warning on line 46 in Sources/Spezi/Dependencies/DependencyContext.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Spezi/Dependencies/DependencyContext.swift#L46

Added line #L46 was not covered by tests
}
return dependency
}

func retrieveOptional<M>(dependency: M.Type) -> M? {
guard let dependency = injectedDependency as? M? else {
preconditionFailure("A injected dependency of type \(type(of: injectedDependency)) didn't match the expected type \(M?.self)!")

Check warning on line 53 in Sources/Spezi/Dependencies/DependencyContext.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Spezi/Dependencies/DependencyContext.swift#L53

Added line #L53 was not covered by tests
}
return dependency
}
}
24 changes: 24 additions & 0 deletions Sources/Spezi/Dependencies/DependencyDeclaration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//


/// Provides mechanism to communicate dependency requirements.
///
/// This protocol allows to communicate dependency requirements of a ``Module`` to the ``DependencyManager``.
protocol DependencyDeclaration {
/// Request from the ``DependencyManager`` to collect all dependencies. Mark required by calling `DependencyManager/require(_:defaultValue:)`.
func collect(into dependencyManager: DependencyManager)
/// Inject the dependency instance from the ``DependencyManager``. Use `DependencyManager/retrieve(module:)`.
func inject(from dependencyManager: DependencyManager)
}

extension Module {
var dependencyDeclarations: [DependencyDeclaration] {
retrieveProperties(ofType: DependencyDeclaration.self)
}
}
26 changes: 0 additions & 26 deletions Sources/Spezi/Dependencies/DependencyDescriptor.swift

This file was deleted.

Loading

0 comments on commit ee34612

Please sign in to comment.