Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Component communication #72

Merged
merged 25 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8cdad10
Minor corrections and spelling
Supereg Jul 14, 2023
23945b5
Add generalized SharedRepository pattern; added @Provide and @Collect…
Supereg Jul 15, 2023
dcbaf0b
Fix usage of collect(allOf:) for stored components
Supereg Jul 15, 2023
1f5a263
Minor modifications
Supereg Jul 15, 2023
59c8a5f
Fix reuse, fix spelling
Supereg Jul 15, 2023
dfc185f
Provide dedicated get and set functions so that overloading of protoc…
Supereg Jul 16, 2023
d04bdf4
Fix precondition tests, oopsie
Supereg Jul 16, 2023
904cb58
Organize files and iterate on the implementation
Supereg Jul 18, 2023
853fc48
Resolve todos in sources
Supereg Jul 18, 2023
5c3c77e
Remove some outdated tests
Supereg Jul 18, 2023
ae39e5d
Add tests
Supereg Jul 19, 2023
ac73c9c
Add precondition tests. For now just use preconditionFailure
Supereg Jul 19, 2023
3d04361
All the spaces
Supereg Jul 19, 2023
4e257a1
Hide some protocols from documentation
Supereg Jul 19, 2023
e816237
Upgrade XCTRuntimeAssertions
Supereg Jul 19, 2023
b425c46
Additional docs
Supereg Jul 19, 2023
8ffc1f6
Minor changes
Supereg Jul 19, 2023
644a1d5
Remove prepare method
Supereg Jul 19, 2023
eb27c2b
Pass the logger through a KnowledgeSource
Supereg Jul 19, 2023
5cdd2a2
Add documentation and fix broken documentation links
Supereg Jul 19, 2023
ac20958
Add Article Extension for Component
Supereg Jul 19, 2023
bbff7fd
Fix REUSE
Supereg Jul 19, 2023
9535827
fix year, copy pasta
Supereg Jul 19, 2023
31f4a17
Typos
Supereg Jul 19, 2023
03e84f3
Update SpeziLogger.swift
PSchmiedmayer Jul 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
only_rules:
# All Images that provide context should have an accessibility label. Purely decorative images can be hidden from accessibility.
- accessibility_label_for_image
# Attributes should be on their own lines in functions and types, but on the same line as variables and imports.
- attributes
# Prefer using Array(seq) over seq.map { $0 } to convert a sequence into an Array.
- array_init
# Prefer the new block based KVO API with keypaths when using Swift 3.2 or later.
Expand Down Expand Up @@ -141,8 +139,6 @@ only_rules:
- implicitly_unwrapped_optional
# Identifiers should use inclusive language that avoids discrimination against groups of people based on race, gender, or socioeconomic status
- inclusive_language
# If defer is at the end of its parent scope, it will be executed right where it is anyway.
- inert_defer
# Prefer using Set.isDisjoint(with:) over Set.intersection(_:).isEmpty.
- is_disjoint
# Discouraged explicit usage of the default separator.
Expand Down Expand Up @@ -331,8 +327,6 @@ only_rules:
- unowned_variable_capture
# Catch statements should not declare error variables without type casting.
- untyped_error_in_catch
# Unused reference in a capture list should be removed.
- unused_capture_list
# Unused parameter in a closure should be replaced with _.
- unused_closure_parameter
# Unused control flow label should be removed.
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ Spezi contributors

