pub trait ResolverExtension: Sized + 'static {
// Required methods
fn new(
subgraph_schemas: Vec<SubgraphSchema>,
config: Configuration,
) -> Result<Self, Error>;
fn resolve(
&mut self,
ctx: &AuthorizedOperationContext,
prepared: &[u8],
headers: SubgraphHeaders,
variables: Variables,
) -> Result<Response, Error>;
// Provided methods
fn prepare(&mut self, field: ResolvedField<'_>) -> Result<Vec<u8>, Error> { ... }
fn resolve_subscription<'s>(
&'s mut self,
ctx: &'s AuthorizedOperationContext,
prepared: &'s [u8],
headers: SubgraphHeaders,
variables: Variables,
) -> Result<impl IntoSubscription<'s>, Error> { ... }
}
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-resolver
This will generate the following:
use grafbase_sdk::{
ResolverExtension,
types::{AuthorizedOperationContext, Configuration, Error, ResolvedField, Response, SubgraphHeaders, SubgraphSchema, Variables},
};
#[derive(ResolverExtension)]
struct MyResolver {
config: Config
}
// Configuration in the TOML for this extension
#[derive(serde::Deserialize)]
struct Config {
#[serde(default)]
key: Option<String>
}
impl ResolverExtension for MyResolver {
fn new(subgraph_schemas: Vec<SubgraphSchema>, config: Configuration) -> Result<Self, Error> {
let config: Config = config.deserialize()?;
Ok(Self { config })
}
fn resolve(
&mut self,
ctx: &AuthorizedOperationContext,
prepared: &[u8],
headers: SubgraphHeaders,
variables: Variables,
) -> Result<Response, Error> {
// field which must be resolved. The prepared bytes can be customized to store anything you need in the operation cache.
let field = ResolvedField::try_from(prepared)?;
Ok(Response::null())
}
}
§Configuration
The configuration provided in the new
method is the one defined in the grafbase.toml
file by the extension user:
[extensions.my-resolver.config]
key = "value"
Once your business logic is written down you can compile your extension with:
grafbase extension build
It will generate all the necessary files in a build
directory which you can specify in the
grafbase.toml
configuration with:
[extensions.my-resolver]
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_DEFINITION
The @httpEndpoint
directive would be provided during the new()
method as a schema crate::types::Directive. The whole subgraph schema is also provided for
each subgraph where this extension is used. While the latter can be accessed with
ResolvedField::directive() in the resolve() method.
Required Methods§
Sourcefn new(
subgraph_schemas: Vec<SubgraphSchema>,
config: Configuration,
) -> Result<Self, Error>
fn new( subgraph_schemas: Vec<SubgraphSchema>, 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 the complete subgraph schema is provided whenever this extension is used.
§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
}
for schema in subgraph_schemas {
for directive in schema.directives() {
match directive.name() {
"httpEndpoint" => {
let http_endpoint: HttpEndpoint = directive.arguments()?;
}
_ => unreachable!()
}
}
}
Sourcefn resolve(
&mut self,
ctx: &AuthorizedOperationContext,
prepared: &[u8],
headers: SubgraphHeaders,
variables: Variables,
) -> Result<Response, Error>
fn resolve( &mut self, ctx: &AuthorizedOperationContext, prepared: &[u8], headers: SubgraphHeaders, variables: Variables, ) -> Result<Response, Error>
Resolves the field with the provided prepared bytes, headers and variables. With the default prepare() you can retrieve all the relevant information with:
let field = ResolvedField::try_from(prepared)?;
If you’re not doing any data transformation it’s best to forward the JSON or CBOR bytes, with Response::json and Response::cbor respectively, directly to the gateway. The gateway will always validate the subgraph data and deal with error propagation. Otherwise use Response::data to use the fastest supported serialization format.
Provided Methods§
Sourcefn prepare(&mut self, field: ResolvedField<'_>) -> Result<Vec<u8>, Error>
fn prepare(&mut self, field: ResolvedField<'_>) -> Result<Vec<u8>, Error>
Prepares the field for resolution. The resulting byte array will be part of the operation cache. Backwards compatibility is not a concern as the cache is only re-used for the same schema and extension versions. By default the ResolvedField is cached for a simpler implementation.
Sourcefn resolve_subscription<'s>(
&'s mut self,
ctx: &'s AuthorizedOperationContext,
prepared: &'s [u8],
headers: SubgraphHeaders,
variables: Variables,
) -> Result<impl IntoSubscription<'s>, Error>
fn resolve_subscription<'s>( &'s mut self, ctx: &'s AuthorizedOperationContext, prepared: &'s [u8], headers: SubgraphHeaders, variables: Variables, ) -> Result<impl IntoSubscription<'s>, Error>
Resolves a subscription for the given prepared bytes, headers, and variables. Subscriptions must implement the Subscription trait. It’s also possible to provide a de-duplication key. If provided the gateway will first check if there is an existing subscription with the same key and if there is, re-use it for the new client. This greatly limits the impact on the upstream service. So you have two choices for the result type:
- return a
Ok(subscription)
directly, without any de-duplication - return a
Ok((key, callback))
with an optional de-duplication key and a callback function. The latter is only called if no existing subscription exists for the given key.
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.