Updating an Encrypted Password

In my user controller the following function compiles fine.

    func patchUser(req: Request) throws -> EventLoopFuture<User.Public> {
        let data = try req.content.decode(PatchUserData.self)
        
        return User.find(req.parameters.get("userID"), on: req.db)
            .unwrap(or: Abort(.notFound)).flatMap { user in
                
                if let password = data.password {
                    user.password           = password
                }
                return user.save(on: req.db).map {
                    user.convertToPublic()
                }
            }
        
    }

But when I try to hash the password, replacing

user.password           = password

with

user.password           = try Bcrypt.hash(password)

I get the following compile error.

error: invalid conversion from throwing function of type β€˜(Optional.WrappedType) throws β†’ EventLoopFuture<User.Public>’ (aka β€˜(User) throws β†’ EventLoopFuture<User.Public>’) to non-throwing function type β€˜(Optional.WrappedType) β†’ EventLoopFuture<User.Public>’ (aka β€˜(User) β†’ EventLoopFuture<User.Public>’)
.unwrap(or: Abort(.notFound)).flatMap { user in
^

As the error says, you can’t throw inside a flatMap. If you need to throw in the closure of a flatMap use the new tryFlatMap instead (make sure you update your dependencies). The books approach is to wrap the throw inside a do/catch as tryFlatMap wasn’t introduced at the time of writing

Thanks!

I coded up the following and now everything works fine.

                if let password = data.password {
                    do {
                        user.password           =  try Bcrypt.hash(password)
                    }
                    catch { }
                }

@randyf Empty catch blocks are not a good programming practice so should definitely be avoided because of that no matter what. You can actually fix this in two different ways in Vapor:

  1. Create a failed future that handles the error accordingly with makeFailedFuture(_:) like this:
func patchUser(req: Request) throws -> EventLoopFuture<User.Public> {
  let data = try req.content.decode(PatchUserData.self)
  return User.find(req.parameters.get("userID"), on: req.db)
             .unwrap(or: Abort(.notFound)).flatMap { user in
                if let password = data.password {
                  do {
                    user.password = try Bcrypt.hash(password)
                  } catch {
                    return req.eventLoop.makeFailedFuture(error)   
                  }
                }
                return user.save(on: req.db).map {
                  user.convertToPublic()
                }
              }  
}         
  1. Throw the error with tryFlatMap(_:) as follows:
func patchUser(req: Request) throws -> EventLoopFuture<User.Public> {
  let data = try req.content.decode(PatchUserData.self)
  return User.find(req.parameters.get("userID"), on: req.db)
             .unwrap(or: Abort(.notFound)).tryflatMap { user in
                if let password = data.password {
                  user.password = try Bcrypt.hash(password)
                }
                return user.save(on: req.db).map {
                  user.convertToPublic()
                }
              }  
} 

Please let me know if you have any questions or issues about the whole thing when you get a chance. Thank you!