grafbase_sdk/extension/resolver.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
use crate::{
types::{Directive, FieldDefinition, FieldInputs, FieldOutput},
wit::{Error, SharedContext},
};
use super::Extension;
type InitFn = Box<dyn Fn(Vec<Directive>) -> Result<Box<dyn Resolver>, Box<dyn std::error::Error>>>;
pub(super) static mut EXTENSION: Option<Box<dyn Resolver>> = None;
pub static mut INIT_FN: Option<InitFn> = None;
pub(super) fn get_extension() -> Result<&'static mut dyn Resolver, Error> {
// Safety: This is hidden, only called by us. Every extension call to an instance happens
// in a single-threaded environment. Do not call this multiple times from different threads.
unsafe {
EXTENSION.as_deref_mut().ok_or_else(|| Error {
message: "Resolver extension not initialized correctly.".to_string(),
extensions: Vec::new(),
})
}
}
/// Initializes the resolver extension with the provided directives using the closure
/// function created with the `register_extension!` macro.
pub(super) fn init(directives: Vec<Directive>) -> Result<(), Box<dyn std::error::Error>> {
// Safety: This function is only called from the SDK macro, so we can assume that there is only one caller at a time.
unsafe {
let init = INIT_FN.as_ref().expect("Resolver extension not initialized correctly.");
EXTENSION = Some(init(directives)?);
}
Ok(())
}
/// This function gets called when the extension is registered in the user code with the `register_extension!` macro.
///
/// This should never be called manually by the user.
#[doc(hidden)]
pub fn register(f: InitFn) {
// Safety: This function is only called from the SDK macro, so we can assume that there is only one caller at a time.
unsafe {
INIT_FN = Some(f);
}
}
/// A trait that extends `Extension` and provides functionality for resolving fields.
///
/// Implementors of this trait are expected to provide a method to resolve field values based on
/// the given context, directive, and inputs. This is typically used in scenarios where field
/// resolution logic needs to be encapsulated within a resolver object, allowing for modular
/// and reusable code design.
pub trait Resolver: Extension {
/// Resolves a field using the provided context, directive, and input values.
///
/// # Arguments
///
/// * `context` - A reference to a shared context that contains any necessary state or
/// environment information needed for resolution.
/// * `directive` - The directive associated with the field being resolved. Directives can
/// modify how fields are resolved, providing additional instructions or constraints.
/// * `inputs` - A vector of `FieldInput` values that provide input parameters required
/// for resolving the field.
///
/// # Returns
///
/// This method returns a `Result` which is:
/// * `Ok(FieldOutput)` on successful resolution, containing the resolved output values.
/// * `Err(Error)` if an error occurs during the resolution process, encapsulating details
/// about what went wrong. This will prevent resolving of the field.
///
/// The `FieldOutput` type has multiple response values, which can be either successful or an
/// error result.
fn resolve_field(
&mut self,
context: SharedContext,
directive: Directive,
definition: FieldDefinition,
inputs: FieldInputs,
) -> Result<FieldOutput, Error>;
}