Decree - Declarative HTTP Requests

Swift platforms Swift Package Manager compatible CocoaPods Compatible MIT Build Status

Twitter @drewag Blog drewag.me

通过在 iOSmacOSLinux 上声明 Web 服务和端点,以清晰且类型安全的方式发出 HTTP 请求

在 Swift 中发出 URL 请求时,您主要有两种选择:使用 Foundation 中的 URLSession API,或使用一些重量级的框架。

该框架旨在轻量级,同时保持可定制性,并专注于以声明方式声明 API 接口。声明后,向各种端点发出请求非常简单且类型安全。它适用于 iOS、macOS 和 Linux。

Andrew 通过实现许多不同的用 Swift 编写的应用程序和后端服务开发了这种策略。 他使用这种范例在他的前端和后端(都用 Swift 实现)之间以及与 Spotify、FreshDesk、Stripe 等服务进行通信。

我们提供了一个单独的存储库 DecreeServices,其中包含流行服务的服务声明

目录

特性

四种类型的端点

这些 协议 声明端点是否具有输入和/或输出。

五种输入格式

这些 格式 用于使用 Swift Encodable 协议对端点的输入进行编码。

两种输出格式

这些 格式 用于使用 Swift Decodable 协议初始化端点的输出。

三种类型的授权

允许设置用于 Web 服务中所有端点的授权。 然后,每个端点都可以指定授权要求

高级功能

可配置

您可以选择执行高级配置以处理请求和响应。

几乎 100% 的代码覆盖率

我们的大部分代码都经过单元测试覆盖,以确保可靠性。

请求和响应日志记录

全面的错误报告

Decree 抛出和返回的错误旨在用户友好,同时还公开详细的诊断信息。

Mocking

允许模拟端点响应,以便轻松进行自动化测试。

第三方服务

我们创建了一个单独的框架,用于定义多个第三方服务的服务和端点。 请在DecreeServices上查看它。

示例

以下是此框架的一些使用示例。

简单 Get

在这里,我们定义了一个 CheckStatus 端点,它是一个 GET(默认值),在路径“/status”上,没有输入或输出。 向下滚动以查看 ExampleService 的定义。

struct CheckStatus: EmptyEndpoint {
    typealias Service = ExampleService

    let path = "status"
}

然后,我们可以使用该定义来发出异步请求。

CheckStatus().makeRequest() { result in
    switch result {
    case .success:
        print("Success :)")
    case .failure(let error):
        print("Error :( \(error)")
    }
}

我们还可以发出同步请求,如果发生错误,该请求只会抛出错误。

try CheckStatus().makeSynchronousRequest()

输入和输出

我们还可以定义具有输入和/或输出的端点。 在这里,我们定义了一个 Login 端点,它是对“/login”的 POST 请求,用户名和密码参数编码为 JSON。 如果成功,则端点应返回一个令牌。

struct Login: InOutEndpoint {
    typealias Service = ExampleService
    static let method = Method.post

    let path = "login"

    struct Input: Encodable {
        let username: String
        let password: String
    }

    struct Output: Decodable {
        let token: String
    }
}

然后我们可以发出异步请求。

Login().makeRequest(with: .init(username: "username", password: "secret")) { result in
    switch result {
    case .success(let output):
        print("Token: \(output.token)")
    case .failure(let error):
        print("Error :( \(error)")
    }
}

或者我们可以发出同步请求,如果成功则返回输出,否则抛出异常。

let token = try Login().makeSynchronousRequest(with: .init(username: "username", password: "secret")).token

下载

对于具有较大输出的端点,我们可以将它们直接下载到文件,而不是将整个响应保存在内存中。

struct GetDocument: OutEndpoint {
    typealias Service = ExampleService
    typealias Output = Data

	let id: Int

    var path: String {
	    return "documents/\(id)"
    }
}

GetDocument(id: 42).makeDownloadRequest() { result in
    switch result {
    case .success(let url):
        // open or move the url
    case .failure(let error):
        print("Error :( \(error)")
    }
}

请注意,您可以在任何具有输出的端点上使用 makeDownloadRequest 方法(无论其格式如何),但是通常使用原始数据输出最有意义。

服务定义

要使以上示例起作用,唯一需要的额外代码是定义 ExampleService

struct ExampleService: WebService {
    // There is no service wide standard response format
    typealias BasicResponse = NoBasicResponse

    // Errors will be in the format {"message": "<reason>"}
    struct ErrorResponse: AnyErrorResponse {
        let message: String
    }

    // Requests should use this service instance by default
    static var shared = ExampleService()

    // All requests will be sent to their endpoint at "https://example.com"
    let baseURL = URL(string: "https://example.com")!
}

在这里,我们定义了一个名为 ExampleServiceWebService,其中包含一些属性。

这就是您所需要的。 然后,您可以根据需要定义任意数量的端点,并以清晰且类型安全的方式使用它们。

真实世界的例子

要查看真实世界的示例,请查看我们如何在 DecreeServices 中声明服务。

期望的功能

我们希望实现的功能已添加为带有 增强标签的问题。 如果您有任何功能请求,请随时创建一个新问题。

贡献

非常鼓励您报告任何问题和/或为新功能提出 pull 请求。