Creating CRUD Functions Using Async-Await with Vapor-Fluent - Swift 5.6

170 views Asked by At

I'm trying to understand how to create CRUD functions with Vapor. It appears first() is optional, but cannot be unwrapped as an optional type

func first() -> EventLoopFuture<Token?>

Here's my Token.swift

import Foundation
import Fluent
import Vapor

final class Token: Model {
  // 1
  static let schema = "tokens"

  // 2
  @ID(key: .id)
  var id: UUID?

  // 3
  @Field(key: "token")
  var token: String

  @Field(key: "debug")
  var debug: Bool

  // 4
  init() { }

  init(token: String, debug: Bool) {
    self.token = token
    self.debug = debug
  }
}

Here's my TokenController.swift

import Foundation
import Fluent
import Vapor

struct TokenController {
    func create(req: Request) async throws -> HTTPStatus {
        let token = try req.content.decode(Token.self)
        try await token.create(on: req.db)
        return .created
    }
    
    func delete(req: Request) async throws -> HTTPStatus {
        guard let token = req.parameters.get("token") else {
            return .badRequest
        }
        
        try await Token.query(on: req.db)
            .filter(\.$token == token)
            .first()
        
        try await delete(req: req.db)

        return .noContent
    }

}

@available(macOS 12, *)
extension TokenController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        let tokens = routes.grouped("token")
        tokens.post(use: create)
        tokens.delete(":token", use: delete)
    }
}

The return token might not be there, so it should be optional, but I get the familiar error

Initializer for conditional binding must have Optional type, not 'EventLoopFuture<Token?>'

I'm using async-await obviously and not EventLoopFuture<> except maybe under the hood, if I understand the documentation correctly.

Based on a tutorial I'm following, this was suggested as a solution to delete rows by getting the first row in the table

func delete(req: Request) async throws -> HTTPStatus {
  guard let token = req.parameters.get("token") else {
    return .badRequest
  }

  guard let row = try await Token.query(on: req.db)
    .filter(\.$token == token)
    .first()
  else {
    return .notFound
  }

  try await row.delete(on: req.db)
  return .noContent
}

But this gives me the same error for token, including another error for the row.delete(on:_) method

Value of type 'EventLoopFuture<Token?>' has no member 'delete'

What am I missing?

• Pouring over the documentation • Retracing my steps from the tutorial • Holding my breath until the computer worked

1

There are 1 answers

2
0xTim On BEST ANSWER

You probably need to give the compiler a hand and force it to use the async version of .first():

guard let row: Token = try await Token.query(on: req.db)
    .filter(\.$token == token)
    .first()

Because the functions have the same signature it can occasionally pick the future version, especially if there are errors elsewhere