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}