1use bytes::Bytes;
34use hyper::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
35use hyper::{Method, Request, Response, StatusCode};
36use serde_json::Value;
37
38use super::translation::{
39 ProtocolTranslator, TranslatedRequest, TranslatedResponse, TranslationBody, TranslationError,
40};
41use crate::config::GraphqlTranslation;
42
43const GRAPHQL_CONTENT_TYPE: &str = "application/graphql+json";
46
47const JSON_CONTENT_TYPE: &str = "application/json";
49
50#[derive(Debug, Clone)]
52pub struct GraphqlTranslator {
53 direction: GraphqlDirection,
55 query_template: String,
58 upstream_path: String,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum GraphqlDirection {
66 RestToGraphql,
68 GraphqlToRest,
71}
72
73impl GraphqlTranslator {
74 pub fn rest_to_graphql(cfg: &GraphqlTranslation) -> Result<Self, TranslationError> {
77 if cfg.query_template.trim().is_empty() {
78 return Err(TranslationError::SchemaNotFound(
79 "graphql translation requires a query_template".to_string(),
80 ));
81 }
82 Ok(GraphqlTranslator {
83 direction: GraphqlDirection::RestToGraphql,
84 query_template: cfg.query_template.clone(),
85 upstream_path: cfg.upstream_path.clone(),
86 })
87 }
88
89 pub fn graphql_to_rest() -> Self {
93 GraphqlTranslator {
94 direction: GraphqlDirection::GraphqlToRest,
95 query_template: String::new(),
96 upstream_path: String::new(),
97 }
98 }
99
100 pub fn direction(&self) -> GraphqlDirection {
102 self.direction
103 }
104}
105
106impl ProtocolTranslator for GraphqlTranslator {
107 fn translate_request(
108 &self,
109 req: &Request<TranslationBody>,
110 ) -> Result<TranslatedRequest, TranslationError> {
111 match self.direction {
112 GraphqlDirection::RestToGraphql => {
113 let body = req.body().as_bytes();
114 let json: Value = serde_json::from_slice(body).map_err(|e| {
115 TranslationError::InvalidBody(format!("expected a JSON body: {e}"))
116 })?;
117 let obj = json.as_object().ok_or_else(|| {
118 TranslationError::InvalidBody(
119 "expected a JSON object for the GraphQL variables".to_string(),
120 )
121 })?;
122
123 let variables = collect_variables(&self.query_template, obj)?;
127
128 let query = substitute_template(&self.query_template, &variables);
133
134 let gql_body = serde_json::json!({
135 "query": query,
136 "variables": variables,
137 });
138 let body = serde_json::to_vec(&gql_body).map_err(|e| {
139 TranslationError::TranslationFailed(format!(
140 "failed to serialize graphql request: {e}"
141 ))
142 })?;
143
144 let mut headers = HeaderMap::new();
145 headers.insert(CONTENT_TYPE, HeaderValue::from_static(GRAPHQL_CONTENT_TYPE));
146
147 Ok(TranslatedRequest {
148 method: Method::POST,
149 path: self.upstream_path.clone(),
150 headers,
151 body: Bytes::from(body),
152 })
153 }
154 GraphqlDirection::GraphqlToRest => {
155 let mut headers = HeaderMap::new();
160 if let Some(ct) = req.headers().get(CONTENT_TYPE).cloned() {
161 headers.insert(CONTENT_TYPE, ct);
162 } else {
163 headers.insert(CONTENT_TYPE, HeaderValue::from_static(JSON_CONTENT_TYPE));
164 }
165 Ok(TranslatedRequest {
166 method: req.method().clone(),
167 path: req.uri().path().to_string(),
168 headers,
169 body: req.body().as_bytes().clone(),
170 })
171 }
172 }
173 }
174
175 fn translate_response(
176 &self,
177 resp: &Response<TranslationBody>,
178 ) -> Result<TranslatedResponse, TranslationError> {
179 match self.direction {
180 GraphqlDirection::RestToGraphql => {
181 let body = resp.body().as_bytes();
185 let gql: Value = serde_json::from_slice(body).map_err(|e| {
186 TranslationError::InvalidBody(format!("expected a graphql JSON response: {e}"))
187 })?;
188 if let Some(errors) = gql.get("errors") {
189 if !errors.is_null() {
190 let body = serde_json::to_vec(errors).map_err(|e| {
191 TranslationError::TranslationFailed(format!(
192 "failed to serialize graphql errors: {e}"
193 ))
194 })?;
195 let mut headers = HeaderMap::new();
196 headers.insert(CONTENT_TYPE, HeaderValue::from_static(JSON_CONTENT_TYPE));
197 return Ok(TranslatedResponse {
198 status: StatusCode::BAD_GATEWAY,
199 headers,
200 body: Bytes::from(body),
201 });
202 }
203 }
204 let data = gql.get("data").cloned().unwrap_or(Value::Null);
205 let body = serde_json::to_vec(&data).map_err(|e| {
206 TranslationError::TranslationFailed(format!(
207 "failed to serialize rest response: {e}"
208 ))
209 })?;
210 let mut headers = HeaderMap::new();
211 headers.insert(CONTENT_TYPE, HeaderValue::from_static(JSON_CONTENT_TYPE));
212 Ok(TranslatedResponse {
213 status: resp.status(),
214 headers,
215 body: Bytes::from(body),
216 })
217 }
218 GraphqlDirection::GraphqlToRest => {
219 let body = resp.body().as_bytes();
223 let data: Value = if body.is_empty() {
224 Value::Null
225 } else {
226 serde_json::from_slice(body).map_err(|e| {
227 TranslationError::InvalidBody(format!(
228 "expected a JSON response from the rest upstream: {e}"
229 ))
230 })?
231 };
232 let gql = serde_json::json!({ "data": data });
233 let body = serde_json::to_vec(&gql).map_err(|e| {
234 TranslationError::TranslationFailed(format!(
235 "failed to serialize graphql envelope: {e}"
236 ))
237 })?;
238 let mut headers = HeaderMap::new();
239 headers.insert(CONTENT_TYPE, HeaderValue::from_static(GRAPHQL_CONTENT_TYPE));
240 Ok(TranslatedResponse {
241 status: resp.status(),
242 headers,
243 body: Bytes::from(body),
244 })
245 }
246 }
247 }
248
249 fn content_type_in(&self) -> &str {
250 match self.direction {
251 GraphqlDirection::RestToGraphql => JSON_CONTENT_TYPE,
252 GraphqlDirection::GraphqlToRest => GRAPHQL_CONTENT_TYPE,
253 }
254 }
255
256 fn content_type_out(&self) -> &str {
257 match self.direction {
258 GraphqlDirection::RestToGraphql => GRAPHQL_CONTENT_TYPE,
259 GraphqlDirection::GraphqlToRest => JSON_CONTENT_TYPE,
260 }
261 }
262}
263
264fn collect_variables(
268 template: &str,
269 body: &serde_json::Map<String, Value>,
270) -> Result<Value, TranslationError> {
271 let mut vars = serde_json::Map::new();
272 for name in referenced_variables(template) {
273 if vars.contains_key(&name) {
274 continue;
275 }
276 let value = body.get(&name).ok_or_else(|| {
277 TranslationError::TranslationFailed(format!(
278 "graphql template references ${name} but the request body has no such field"
279 ))
280 })?;
281 vars.insert(name.clone(), value.clone());
282 }
283 Ok(Value::Object(vars))
284}
285
286fn referenced_variables(template: &str) -> Vec<String> {
291 let bytes = template.as_bytes();
292 let mut names = Vec::new();
293 let mut seen = std::collections::HashSet::new();
294 let mut i = 0;
295 while i < bytes.len() {
296 if bytes[i] == b'$' && i + 1 < bytes.len() && is_name_start(bytes[i + 1]) {
297 let start = i + 1;
298 let mut j = start;
299 while j < bytes.len() && is_name_char(bytes[j]) {
300 j += 1;
301 }
302 let name = &template[start..j];
303 if seen.insert(name.to_string()) {
304 names.push(name.to_string());
305 }
306 i = j;
307 continue;
308 }
309 i += 1;
310 }
311 names
312}
313
314fn substitute_template(template: &str, variables: &Value) -> String {
319 let bytes = template.as_bytes();
320 let mut out = String::with_capacity(template.len());
321 let mut i = 0;
322 while i < bytes.len() {
323 if bytes[i] == b'$' && i + 1 < bytes.len() && is_name_start(bytes[i + 1]) {
324 let start = i + 1;
325 let mut j = start;
326 while j < bytes.len() && is_name_char(bytes[j]) {
327 j += 1;
328 }
329 let name = &template[start..j];
330 if let Some(value) = variables.get(name) {
331 out.push_str(&json_to_graphql_literal(value));
332 } else {
333 out.push('$');
336 out.push_str(name);
337 }
338 i = j;
339 continue;
340 }
341 out.push(bytes[i] as char);
342 i += 1;
343 }
344 out
345}
346
347fn json_to_graphql_literal(value: &Value) -> String {
349 match value {
350 Value::Null => "null".to_string(),
351 Value::Bool(b) => b.to_string(),
352 Value::Number(n) => n.to_string(),
353 Value::String(s) => {
354 let mut out = String::with_capacity(s.len() + 2);
359 out.push('"');
360 for c in s.chars() {
361 match c {
362 '"' => out.push_str("\\\""),
363 '\\' => out.push_str("\\\\"),
364 '\n' => out.push_str("\\n"),
365 '\r' => out.push_str("\\r"),
366 '\t' => out.push_str("\\t"),
367 c if (c as u32) < 0x20 => {
368 out.push_str(&format!("\\u{:04x}", c as u32));
369 }
370 c => out.push(c),
371 }
372 }
373 out.push('"');
374 out
375 }
376 Value::Array(arr) => {
377 let items: Vec<String> = arr.iter().map(json_to_graphql_literal).collect();
378 format!("[{}]", items.join(", "))
379 }
380 Value::Object(obj) => {
381 let items: Vec<String> = obj
382 .iter()
383 .map(|(k, v)| format!("{}: {}", k, json_to_graphql_literal(v)))
384 .collect();
385 format!("{{{}}}", items.join(", "))
386 }
387 }
388}
389
390fn is_name_start(b: u8) -> bool {
391 b.is_ascii_alphabetic() || b == b'_'
392}
393
394fn is_name_char(b: u8) -> bool {
395 b.is_ascii_alphanumeric() || b == b'_'
396}