Ga naar inhoud

Errors

Vapor bouwt voort op Swift's Error protocol voor foutafhandeling. Route handlers kunnen een fout gooien of een mislukte EventLoopFuture teruggeven. Het gooien of retourneren van een Swift Error zal resulteren in een 500 status response en de fout zal worden gelogd. AbortError en DebuggableError kunnen gebruikt worden om respectievelijk de resulterende respons en logging te veranderen. De afhandeling van fouten wordt gedaan door ErrorMiddleware. Deze middleware wordt standaard aan de applicatie toegevoegd en kan desgewenst worden vervangen door aangepaste logica.

Abort

Vapor levert een standaard error struct genaamd Abort. Deze struct voldoet aan zowel AbortError als DebuggableError. U kunt het initialiseren met een HTTP status en optionele faal reden.

// 404 fout, standaard "Not Found" reden gebruikt.
throw Abort(.notFound)

// 401 fout, aangepaste reden gebruikt.
throw Abort(.unauthorized, reason: "Invalid Credentials")

In oude asynchrone situaties waar gooien niet ondersteund wordt en je een EventLoopFuture moet teruggeven, zoals in een flatMap closure, kun je een mislukte future teruggeven.

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

Vapor bevat een helper-extensie voor het unwrappen van futures met optionele waarden: unwrap(of:).

User.find(id, on: db)
    .unwrap(or: Abort(.notFound))
    .flatMap 
{ user in
    // Niet-optionele gebruiker geleverd aan closure.
}

Als User.find nil retourneert, zal de future mislukt zijn met de bijgeleverde fout. Anders zal de flatMap worden voorzien van een niet-optionele waarde. Als u async/await gebruikt, dan kunt u optionals als normaal afhandelen:

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

Abort Error

Standaard zal elke Swift Error die wordt gegooid of geretourneerd door een route closure resulteren in een 500 Internal Server Error response. Wanneer het project in debug modus is gebouwd, zal ErrorMiddleware een beschrijving van de fout bevatten. Dit wordt om veiligheidsredenen verwijderd wanneer het project in release mode wordt gebouwd.

Om de resulterende HTTP response status of reden voor een bepaalde fout te configureren, conformeer het aan 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 gebruikt de Logger.report(error:) methode voor het loggen van fouten die door uw routes worden gegooid. Deze methode controleert op conformiteit met protocollen als CustomStringConvertible en LocalizedError om leesbare berichten te loggen.

Om het loggen van fouten aan te passen, kun je je fouten conformeren aan DebuggableError. Dit protocol bevat een aantal nuttige eigenschappen zoals een unieke identifier, bron locatie, en stack trace. De meeste van deze eigenschappen zijn optioneel, wat het overnemen van de conformiteit eenvoudig maakt.

Om zo goed mogelijk te voldoen aan DebuggableError, moet uw fout een struct zijn, zodat het bron- en stack trace informatie kan opslaan indien nodig. Hieronder is een voorbeeld van de eerder genoemde MyError enum bijgewerkt om een struct te gebruiken en bron informatie op te slaan.

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 heeft verschillende andere eigenschappen zoals possibleCauses en suggestedFixes die je kunt gebruiken om de debuggability van je fouten te verbeteren. Kijk in het protocol zelf voor meer informatie.

Stack Traces

Vapor bevat ondersteuning voor het bekijken van stack traces voor zowel normale Swift fouten als crashes.

Swift Backtrace

Vapor gebruikt de SwiftBacktrace library om stack traces te leveren na een fatale fout of assertion op Linux. Om dit te laten werken, moet uw app debug symbolen bevatten tijdens het compileren.

swift build -c release -Xswiftc -g

Error Traces

Standaard zal Abort de huidige stack trace vastleggen bij initialisatie. Uw aangepaste fout types kunnen dit bereiken door te voldoen aan DebuggableError en StackTrace.capture() op te slaan.

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
    }
}

Wanneer het log level van uw applicatie is ingesteld op .debug of lager, zullen stack traces van fouten worden opgenomen in de log output.

Stack traces worden niet opgevangen als het log level groter is dan .debug. Om dit gedrag op te heffen, stel StackTrace.isCaptureEnabled handmatig in configure in.

// Leg altijd stack traces vast, ongeacht het log niveau.
StackTrace.isCaptureEnabled = true

Error Middleware

ErrorMiddleware is de enige middleware die standaard aan je applicatie wordt toegevoegd. Deze middleware converteert Swift fouten die zijn gegooid of geretourneerd door uw route handlers naar HTTP responses. Zonder deze middleware, zullen gegooide fouten resulteren in het sluiten van de verbinding zonder een reactie.

Om de foutafhandeling verder aan te passen dan AbortError en DebuggableError bieden, kunt u ErrorMiddleware vervangen door uw eigen logica voor foutafhandeling. Om dit te doen, verwijdert u eerst de standaard error middleware door app.middleware op een lege configuratie te zetten. Voeg vervolgens uw eigen error afhandeling middleware toe als de eerste middleware aan uw applicatie.

// Verwijder alle bestaande middleware.
app.middleware = .init()
// Voeg eerst aangepaste foutafhandeling middleware toe.
app.middleware.use(MyErrorMiddleware())

Zeer weinig middleware zou vóór de foutafhandeling middleware moeten gaan. Een opmerkelijke uitzondering op deze regel is CORSMiddleware.