1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
use futures::future::LocalBoxFuture;
use ntex::{
http::Response,
web::{self, DefaultError, WebRequest},
};
use std::sync::Arc;
use crate::executor::{
hooks::on_supergraph_load::Supergraph,
plugin_context::PluginContext,
plugin_trait::{EndHookPayload, EndHookResult, StartHookPayload, StartHookResult},
request_context::RequestContextPluginApi,
};
type RequestContextApi = RequestContextPluginApi<super::OnHttpRequest>;
pub struct OnHttpRequestHookPayload<'req> {
/// The raw incoming HTTP request to the router
/// It includes all the details of the request such as headers, body, etc.
///
/// Example:
/// ```
/// use hive_router::{
/// plugins::hooks::on_http_request::{OnHttpRequestHookPayload, OnHttpRequestHookFuture},
/// };
///
/// fn on_http_request<'req>(mut payload: OnHttpRequestHookPayload<'req>) -> OnHttpRequestHookFuture<'req> {
/// Box::pin(async move {
/// let my_header = payload.router_http_request.headers().get("my-header");
/// // do something with the header...
/// payload.proceed()
/// })
/// }
/// ```
pub router_http_request: WebRequest<DefaultError>,
/// The context object that can be used to share data across different plugin hooks for the same request.
/// It is unique per request and is dropped after the response is sent.
///
/// [Learn more about the context data sharing in the docs](https://the-guild.dev/graphql/hive/docs/router/extensibility/plugin_system#context-data-sharing)
///
/// Example:
/// ```
/// use hive_router::{
/// plugins::hooks::{
/// on_http_request::{OnHttpRequestHookPayload, OnHttpRequestHookFuture},
/// on_execute::{OnExecuteStartHookPayload, OnExecuteStartHookResult}
/// },
/// plugin_context::PluginContext,
/// async_trait::async_trait,
/// };
///
/// struct ContextData {
/// greetings: String
/// }
///
/// #[async_trait]
/// impl RouterPlugin for MyPlugin {
/// fn on_http_request<'req>(mut payload: OnHttpRequestHookPayload<'req>) -> OnHttpRequestHookFuture<'req> {
/// Box::pin(async move {
/// let context_data = ContextData {
/// greetings: "Hello from context!".to_string()
/// };
///
/// payload.context.insert(context_data);
///
/// payload.proceed()
/// })
/// }
///
/// async fn on_execute<'exec>(&'exec self, payload: OnExecuteStartHookPayload<'exec>) -> OnExecuteStartHookResult<'exec> {
/// let context_data = payload.context.get::<ContextData>().unwrap();
/// println!("{}", context_data.greetings); // prints "Hello from context!"
/// payload.proceed()
/// }
/// }
/// ```
pub context: &'req PluginContext,
pub request_context: RequestContextApi,
}
impl<'req> OnHttpRequestHookPayload<'req> {
/// Selects the supergraph used for this request. Set this to swap the entire GraphQL schema
/// (and everything derived from it: validation, introspection, planning, execution) for a
/// different one, on a per-request basis, e.g. to serve a different set of fields depending
/// on the caller.
///
/// The plugin owns construction, retention, replacement and removal of every
/// `Arc<Supergraph>` it selects from. This method stores a cheap, read-only snapshot in
/// request extensions and never retains the owner `Arc` itself. A later plugin can replace
/// that snapshot, preserving normal last-write-wins hook behavior. This means a
/// plugin can drop or replace a variant at any time: new requests simply stop being able to
/// select it, existing in-flight requests already holding a snapshot finish normally, and any
/// active subscriptions selected from it are closed with a schema-reload error.
///
/// ### Example
///
/// ```ignore
/// payload.set_supergraph(supergraph.clone());
/// ```
pub fn set_supergraph(&self, supergraph: Arc<Supergraph>) {
self.router_http_request
.extensions_mut()
.insert(supergraph.snapshot());
}
}
impl<'req> StartHookPayload<OnHttpResponseHookPayload<'req>, Response>
for OnHttpRequestHookPayload<'req>
{
}
pub type OnHttpRequestHookResult<'req> = StartHookResult<
'req,
OnHttpRequestHookPayload<'req>,
OnHttpResponseHookPayload<'req>,
Response,
>;
/// A local (non-`Send`) boxed future - always polled on its own worker thread
pub type OnHttpRequestHookFuture<'req> = LocalBoxFuture<'req, OnHttpRequestHookResult<'req>>;
pub struct OnHttpResponseHookPayload<'req> {
pub response: web::WebResponse,
pub context: &'req PluginContext,
pub request_context: RequestContextApi,
}
impl<'req> OnHttpResponseHookPayload<'req> {
/// Manipulate the outgoing HTTP response before it's sent to the client.
/// This can be used to modify headers, change the body, etc.
///
/// Example:
/// ```
/// fn on_http_request<'req>(
/// &'req self,
/// payload: OnHttpRequestHookPayload<'req>,
/// ) -> OnHttpRequestHookFuture<'req> {
/// Box::pin(async move {
/// payload.on_end(|payload| {
/// payload.map_response(|mut response| {
/// response.response_mut().headers_mut().insert(
/// "x-served-by",
/// "hive-router".parse().unwrap(),
/// );
/// response
/// }).proceed()
/// })
/// })
/// }
/// ```
pub fn map_response<F>(mut self, f: F) -> Self
where
F: FnOnce(web::WebResponse) -> web::WebResponse,
{
self.response = f(self.response);
self
}
}
impl<'req> EndHookPayload<Response> for OnHttpResponseHookPayload<'req> {}
pub type OnHttpResponseHookResult<'req> = EndHookResult<OnHttpResponseHookPayload<'req>, Response>;