Skip to content

Instantly share code, notes, and snippets.

@dehrom
Last active August 12, 2022 17:17
Show Gist options
  • Save dehrom/8d2b03a3bbb16b4ec74d59dded821c65 to your computer and use it in GitHub Desktop.
Save dehrom/8d2b03a3bbb16b4ec74d59dded821c65 to your computer and use it in GitHub Desktop.
Repository pattern impl
import Combine
import Foundation
// MARK: - Typealiases
public typealias Seal<A, B: Error> = AnyPublisher<A, B>
// MARK: - Error
public enum DataSourceError: Error {
case notFound
case notAdd
case notUpdated
case notImplemented
}
// MARK: - Enums
public enum DataSourceUpdateType<Element> {
case none
case first(Element)
case second(Element)
}
// MARK: - Protocols
public protocol DataSourceTypes {
associatedtype Element
associatedtype Parameter
}
public protocol DataSource: DataSourceTypes {
func fetch() -> Seal<Element, DataSourceError>
func fetch(with parameter: Parameter) -> Seal<Element, DataSourceError>
func update(with element: Element) -> Seal<Never, DataSourceError>
}
public extension DataSource {
func asAnySource() -> AnyDataSource<Element, Parameter> { AnyDataSource(self) }
func concat<S: DataSource, M: DataSourcesMediatable>(
_ other: S,
using mediator: M
) -> AnyDataSource<Element, Parameter> where S.Element == Element, S.Parameter == Parameter,
M.Element == Element, M.Parameter == Parameter
{
SourcePair(
first: self,
second: other,
mediator: mediator
)
.asAnySource()
}
}
public protocol DataSourcesMediatable: DataSourceTypes {
func onFetch(
from first: Seal<Element, DataSourceError>,
and second: Seal<Element, DataSourceError>
) -> Seal<Element, DataSourceError>
func onFetch(
with parameter: Parameter,
from first: Seal<Element, DataSourceError>,
and second: Seal<Element, DataSourceError>
) -> Seal<Element, DataSourceError>
func onUpdate(with element: Element) -> DataSourceUpdateType<Element>
}
// MARK: - Classes
public final class AnyDataSource<Element, Parameter> {
init<S: DataSource>(_ concrete: S) where S.Element == Element, S.Parameter == Parameter {
_fetch = Function0(concrete.fetch)
_fetchP = Function1(concrete.fetch(with:))
_update = Function1(concrete.update(with:))
}
private let _fetch: Function0<Seal<Element, DataSourceError>>
private let _fetchP: Function1<Parameter, Seal<Element, DataSourceError>>
private let _update: Function1<Element, Seal<Never, DataSourceError>>
}
extension AnyDataSource: DataSource {
public func fetch() -> Seal<Element, DataSourceError> { _fetch.invoke() }
public func fetch(with parameter: Parameter) -> Seal<Element, DataSourceError> { _fetchP.invoke(parameter) }
public func update(with element: Element) -> Seal<Never, DataSourceError> { _update.invoke(element) }
}
final class SourcePair<S0: DataSource, S1: DataSource, Mediator: DataSourcesMediatable> {
init(
first: S0,
second: S1,
mediator: Mediator
) where S0.Element == Mediator.Element, S0.Parameter == Mediator.Parameter,
S1.Element == Mediator.Element, S1.Parameter == Mediator.Parameter
{
self.first = first.asAnySource()
self.second = second.asAnySource()
self.mediator = mediator
}
private let first: AnyDataSource<Mediator.Element, Mediator.Parameter>
private let second: AnyDataSource<Mediator.Element, Mediator.Parameter>
private let mediator: Mediator
}
extension SourcePair: DataSource {
public typealias Element = Mediator.Element
public typealias Parameter = Mediator.Parameter
public func fetch() -> Seal<Element, DataSourceError> {
let mUpdate = Function1(mediator.onUpdate(with:))
let sUpdate = Function1(updateSources)
let fetch = mediator
.onFetch(
from: first.fetch(),
and: second.fetch()
)
.share()
let update = fetch
.map(mUpdate.invoke)
.flatMap(sUpdate.invoke)
return Publishers.Zip(
fetch,
update
)
.map { a, _ in a }
.eraseToAnyPublisher()
}
public func fetch(with parameter: Parameter) -> Seal<Element, DataSourceError> {
let mUpdate = Function1(mediator.onUpdate(with:))
let sUpdate = Function1(updateSources)
let fetchP = mediator
.onFetch(
with: parameter,
from: first.fetch(with: parameter),
and: second.fetch(with: parameter)
)
.share()
let update = fetchP
.map(mUpdate.invoke)
.flatMap(sUpdate.invoke)
return Publishers.Zip(
fetchP,
update
)
.map { a, _ in a }
.eraseToAnyPublisher()
}
public func update(with element: Mediator.Element) -> Seal<Never, DataSourceError> {
Publishers.Merge(
first.update(with: element),
second.update(with: element)
)
.eraseToAnyPublisher()
}
}
private extension SourcePair {
func updateSources(_ type: DataSourceUpdateType<Mediator.Element>) -> Seal<Never, DataSourceError> {
switch type {
case .none:
return Empty().eraseToAnyPublisher()
case let .first(element):
return first.update(with: element)
case let .second(element):
return second.update(with: element)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment