Can I add functions from modules to a struct?

I'm new to Rust so I could be barking up the wrong tree.

I'm writing a GraphQL server and I like being able to break things up into multiple files, each file concentrating on just one thing.

For my queries, rather than having one big file with all the code to handle graphql queries I'd like to create a query struct and then have each of the queries in there own files ( I know I'm using a Juniper project as an example but it's jut a way to demonstrate what I'm trying to learn).

So for now I have this.

// query.rs

#[juniper::object(Context = Context)]
impl QueryRoot {
    fn members(context: &Context) -> Vec<Member> {
        use crate::schema::members::dsl::*;
        let connection = context.db.get().unwrap();
        members
            .limit(100)
            .load::<Member>(&connection)
            .expect("Error loading members")
    }

    fn teams(context: &Context) -> Vec<Team> {
        use crate::schema::teams::dsl::*;
        let connection = context.db.get().unwrap();
        teams
            .limit(100)
            .load::<Team>(&connection)
            .expect("Error loading teams")
    }
}

Is there a way I can move the two fn's (members and teams) into their own files and if so how would I pull them into the struct?

If I have to keep all the queries in one file then the code will mushroom.

Maybe, in the first file:

#[juniper::object(Context = Context)]
impl QueryRoot {
    fn members(context: &Context) -> Vec<Member> {
        use crate::schema::members::dsl::*;
        let connection = context.db.get().unwrap();
        members
            .limit(100)
            .load::<Member>(&connection)
            .expect("Error loading members")
    }
}

And the second file:

#[juniper::object(Context = Context)]
impl QueryRoot {
    fn teams(context: &Context) -> Vec<Team> {
        use crate::schema::teams::dsl::*;
        let connection = context.db.get().unwrap();
        teams
            .limit(100)
            .load::<Team>(&connection)
            .expect("Error loading teams")
    }
}

In your own crate, you can freely split the impl of a type into multiple files.

I didn’t realise you can do that. Nice!

Almost, except I get

error[E0119]: conflicting implementations of trait `juniper::GraphQLType` for type `graphql_schema::query::QueryRoot`:
 --> src/graphql_schema/query/teams.rs:6:1
  |
6 | #[juniper::object(Context = Context)]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `graphql_schema::query::QueryRoot`
  | 
 ::: src/graphql_schema/query/members.rs:6:1
  |
6 | #[juniper::object(Context = Context)]
  | ------------------------------------- first implementation here

Adding this got rid of that but the query functions now don't get picked up.

Getting close though :slight_smile:

https://github.com/ztolley/rust-graphql-example/tree/split-queries/src/graphql_schema/query

Okay, this is different, because that attribute turns the impl into a trait impl. A trait impl cannot be split up.

You can do this though: have the trait impl delegate to inherent methods defined in other files.

#[juniper::object(Context = Context)]
impl QueryRoot {
    fn members(context: &Context) -> Vec<Member> {
        QueryRoot::members_impl(context)
    }

    fn teams(context: &Context) -> Vec<Team> {
        QueryRoot::teams_impl(context)
    }
}

mod members {
    impl QueryRoot {
        pub(crate) fn members_impl(context: &Context) -> Vec<Member> {
            use crate::schema::members::dsl::*;
            let connection = context.db.get().unwrap();
            members
                .limit(100)
                .load::<Member>(&connection)
                .expect("Error loading members")
    }
}

mod teams {
    impl QueryRoot {
        pub(crate) fn teams_impl(context: &Context) -> Vec<Team> {
            use crate::schema::teams::dsl::*;
            let connection = context.db.get().unwrap();
            teams
                .limit(100)
                .load::<Team>(&connection)
                .expect("Error loading teams")
        }
    }
}
4 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.