16 releases

new 0.1.15 Feb 12, 2025
0.1.14 Feb 12, 2025
0.1.3 Jan 30, 2025

#77 in HTTP server

Download history 182/week @ 2025-01-24 326/week @ 2025-01-31 1175/week @ 2025-02-07

1,683 downloads per month

MPL-2.0 license

180KB
3.5K SLoC

Grafbase Gateway SDK for Extensions

docs.rs

This crate provides building blocks for creating Grafbase Gateway extensions.

Usage

Extensions are still under development. Expect issues if you try them out before we complete development.

Initialize a new project with the Grafbase CLI:

grafbase extension init --type auth/resolver my-extension

This creates a new project with the necessary files and dependencies to get you started. Edit the src/lib.rs file to add your extension logic. The Grafbase Gateway initializes the struct TestProject once during the initial extension call. The Gateway maintains extensions in a connection pool and reuses the struct for multiple requests. Because an extension runs single-threaded, we maintain multiple instances in the gateway memory to handle multiple requests concurrently.

Resolver Example

You can initialize a new resolver extension with the Grafbase CLI:

grafbase extension init --type resolver my-extension

The initialization accepts a list of schema directives from the federated schema (defined in the schema file) and a Configuration object that remains empty for resolver extensions. The ResolverExtension derive macro generates the necessary code to initialize a resolver extension and guides you to implement two traits: Extension and Resolver. The Extension trait initializes the extension, and the Resolver trait implements the extension logic to resolve a field:

# use grafbase_sdk::{
#    types::{Configuration, Directive, FieldDefinition, FieldInputs, FieldOutput},
#    Error, Extension, Resolver, ResolverExtension, SharedContext,
# };
#[derive(ResolverExtension)]
struct TestProject;

impl Extension for TestProject {
    fn new(schema_directives: Vec<Directive>, config: Configuration) -> Result<Self, Box<dyn std::error::Error>> {
        Ok(Self)
    }
}

impl Resolver for TestProject {
    fn resolve_field(
        &mut self,
        context: SharedContext,
        directive: Directive,
        field_definition: FieldDefinition,
        inputs: FieldInputs,
    ) -> Result<FieldOutput, Error> {
        todo!()
    }
}

The schema_directives in the constructor provides serialized access to all the SCHEMA directives from the subgraph SDL defined in the definitions.graphql file. The directive in the resolve_field provides serialized access to the directive that triggered the resolver extension.

The FieldOutput contains the serialized output of the resolver which transfers back to the gateway. Remember to match the serialized response to the type of the field resolver.

You can find a full example of a REST resolver extension in the Grafbase repository.

Authentication Example

You can initialize a new authentication extension with the Grafbase CLI:

grafbase extension init --type auth my-extension

The initialization needs a list of schema directives from the federated schema (empty for authentication extensions) and a Configuration object that reflects the extension provider configuration in grafbase.toml. The AuthenticationExtension derive macro generates code to initialize a resolver extension and guides you to implement two traits: Extension and Authenticator. The Extension trait initializes the extension, and the Authenticator trait implements the extension logic to authenticate a request:

# use grafbase_sdk::{
#     types::{Configuration, Directive, ErrorResponse, Token},
#     AuthenticationExtension, Authenticator, Extension, Headers,
# };

#[derive(AuthenticationExtension)]
struct TestProject;

impl Extension for TestProject {
    fn new(schema_directives: Vec<Directive>, config: Configuration) -> Result<Self, Box<dyn std::error::Error>>
        where
            Self: Sized,
        {
            todo!()
        }
    }

impl Authenticator for TestProject {
    fn authenticate(&mut self, headers: Headers) -> Result<Token, ErrorResponse> {
        todo!()
    }
}

The system deserializes the configuration from the grafbase.toml configuration. As an example, here's the configuration data from the JWT extension:

[[authentication.providers]]

[authentication.providers.extension]
extension = "jwt"

[authentication.providers.extension.config]
url = "https://example.com/.well-known/jwks.json"
issuer = "example.com"
audience = "my-project"
poll_interval = 60
header_name = "Authorization"
header_value_prefix = "Bearer "

The config section becomes available through the Configuration struct, and structs implementing serde::Deserialize can deserialize it using with the correspondig deserialization method.

The authenticate method receives request headers as input. A returned token allows the request to continue. You can serialize any data with serde::Serialize and pass it to the token initializer. For certain directives like @requiredScopes, define the scope claim in the token.

Find a complete example of a JWT authentication extension in the Grafbase repository.

Building

You can build your extension with the Grafbase CLI. For this to work, you must have a working rustup installation:

grafbase extension build

This compiles your extension and creates two files:

build/
├── extension.wasm
└── manifest.json

You can use the path to the build directory in your gateway configuration to try out the extension.

Testing

You can enable the test-utils feature for this crate in your extension. The feature provides tooling for testing the extensions against the Grafbase Gateway. Keep in mind to add it as a dev dependency, the test utils do not compile to WebAssembly. Your tests run as native code, and only the extension compiles into a WebAssembly component and tests with the gateway binary.

cargo add --dev grafbase-gateway --features test-utils

Write your tests in the tests/integration_tests.rs file in your extension project.

See the integration tests of the REST extension for an example of how to use the test utils.

You can run the tests with cargo:

cargo test

Dependencies

~4–22MB
~351K SLoC