Skip to content

Commit

Permalink
Adapters (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
PSchmiedmayer authored Jan 15, 2023
1 parent 3f99e61 commit 97269e8
Show file tree
Hide file tree
Showing 36 changed files with 504 additions and 427 deletions.
2 changes: 1 addition & 1 deletion Sources/Account/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"UAP_SIGNUP_PASSWORD_REPEAT_TITLE" = "Repeat
Password";
"UAP_SIGNUP_PASSWORD_REPEAT_PLACEHOLDER" = "Repeat your password ...";
"UAP_SIGNUP_PASSWORD_NOT_EQUAL_ERROR" = "The entered passwords are not equal";
"UAP_SIGNUP_PASSWORD_NOT_EQUAL_ERROR" = "The entered passwords are not equal.";
"UAP_SIGNUP_GIVEN_NAME_TITLE" = "Given Name";
"UAP_SIGNUP_GIVEN_NAME_PLACEHOLDER" = "Enter your given name ...";
"UAP_SIGNUP_FAMILY_NAME_TITLE" = "Family Name";
Expand Down
34 changes: 34 additions & 0 deletions Sources/CardinalKit/Adapter/Adapter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// This source file is part of the CardinalKit open-source project
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//


/// A ``Adapter`` can be used to transfrom an input `DataChange` (`InputElement` and `InputRemovalContext`)
/// to an output `DataChange` (`OutputElement` and `OutputRemovalContext`).
///
/// Use the ``AdapterBuilder`` to offer developers to option to pass in a `Adapter` instance to your components.
public protocol Adapter<InputElement, InputRemovalContext, OutputElement, OutputRemovalContext>: Actor {
/// The input element of the ``Adapter``
associatedtype InputElement: Identifiable, Sendable where InputElement.ID: Sendable
/// The input removal context of the ``Adapter``
associatedtype InputRemovalContext: Identifiable, Sendable where InputElement.ID == InputRemovalContext.ID
/// The output element of the ``Adapter``
associatedtype OutputElement: Identifiable, Sendable where OutputElement.ID: Sendable
/// The output removal context of the ``Adapter``
associatedtype OutputRemovalContext: Identifiable, Sendable where OutputElement.ID == OutputRemovalContext.ID


/// Transforms any `TypedAsyncSequence<DataChange<InputElement, InputRemovalContext>>` to an `TypedAsyncSequence` with
/// the `TypedAsyncSequence<DataChange<OutputElement, OutputRemovalContext>>` generic constraint fulfilling the transformation,
///
/// Implement this method in an instance of a `Adapter`.
/// - Parameter asyncSequence: The input `TypedAsyncSequence`.
/// - Returns: The transformed `TypedAsyncSequence`.
func transform(
_ asyncSequence: some TypedAsyncSequence<DataChange<InputElement, InputRemovalContext>>
) async -> any TypedAsyncSequence<DataChange<OutputElement, OutputRemovalContext>>
}
134 changes: 134 additions & 0 deletions Sources/CardinalKit/Adapter/AdapterBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//
// This source file is part of the CardinalKit open-source project
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import Foundation


// swiftlint:disable generic_type_name line_length

private actor TwoAdapterChain<
InputElement: Identifiable & Sendable,
InputRemovalContext: Identifiable & Sendable,
IntermediateElement: Identifiable & Sendable,
IntermediateRemovalContext: Identifiable & Sendable,
OutputElement: Identifiable & Sendable,
OutputRemovalContext: Identifiable & Sendable
>: Actor, Adapter
where InputElement.ID: Sendable, InputElement.ID == InputRemovalContext.ID,
IntermediateElement.ID: Sendable, IntermediateElement.ID == IntermediateRemovalContext.ID,
OutputElement.ID: Sendable, OutputElement.ID == OutputRemovalContext.ID {
let firstDataSourceRegistryAdapter: any Adapter<InputElement, InputRemovalContext, IntermediateElement, IntermediateRemovalContext>
let secondDataSourceRegistryAdapter: any Adapter<IntermediateElement, IntermediateRemovalContext, OutputElement, OutputRemovalContext>


init(
firstDataSourceRegistryAdapter: any Adapter<InputElement, InputRemovalContext, IntermediateElement, IntermediateRemovalContext>,
secondDataSourceRegistryAdapter: any Adapter<IntermediateElement, IntermediateRemovalContext, OutputElement, OutputRemovalContext>
) {
self.firstDataSourceRegistryAdapter = firstDataSourceRegistryAdapter
self.secondDataSourceRegistryAdapter = secondDataSourceRegistryAdapter
}


func transform(
_ asyncSequence: some TypedAsyncSequence<DataChange<InputElement, InputRemovalContext>>
) async -> any TypedAsyncSequence<DataChange<OutputElement, OutputRemovalContext>> {
let firstDataSourceRegistryTransformation = await firstDataSourceRegistryAdapter.transform(asyncSequence)
return await secondDataSourceRegistryAdapter.transform(firstDataSourceRegistryTransformation)
}
}

private actor ThreeAdapterChain<
InputElement: Identifiable & Sendable,
InputRemovalContext: Identifiable & Sendable,
IntermediateElementOne: Identifiable & Sendable,
IntermediateRemovalContextOne: Identifiable & Sendable,
IntermediateElementTwo: Identifiable & Sendable,
IntermediateRemovalContextTwo: Identifiable & Sendable,
OutputElement: Identifiable & Sendable,
OutputRemovalContext: Identifiable & Sendable
>: Actor, Adapter
where InputElement.ID: Sendable, InputElement.ID == InputRemovalContext.ID,
IntermediateElementOne.ID: Sendable, IntermediateElementOne.ID == IntermediateRemovalContextOne.ID,
IntermediateElementTwo.ID: Sendable, IntermediateElementTwo.ID == IntermediateRemovalContextTwo.ID,
OutputElement.ID: Sendable, OutputElement.ID == OutputRemovalContext.ID {
let firstDataSourceRegistryAdapter: any Adapter<InputElement, InputRemovalContext, IntermediateElementOne, IntermediateRemovalContextOne>
let secondDataSourceRegistryAdapter: any Adapter<IntermediateElementOne, IntermediateRemovalContextOne, IntermediateElementTwo, IntermediateRemovalContextTwo>
let thirdDataSourceRegistryAdapter: any Adapter<IntermediateElementTwo, IntermediateRemovalContextTwo, OutputElement, OutputRemovalContext>


init(
firstDataSourceRegistryAdapter: any Adapter<InputElement, InputRemovalContext, IntermediateElementOne, IntermediateRemovalContextOne>,
secondDataSourceRegistryAdapter: any Adapter<IntermediateElementOne, IntermediateRemovalContextOne, IntermediateElementTwo, IntermediateRemovalContextTwo>,
thirdDataSourceRegistryAdapter: any Adapter<IntermediateElementTwo, IntermediateRemovalContextTwo, OutputElement, OutputRemovalContext>
) {
self.firstDataSourceRegistryAdapter = firstDataSourceRegistryAdapter
self.secondDataSourceRegistryAdapter = secondDataSourceRegistryAdapter
self.thirdDataSourceRegistryAdapter = thirdDataSourceRegistryAdapter
}


func transform(
_ asyncSequence: some TypedAsyncSequence<DataChange<InputElement, InputRemovalContext>>
) async -> any TypedAsyncSequence<DataChange<OutputElement, OutputRemovalContext>> {
let firstDataSourceRegistryTransformation = await firstDataSourceRegistryAdapter.transform(asyncSequence)
let secondDataSourceRegistryTransformation = await secondDataSourceRegistryAdapter.transform(firstDataSourceRegistryTransformation)
return await thirdDataSourceRegistryAdapter.transform(secondDataSourceRegistryTransformation)
}
}


/// A function builder used to generate data source registry adapter chains.
@resultBuilder
public enum AdapterBuilder<OutputElement: Identifiable & Sendable, OutputRemovalContext: Identifiable & Sendable> where OutputElement.ID: Sendable, OutputElement.ID == OutputRemovalContext.ID {
/// Required by every result builder to build combined results from statement blocks.
public static func buildBlock<
InputElement: Identifiable & Sendable, InputRemovalContext: Identifiable & Sendable
> (
_ dataSourceRegistryAdapter: any Adapter<InputElement, InputRemovalContext, OutputElement, OutputRemovalContext>
) -> any Adapter<InputElement, InputRemovalContext, OutputElement, OutputRemovalContext>
where InputElement.ID: Sendable, InputElement.ID == InputRemovalContext.ID {
dataSourceRegistryAdapter
}

/// Required by every result builder to build combined results from statement blocks.
public static func buildBlock<
InputElement: Identifiable & Sendable, InputRemovalContext: Identifiable & Sendable,
IntermediateElement: Identifiable & Sendable, IntermediateRemovalContext: Identifiable & Sendable
> (
_ firstDataSourceRegistryAdapter: any Adapter<InputElement, InputRemovalContext, IntermediateElement, IntermediateRemovalContext>,
_ secondDataSourceRegistryAdapter: any Adapter<IntermediateElement, IntermediateRemovalContext, OutputElement, OutputRemovalContext>
) -> any Adapter<InputElement, InputRemovalContext, OutputElement, OutputRemovalContext>
where InputElement.ID: Sendable, InputElement.ID == InputRemovalContext.ID,
IntermediateElement.ID: Sendable, IntermediateElement.ID == IntermediateRemovalContext.ID {
TwoAdapterChain(
firstDataSourceRegistryAdapter: firstDataSourceRegistryAdapter,
secondDataSourceRegistryAdapter: secondDataSourceRegistryAdapter
)
}

/// Required by every result builder to build combined results from statement blocks.
public static func buildBlock<
InputElement: Identifiable & Sendable, InputRemovalContext: Identifiable & Sendable,
IntermediateElement1: Identifiable & Sendable, IntermediateRemovalContext1: Identifiable & Sendable,
IntermediateElement2: Identifiable & Sendable, IntermediateRemovalContext2: Identifiable & Sendable
> (
_ firstDataSourceRegistryAdapter: any Adapter<InputElement, InputRemovalContext, IntermediateElement1, IntermediateRemovalContext1>,
_ secondDataSourceRegistryAdapter: any Adapter<IntermediateElement1, IntermediateRemovalContext1, IntermediateElement2, IntermediateRemovalContext2>,
_ thirdDataSourceRegistryAdapter: any Adapter<IntermediateElement2, IntermediateRemovalContext2, OutputElement, OutputRemovalContext>
) -> any Adapter<InputElement, InputRemovalContext, OutputElement, OutputRemovalContext>
where InputElement.ID: Sendable, InputElement.ID == InputRemovalContext.ID,
IntermediateElement1.ID: Sendable, IntermediateElement1.ID == IntermediateRemovalContext1.ID,
IntermediateElement2.ID: Sendable, IntermediateElement2.ID == IntermediateRemovalContext2.ID {
ThreeAdapterChain(
firstDataSourceRegistryAdapter: firstDataSourceRegistryAdapter,
secondDataSourceRegistryAdapter: secondDataSourceRegistryAdapter,
thirdDataSourceRegistryAdapter: thirdDataSourceRegistryAdapter
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@


/// A ``DataChange`` tracks the addition or removel of elements across components.
public enum DataChange<Element: Identifiable & Sendable>: Sendable where Element.ID: Sendable {
public enum DataChange<
Element: Identifiable & Sendable,
RemovalContext: Identifiable & Sendable
>: Sendable where Element.ID: Sendable, Element.ID == RemovalContext.ID {
/// A new element was added
case addition(Element)
/// An element was removed
case removal(Element.ID)
case removal(RemovalContext)


/// The identifier of the `Element`.
public var id: Element.ID {
switch self {
case let .addition(element):
return element.id
case let .removal(elementId):
return elementId
case let .removal(removalContext):
return removalContext.id
}
}

Expand All @@ -31,15 +34,15 @@ public enum DataChange<Element: Identifiable & Sendable>: Sendable where Element
/// - 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.
/// - Returns: Returns the mapped element
public func map<I: Identifiable & Sendable>(
element elementMap: (Element) -> I,
id idMap: (Element.ID) -> I.ID
) -> DataChange<I> {
public func map<E, R> (
element elementMap: (Element) -> (E),
removalContext removalContextMap: (RemovalContext) -> (R)
) -> DataChange<E, R> {
switch self {
case let .addition(element):
return .addition(elementMap(element))
case let .removal(elementId):
return .removal(idMap(elementId))
case let .removal(removalContext):
return .removal(removalContextMap(removalContext))
}
}
}
42 changes: 42 additions & 0 deletions Sources/CardinalKit/Adapter/SingleValueAdapter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// This source file is part of the CardinalKit open-source project
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//


/// A ``SingleValueAdapter`` is a ``Adapter`` that can be used to more simply transform data using the
/// ``SingleValueAdapter/transform(element:)`` and ``SingleValueAdapter/transform(id:)`` functions.
///
/// See ``Adapter`` for more detail about data source registry adapters.
public protocol SingleValueAdapter<InputElement, InputRemovalContext, OutputElement, OutputRemovalContext>: Adapter {
/// Map the element of the transformed async streams from additions.
/// - Parameter element: The element that should be transformed.
/// - Returns: Returns the transformed element
func transform(element: InputElement) -> OutputElement

/// Map the element's removal of the transformed async streams from removals.
/// - Parameter removalContext: The element's removal context that should be transformed.
/// - Returns: Returns the transformed removal context.
func transform(removalContext: InputRemovalContext) -> OutputRemovalContext
}


extension SingleValueAdapter {
// A documentation for this methodd exists in the `Adapter` type which SwiftLint doesn't recognize.
// swiftlint:disable:next missing_docs
public func transform(
_ asyncSequence: some TypedAsyncSequence<DataChange<InputElement, InputRemovalContext>>
) async -> any TypedAsyncSequence<DataChange<OutputElement, OutputRemovalContext>> {
asyncSequence.map { [self] element in
switch element {
case let .addition(element):
return await .addition(transform(element: element))
case let .removal(elementId):
return await .removal(transform(removalContext: elementId))
}
}
}
}
40 changes: 40 additions & 0 deletions Sources/CardinalKit/Adapter/Types+Identifiable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// This source file is part of the CardinalKit open-source project
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import Foundation


extension UUID: Identifiable {
public var id: UUID {
self
}
}

extension Int: Identifiable {
public var id: Int {
self
}
}

extension Double: Identifiable {
public var id: Double {
self
}
}

extension Float: Identifiable {
public var id: Float {
self
}
}

extension String: Identifiable {
public var id: String {
self
}
}
3 changes: 2 additions & 1 deletion Sources/CardinalKit/CardinalKit/CardinalKitAppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ import SwiftUI
open class CardinalKitAppDelegate: NSObject, UIApplicationDelegate {
private actor DefaultStandard: Standard {
typealias BaseType = StandardType
typealias RemovalContext = BaseType


struct StandardType: Identifiable {
var id: UUID
}


func registerDataSource(_ asyncSequence: some TypedAsyncSequence<DataChange<BaseType>>) { }
func registerDataSource(_ asyncSequence: some TypedAsyncSequence<DataChange<BaseType, RemovalContext>>) { }
}


Expand Down
20 changes: 11 additions & 9 deletions Sources/CardinalKit/DataSource/DataSourceRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,30 @@

/// A ``DataSourceRegistry`` can recieve data from data sources using the ``DataSourceRegistry/registerDataSource(_:)`` method.
/// Each ``DataSourceRegistry`` has a ``DataSourceRegistry/BaseType`` that all data sources should provide.
/// Use ``DataSourceRegistryAdapter``s to transform data of different data sources.
public protocol DataSourceRegistry<BaseType>: Actor {
/// The ``DataSourceRegistry/BaseType`` that all data sources should provide.
associatedtype BaseType: Identifiable, Sendable where BaseType.ID: Sendable
/// Use ``Adapter``s to transform data of different data sources.
public protocol DataSourceRegistry<BaseType, RemovalContext>: Actor {
/// The ``DataSourceRegistry/BaseType`` that all data sources should provide when adding or updating an element.
associatedtype BaseType: Identifiable, Sendable where BaseType.ID: Sendable, BaseType.ID == RemovalContext.ID
/// The ``DataSourceRegistry/RemovalContext`` that all data sources should provide when removing an element.
associatedtype RemovalContext: Identifiable, Sendable


/// Registers a new data source for the ``DataSourceRegistry``.
/// - Parameter asyncSequence: The `TypedAsyncSequence<DataChange<BaseType>>` providing the data to the ``DataSourceRegistry``.
func registerDataSource(_ asyncSequence: some TypedAsyncSequence<DataChange<BaseType>>)
func registerDataSource(_ asyncSequence: some TypedAsyncSequence<DataChange<BaseType, RemovalContext>>)
}


extension DataSourceRegistry {
/// Overload of the ``DataSourceRegistry/registerDataSource(_:)`` method to recieve `AsyncStream`s.
/// - Parameter asyncStream: The `AsyncStream<DataChange<BaseType>>` providing the data to the ``DataSourceRegistry``.
public func registerDataSource(asyncStream: AsyncStream<DataChange<BaseType>>) {
/// - 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.
/// - Parameter asyncThrowingStream: The `AsyncThrowingStream<DataChange<BaseType>>` providing the data to the ``DataSourceRegistry``.
public func registerDataSource(asyncThrowingStream: AsyncThrowingStream<DataChange<BaseType>, Error>) {
/// - Parameter asyncThrowingStream: The `AsyncThrowingStream<DataChange<BaseType, RemovalContext>>` providing the data to the ``DataSourceRegistry``.
public func registerDataSource(asyncThrowingStream: AsyncThrowingStream<DataChange<BaseType, RemovalContext>, Error>) {
registerDataSource(asyncThrowingStream)
}
}
29 changes: 0 additions & 29 deletions Sources/CardinalKit/DataSource/DataSourceRegistryAdapter.swift

This file was deleted.

Loading

0 comments on commit 97269e8

Please sign in to comment.