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