* [Paul Schmiedmayer](https://github.com/PSchmiedmayer)
* [Vishnu Ravi](https://github.com/vishnuravi)
* [Andreas Bauer](https://github.com/Supereg)
Supereg marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ let package = Package(
.library(name: "XCTSpezi", targets: ["XCTSpezi"])
],
dependencies: [
.package(url: "https://github.com/StanfordSpezi/XCTRuntimeAssertions", .upToNextMinor(from: "0.2.1"))
.package(url: "https://github.com/StanfordBDHG/XCTRuntimeAssertions", .upToNextMinor(from: "0.2.5"))
],
targets: [
.target(
Expand All @@ -41,6 +41,7 @@ let package = Package(
name: "SpeziTests",
dependencies: [
.target(name: "Spezi"),
.target(name: "XCTSpezi"),
.product(name: "XCTRuntimeAssertions", package: "XCTRuntimeAssertions")
]
)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Spezi/Adapter/Adapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//


/// A ``Adapter`` can be used to transfrom an input `DataChange` (`InputElement` and `InputRemovalContext`)
/// A ``Adapter`` can be used to transform an input `DataChange` (`InputElement` and `InputRemovalContext`)
/// to an output `DataChange` (`OutputElement` and `OutputRemovalContext`).
///
///
Expand Down
6 changes: 3 additions & 3 deletions Sources/Spezi/Adapter/DataChange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ public enum DataChange<
/// Maps a ``DataChange`` to an other `Element` type.
/// - Parameters:
/// - elementMap: The element map function maps the complete `Element` instance used for the ``DataChange/addition(_:)`` case.
/// - idMap: The id map function only maps the identifier or an `Element` used for the ``DataChange/removal(_:)`` case.
/// - removalContextMap: The id map function only maps the identifier or an `Element` used for the ``DataChange/removal(_:)`` case.
/// - Returns: Returns the mapped element
public func map<E, R> (
element elementMap: (Element) -> (E),
removalContext removalContextMap: (RemovalContext) -> (R)
element elementMap: (Element) -> E,
Supereg marked this conversation as resolved.
Show resolved Hide resolved
removalContext removalContextMap: (RemovalContext) -> R
) -> DataChange<E, R> {
switch self {
case let .addition(element):
Expand Down
2 changes: 1 addition & 1 deletion Sources/Spezi/Adapter/SingleValueAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public protocol SingleValueAdapter<InputElement, InputRemovalContext, OutputElem


extension SingleValueAdapter {
// A documentation for this methodd exists in the `Adapter` type which SwiftLint doesn't recognize.
// A documentation for this method exists in the `Adapter` type which SwiftLint doesn't recognize.
// swiftlint:disable:next missing_docs
public func transform(
_ asyncSequence: some TypedAsyncSequence<DataChange<InputElement, InputRemovalContext>>
Expand Down
49 changes: 6 additions & 43 deletions Sources/Spezi/Configuration/Component.swift
Supereg marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -7,59 +7,22 @@
//


/// A ``Component`` defines a software subsystem that can be configured as part of the ``SpeziAppDelegate/configuration``.
///
/// The ``Component/ComponentStandard`` defines what Standard the component supports.
/// The ``Component/configure()-m7ic`` method is called on the initialization of Spezi.
///
///
/// **The Component Standard**
///
/// A ``Component`` can support any generic standard or add additional constraints using an optional where clause:
/// ```swift
/// class ExampleComponent<ComponentStandard: Standard>: Component where ComponentStandard: /* ... */ {
/// /*... */
/// }
/// ```
///
/// ``Component``s can also specify support for only one specific ``Standard`` using a `typealias` definition:
/// ```swift
/// class ExampleFHIRComponent: Component {
/// typealias ComponentStandard = FHIR
/// }
/// ```
///
///
/// **Dependencies**
///
/// ``Component``s can define dependencies between each other using the @``Component/Dependency`` property wrapper.
/// ```swift
/// class ExampleComponent<ComponentStandard: Standard>: Component {
/// @Dependency var exampleComponentDependency = ExampleComponentDependency()
/// }
/// ```
///
///
/// **Additional Capabilities**
///
/// Components can also conform to different additional protocols to provide additional access to Spezi features.
/// - ``LifecycleHandler``: Delegate methods are related to the `UIApplication` and ``Spezi/Spezi`` lifecycle.
/// - ``ObservableObjectProvider``: A ``Component`` can conform to ``ObservableObjectProvider`` to inject `ObservableObject`s in the SwiftUI view hierarchy.
///
/// All these protocols are combined in the ``Module`` protocol, making it an easy one-stop solution to support all these different functionalities and build a capable Spezi module.
public protocol Component<ComponentStandard>: AnyObject, TypedCollectionKey {
// note: detailed documentation is provided as an article extension in the DocC bundle
/// A `Component` defines a software subsystem that can be configured as part of the ``SpeziAppDelegate/configuration``.
public protocol Component<ComponentStandard>: AnyObject, KnowledgeSource<SpeziAnchor> {
/// A ``Component/ComponentStandard`` defines what ``Standard`` the component supports.
associatedtype ComponentStandard: Standard

/// The ``Component/configure()-m7ic`` method is called on the initialization of the Spezi instance to perform a lightweight configuration of the component.
/// The ``Component/configure()-27tt1`` method is called on the initialization of the Spezi instance to perform a lightweight configuration of the component.
///
/// Both ``Component/Dependency`` and ``Component/DynamicDependencies`` are available and configured at this point.
/// It is advised that longer setup tasks are done in an asynchronous task and started during the call of the configure method.
func configure()
}


extension Component {
// A documentation for this methodd exists in the `Component` type which SwiftLint doesn't recognize.
// A documentation for this method exists in the `Component` type which SwiftLint doesn't recognize.
// swiftlint:disable:next missing_docs
public func configure() {}
}
2 changes: 1 addition & 1 deletion Sources/Spezi/Configuration/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public struct Configuration {
/// - components: The ``Component``s used in the Spezi project. You can define the ``Component``s using the ``ComponentBuilder`` result builder.
public init<S: Standard>(
standard: S,
@ComponentBuilder<S> _ components: () -> (ComponentCollection<S>)
@ComponentBuilder<S> _ components: () -> ComponentCollection<S>
) {
self.spezi = Spezi<S>(standard: standard, components: components().elements)
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/Spezi/DataSource/DataSourceRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//


/// A ``DataSourceRegistry`` can recieve data from data sources using the ``DataSourceRegistry/registerDataSource(_:)`` method.
/// A ``DataSourceRegistry`` can receive data from data sources using the ``DataSourceRegistry/registerDataSource(_:)`` method.
///
/// Each ``DataSourceRegistry`` has a ``DataSourceRegistry/BaseType`` that all data sources should provide.
/// Use ``Adapter``s to transform data from different data sources.
Expand All @@ -27,13 +27,13 @@ public protocol DataSourceRegistry<BaseType, RemovalContext>: Actor {


extension DataSourceRegistry {
/// Overload of the ``DataSourceRegistry/registerDataSource(_:)`` method to recieve `AsyncStream`s.
/// Overload of the ``DataSourceRegistry/registerDataSource(_:)`` method to receive `AsyncStream`s.
/// - Parameter asyncStream: The `AsyncStream<DataChange<BaseType, RemovalContext>>` providing the data to the ``DataSourceRegistry``.
public func registerDataSource(asyncStream: AsyncStream<DataChange<BaseType, RemovalContext>>) {
registerDataSource(asyncStream)
}

/// Overload of the ``DataSourceRegistry/registerDataSource(_:)`` method to recieve `AsyncThrowingStream`s.
/// Overload of the ``DataSourceRegistry/registerDataSource(_:)`` method to receive `AsyncThrowingStream`s.
/// - Parameter asyncThrowingStream: The `AsyncThrowingStream<DataChange<BaseType, RemovalContext>>` providing the data to the ``DataSourceRegistry``.
public func registerDataSource(asyncThrowingStream: AsyncThrowingStream<DataChange<BaseType, RemovalContext>, Error>) {
registerDataSource(asyncThrowingStream)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class _DataStorageProvidersPropertyWrapper<S: Standard> {
private var dataStorageProviders: [any DataStorageProvider<S>]?


/// The injected ``[any DataStorageProvider<S>]`` that are resolved by ``Spezi``
/// The injected list of ``DataStorageProvider`` that are resolved by ``Spezi``
public var wrappedValue: [any DataStorageProvider<S>] {
guard let dataStorageProviders else {
preconditionFailure(
Expand Down
4 changes: 2 additions & 2 deletions Sources/Spezi/DataStorageProvider/EncodableAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
public protocol EncodableAdapter<InputType, ID>: Actor {
/// The input type conforming to `Sendable` and `Identifiable` where the `InputType.ID` is `Sendable` as well.
associatedtype InputType: Sendable, Identifiable where InputType.ID: Sendable
/// The ouput of the ``transform(id:)`` function that has to confrom to `Sendable` and `Hashable`.
/// The output of the ``transform(id:)`` function that has to conform to `Sendable` and `Hashable`.
associatedtype ID: Sendable, Hashable


/// Transforms an element to an `Encodable` and `Sendable` value.
/// - Parameter element: The element that is tranformed.
/// - Parameter element: The element that is transformed.
/// - Returns: Returns an element conforming to an `Encodable` and `Sendable`
func transform(element: InputType) async -> any Encodable & Sendable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@

extension Spezi {
nonisolated var dataStorageProviders: [any DataStorageProvider<S>] {
typedCollection.get(allThatConformTo: (any DataStorageProvider<S>).self)
storage.collect(allOf: (any DataStorageProvider<S>).self)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,8 @@

extension Standard {
nonisolated func inject(dataStorageProviders: [any DataStorageProvider<Self>]) {
let mirror = Mirror(reflecting: self)
for child in mirror.children {
guard let standardPropertyWrapper = child.value as? DataStorageProviders else {
continue
}
standardPropertyWrapper.inject(dataStorageProviders: dataStorageProviders)
for provider in retrieveProperties(ofType: DataStorageProviders.self) {
Supereg marked this conversation as resolved.
Show resolved Hide resolved
provider.inject(dataStorageProviders: dataStorageProviders)
}
}
}
Expand All @@ -33,7 +29,7 @@ extension Standard {
/// }
/// ```
///
/// You can access the wrapped value of the `@` ``Standard/DataStorageProviders`` after the ``Standard`` is configured using ``Component/configure()-5lup3``,
/// e.g. in the ``LifecycleHandler/willFinishLaunchingWithOptions(_:launchOptions:)-26h4k`` function.
/// You can access the wrapped value of the `@` ``Standard/DataStorageProviders`` after the ``Standard`` is configured using ``Component/configure()-27tt1``,
/// e.g. in the ``LifecycleHandler/willFinishLaunchingWithOptions(_:launchOptions:)-8jatp`` function.
public typealias DataStorageProviders = _DataStorageProvidersPropertyWrapper<Self>
}
Supereg marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// 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


/// Refer to the documentation of ``Component/Collect``.
@propertyWrapper
public class _CollectPropertyWrapper<Value> {
// swiftlint:disable:previous type_name
// We want the type to be hidden from autocompletion and documentation generation

private var injectedValues: [Value]? // swiftlint:disable:this discouraged_optional_collection

Supereg marked this conversation as resolved.
Show resolved Hide resolved

/// Access the collected values.
/// - Note: The property is only accessible within the ``Component/configure()`` method.
public var wrappedValue: [Value] {
guard let values = injectedValues else {
preconditionFailure("""
Tried to access @Collect for value [\(Value.self)] which wasn't injected yet. \
Are you sure that you are only accessing @Collect within the `Component/configure` method?
""")
}

return values
}

Supereg marked this conversation as resolved.
Show resolved Hide resolved

/// Initialize a new `@Collect` property wrapper.
public init() {}
}


extension Component {
/// The `@Collect` property wrapper can be used to retrieve data communicated by other ``Component``s by
/// retrieving them from the central ``SpeziStorage`` repository.
///
/// ### Retrieving Data
/// ``Component/Collect`` retrieves data provided through the ``Component/Provide`` property wrapper.
/// You declare `@Collect` as an Array with a given type. The type is used to match `@Provide` properties.
///
/// - Important: The property is only accessible within the ``Component/configure()-27tt1`` method.
///
/// Below is an example where the `ExampleComponent` collects an array of `Numeric` types from all other `Components`.
///
/// ```swift
/// class ExampleComponent<ComponentStandard: Standard>: Component {
Supereg marked this conversation as resolved.
Show resolved Hide resolved
/// @Collect var favoriteNumbers: [Numeric]
///
/// func configure() {
/// // you can safely access `favoriteNumbers` inside the configure method.
/// }
/// }
/// ```
public typealias Collect = _CollectPropertyWrapper
}


extension _CollectPropertyWrapper: _StorageValueCollector {
public func retrieve<Repository: SharedRepository<SpeziAnchor>>(from repository: Repository) {
injectedValues = repository[CollectedComponentValue<Value>.self] ?? []
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// 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 the ``KnowledgeSource`` for any value we store in the ``SpeziStorage`` that is
/// provided or request from am ``Component``.
///
/// For more information, look at the ``Component/Provide`` or ``Component/Collect`` property wrappers.
struct CollectedComponentValue<ComponentValue>: DefaultProvidingKnowledgeSource {
typealias Anchor = SpeziAnchor

typealias Value = [ComponentValue]


static var defaultValue: [ComponentValue] {
[]
}


static func reduce(value: inout [ComponentValue], nextValue: [ComponentValue]) {
value.append(contentsOf: nextValue)
}
}
Loading