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}