use http::HeaderMap;
use http::Method;
use http::StatusCode;
use http::Uri;
use rhai::Engine;
use rhai::EvalAltResult;
use super::super::types::OptionDance;
use super::super::types::SharedMut;
use crate::context::Context;
use crate::graphql::Request;
use crate::plugins::rhai::subgraph;
pub(super) fn register(engine: &mut Engine) {
engine
.register_get(
"context",
|obj: &mut SharedMut<subgraph::Request>| -> Result<Context, Box<EvalAltResult>> {
Ok(obj.with_mut(|request| request.context.clone()))
},
)
.register_get(
"context",
|obj: &mut SharedMut<subgraph::Response>| -> Result<Context, Box<EvalAltResult>> {
Ok(obj.with_mut(|response| response.context.clone()))
},
);
engine.register_get(
"status_code",
|obj: &mut SharedMut<subgraph::Response>| -> Result<StatusCode, Box<EvalAltResult>> {
Ok(obj.with_mut(|response| response.response.status()))
},
);
engine
.register_set(
"context",
|obj: &mut SharedMut<subgraph::Request>, context: Context| {
obj.with_mut(|request| request.context = context);
Ok(())
},
)
.register_set(
"context",
|obj: &mut SharedMut<subgraph::Response>, context: Context| {
obj.with_mut(|response| response.context = context);
Ok(())
},
);
engine
.register_get("id", |obj: &mut SharedMut<subgraph::Request>| -> String {
obj.with_mut(|request| request.context.id.clone())
})
.register_get("id", |obj: &mut SharedMut<subgraph::Response>| -> String {
obj.with_mut(|response| response.context.id.clone())
});
engine.register_get(
"headers",
|obj: &mut SharedMut<subgraph::Request>| -> Result<HeaderMap, Box<EvalAltResult>> {
Ok(obj.with_mut(|request| request.supergraph_request.headers().clone()))
},
);
engine.register_get(
"method",
|obj: &mut SharedMut<subgraph::Request>| -> Result<Method, Box<EvalAltResult>> {
Ok(obj.with_mut(|request| request.supergraph_request.method().clone()))
},
);
engine.register_get(
"body",
|obj: &mut SharedMut<subgraph::Request>| -> Result<Request, Box<EvalAltResult>> {
Ok(obj.with_mut(|request| request.supergraph_request.body().clone()))
},
);
engine.register_get(
"uri",
|obj: &mut SharedMut<subgraph::Request>| -> Result<Uri, Box<EvalAltResult>> {
Ok(obj.with_mut(|request| request.supergraph_request.uri().clone()))
},
);
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use http::HeaderValue;
use http::StatusCode;
use parking_lot::Mutex;
use super::*;
use crate::context::Context;
use crate::plugins::rhai::engine::registration;
use crate::plugins::rhai::subgraph;
use crate::services::SubgraphRequest;
fn create_engine_with_helpers() -> rhai::Engine {
let mut engine = rhai::Engine::new();
crate::plugins::rhai::Rhai::register_global_modules(&mut engine);
registration::register(&mut engine);
engine
}
fn create_test_subgraph_request() -> SharedMut<subgraph::Request> {
Arc::new(Mutex::new(Some(SubgraphRequest::fake_builder().build())))
}
fn create_test_subgraph_response() -> SharedMut<subgraph::Response> {
Arc::new(Mutex::new(Some(subgraph::Response::fake_builder().build())))
}
#[test]
fn test_context_getter() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
let expected_id = request.lock().as_ref().unwrap().context.id.clone();
let script = "fn test(req) { req.context }";
let ast = engine.compile(script).unwrap();
let result: Context = engine
.call_fn(&mut rhai::Scope::new(), &ast, "test", (request,))
.unwrap();
assert_eq!(result.id, expected_id);
}
#[test]
fn test_context_setter() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
let new_context = Context::new();
let expected_id = new_context.id.clone();
let script = "fn test(req, ctx) { req.context = ctx; }";
let ast = engine.compile(script).unwrap();
let _: () = engine
.call_fn(
&mut rhai::Scope::new(),
&ast,
"test",
(request.clone(), new_context),
)
.unwrap();
assert_eq!(request.lock().as_ref().unwrap().context.id, expected_id);
}
#[test]
fn test_id_getter() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
let expected_id = request.lock().as_ref().unwrap().context.id.clone();
let script = "fn test(req) { req.id }";
let ast = engine.compile(script).unwrap();
let result: String = engine
.call_fn(&mut rhai::Scope::new(), &ast, "test", (request,))
.unwrap();
assert_eq!(result, expected_id);
}
#[test]
fn test_headers_getter_read_only() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
{
let mut guard = request.lock();
let req = guard.as_mut().unwrap();
let mut new_req = (*req.supergraph_request).clone();
new_req
.headers_mut()
.insert("test-header", HeaderValue::from_static("test-value"));
req.supergraph_request = Arc::new(new_req);
}
let script = r#"fn test(req) { req.headers["test-header"] }"#;
let ast = engine.compile(script).unwrap();
let result: String = engine
.call_fn(&mut rhai::Scope::new(), &ast, "test", (request,))
.unwrap();
assert_eq!(result, "test-value");
}
#[test]
fn test_headers_no_setter() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
let script = r#"
fn test(req) {
let headers = req.headers;
req.headers = headers;
}
"#;
let ast = engine.compile(script).unwrap();
let result: Result<(), _> =
engine.call_fn(&mut rhai::Scope::new(), &ast, "test", (request,));
assert!(
result.is_err(),
"Should not be able to set read-only headers property"
);
}
#[test]
fn test_headers_indexer_setter_blocked() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
{
let mut guard = request.lock();
let req = guard.as_mut().unwrap();
let mut new_req = (*req.supergraph_request).clone();
new_req
.headers_mut()
.insert("test-header", HeaderValue::from_static("original"));
req.supergraph_request = Arc::new(new_req);
}
let script = r#"
fn test(req) {
req.headers["test-header"] = "modified";
}
"#;
let ast = engine.compile(script).unwrap();
let result: Result<(), _> =
engine.call_fn(&mut rhai::Scope::new(), &ast, "test", (request.clone(),));
assert!(result.is_ok(), "Script should run without error");
let guard = request.lock();
let final_value = guard
.as_ref()
.unwrap()
.supergraph_request
.headers()
.get("test-header")
.map(|v| v.to_str().unwrap())
.unwrap();
assert_eq!(
final_value, "original",
"Header should remain unchanged despite setter attempt"
);
}
#[test]
fn test_headers_property_chain_with_split() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
{
let mut guard = request.lock();
let req = guard.as_mut().unwrap();
let mut new_req = (*req.supergraph_request).clone();
new_req
.headers_mut()
.insert("cookie", HeaderValue::from_static("a=1; b=2"));
req.supergraph_request = Arc::new(new_req);
}
let script = r#"
fn test(req) {
let cookies = req.headers["cookie"].split(';');
cookies.len()
}
"#;
let ast = engine.compile(script).unwrap();
let result: i64 = engine
.call_fn(&mut rhai::Scope::new(), &ast, "test", (request,))
.unwrap();
assert_eq!(result, 2);
}
#[test]
fn test_cookie_parsing_pattern() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
{
let mut guard = request.lock();
let req = guard.as_mut().unwrap();
let mut new_req = (*req.supergraph_request).clone();
new_req.headers_mut().insert(
"cookie",
HeaderValue::from_static("session=abc; user=john; theme=dark"),
);
req.supergraph_request = Arc::new(new_req);
}
let script = r#"
fn test(req) {
req.headers["cookie"].split(';').len()
}
"#;
let ast = engine.compile(script).unwrap();
let result: i64 = engine
.call_fn(&mut rhai::Scope::new(), &ast, "test", (request,))
.unwrap();
assert_eq!(result, 3);
}
#[test]
fn test_headers_property_chain_with_string_methods() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
{
let mut guard = request.lock();
let req = guard.as_mut().unwrap();
let mut new_req = (*req.supergraph_request).clone();
new_req
.headers_mut()
.insert("content-type", HeaderValue::from_static("application/json"));
req.supergraph_request = Arc::new(new_req);
}
let script = r#"
fn test(req) {
// Test to_upper() in chain
let upper = req.headers["content-type"].to_upper();
// Test contains() in chain
let has_json = req.headers["content-type"].contains("json");
// Return results
[upper, has_json]
}
"#;
let ast = engine.compile(script).unwrap();
let result: rhai::Array = engine
.call_fn(&mut rhai::Scope::new(), &ast, "test", (request,))
.unwrap();
assert_eq!(result[0].clone().cast::<String>(), "APPLICATION/JSON");
assert!(result[1].clone().cast::<bool>());
}
#[test]
fn test_body_getter() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
let script = "fn test(req) { req.body }";
let ast = engine.compile(script).unwrap();
let result: Request = engine
.call_fn(&mut rhai::Scope::new(), &ast, "test", (request,))
.unwrap();
assert!(result.query.is_some() || result.query.is_none());
}
#[test]
fn test_body_no_setter() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
let script = "fn test(req) { let b = req.body; req.body = b; }";
let ast = engine.compile(script).unwrap();
let result: Result<(), _> =
engine.call_fn(&mut rhai::Scope::new(), &ast, "test", (request,));
assert!(
result.is_err(),
"Should not be able to set read-only body property"
);
}
#[test]
fn test_uri_getter() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
let script = "fn test(req) { req.uri }";
let ast = engine.compile(script).unwrap();
let result: http::Uri = engine
.call_fn(&mut rhai::Scope::new(), &ast, "test", (request,))
.unwrap();
assert_eq!(result.path(), "/");
}
#[test]
fn test_uri_no_setter() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
let script = "fn test(req) { let u = req.uri; req.uri = u; }";
let ast = engine.compile(script).unwrap();
let result: Result<(), _> =
engine.call_fn(&mut rhai::Scope::new(), &ast, "test", (request,));
assert!(
result.is_err(),
"Should not be able to set read-only uri property"
);
}
#[test]
fn test_method_getter() {
let engine = create_engine_with_helpers();
let request = create_test_subgraph_request();
let script = "fn test(req) { req.method }";
let ast = engine.compile(script).unwrap();
let result: http::Method = engine
.call_fn(&mut rhai::Scope::new(), &ast, "test", (request,))
.unwrap();
assert_eq!(result, http::Method::GET);
}
#[test]
fn test_response_status_code() {
let engine = create_engine_with_helpers();
let response = create_test_subgraph_response();
let script = "fn test(resp) { resp.status_code }";
let ast = engine.compile(script).unwrap();
let result: StatusCode = engine
.call_fn(&mut rhai::Scope::new(), &ast, "test", (response,))
.unwrap();
assert_eq!(result, StatusCode::OK);
}
}