Vapor 4 - Routes, closure & throw Abort

Hello tout le monde,

Je suis occupé de bosser avec Vapor et je me posais une question: comment faire pour retourner une erreur quand on est dans une closure dans une route ?

Je m’explique:

Quand on a une route GET comme celle-ci par exemple:

app.get("activities") { request -> String in 
    // Le code de la route
}

On peut throw une erreur comme ça:

app.get("activities") { request -> String in 
    throw Abort(.badRequest)
}

Mais qu’en est-il si dans cette route, on appelle un web service externe par exemple ?

Comme ceci:

app.get("activities") { request -> String in 
    client.execute(request: myRequest).whenComplete { result in
        // Comment throw une erreur ici ?
    }
}

J’ai essayé avec throw Abort(.badRequest), mais Xcode n’a pas l’air ravi:

En cherchant sur Internet, certaines personnes parlaient de:

request.eventLoop.makeFailedFuture(Abort(.badRequest))

Mais je suis pas sur de comprendre comment ils l’utilisent… Dans mon cas, le code tel quel ne fonctionne pas, je dois utiliser une variable, et ensuite j’ai le même soucis.

Bref, si vous avez une idée de comment je peux m’en sortir, je suis preneur (et si c’est expliqué dans le cours sur Vapor de @mbritto, je suis preneur si vous savez m’indiquer où trouver l’info)

Merci bien,
Bonne soirée,

Alexandre

Salut @Alexandre,

À ce que je vois sur ton message d’erreur, la fonction ne supporte pas le throwing, donc par rapport à la documentation de Vapor, c’est bien ce que tu as trouvé sur internet.

À savoir :

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

Plus d’information ici : https://docs.vapor.codes/4.0/errors/

Hello @ThonyF,

Oui, c’est ce que j’ai vu dans la doc, mais impossible de l’appliquer à mon code…
Je continue de creuser dans cette direction.

Si vous avez des idées, je suis preneur.

Bonne soirée,

Ta fonction execute, elle est comment ?

Je pense surtout qu’il ne faut pas utiliser la fonction whenComplete dans ton cas car elle ne retourne rien. C’est juste un callback pour executer du code non lié à ta tâche principale.
Tu devrais plutôt utiliser map, flatMap et flatMapThrowing pour finaliser ton résultat en fonction de l’étape précédente.
Dans les flatMap tu peux retourner un futur en erreur et dans les flatMapThrowing tu peux lancer des exceptions

1 J'aime

Hello @ThonyF et @mbritto,

Désolé pour le délai de réponse.

J’ai changé le whenComplete par un flatMapThrowing, du coup en effet, j’arrive bien à compiler mon code (c’est déjà un bon point). Par contre, quand j’exécute mon app Vapor, dès que j’appelle cette route, j’ai un crash:


Voici mon code:

@discardableResult
func getActivities(request: Request) throws -> EventLoopFuture<[Activity]> {
    let client: HTTPClient = HTTPClient(eventLoopGroupProvider: .createNew)
    let headers: HTTPHeaders = HTTPHeaders([("Authorization", "Bearer \(accessToken)")])

    guard let request = try? HTTPClient.Request(url: requestUrl, method: .GET, headers: headers, body: nil) else { throw Abort(.badRequest) }

    return client.execute(request: request).flatMapThrowing { response -> [Activity] in
        guard let buffer = response.body else { throw Abort(.badRequest) }
        guard let data = String(buffer: buffer).data(using: .utf8) else { throw Abort(.badRequest) }
            
        do {
            guard let json = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? [Dictionary<String, Any>] else { throw Abort(.badRequest) }
            // TODO: Map JSON to [Activity]
            return []
        } catch {
            throw Abort(.badRequest)
        }
    }
}

Je dois passer à côté de quelque chose, mais je ne trouve rien…
Si vous avez une idée, je suis preneur :slightly_smiling_face:

Bonne journée,

Alexandre

Mmh, on dirait qu’en faisant:

func getActivities(request: Request) throws -> EventLoopFuture<[Activity]> {
    let headers: HTTPHeaders = HTTPHeaders([("Authorization", "Bearer \(accessToken)")])
    let uri: URI = URI(string: stravaActivitiesUrl)
        
    return request.client.get(uri, headers: headers).flatMapThrowing { response in
        guard response.status == .ok else { throw Abort(.unauthorized) }
        return []
    }
}

tout fonctionne bien. C’est comme si la création d’un nouveau HTTPClient dans le code au dessus posait problème.

Je continue de creuser pour voir si j’arrive à ce que je veux avec cette méthode.

Bon, apparement ça fonctionne correctement avec le request.client.

Voici le code qui fonctionne, si jamais ça peut en aider certains:

func getActivities(request: Request) throws -> EventLoopFuture<[Activity]> {
    let headers: HTTPHeaders = .bearerAuthorization(token: accessToken)
    let uri: URI = URI(string: requestUrlAsString)
            
    return request.client.get(uri, headers: headers).flatMapThrowing { response in
        guard response.status == .ok else { throw Abort(.unauthorized) }
        guard let buffer = response.body else { throw Abort(.badRequest) }
        guard let data = String(buffer: buffer).data(using: .utf8) else { throw Abort(.badRequest) }
                  
        let decoder: JSONDecoder = JSONDecoder()
        do {
            return try decoder.decode([Activity].self, from: data)
        } catch {
            throw Abort(.badRequest)
        }
    }
}

Si vous avez un autre moyen de faire, je suis pas contre de le voir :slight_smile:

Bonne journée,

2 J'aime