Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error load fields #30

Closed
ZirgVoice opened this issue May 18, 2022 · 11 comments
Closed

Error load fields #30

ZirgVoice opened this issue May 18, 2022 · 11 comments
Labels
bug Something isn't working

Comments

@ZirgVoice
Copy link

When I try to get the parent id, I get an error "Fatal error: Cannot access field before it is initialized or fetched: country_id".
I found that all fields are empty except id

extension AddressModel {
    func getCountry(context: Context, _: NoArguments, eventLoopGroup: EventLoopGroup) async throws -> CountryModel? {
        let id = self.$country.id
        return try await context.request.graphQLDataLoader
            .countryLoader.load(key: id, on: eventLoopGroup).get()
    }
}
@d-exclaimation
Copy link
Owner

d-exclaimation commented May 18, 2022

Is your situation similar to this issue or this issue with optional parent?

@d-exclaimation d-exclaimation added the bug Something isn't working label May 19, 2022
@ZirgVoice
Copy link
Author

ZirgVoice commented May 19, 2022

This is Parent and not OptionalParent. The country_id field in the database is filled. I don’t do join and with because I just need to get the parent’s id, I also tried to get here and other fields, for example zip_code, this field is also filled in the database but comes empty. As a result, I can only get the id. I used this library before and getting the parent id worked.

@d-exclaimation
Copy link
Owner

d-exclaimation commented May 19, 2022

Can you share more about the AddressModel and the schema as well? I'll look into it and figure out why this is could be happening.

I am not sure immediately why this could be happening, since the way GraphQL request is executed with this library and the graphql-kit library is similar, the only difference is that this library uses async-await instead of EventLoopGroup. Everything else should be properly handled by the GraphQL schema library

@ZirgVoice
Copy link
Author

This happens with any model, not just the AddressModel. Here's what the AddressModel looks like:

final class AddressModel: Model {
    static let schema = "addresses"

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

    @Field(key: .address)
    var address: String

    @Field(key: .zipCode)
    var zipCode: String

    @Field(key: .city)
    var city: String

    @OptionalField(key: .longitude)
    var longitude: Double?

    @OptionalField(key: .latitude)
    var latitude: Double?

    @Parent(key: .countryID)
    var country: CountryModel

    @Timestamp(key: .createdAt, on: .create)
    var createdAt: Date?

    @Timestamp(key: .updatedAt, on: .update)
    var updatedAt: Date?

    @Timestamp(key: .deletedAt, on: .delete)
    var deletedAt: Date?

    init() {}

    init(
        address: String,
        zipCode: String,
        city: String,
        longitude: Double?,
        latitude: Double?,
        countryID: CountryModel.IDValue
    ) {
        self.address = address
        self.zipCode = zipCode
        self.city = city
        self.longitude = longitude
        self.latitude = latitude
        self.$country.id = countryID
    }

    init(_ arguments: CreateAddressArguments) {
        self.id = UUID()
        self.address = arguments.address
        self.zipCode = arguments.zipCode
        self.city = arguments.city
        self.longitude = arguments.longitude
        self.latitude = arguments.latitude
        self.$country.id = arguments.countryID
    }
}

In the schema, the type looks like this:

    Type(AddressModel.self) {
        Field("id", at: \.id)
        Field("address", at: \.address)
        Field("zipCode", at: \.zipCode)
        Field("city", at: \.city)
        Field("longitude", at: \.longitude)
        Field("latitude", at: \.latitude)
        Field("country", at: AddressModel.getCountry, as: TypeReference<CountryModel>.self)
        Field("createdAt", at: \.createdAt)
        Field("updatedAt", at: \.updatedAt)
    }

@d-exclaimation
Copy link
Owner

d-exclaimation commented May 19, 2022

I couldn't reproduce the issue.

