Confused about Vapor (Fluent?) errors

Hi :slight_smile:

After adding the following guard statement in my route I get what feels like a million error messages and not really sure why

guard let activity = try await PersonActivity.query(on: req.db)
    .filter(\.$id == activityId)
    .with(\.$person) { person in
        person.with(\.$user)
    }
    .first()
else {
       throw Abort(.notFound)
}

results in error messages like Initializer for conditional binding must have Optional type, not 'QueryBuilder<PersonActivity>', Cannot find 'person' in scope and Expected 'else' after 'guard' condition. But adding the guard after the query I get no errors

let activity = try await PersonActivity.query(on: req.db)
    .filter(\.$id == activityId)
    .with(\.$person) { person in
        person.with(\.$user)
    }
    .first()
        
guard activity != nil else {
    throw Abort(.notFound)
}

I hope someone knows why the first snippet results in a million errors but the second snippet doesn't :slight_smile:

What's the context of the code? The error message would suggest that it's not picking up the .first() call. Maybe hidden characters, broken line spaces etc?

A classic :) Wrap your closure arguments in parenthesis in the guard and if clauses:

// Won't compile
if let x = a.first { ... } { ... }
// OK
if let x = a.first(where: { ... }) { ... }
2 Likes

The entire route looks like this (ignore the return type/value)

    func perform(req: Request) async throws -> String {
        guard let userId = req.userId else {
            throw Abort(.unauthorized)
        }
        
        guard let id = req.parameters.get("id"),
              let activityId = UUID(uuidString: id)
        else {
            throw Abort(.badRequest)
        }
        
        guard let activity = try await PersonActivity.query(on: req.db)
            .filter(\.$id == activityId)
            .with(\.$person) { person in
                person.with(\.$user)
            }
            .first()
        else {
            throw Abort(.notFound)
        }
        
        return "Person Activity Perform"
    }

where PersonActivity is a Model with @Parent person: Person and Person has @Parent user: User. At the top of my file I import both Fluent and Vapor.

EDIT:

Doing guard let activity = try await PersonActivity.query(on: req.db) ... adds the following warnings No 'async' operations occur within 'await' expression and No calls to throwing functions occur within 'try' expression - but these warnings are not shown when I split up the query and guard statement as in the second snippet in my original post.

Do you have a sample project? Because it sounds very much like the compiler is not seeing the async functions

        guard let activity = try await PersonActivity.query(on: req.db)
            .filter(\.$id == activityId)
            .with(\.$person)
            .first()
        else {
            throw Abort(.notFound)
        }

I tried to remove the nested eager loading part from the query and now there is no more errors or warnings :thinking:

final class PersonActivity: Model {
    // ...
    
    @Parent(key: "person_id")
    var person: Person

    // ...
}

final class Person: Model {
    // ...

    @Children(for: \.$person)
    var activities: [PersonActivity]

    @Parent(key: "user_id")
    var user: User

    // ...
}

final class User: Model {
    // ...

    @Children(for: \.$user)
    var persons: [Person]

    // ...
}

Am I shadow-banned or what? Of course removing the nested eager load fixes the problem, as explained above: Confused about Vapor (Fluent?) errors - #3 by gwendal.roue

The .first() that I am using is part of Fluent which has no .first(where:)

Fluent - .first()

Right, I did not use an example that exactly matches your code (I was on a mobile phone with mittens on my hands, sorry). But you still have to wrap you closure arguments inside parenthesis when you're in a if or guard clause (between the guard and the else { ... }). In other words, don't use the trailing closure syntax. That's how the language works, even if the compiler is too confused to provide clear error messages.

2 Likes

Changing from

.with(\.$person) { person in
    person.with(\.$user)
}

to

.with(\.$person, { person in
    person.with(\.$user)
})

fixed it! :partying_face:

Thank you, I was not aware of this closure syntax rule :see_no_evil:

1 Like