pub trait ResolverExtension: Sized + 'static {
// Required methods
fn new(
schema_directives: Vec<SchemaDirective>,
config: Configuration,
) -> Result<Self, Error>;
fn resolve_field(
&mut self,
headers: SubgraphHeaders,
subgraph_name: &str,
directive: FieldDefinitionDirective<'_>,
inputs: FieldInputs<'_>,
) -> Result<FieldOutputs, Error>;
// Provided methods
fn resolve_subscription(
&mut self,
headers: SubgraphHeaders,
subgraph_name: &str,
directive: FieldDefinitionDirective<'_>,
) -> Result<Box<dyn Subscription>, Error> { ... }
fn subscription_key(
&mut self,
headers: &SubgraphHeaders,
subgraph_name: &str,
directive: FieldDefinitionDirective<'_>,
) -> Option<Vec<u8>> { ... }
}Expand description
A resolver extension is called by the gateway to resolve a specific field.
§Example
You can initialize a new resolver extension with the Grafbase CLI:
grafbase extension init --type resolver my-resolverThis will generate the following:
use grafbase_sdk::{
ResolverExtension,
types::{SubgraphHeaders, FieldDefinitionDirective, FieldInputs, FieldOutputs, Configuration, Error, SchemaDirective}
};
#[derive(ResolverExtension)]
struct MyResolver {
config: Config
}
#[derive(serde::Deserialize)]
struct Config {
my_custom_key: String
}
impl ResolverExtension for MyResolver {
fn new(schema_directives: Vec<SchemaDirective>, config: Configuration) -> Result<Self, Error> {
let config: Config = config.deserialize()?;
Ok(Self { config })
}
fn resolve_field(
&mut self,
headers: SubgraphHeaders,
subgraph_name: &str,
directive: FieldDefinitionDirective<'_>,
inputs: FieldInputs<'_>,
) -> Result<FieldOutputs, Error> {
unimplemented!()
}
}§Configuration
The configuration provided in the new method is the one defined in the grafbase.toml
file by the extension user:
[extensions.my-auth.config]
my_custom_key = "value"Once your business logic is written down you can compile your extension with:
grafbase extension buildIt will generate all the necessary files in a build directory which you can specify in the
grafbase.toml configuration with:
[extensions.my-auth]
path = "<project path>/build"§Directives
In addition to the Rust extension, a definitions.graphql file will be also generated. It
should define directives for subgraph owners and any necessary input types, scalars or enum
necessary for those. Directives have two purposes for resolvers: define which fields can be
resolved, providing the necessary metadata for it, and provide global metadata with schema
directive.
A HTTP resolver extension could have the following directives for example:
scalar URL
directive @httpEndpoint(name: String!, url: URL!) on SCHEMA
directive @http(endpoint: String!, path: String!) on FIELD_DEFINITIONThe @httpEndpoint directive would be provided during the new()
method as a SchemaDirective. While the latter would be provided as a FieldDefinitionDirective during
the resolve_field() method.
Required Methods§
Sourcefn new(
schema_directives: Vec<SchemaDirective>,
config: Configuration,
) -> Result<Self, Error>
fn new( schema_directives: Vec<SchemaDirective>, config: Configuration, ) -> Result<Self, Error>
Creates a new instance of the extension. The Configuration will contain all the
configuration defined in the grafbase.toml by the extension user in a serialized format.
Furthermore all schema directives from all subgraphs will be provided as
SchemaDirectives.
§Configuration example
The following TOML configuration:
[extensions.my-auth.config]
my_custom_key = "value"can be easily deserialized with:
#[derive(serde::Deserialize)]
struct Config {
my_custom_key: String
}
let config: Config = config.deserialize()?;§Directive example
extend schema @httpEdnpoint(name: "example", url: "https://example.com")can be easily deserialized with:
#[derive(serde::Deserialize)]
struct HttpEndpoint {
name: String,
url: String
}
let config: Vec<HttpEndpoint> = schema_directives
.into_iter()
.map(|dir| dir.arguments())
.collect::<Result<_, _>>()?;Sourcefn resolve_field(
&mut self,
headers: SubgraphHeaders,
subgraph_name: &str,
directive: FieldDefinitionDirective<'_>,
inputs: FieldInputs<'_>,
) -> Result<FieldOutputs, Error>
fn resolve_field( &mut self, headers: SubgraphHeaders, subgraph_name: &str, directive: FieldDefinitionDirective<'_>, inputs: FieldInputs<'_>, ) -> Result<FieldOutputs, Error>
Resolves a GraphQL field. This function receives a batch of inputs and is called at most once per query field.
Supposing we have the following directive applied on this schema:
extend schema
@link(
url: "https://specs.grafbase.com/grafbase"
import: ["FieldSet"]
)
directive @resolve(fields: FieldSet!) on FIELD_DEFINITIONtype Query {
users: [User] # from a different subgraph
}
type User {
id : ID!
field: JSON @resolve(fields: "id")
}and a query like:
query {
users {
field
}
}The subgraph providing Query.users will return an arbitrary number N of users. Instead of
being called N times, this resolver will be called once with a FieldInputs containing N
FieldInput. This allows you to batch everything together at
once.
// Static arguments passed on to the directive that do not depend on the response data.
#[derive(serde::Deserialize)]
struct StaticArguments<'a> {
#[serde(borrow)]
endpoint_name: &'a str,
}
let StaticArguments { endpoint_name } = directive.arguments()?;
let mut builder = FieldOutputs::builder(inputs);
for input in inputs {
// Field arguments that depend on response data.
#[derive(serde::Deserialize)]
struct ResponseArguments<'a> {
#[serde(borrow)]
id: &'a str,
}
let ResponseArguments { id } = directive.arguments()?;
builder.insert(input, "data");
}
Ok(builder.build())FieldOutputs can also be initialized with a single error or a single data for convenience.
We also want to support providing raw JSON and CBOR bytes directly for batched and non-batched data later on, if it’s of interested let us know!
In addition to this the method also receives the subgraph headers after all the
subgraph-related header rules. And metadata the
FieldDefinitionDirectiveSite is also
available with directive.site().
Provided Methods§
Sourcefn resolve_subscription(
&mut self,
headers: SubgraphHeaders,
subgraph_name: &str,
directive: FieldDefinitionDirective<'_>,
) -> Result<Box<dyn Subscription>, Error>
fn resolve_subscription( &mut self, headers: SubgraphHeaders, subgraph_name: &str, directive: FieldDefinitionDirective<'_>, ) -> Result<Box<dyn Subscription>, Error>
Resolves a subscription field by setting up a subscription handler.
§Arguments
headers- The subgraph headers associated with this field resolutiondirective- The directive associated with this subscription fielddefinition- The field definition containing metadata about the subscription
§Returns
Returns a Result containing either a boxed Subscriber implementation or an Error
Sourcefn subscription_key(
&mut self,
headers: &SubgraphHeaders,
subgraph_name: &str,
directive: FieldDefinitionDirective<'_>,
) -> Option<Vec<u8>>
fn subscription_key( &mut self, headers: &SubgraphHeaders, subgraph_name: &str, directive: FieldDefinitionDirective<'_>, ) -> Option<Vec<u8>>
Returns a key for a subscription field.
This method is used to identify unique subscription channels or connections when managing multiple active subscriptions. The returned key can be used to track, manage, or deduplicate subscriptions.
§Arguments
headers- The subgraph headers associated with this subscriptionsubgraph_name- The name of the subgraph associated with this subscriptiondirective- The directive associated with this subscription field
§Returns
Returns an Option<Vec<u8>> containing either a unique key for this
subscription or None if no deduplication is desired.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.