1use std::collections::HashMap;
2
3use handlebars::Handlebars;
4use serde::Serialize;
5
6use crate::http::graphiql_plugin::GraphiQLPlugin;
7
8#[derive(Debug, Serialize, Default)]
12#[serde(rename_all = "kebab-case")]
13pub enum Credentials {
14 #[default]
17 SameOrigin,
18 Include,
20 Omit,
22}
23
24#[derive(Default, Serialize)]
40pub struct GraphiQLSource<'a> {
41 endpoint: &'a str,
42 subscription_endpoint: Option<&'a str>,
43 headers: Option<HashMap<&'a str, &'a str>>,
44 ws_connection_params: Option<HashMap<&'a str, &'a str>>,
45 title: Option<&'a str>,
46 credentials: Credentials,
47 plugins: &'a [GraphiQLPlugin<'a>],
48}
49
50impl<'a> GraphiQLSource<'a> {
51 pub fn build() -> GraphiQLSource<'a> {
53 Default::default()
54 }
55
56 #[must_use]
58 pub fn endpoint(self, endpoint: &'a str) -> GraphiQLSource<'a> {
59 GraphiQLSource { endpoint, ..self }
60 }
61
62 pub fn subscription_endpoint(self, endpoint: &'a str) -> GraphiQLSource<'a> {
64 GraphiQLSource {
65 subscription_endpoint: Some(endpoint),
66 ..self
67 }
68 }
69
70 pub fn header(self, name: &'a str, value: &'a str) -> GraphiQLSource<'a> {
72 let mut headers = self.headers.unwrap_or_default();
73 headers.insert(name, value);
74 GraphiQLSource {
75 headers: Some(headers),
76 ..self
77 }
78 }
79
80 pub fn ws_connection_param(self, name: &'a str, value: &'a str) -> GraphiQLSource<'a> {
82 let mut ws_connection_params = self.ws_connection_params.unwrap_or_default();
83 ws_connection_params.insert(name, value);
84 GraphiQLSource {
85 ws_connection_params: Some(ws_connection_params),
86 ..self
87 }
88 }
89
90 pub fn title(self, title: &'a str) -> GraphiQLSource<'a> {
92 GraphiQLSource {
93 title: Some(title),
94 ..self
95 }
96 }
97
98 pub fn credentials(self, credentials: Credentials) -> GraphiQLSource<'a> {
100 GraphiQLSource {
101 credentials,
102 ..self
103 }
104 }
105
106 pub fn plugins(self, plugins: &'a [GraphiQLPlugin]) -> GraphiQLSource<'a> {
108 GraphiQLSource { plugins, ..self }
109 }
110
111 pub fn finish(self) -> String {
113 let mut handlebars = Handlebars::new();
114 handlebars
115 .register_template_string(
116 "graphiql_v2_source",
117 include_str!("./graphiql_v2_source.hbs"),
118 )
119 .expect("Failed to register template");
120
121 handlebars
122 .render("graphiql_v2_source", &self)
123 .expect("Failed to render template")
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_with_only_url() {
133 let graphiql_source = GraphiQLSource::build().endpoint("/").finish();
134
135 assert_eq!(
136 graphiql_source,
137 r#"<!DOCTYPE html>
138<html lang="en">
139 <head>
140 <meta charset="utf-8">
141 <meta name="robots" content="noindex">
142 <meta name="viewport" content="width=device-width, initial-scale=1">
143 <meta name="referrer" content="origin">
144
145 <title>GraphiQL IDE</title>
146
147 <style>
148 body {
149 height: 100%;
150 margin: 0;
151 width: 100%;
152 overflow: hidden;
153 }
154
155 #graphiql {
156 height: 100vh;
157 }
158 </style>
159 <script
160 crossorigin
161 src="https://unpkg.com/react@17/umd/react.development.js"
162 ></script>
163 <script
164 crossorigin
165 src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"
166 ></script>
167 <link rel="icon" href="https://graphql.org/favicon.ico">
168 <link rel="stylesheet" href="https://unpkg.com/graphiql/graphiql.min.css" />
169 </head>
170
171 <body>
172 <div id="graphiql">Loading...</div>
173 <script
174 src="https://unpkg.com/graphiql/graphiql.min.js"
175 type="application/javascript"
176 ></script>
177 <script>
178 customFetch = (url, opts = {}) => {
179 return fetch(url, {...opts, credentials: 'same-origin'})
180 }
181
182 createUrl = (endpoint, subscription = false) => {
183 const url = new URL(endpoint, window.location.origin);
184 if (subscription) {
185 url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
186 }
187 return url.toString();
188 }
189
190 ReactDOM.render(
191 React.createElement(GraphiQL, {
192 fetcher: GraphiQL.createFetcher({
193 url: createUrl('/'),
194 fetch: customFetch,
195 }),
196 defaultEditorToolsVisibility: true,
197 }),
198 document.getElementById("graphiql")
199 );
200 </script>
201 </body>
202</html>"#
203 )
204 }
205
206 #[test]
207 fn test_with_both_urls() {
208 let graphiql_source = GraphiQLSource::build()
209 .endpoint("/")
210 .subscription_endpoint("/ws")
211 .finish();
212
213 assert_eq!(
214 graphiql_source,
215 r#"<!DOCTYPE html>
216<html lang="en">
217 <head>
218 <meta charset="utf-8">
219 <meta name="robots" content="noindex">
220 <meta name="viewport" content="width=device-width, initial-scale=1">
221 <meta name="referrer" content="origin">
222
223 <title>GraphiQL IDE</title>
224
225 <style>
226 body {
227 height: 100%;
228 margin: 0;
229 width: 100%;
230 overflow: hidden;
231 }
232
233 #graphiql {
234 height: 100vh;
235 }
236 </style>
237 <script
238 crossorigin
239 src="https://unpkg.com/react@17/umd/react.development.js"
240 ></script>
241 <script
242 crossorigin
243 src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"
244 ></script>
245 <link rel="icon" href="https://graphql.org/favicon.ico">
246 <link rel="stylesheet" href="https://unpkg.com/graphiql/graphiql.min.css" />
247 </head>
248
249 <body>
250 <div id="graphiql">Loading...</div>
251 <script
252 src="https://unpkg.com/graphiql/graphiql.min.js"
253 type="application/javascript"
254 ></script>
255 <script>
256 customFetch = (url, opts = {}) => {
257 return fetch(url, {...opts, credentials: 'same-origin'})
258 }
259
260 createUrl = (endpoint, subscription = false) => {
261 const url = new URL(endpoint, window.location.origin);
262 if (subscription) {
263 url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
264 }
265 return url.toString();
266 }
267
268 ReactDOM.render(
269 React.createElement(GraphiQL, {
270 fetcher: GraphiQL.createFetcher({
271 url: createUrl('/'),
272 fetch: customFetch,
273 subscriptionUrl: createUrl('/ws', true),
274 }),
275 defaultEditorToolsVisibility: true,
276 }),
277 document.getElementById("graphiql")
278 );
279 </script>
280 </body>
281</html>"#
282 )
283 }
284
285 #[test]
286 fn test_with_all_options() {
287 use crate::http::graphiql_plugin_explorer;
288 let graphiql_source = GraphiQLSource::build()
289 .endpoint("/")
290 .subscription_endpoint("/ws")
291 .header("Authorization", "Bearer [token]")
292 .ws_connection_param("token", "[token]")
293 .title("Awesome GraphiQL IDE Test")
294 .credentials(Credentials::Include)
295 .plugins(&[graphiql_plugin_explorer()])
296 .finish();
297
298 assert_eq!(
299 graphiql_source,
300 r#"<!DOCTYPE html>
301<html lang="en">
302 <head>
303 <meta charset="utf-8">
304 <meta name="robots" content="noindex">
305 <meta name="viewport" content="width=device-width, initial-scale=1">
306 <meta name="referrer" content="origin">
307
308 <title>Awesome GraphiQL IDE Test</title>
309
310 <style>
311 body {
312 height: 100%;
313 margin: 0;
314 width: 100%;
315 overflow: hidden;
316 }
317
318 #graphiql {
319 height: 100vh;
320 }
321 </style>
322 <script
323 crossorigin
324 src="https://unpkg.com/react@17/umd/react.development.js"
325 ></script>
326 <script
327 crossorigin
328 src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"
329 ></script>
330 <link rel="icon" href="https://graphql.org/favicon.ico">
331 <link rel="stylesheet" href="https://unpkg.com/graphiql/graphiql.min.css" />
332 <link rel="stylesheet" href="https://unpkg.com/@graphiql/plugin-explorer/dist/style.css" />
333 </head>
334
335 <body>
336 <div id="graphiql">Loading...</div>
337 <script
338 src="https://unpkg.com/graphiql/graphiql.min.js"
339 type="application/javascript"
340 ></script>
341 <script
342 src="https://unpkg.com/@graphiql/plugin-explorer/dist/index.umd.js"
343 crossorigin
344 ></script>
345 <script>
346 customFetch = (url, opts = {}) => {
347 return fetch(url, {...opts, credentials: 'include'})
348 }
349
350 createUrl = (endpoint, subscription = false) => {
351 const url = new URL(endpoint, window.location.origin);
352 if (subscription) {
353 url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
354 }
355 return url.toString();
356 }
357
358 const plugins = [];
359 plugins.push(GraphiQLPluginExplorer.explorerPlugin());
360
361 ReactDOM.render(
362 React.createElement(GraphiQL, {
363 fetcher: GraphiQL.createFetcher({
364 url: createUrl('/'),
365 fetch: customFetch,
366 subscriptionUrl: createUrl('/ws', true),
367 headers: {
368 'Authorization': 'Bearer [token]',
369 },
370 wsConnectionParams: {
371 'token': '[token]',
372 },
373 }),
374 defaultEditorToolsVisibility: true,
375 plugins,
376 }),
377 document.getElementById("graphiql")
378 );
379 </script>
380 </body>
381</html>"#
382 )
383 }
384}