grafbase_sdk/extension/
resolver.rs

1use crate::{
2    types::{Configuration, Directive, FieldDefinition, FieldInputs, FieldOutput},
3    wit::{Error, SharedContext},
4};
5
6use super::Extension;
7
8type InitFn = Box<dyn Fn(Vec<Directive>, Configuration) -> Result<Box<dyn Resolver>, Box<dyn std::error::Error>>>;
9
10pub(super) static mut EXTENSION: Option<Box<dyn Resolver>> = None;
11pub static mut INIT_FN: Option<InitFn> = None;
12
13pub(super) fn get_extension() -> Result<&'static mut dyn Resolver, Error> {
14    // Safety: This is hidden, only called by us. Every extension call to an instance happens
15    // in a single-threaded environment. Do not call this multiple times from different threads.
16    unsafe {
17        EXTENSION.as_deref_mut().ok_or_else(|| Error {
18            message: "Resolver extension not initialized correctly.".to_string(),
19            extensions: Vec::new(),
20        })
21    }
22}
23
24/// Initializes the resolver extension with the provided directives using the closure
25/// function created with the `register_extension!` macro.
26pub(super) fn init(directives: Vec<Directive>, configuration: Configuration) -> Result<(), Box<dyn std::error::Error>> {
27    // Safety: This function is only called from the SDK macro, so we can assume that there is only one caller at a time.
28    unsafe {
29        let init = INIT_FN.as_ref().expect("Resolver extension not initialized correctly.");
30        EXTENSION = Some(init(directives, configuration)?);
31    }
32
33    Ok(())
34}
35
36/// This function gets called when the extension is registered in the user code with the `register_extension!` macro.
37///
38/// This should never be called manually by the user.
39#[doc(hidden)]
40pub fn register(f: InitFn) {
41    // Safety: This function is only called from the SDK macro, so we can assume that there is only one caller at a time.
42    unsafe {
43        INIT_FN = Some(f);
44    }
45}
46
47/// A trait that extends `Extension` and provides functionality for resolving fields.
48///
49/// Implementors of this trait are expected to provide a method to resolve field values based on
50/// the given context, directive, and inputs. This is typically used in scenarios where field
51/// resolution logic needs to be encapsulated within a resolver object, allowing for modular
52/// and reusable code design.
53pub trait Resolver: Extension {
54    /// Resolves a field value based on the given context, directive, definition, and inputs.
55    ///
56    /// # Arguments
57    ///
58    /// * `context` - The shared context containing runtime information
59    /// * `directive` - The directive associated with this field resolution
60    /// * `definition` - The field definition containing metadata
61    /// * `inputs` - The input values provided for this field
62    ///
63    /// # Returns
64    ///
65    /// Returns a `Result` containing either the resolved `FieldOutput` value or an `Error`
66    fn resolve_field(
67        &mut self,
68        context: SharedContext,
69        directive: Directive,
70        definition: FieldDefinition,
71        inputs: FieldInputs,
72    ) -> Result<FieldOutput, Error>;
73
74    /// Resolves a subscription field based on the given context, directive, and definition.
75    ///
76    /// The function should initialize a subscription internally, but not return any data.
77    /// Use the `resolve_next_subscription_item` method to retrieve the next item in the subscription.
78    ///
79    /// # Arguments
80    ///
81    /// * `context` - The shared context containing runtime information
82    /// * `directive` - The directive associated with this subscription
83    /// * `definition` - The field definition containing metadata
84    fn resolve_field_subscription(
85        &mut self,
86        context: SharedContext,
87        directive: Directive,
88        definition: FieldDefinition,
89    ) -> Result<(), Error>;
90
91    /// Retrieves the next item in a subscription stream.
92    ///
93    /// This method is called repeatedly to get sequential items from an active subscription.
94    ///
95    /// # Returns
96    ///
97    /// Returns a `Result` containing either the next `FieldOutput` value in the subscription stream or an `Error`
98    fn resolve_next_subscription_item(&mut self) -> Result<Option<FieldOutput>, Error>;
99}