Complex fields

If you've got a struct that can't be mapped directly to GraphQL, that contains computed fields or circular structures, you have to use a more powerful tool: the object procedural macro. This macro lets you define GraphQL object fields in a Rust impl block for a type. Continuing with the example from the last chapter, this is how you would define Person using the macro:


struct Person {
    name: String,
    age: i32,
}

#[juniper::object]
impl Person {
    fn name(&self) -> &str {
        self.name.as_str()
    }

    fn age(&self) -> i32 {
        self.age
    }
}

# fn main() { }

While this is a bit more verbose, it lets you write any kind of function in the field resolver. With this syntax, fields can also take arguments:

#[derive(juniper::GraphQLObject)]
struct Person {
    name: String,
    age: i32,
}

struct House {
    inhabitants: Vec<Person>,
}

#[juniper::object]
impl House {
    // Creates the field inhabitantWithName(name), returning a nullable person
    fn inhabitant_with_name(&self, name: String) -> Option<&Person> {
        self.inhabitants.iter().find(|p| p.name == name)
    }
}

# fn main() {}

To access global data such as database connections or authentication information, a context is used. To learn more about this, see the next chapter: Using contexts.

Description, renaming, and deprecation

Like with the derive attribute, field names will be converted from snake_case to camelCase. If you need to override the conversion, you can simply rename the field. Also, the type name can be changed with an alias:


struct Person {
}

/// Doc comments are used as descriptions for GraphQL.
#[juniper::object(
    // With this attribtue you can change the public GraphQL name of the type.
    name = "PersonObject",
    // You can also specify a description here, which will overwrite 
    // a doc comment description.
    description = "...",
)]
impl Person {

    /// A doc comment on the field will also be used for GraphQL.
    #[graphql(
        // Or provide a description here.
        description = "...",
    )]
    fn doc_comment(&self) -> &str {
        ""
    }

    // Fields can also be renamed if required.
    #[graphql(
        name = "myCustomFieldName",
    )]
    fn renamed_field() -> bool {
        true
    }

    // Deprecations also work as you'd expect.
    // Both the standard Rust syntax and a custom attribute is accepted.
    #[deprecated(note = "...")]
    fn deprecated_standard() -> bool {
        false
    }

    #[graphql(deprecated = "...")]
    fn deprecated_graphql() -> bool {
        true
    }
}

# fn main() { }

Customizing arguments

Method field arguments can also be customized.

They can have custom descriptions and default values.

Note: The syntax for this is currently a little awkward. This will become better once the Rust RFC 2565 is implemented.


struct Person {}

#[juniper::object]
impl Person {
    #[graphql(
        arguments(
            arg1(
                // Set a default value which will be injected if not present.
                // The default can be any valid Rust expression, including a function call, etc.
                default = true,
                // Set a description.
                description = "The first argument..."
            ),
            arg2(
                default = 0,
            )
        )
    )]
    fn field1(&self, arg1: bool, arg2: i32) -> String {
        format!("{} {}", arg1, arg2)
    }
}

# fn main() { }

More features

GraphQL fields expose more features than Rust's standard method syntax gives us:

  • Per-field description and deprecation messages
  • Per-argument default values
  • Per-argument descriptions

These, and more features, are described more thorougly in the reference documentation.