Errori¶
Vapor si basa sul protocollo Error di Swift per la gestione degli errori. Gli handler delle route possono sia lanciare (throw) un errore che restituire un EventLoopFuture fallito. Lanciare o restituire un Error di Swift risulterà in una risposta con stato 500 e l'errore verrà registrato nel log. AbortError e DebuggableError possono essere usati per cambiare rispettivamente la risposta risultante e il logging. La gestione degli errori è affidata a ErrorMiddleware. Questo middleware viene aggiunto all'applicazione per impostazione predefinita e può essere sostituito con logica personalizzata se desiderato.
Abort¶
Vapor fornisce una struct di errore predefinita chiamata Abort. Questa struct è conforme sia a AbortError che a DebuggableError. Puoi inizializzarla con uno stato HTTP e un motivo di fallimento opzionale.
// Errore 404, viene usato il motivo predefinito "Not Found".
throw Abort(.notFound)
// Errore 401, viene usato un motivo personalizzato.
throw Abort(.unauthorized, reason: "Invalid Credentials")
Nei vecchi contesti asincroni dove il lancio di eccezioni non è supportato e devi restituire un EventLoopFuture, come in una closure flatMap, puoi restituire una future fallita.
guard let user = user else {
req.eventLoop.makeFailedFuture(Abort(.notFound))
}
return user.save()
Vapor include un'estensione helper per estrarre future con valori opzionali: unwrap(or:).
User.find(id, on: db)
.unwrap(or: Abort(.notFound))
.flatMap
{ user in
// User non-opzionale fornito alla closure.
}
Se User.find restituisce nil, la future fallirà con l'errore fornito. Altrimenti, il flatMap riceverà un valore non opzionale. Se si usa async/await è possibile gestire gli opzionali normalmente:
guard let user = try await User.find(id, on: db) {
throw Abort(.notFound)
}
Abort Error¶
Per impostazione predefinita, qualsiasi Error di Swift lanciato o restituito da una closure di route risulterà in una risposta 500 Internal Server Error. Quando compilato in modalità debug, ErrorMiddleware includerà una descrizione dell'errore. Questa viene rimossa per ragioni di sicurezza quando il progetto viene compilato in modalità release.
Per configurare lo stato HTTP della risposta risultante o il motivo per un particolare errore, conformalo a AbortError.
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
}
}
}
Debuggable Error¶
ErrorMiddleware usa il metodo Logger.report(error:) per registrare nel log gli errori lanciati dalle tue route. Questo metodo verificherà la conformità a protocolli come CustomStringConvertible e LocalizedError per registrare messaggi leggibili.
Per personalizzare il logging degli errori, puoi conformare i tuoi errori a DebuggableError. Questo protocollo include una serie di proprietà utili come un identificatore univoco, la posizione nel codice sorgente e lo stack trace. La maggior parte di queste proprietà è opzionale, il che rende facile adottare la conformità.
Per essere conformi al meglio a DebuggableError, il tuo errore dovrebbe essere una struct in modo da poter memorizzare informazioni sulla sorgente e sullo stack trace se necessario. Di seguito è riportato un esempio dell'enum MyError precedentemente menzionato aggiornato per usare una struct e acquisire informazioni sulla sorgente dell'errore.
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 ha molte altre proprietà come possibleCauses e suggestedFixes che puoi usare per migliorare la risolvibilità dei tuoi errori. Dai un'occhiata al protocollo stesso per maggiori informazioni.
Error Middleware¶
ErrorMiddleware è uno dei soli due middleware aggiunti alla tua applicazione per impostazione predefinita. Questo middleware converte gli errori Swift che sono stati lanciati o restituiti dai tuoi route handler in risposte HTTP. Senza questo middleware, gli errori lanciati risulteranno nella chiusura della connessione senza una risposta.
Per personalizzare la gestione degli errori al di là di quanto fornito da AbortError e DebuggableError, puoi sostituire ErrorMiddleware con la tua logica di gestione degli errori. Per farlo, prima rimuovi il middleware di errore predefinito inizializzando manualmente app.middleware. Poi, aggiungi il tuo middleware di gestione degli errori come primo middleware alla tua applicazione.
// Cancella tutti i middleware predefiniti (poi, riaggiungi il route logging)
app.middleware = .init()
app.middleware.use(RouteLoggingMiddleware(logLevel: .info))
// Aggiungi il middleware di gestione degli errori personalizzato per primo.
app.middleware.use(MyErrorMiddleware())
Pochissimi middleware dovrebbero andare prima del middleware di gestione degli errori. Un'eccezione degna di nota a questa regola è CORSMiddleware.