swift-identified-storage

CI

一个 Swift 包,用于模拟带有类似 CRUD 接口的远程存储。

动机

通常需要模拟数据库客户端,以便进行 Xcode 预览或在不使用实际客户端的情况下测试代码。 这个包构建在 swift-identified-collections 中的 IdentifiedArray 类型之上,并依赖 swift-dependencies 来实现可控的 clock 操作。

安装

将其作为 Swift 包安装到你的项目中。

import PackageDescription

let package = Package(
    ...
    dependencies: [
      .package(url: "https://github.com/m-housh/swift-identified-storage.git", from: "0.1.0")
    ],
    targets: [
      .target(
        name: "<My Target>",
        dependencies: [
          .product(name: "IdentifiedStorage", package: "swift-identified-storage")
        ]
      )
    ]
)

基本用法

给定以下 Todo 模型。

struct Todo: Equatable, Identifiable {
  var id: UUID
  var description: String
  var isComplete: Bool = false
}

#if DEBUG
extension Todo {
  static let mocks: [Self] = [
    .init(id: UUID(0), description: "Buy milk"),
    .init(id: UUID(1), description: "Walk the dog"),
    .init(id: UUID(2), description: "Wash the car", isComplete: true)
  ]
}
#endif

以及 todo 客户端接口。

struct TodoClient {

  var delete: (Todo.ID) async throws -> Void
  var fetch: (FetchRequest) async throws -> IdentifiedArrayOf<Todo>
  var insert: (InsertRequest) async throws -> Todo
  var update: (Todo.ID, UpdateRequest) async throws -> Todo

  func fetch() async throws -> IdentifiedArrayOf<Todo> {
    try await self.fetch(.all)
  }

  enum FetchRequest {
    case all
    case filtered(by: Filter)

    enum Filter {
      case complete
      case incomplete
    }
  }

  struct InsertRequest {
    let description: String
  }

  struct UpdateRequest {
    let description: String
  }
}

使请求类型符合适当的转换类型。

#if DEBUG
import IdentifiedStorage

extension TodoClient.FetchRequest: FetchRequestConvertible {

  func fetch(from values: IdentifiedArrayOf<Todo>) -> IdentifiedArrayOf<Todo> {
    switch self {
    case .all:
      return values
    case let .filtered(by: filter):
      return values.filter {
        $0.isComplete == (filter == .complete ? true : false)
      }
    }
  }
}

extension TodoClient.InsertRequest: InsertRequestConvertible {

  typealias ID = Todo.ID
  
  func transform() -> Todo {
    @Dependency(\.uuid) var uuid;
    return .init(id: uuid(), description: description)
  }
}

extension TodoClient.UpdateRequest: UpdateRequestConvertible {

  typealias ID = Todo.ID
  
  func apply(to state: inout Todo) {
    state.description = description
  }
}
#endif

创建一个模拟客户端工厂。

extension TodoClient {
  static func mock(
    initialValues todos: [Todo],
    timeDelays: IdentifiedStorageDelays? = .default
  ) -> Self {

    // using the `IdentifiedStorage` as the storage for the mock client.
    // this uses the passed in time delays to simulate a remote data store for
    // use in previews and tests.
    let storage = IdentifiedStorageOf<Todo>(
      initialValues: todos,
      timeDelays: timeDelays
    )

    return TodoClient(
      delete: { try await storage.delete(id: $0) },
      fetch: { try await storage.fetch(request: $0) },
      insert: { try await storage.insert(request: $0) },
      update: { try await storage.update(id: $0, request: $1) }
    )
  }
}

extension TodoClient: DependencyKey {

    ...

    static var previewValue: Self {
      TodoClient.mock(initialValues: .init(uniqueElements: Todo.mocks))
    }
}

文档

在此处查看 API 文档:here.

许可证

所有模块均在 MIT 许可证下发布。 有关详细信息,请参见 LICENSE