grafbase_sdk/extension/
contracts.rs

1use crate::{
2    component::AnyExtension,
3    types::{Configuration, Contract, ContractDirective, Error, GraphqlSubgraph},
4};
5
6/// The Contracts extension allows you to control which part of the schema will be exposed to
7/// clients for GraphQL queries, introspection and also the MCP endpoint if active.
8///
9/// Contracts are built and cached for a particular key. This can be statically defined the
10/// `grafbase.toml` file:
11///
12/// ```toml
13/// [graph]
14/// contract_key = "<key>"
15/// ```
16///
17/// Or dynamically provided by the [`on_request()`](crate::HooksExtension::on_request()) hook:
18///
19/// ```rust
20/// # use grafbase_sdk::{HooksExtension, host_io::http::Method, types::{ErrorResponse, Error, Configuration, GatewayHeaders, OnRequestOutput}};
21/// struct Hooks;
22/// impl HooksExtension for Hooks {
23/// # fn new(config: Configuration) -> Result<Self, Error> { Ok(Hooks) }
24///     #[allow(refining_impl_trait)]
25///     fn on_request(&mut self, url: &str, method: Method, headers: &mut GatewayHeaders) -> Result<OnRequestOutput, ErrorResponse> {
26///         Ok(OnRequestOutput::new().contract_key("my-contract-key"))
27///     }
28/// }
29/// ```
30///
31/// In addition to the key, the extension will receive a list of all the directives defined by said
32/// extension and the list of GraphQL subgraphs. For each directive it must specify whether the
33/// decorated element is part of the exposed API or not. If not, they're treated as if
34/// `@inaccessible` was applied on them.
35///
36/// # Example
37///
38/// You can initialize a new resolver extension with the Grafbase CLI:
39///
40/// ```bash
41/// grafbase extension init --type contracts my-contracts
42/// ```
43///
44/// ```rust
45/// use grafbase_sdk::{
46///     ContractsExtension,
47///     types::{Configuration, Error, Contract, ContractDirective, GraphqlSubgraph},
48/// };
49///
50/// #[derive(ContractsExtension)]
51/// struct MyContracts;
52///
53/// impl ContractsExtension for MyContracts {
54///     fn new(config: Configuration) -> Result<Self, Error> {
55///         Ok(Self)
56///     }
57///
58///     fn construct(
59///         &mut self,
60///         key: String,
61///         directives: Vec<ContractDirective<'_>>,
62///         subgraphs: Vec<GraphqlSubgraph>,
63///     ) -> Result<Contract, Error> {
64///         Ok(Contract::new(&directives, true))
65///     }
66/// }
67/// ```
68///
69/// I
70pub trait ContractsExtension: Sized + 'static {
71    /// Creates a new instance of the extension. The [`Configuration`] will contain all the
72    /// configuration defined in the `grafbase.toml` by the extension user in a serialized format.
73    ///
74    /// # Example
75    ///
76    /// The following TOML configuration:
77    /// ```toml
78    /// [extensions.my-contracts.config]
79    /// my_custom_key = "value"
80    /// ```
81    ///
82    /// can be easily deserialized with:
83    ///
84    /// ```rust
85    /// # use grafbase_sdk::types::{Configuration, Error};
86    /// # fn dummy(config: Configuration) -> Result<(), Error> {
87    /// #[derive(Default, serde::Deserialize)]
88    /// #[serde(default, deny_unknown_fields)]
89    /// struct Config {
90    ///     my_custom_key: Option<String>
91    /// }
92    ///
93    /// let config: Config = config.deserialize()?;
94    /// # Ok(())
95    /// # }
96    /// ```
97    fn new(config: Configuration) -> Result<Self, Error>;
98
99    /// Create the contract based on the provided key. The contract specifies whether the elements
100    /// decorated by directives are part of the exposed API or not. Furthermore it's possible to
101    /// modify the GraphQL subgraphs for this contract.
102    fn construct(
103        &mut self,
104        key: String,
105        directives: Vec<ContractDirective<'_>>,
106        subgraphs: Vec<GraphqlSubgraph>,
107    ) -> Result<Contract, Error>;
108}
109
110#[doc(hidden)]
111pub fn register<T: ContractsExtension>() {
112    pub(super) struct Proxy<T: ContractsExtension>(T);
113
114    impl<T: ContractsExtension> AnyExtension for Proxy<T> {
115        fn construct(
116            &mut self,
117            key: String,
118            directives: Vec<ContractDirective<'_>>,
119            subgraphs: Vec<GraphqlSubgraph>,
120        ) -> Result<Contract, Error> {
121            ContractsExtension::construct(&mut self.0, key, directives, subgraphs)
122        }
123    }
124
125    crate::component::register_extension(Box::new(|_, config| {
126        <T as ContractsExtension>::new(config).map(|extension| Box::new(Proxy(extension)) as Box<dyn AnyExtension>)
127    }))
128}