Skip to content

Instantly share code, notes, and snippets.

@jessegrosjean
Created April 28, 2021 16:34
Show Gist options
  • Save jessegrosjean/243102a1153dc8c7a7217c5abafd9cbf to your computer and use it in GitHub Desktop.
Save jessegrosjean/243102a1153dc8c7a7217c5abafd9cbf to your computer and use it in GitHub Desktop.
import Combine
import Foundation
import CoreGraphics
protocol Store {
associatedtype Object
func insert(_ object: Object) -> Future<Object, Error>
func update(_ object: Object) -> Future<Object, Error>
func delete(_ object: Object) -> Future<Object, Error>
func execute(_ query: Query<Self>) -> Future<[Object], Error>
}
extension Store {
func filter(where predicate: Predicate<Object>) -> Future<[Object], Error> {
execute(Query(store: self, predicate: predicate, sortCriteria: []))
}
}
struct SortCriterion<T> {
let property: PartialKeyPath<T>
let order: Order
enum Order {
case ascending
case descending
}
}
struct Query<T: Store> {
let store: T
let predicate: Predicate<T.Object>
let sortCriteria: [SortCriterion<T.Object>]
func result() -> Future<[T.Object], Error> {
store.execute(self)
}
func sorted<U: Comparable>(
by property: KeyPath<T.Object, U>,
inOrder order: SortCriterion<T.Object>.Order = .ascending
) -> Query<T> {
Query(
store: store,
predicate: predicate,
sortCriteria: sortCriteria + [SortCriterion(
property: property,
order: order
)]
)
}
}
indirect enum Predicate<T> {
case comparison(PartialKeyPath<T>, Operator, Primitive)
case and(Predicate<T>, Predicate<T>)
case or(Predicate<T>, Predicate<T>)
case not(Predicate<T>)
}
func == <T, U: Equatable & Primitive> (lhs: KeyPath<T, U>, rhs: U) -> Predicate<T> {
return .comparison(lhs, .equalTo, rhs)
}
func < <T, U: Comparable & Primitive> (lhs: KeyPath<T, U>, rhs: U) -> Predicate<T> {
.comparison(lhs, .lessThan, rhs)
}
func <= <T, U: Comparable & Primitive> (lhs: KeyPath<T, U>, rhs: U) -> Predicate<T> {
.comparison(lhs, .lessThanOrEqualTo, rhs)
}
func > <T, U: Comparable & Primitive> (lhs: KeyPath<T, U>, rhs: U) -> Predicate<T> {
.comparison(lhs, .greaterThan, rhs)
}
func >= <T, U: Comparable & Primitive> (lhs: KeyPath<T, U>, rhs: U) -> Predicate<T> {
.comparison(lhs, .greaterThanOrEqualTo, rhs)
}
func && <T> (lhs: Predicate<T>, rhs: Predicate<T>) -> Predicate<T> {
.and(lhs, rhs)
}
func || <T> (lhs: Predicate<T>, rhs: Predicate<T>) -> Predicate<T> {
.or(lhs, rhs)
}
prefix func ! <T> (predicate: Predicate<T>) -> Predicate<T> {
.not(predicate)
}
enum Operator {
case lessThan
case lessThanOrEqualTo
case equalTo
case greaterThanOrEqualTo
case greaterThan
}
protocol Primitive {
var type: Type { get }
}
indirect enum Type {
case bool
case int
case int8
case int16
case int32
case int64
case uint
case uint8
case uint16
case uint32
case uint64
case double
case float
case cgFloat
case string
case date
case wrapped(Any, Type)
}
extension Bool: Primitive {
var type: Type { .bool }
}
extension Int: Primitive {
var type: Type { .int }
}
extension Int8: Primitive {
var type: Type { .int8 }
}
extension Int16: Primitive {
var type: Type { .int16 }
}
extension Int32: Primitive {
var type: Type { .int32 }
}
extension Int64: Primitive {
var type: Type { .int64 }
}
extension UInt: Primitive {
var type: Type { .uint }
}
extension UInt8: Primitive {
var type: Type { .uint8 }
}
extension UInt16: Primitive {
var type: Type { .uint16 }
}
extension UInt32: Primitive {
var type: Type { .uint32 }
}
extension UInt64: Primitive {
var type: Type { .uint64 }
}
extension Double: Primitive {
var type: Type { .double }
}
extension Float: Primitive {
var type: Type { .float }
}
extension CGFloat: Primitive {
var type: Type { .cgFloat }
}
extension String: Primitive {
var type: Type { .string }
}
extension Date: Primitive {
var type: Type { .date }
}
extension Primitive where Self: RawRepresentable, RawValue == Int {
var type: Type { .wrapped(rawValue, .int) }
}
extension Primitive where Self: RawRepresentable, RawValue == Int8 {
var type: Type { .wrapped(rawValue, .int8) }
}
extension Primitive where Self: RawRepresentable, RawValue == Int16 {
var type: Type { .wrapped(rawValue, .int16) }
}
extension Primitive where Self: RawRepresentable, RawValue == Int32 {
var type: Type { .wrapped(rawValue, .int32) }
}
extension Primitive where Self: RawRepresentable, RawValue == Int64 {
var type: Type { .wrapped(rawValue, .int64) }
}
extension Primitive where Self: RawRepresentable, RawValue == UInt {
var type: Type { .wrapped(rawValue, .uint) }
}
extension Primitive where Self: RawRepresentable, RawValue == UInt8 {
var type: Type { .wrapped(rawValue, .uint8) }
}
extension Primitive where Self: RawRepresentable, RawValue == UInt16 {
var type: Type { .wrapped(rawValue, .uint16) }
}
extension Primitive where Self: RawRepresentable, RawValue == UInt32 {
var type: Type { .wrapped(rawValue, .uint32) }
}
extension Primitive where Self: RawRepresentable, RawValue == UInt64 {
var type: Type { .wrapped(rawValue, .uint64) }
}
extension Primitive where Self: RawRepresentable, RawValue == Double {
var type: Type { .wrapped(rawValue, .double) }
}
extension Primitive where Self: RawRepresentable, RawValue == Float {
var type: Type { .wrapped(rawValue, .float) }
}
extension Primitive where Self: RawRepresentable, RawValue == CGFloat {
var type: Type { .wrapped(rawValue, .cgFloat) }
}
extension Primitive where Self: RawRepresentable, RawValue == String {
var type: Type { .wrapped(rawValue, .string) }
}
extension Predicate {
func isIncluded() -> (T) -> Bool {
switch self {
case let .comparison(keyPath, .greaterThan, value):
return { $0[keyPath: keyPath] > value }
case let .comparison(keyPath, .greaterThanOrEqualTo, value):
return { $0[keyPath: keyPath] >= value }
case let .comparison(keyPath, .equalTo, value):
return { $0[keyPath: keyPath] == value }
case let .comparison(keyPath, .lessThanOrEqualTo, value):
return { $0[keyPath: keyPath] <= value }
case let .comparison(keyPath, .lessThan, value):
return { $0[keyPath: keyPath] < value }
case let .and(firstPredicate, secondPredicate):
return { firstPredicate.isIncluded()($0) && secondPredicate.isIncluded()($0) }
case let .or(firstPredicate, secondPredicate):
return { firstPredicate.isIncluded()($0) || secondPredicate.isIncluded()($0) }
case let .not(predicate):
return { predicate.isIncluded()($0) == false }
}
}
}
struct Movie {
let id: Id
let title: String
let genre: String
let budget: Double
typealias Id = String
}
final class MovieStoreMock: Store {
private(set) var movies: [Movie] = []
func insert(_ object: Movie) -> Future<Movie, Error> {
return Future { completion in
self.movies.append(object)
completion(.success(object))
}
}
func update(_ object: Movie) -> Future<Movie, Error> {
return Future { completion in
if let index = self.movies.firstIndex(where: { $0.id == object.id }) {
self.movies[index] = object
}
completion(.success(object))
}
}
func delete(_ object: Movie) -> Future<Movie, Error> {
return Future { completion in
if let index = self.movies.firstIndex(where: { $0.id == object.id }) {
self.movies.remove(at: index)
}
completion(.success(object))
}
}
func execute(_ query: Query<MovieStoreMock>) -> Future<[Movie], Error> {
return Future { completion in
let includedMovies = self.movies.filter(query.predicate.isIncluded())
completion(.success(includedMovies))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment