# 错误

Vapor 的错误处理基于 Swift 的 `Error` 协议。路由处理可以通过 `throw` 抛出或返回 `EventLoopFuture` 对象。抛出或返回 Swift 的 `Error` 将导致`500`状态响应并记录错误。`AbortError` 和 `DebuggableError` 分别用于更改响应结果和记录日志。错误的处理由 `ErrorMiddleware` 中间件完成。此中间件默认添加到应用程序中，如果需要，可以用自定义逻辑替换

## 中断(Abort)

Vapor 提供了名为 `Abort` 的默认错误结构。该结构遵循 `AbortError` 和 `DebuggableError` 协议。你可以使用 HTTP 状态和可选的失败原因对其进行初始化。

```swift
// 404 错误，默认原因”未找到“。
throw Abort(.notFound)

// 401 错误，自定义错误原因。
throw Abort(.unauthorized, reason: "Invalid Credentials")
```

在旧的异步情况下不支持抛出错误，你必须返回一个 `EventLoopFuture`，就像在 `flatMap` 闭包中一样，你可以返回一个失败的 future。

```swift
guard let user = user else {
    req.eventLoop.makeFailedFuture(Abort(.notFound))    
}
return user.save()
```

Vapor 提供了一个辅助扩展，用于解包具有可选值的 future 对象：`unwrap(or:)`。

```swift
User.find(id, on: db)
    .unwrap(or: Abort(.notFound))
    .flatMap 
{ user in
    // 非可选，提供给闭包的用户。
}
```

如果 `User.find` 返回 `nil`，future 将因提供的错误而失败。否则，`flatMap` 将提供一个非可选值。如果使用 `async`/`await` 那么你可以正常处理可选值：

```swift
guard let user = try await User.find(id, on: db) {
    throw Abort(.notFound)
}
```


## 中断错误

默认情况下，路由闭包抛出或返回的任何 Swift 的 `Error` 都会导致`500 服务器内部错误`的响应。在调试模式下构建时，`ErrorMiddleware` 中间件将包含错误描述。当项目以发布模式构建时，出于安全原因将其删除。

要配置生成的 HTTP 状态响应或特定错误的原因，请将其遵循 `AbortError` 协议。

```swift
import Vapor

enum MyError {
    case userNotLoggedIn
    case invalidEmail(String)
}

extension MyError: AbortError {
    var reason: String {
        switch self {
        case .userNotLoggedIn:
            return "User is not logged in."
        case .invalidEmail(let email):
            return "Email address is not valid: \(email)."
        }
    }

    var status: HTTPStatus {
        switch self {
        case .userNotLoggedIn:
            return .unauthorized
        case .invalidEmail:
            return .badRequest
        }
    }
}
```

## 调试错误

`ErrorMiddleware` 中间件使用 `Logger.report(error:)` 方法记录路由抛出的错误。此方法将检查是否遵循 `CustomStringConvertible` 和 `LocalizedError` 等协议，以记录可读消息。

要自定义错误日志记录，你可以遵循 `DebuggableError` 协议。该协议包括许多有用的属性，例如唯一标识符、源位置和堆栈跟踪。大多数这些属性都是可选的，这使得采用一致性变得容易。

为了更好的遵循 `DebuggableError` 协议，你的错误应该是一个结构，以便它可以在需要时存储源和堆栈跟踪信息。下面是上述 `MyError` 枚举的示例，更新为使用 `struct` 并捕获错误源信息。

```swift
import Vapor

struct MyError: DebuggableError {
    enum Value {
        case userNotLoggedIn
        case invalidEmail(String)
    }

    var identifier: String {
        switch self.value {
        case .userNotLoggedIn:
            return "userNotLoggedIn"
        case .invalidEmail:
            return "invalidEmail"
        }
    }

    var reason: String {
        switch self.value {
        case .userNotLoggedIn:
            return "User is not logged in."
        case .invalidEmail(let email):
            return "Email address is not valid: \(email)."
        }
    }

    var value: Value
    var source: ErrorSource?

    init(
        _ value: Value,
        file: String = #file,
        function: String = #function,
        line: UInt = #line,
        column: UInt = #column
    ) {
        self.value = value
        self.source = .init(
            file: file,
            function: function,
            line: line,
            column: column
        )
    }
}
```

`DebuggableError` 协议有几个其他属性，如 `possibleCauses` 和 `suggestedFixes` 你可以使用它们来提高错误的可调试性。查看协议本身以获取更多信息。

## 堆栈跟踪

Vapor 支持查看正常 Swift 错误和崩溃的堆栈跟踪。

### Swift 回溯

在 Linux 上，当出现致命错误或断言时，Vapor 使用 [SwiftBacktrace](https://github.com/swift-server/swift-backtrace) 库提供堆栈跟踪。为了让它正常工作，你的应用程序必须在编译过程中包含调试符号。

```sh
swift build -c release -Xswiftc -g
```

### 错误跟踪

默认情况下，`Abort` 将在初始化时捕获当前堆栈跟踪。你的自定义错误类型可以通过遵循 `DebuggableError` 协议并存储 `StackTrace.capture()` 来实现。

```swift
import Vapor

struct MyError: DebuggableError {
    var identifier: String
    var reason: String
    var stackTrace: StackTrace?

    init(
        identifier: String,
        reason: String,
        stackTrace: StackTrace? = .capture()
    ) {
        self.identifier = identifier
        self.reason = reason
        self.stackTrace = stackTrace
    }
}
```

当你的应用程序的[日志级别](logging.zh.md#level)设置为 `.debug` 或更低时，错误堆栈跟踪将包含在日志输出中。

当日志级别大于 `.debug` 时，不会捕获堆栈跟踪。要覆盖此行为，请在 `StackTrace.isCaptureEnabled` 中手动设置 `configure`。

```swift
// 无论日志级别如何，始终捕获堆栈跟踪。
StackTrace.isCaptureEnabled = true
```

## 错误中间件

`ErrorMiddleware` 是默认添加到应用程序的唯一中间件。该中间件将路由处理抛出或返回的 Swift 错误转换为 HTTP 响应。如果没有这个中间件，抛出的错误将导致连接被关闭而没有响应。

要定制 `AbortError` 和 `DebuggableError` 所提供的错误处理之外的错误处理，你可以用自己的错误处理逻辑替换 `ErrorMiddleware` 中间件。要做到这一点，首先通过设置 `app.middleware` 为空删除默认的错误中间件。然后，将你自己的错误处理中间件作为第一个中间件添加到应用程序中。

```swift
// 移除已存在的中间件。
app.middleware = .init()
// 首先添加自定义错误中间件。
app.middleware.use(MyErrorMiddleware())
```

很少有中间件应该放在错误处理中间件*之前*。但 `CORSMiddleware` 中间件不适用该规则。
