Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Beta] Handle errors thrown anywhere in chained Future methods #68

Open
bensyverson opened this issue Feb 10, 2018 · 2 comments
Open

[Beta] Handle errors thrown anywhere in chained Future methods #68

bensyverson opened this issue Feb 10, 2018 · 2 comments
Assignees

Comments

@bensyverson
Copy link

Summary

With Async, it's idiomatic to chain methods. However, in methods that create new Futures (such as Fluent's Model.save()), any previous errors thrown seem to be discarded. This means calls to Abort() will not always abort the request.

Details

It's highly desirable to be able to do something like the following:

return try User.query(on: req)
		.filter(\User.username == userReq.id)
		.count()
		.makeSureCountIsZero()
		.saveUser(userRequest: userReq, on: req)

Where makeSureCountIsZero throws if count != 0, and saveUser is like this (simplified for clarity):

extension Future where T == Bool {
  func saveUser(userRequest: UserRequest, on connectable: Request) throws -> Future<User> {
    let newUser = User(username: name)
    return newUser.save(on: connectable)
  }
}

However, Model.save() is creating a brand new Future, which I believe is discarding any previous exceptions/errors on the previous Future.

Simplified Example

In TodoController.swift:

extension Future where T == String {
  func returnString() throws -> Future<String> {
    return Future("✨ everything is perfect ✨")
  }
}

final class TodoController {
  func getThrow(_ req: Request) throws -> Future<String> {
    return try Todo.query(on: req)
      .filter(\Todo.title == "title")
      .count()
      .map(to: String.self) { _ in
        throw Abort(.badRequest, reason: "Everything is not perfect")
      }
      .returnString()
  }
}

Calling the getThrow method should return a 400, but instead we get a 200 OK and successful response from returnString

Steps to Reproduce

  1. Download FutureAsync.zip
  2. Unpack and build (may require 2018-01-30 toolchain)
  3. Run and request GET /throw

Workarounds / Regression

I think the correct thing to do is actually to flatMap the future before returning a new future, as in the following, which does throw the Abort correctly.

extension Future where T == String {
  func returnString() throws -> Future<String> {
    return self.flatMap(to: String.self) { _ in
      return Future("✨ everything is perfect ✨")
    }
  }
}

However, many users will not realize this, and their errors will be silently caught and ignored. Obviously that could be extremely dangerous. I'm hoping there's a way to force the error to be bubbled up. If there isn't, hopefully there's a way to catch this user error at compile time.

@twof
Copy link
Contributor

twof commented Feb 11, 2018

This is most likely the issue mentioned in the comments of
#63

@bensyverson
Copy link
Author

@twof Yeah, I think so. It's cool for #63 to be spun out to a separate helper package, but to me, this issue (#68) is a bug with Async itself.

@tanner0101 tanner0101 self-assigned this Feb 23, 2018
@tanner0101 tanner0101 added this to Backlog in Vapor 3 Feb 26, 2018
@tanner0101 tanner0101 removed this from Backlog in Vapor 3 Mar 21, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

3 participants