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?>
?
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.