grafbase_sdk/extension/
resolver.rs

1use crate::{
2    component::AnyExtension,
3    host::Headers,
4    types::{
5        Configuration, Error, FieldDefinitionDirective, FieldInputs, FieldOutput, SchemaDirective, SubscriptionOutput,
6    },
7};
8
9/// A trait that extends `Extension` and provides functionality for resolving fields.
10///
11/// Implementors of this trait are expected to provide a method to resolve field values based on
12/// the given context, directive, and inputs. This is typically used in scenarios where field
13/// resolution logic needs to be encapsulated within a resolver object, allowing for modular
14/// and reusable code design.
15pub trait ResolverExtension: Sized + 'static {
16    /// Creates a new instance of the extension.
17    ///
18    /// # Arguments
19    ///
20    /// * `schema_directives` - List of all schema directives for all subgraphs defined in this
21    ///                         extension.
22    /// * `config` - The configuration for this extension, from the gateway TOML.
23    ///
24    /// # Returns
25    ///
26    /// Returns an instance of this resolver. Upon failure, every call to this extension will fail.
27    /// Similar to how every request to a subgraph would fail if it went down.
28    fn new(schema_directives: Vec<SchemaDirective>, config: Configuration) -> Result<Self, Error>;
29
30    /// Resolves a GraphQL field, called at most once per occurrence in the query. If contained
31    /// inside lists, this resolver may receive multiple inputs to resolve. So for a schema like:
32    ///
33    /// ```ignore,graphql
34    /// type Query {
35    ///     users: [User]
36    /// }
37    ///
38    /// type User {
39    ///     id: ID!
40    ///     field: JSON # <- field resolver by this extension
41    /// }
42    /// ```
43    ///
44    /// and a query like:
45    ///
46    /// ```ignore,graphql
47    /// query {
48    ///    users {
49    ///       field
50    ///    }
51    /// }
52    /// ```
53    ///
54    /// This function will called at most once, independently of the number of users.
55    /// `FieldInputs` will have an entry for every occurrence of said field within the response.
56    /// So if there are 10 users, `FieldInputs` will contain 10 `ResolverInput`.
57    ///
58    /// Any requested response data such as the user id will be included in the `FieldInput`,
59    /// but every other directive argument that is either static or depends solely on the field arguments
60    /// will be provided in the `FieldDefinitionDirective`.
61    ///
62    /// The output of this function is fairly flexible, you can return individual elements/errors or
63    /// everything batched together. The data may contain additional fields, they'll be ignored.
64    /// But it MUST have the proper shape and the appropriate names. The gateway will validate the
65    /// every element.
66    ///
67    /// # Arguments
68    ///
69    /// * `headers` - The subgraph headers associated with this field resolution
70    /// * `subgraph_name` - The name of the subgraph associated with this field resolution
71    /// * `directive` - The directive associated with this field resolution
72    /// * `definition` - The field definition containing metadata
73    /// * `inputs` - The input values provided for this field
74    ///
75    /// # Returns
76    ///
77    /// Returns a `Result` containing either the resolved `FieldOutput` value or an `Error`
78    fn resolve_field(
79        &mut self,
80        headers: Headers,
81        subgraph_name: &str,
82        directive: FieldDefinitionDirective<'_>,
83        inputs: FieldInputs<'_>,
84    ) -> Result<FieldOutput, Error>;
85
86    /// Resolves a subscription field by setting up a subscription handler.
87    ///
88    /// # Arguments
89    ///
90    /// * `headers` - The subgraph headers associated with this field resolution
91    /// * `directive` - The directive associated with this subscription field
92    /// * `definition` - The field definition containing metadata about the subscription
93    ///
94    /// # Returns
95    ///
96    /// Returns a `Result` containing either a boxed `Subscriber` implementation or an `Error`
97    fn resolve_subscription(
98        &mut self,
99        headers: Headers,
100        subgraph_name: &str,
101        directive: FieldDefinitionDirective<'_>,
102    ) -> Result<Box<dyn Subscription>, Error>;
103
104    /// Returns a key for a subscription field.
105    ///
106    /// This method is used to identify unique subscription channels or connections
107    /// when managing multiple active subscriptions. The returned key can be
108    /// used to track, manage, or deduplicate subscriptions.
109    ///
110    /// # Arguments
111    ///
112    /// * `headers` - The subgraph headers associated with this subscription
113    /// * `subgraph_name` - The name of the subgraph associated with this subscription
114    /// * `directive` - The directive associated with this subscription field
115    ///
116    /// # Returns
117    ///
118    /// Returns an `Option<Vec<u8>>` containing either a unique key for this
119    /// subscription or `None` if no deduplication is desired.
120    #[allow(unused)]
121    fn subscription_key(
122        &mut self,
123        headers: Headers,
124        subgraph_name: &str,
125        directive: FieldDefinitionDirective<'_>,
126    ) -> Option<Vec<u8>> {
127        None
128    }
129}
130
131/// A trait for consuming field outputs from streams.
132///
133/// This trait provides an abstraction over different implementations
134/// of subscriptions to field output streams. Implementors should handle
135/// the details of their specific transport mechanism while providing a
136/// consistent interface for consumers.
137pub trait Subscription {
138    /// Retrieves the next field output from the subscription.
139    ///
140    /// Returns:
141    /// - `Ok(Some(FieldOutput))` if a field output was available
142    /// - `Ok(None)` if the subscription has ended normally
143    /// - `Err(Error)` if an error occurred while retrieving the next field output
144    fn next(&mut self) -> Result<Option<SubscriptionOutput>, Error>;
145}
146
147#[doc(hidden)]
148pub fn register<T: ResolverExtension>() {
149    pub(super) struct Proxy<T: ResolverExtension>(T);
150
151    impl<T: ResolverExtension> AnyExtension for Proxy<T> {
152        fn resolve_field(
153            &mut self,
154            headers: Headers,
155            subgraph_name: &str,
156            directive: FieldDefinitionDirective<'_>,
157            inputs: FieldInputs<'_>,
158        ) -> Result<FieldOutput, Error> {
159            ResolverExtension::resolve_field(&mut self.0, headers, subgraph_name, directive, inputs)
160        }
161        fn resolve_subscription(
162            &mut self,
163            headers: Headers,
164            subgraph_name: &str,
165            directive: FieldDefinitionDirective<'_>,
166        ) -> Result<Box<dyn Subscription>, Error> {
167            ResolverExtension::resolve_subscription(&mut self.0, headers, subgraph_name, directive)
168        }
169
170        fn subscription_key(
171            &mut self,
172            headers: Headers,
173            subgraph_name: &str,
174            directive: FieldDefinitionDirective<'_>,
175        ) -> Result<Option<Vec<u8>>, Error> {
176            Ok(ResolverExtension::subscription_key(
177                &mut self.0,
178                headers,
179                subgraph_name,
180                directive,
181            ))
182        }
183    }
184
185    crate::component::register_extension(Box::new(|schema_directives, config| {
186        <T as ResolverExtension>::new(schema_directives, config)
187            .map(|extension| Box::new(Proxy(extension)) as Box<dyn AnyExtension>)
188    }))
189}