I tried getting the Parent relation (getting the parent's id) and make a similar models. However, my code worked fine as long as it's correct in the database.

Here is the code I used

Model classes
final class User: Model, Content {
    static var schema: String = "users"
    
    @ID(key: .id)
    var id: UUID?
    
    @Field(key: "name")
    var name: String

    init() { }
    
    init(id: UUID? = nil, name: String) {
        self.id = id
        self.name = name
    }
}

final class Item: Model, Content {
    static let schema = "items"

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

    @Field(key: "name")
    var name: String

    @Parent(key: "user_id")
    var user: User
    
    init() { }

    init(id: UUID? = nil, name: String, userID: UUID) {
        self.id = id
        self.name = name
        self.$user.id = userID
    }
}
struct Resolver {
    func items(ctx: Context, _: NoArguments) async throws -> [Item] {
        let res = try await Item.query(on: ctx.req.db).all()
        res.forEach { item in
            print("\(item.$user.id)")
        }
        return res
    }
}

extension Item {
    func owner(ctx: Context, _: NoArguments) async throws -> User? {
        // I receive no fatal error here
        print("\(self.$user.id)")
        // No fatal error here either
        return try await User.query(on: ctx.req.db).filter(\.$id == $user.id).first()
    }
}
        
// Schema

Type(Item.self) {
    Field("name", at: \.name)
    Field("owner", at: Item.owner, as: TypeReference<User>.self)
}

Query {
    Field("items", at: Resolver.items)
}

Can share how do you fetch the AddressModel itself (the Query / Mutation resolver) before the getCountry method was called?

@ZirgVoice
Copy link
Author

ZirgVoice commented May 19, 2022

Resolver

    func getAddress(
        context: Context,
        arguments: IDArgument
    ) async throws -> AddressModel {
        guard
            let model = try await AddressModel.query(on: context.request.db)
            .filter(\.$id == arguments.id)
            .first()
        else {
            throw Abort(.notFound, reason: "Address with id \(arguments.id) not found")
        }
        return model
    }

Schema

    Query {
        Field("getAddress", at: GraphQLResolver.getAddress) {
            Argument("id", at: \.id)
        }
    }

Address in DB
Bildschirmfoto 2022-05-19 um 12 24 54

@ZirgVoice
Copy link
Author

Country field is empty
Bildschirmfoto 2022-05-19 um 12 34 27

@d-exclaimation
Copy link
Owner

I'll try to look into it more to find what went wrong here.

Honestly, I am not sure why is this happening. My test code also uses PostgreSQL but mine worked. There is nothing out of the ordinary with the code you shared, so it should work fine. The only notable thing is that your model is @Parent(key: .countryID) instead of @Parent(key: "country_id"), but if that part compiles, it shouldn't break all of the sudden.

I'll also share my migrations code used to build the tables for my test code, just in case you manage to find the problem from it

struct CreateUser: AsyncMigration {
    func prepare(on database: Database) async throws {
        try await database.schema("users")
            .id()
            .field("name", .string)
            .create()
    }

    func revert(on database: Database) async throws {
        try await database.schema("users").delete()
    }
}

struct CreateItem: AsyncMigration {
    func prepare(on database: Database) async throws {
        try await database.schema("items")
            .id()
            .field("name", .string)
            .field("user_id", .uuid, .references("users", "id"))
            .create()
    }

    func revert(on database: Database) async throws {
        try await database.schema("items").delete()
    }
}

@ZirgVoice
Copy link
Author

I found what the problem is, I wrote a parser for the fields that come in the request to get from the database only those fields that were requested, and there was no country field in it. This parser is more like a crutch. Perhaps you know if there is a way to get the list of requested fields in Graphiti?

@d-exclaimation d-exclaimation added the help wanted Extra attention is needed label May 19, 2022
@d-exclaimation
Copy link
Owner

d-exclaimation commented May 19, 2022

It's good to know that you figured out why the issue is happening. I don't think you can get it from Graphiti directly but you could probably grab those requested fields from the Request object, probably by parsing the request into GraphQLRequest.

From that you can access the source and using the GraphQLSwift/GraphQL parse method to get the Document and then get the OperationDefinition and manually find the field being requested from SelectionSet.

You will have to go low level into the GraphQLSwift/GraphQL library.

You'll ended up doing something like this.

let gql = try ctx.req.content.decode(GraphQLRequest.self)
let fieldsRequested = try parse(source: Source(body: gql.query))
    .definitions
    .flatMap { def -> [Selection] in
        guard let op = def as? OperationDefinition else {
            return []
        }
        return op.selectionSet.selections
    }
    .compactMap { selection -> Field? in
        guard let field = selection as? Field else {
            return nil
        }
        return field
    }

@d-exclaimation d-exclaimation removed the help wanted Extra attention is needed label May 19, 2022
@ZirgVoice
Copy link
Author

Thanks for the help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants