2

I am making a server with Swift 5 and Vapor 3. When setting up a route I want to call a function from my controller that returns an optional like so:

//Person.swift

struct Person: Content {
    ...
}
//PersonController.swift

func update(_ request: Request) throws -> Future<Person> {
    let uuid = try request.parameters.next(UUID.self)

    return try request.content.decode(Person.self).flatMap { content in
        request.withPooledConnection(to: DatabaseIdentifier<PostgreSQLDatabase>.psql) { connection in
            /*
            *  No code completion beyond this point,
            *  even connection appears as type '_' instead of
            *  PostgreSQLConnection (not relevant to the question tho,
            *  just worth noting)
            */
            if content.lastName != nil {
                return connection.raw("Very long SQL query...")
                .binds([...])
                .first(decoding: Person.self)
            }

            return connection.raw("Other long SQL query")
            .binds([...])
            .first(decoding: Person.self)
        }
    }

}
router.put("people", UUID.parameter, use: personController.update)

But then I get this error

Cannot convert value of type '(Request) throws -> EventLoopFuture<Person?>' to expected argument type '(Request) throws -> _'

I see many instances when working with Vapor in which Xcode just gives up on autocompleting, and everything is just typed as _. Mostly inside closures that are used as callbacks. It is VERY annoying, and quite frankly I am unsure if it is caused because of Vapor, Swift, or Xcode. It is a huge PITA but it all gets resolved once I compile, the types just get sorted out. However in this case it is just not working.

So the question is: Why is does Xcode say the expected type is (Request) throws -> _ when the actual definition for Request.put(_:use:) requires a (Request) throws -> T and how does that make a difference between T being Future<Person> and Future<Person?>?

Adrian Bobrowski
  • 2,681
  • 1
  • 16
  • 26
lsauceda
  • 315
  • 3
  • 12
  • Can you show your `EventLoopFuture` class, and `put` function in your `router` – Adrian Bobrowski Apr 19 '19 at 04:56
  • @AdrianBobrowski umm not sure what you are asking for, I guess by `put` function you mean the contents of `personController.update` and by `EventLoopFuture` class you mean the `Person` class (actually it is a struct). Is that it? – lsauceda Apr 19 '19 at 05:54
  • If `EventLoopFuture` is compatible with `Future` then you have problem because in your case you use different type for `T`. In `EventLoopFuture` you use `Optional` and in `Future` you have `Person`. – Adrian Bobrowski Apr 19 '19 at 06:23
  • I sort of get that but isn't `Future` just an alias to `EventLoopFuture` – lsauceda Apr 19 '19 at 06:32
  • @AdrianBobrowski I updated the question to include the contents of the `update` function (called by `Router.put`), which I think is what you are referring to. – lsauceda Apr 19 '19 at 06:52

1 Answers1

2

The .first method you are calling here:

return connection.raw("Other long SQL query")
.binds([...])
.first(decoding: Person.self)

Returns an Future<Optional<Person>>, or Future<Person?>. You route handler's return type is Future<Person>, so your return type is incorrect. But even if you did change the handler's return type, that wouldn't fix it.

Your main problem is that you can't return an optional from a route handler, because in no way is Optional ever conformed to ResponseEncodable. You could add the conformance yourself if you wanted to.

If you don't want to add the conformance, you can just use the .unwrap(or:) method after you decode the query result:

return connection.raw("Other long SQL query")
.binds([...])
.first(decoding: Person.self)
.unwrap(or: Abort(.notFound))

This will check to see if the value in the future exists. If it does, then the value is passed on. Otherwise, the future chain receives the error you pass in and that will be returned instead.

Caleb Kleveter
  • 11,170
  • 8
  • 62
  • 92
  • OK sorry I mistyped actually the return already is `Future` but just as you said that is what actually causes the error described so I guess I'll have to unwrap the value, which is what I was trying to avoid. Yet I don't understand why Xcode doesn't mark not being `ResponseEncodable` as the error, instead of dropping code completion. – lsauceda Apr 19 '19 at 15:56
  • Swift's type checker isn't all that great when it comes to closures. Add some protocols into the mix and something is sure to break. Just keep in mind that if you get any error like the one you just did, anything could be wrong – Caleb Kleveter Apr 19 '19 at 16:11
  • So, while you could blame Vapor for all the closures you have to use, it's really Swift's fault. – Caleb Kleveter Apr 19 '19 at 16:12
  • Yeah I hadn't used Swift since 4.0 came out and damn has it become so weird. Also Xcode used to show documentation when you force clicked some identifier, which I always thought was great, but it stopped doing that ages ago (haven't found a setting to turn it back up). Still prefer using Swift than any of those weird dynamically typed languages . – lsauceda Apr 19 '19 at 16:31