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}