surrealdb_core/api/
invocation.rs

1use std::{collections::BTreeMap, sync::Arc};
2
3use http::HeaderMap;
4use reblessive::{tree::Stk, TreeStack};
5
6use super::{
7	body::ApiBody,
8	context::InvocationContext,
9	method::Method,
10	middleware::CollectMiddleware,
11	response::{ApiResponse, ResponseInstruction},
12};
13use crate::{
14	api::middleware::RequestMiddleware,
15	ctx::{Context, MutableContext},
16	dbs::{Options, Session},
17	err::Error,
18	kvs::{Datastore, Transaction},
19	sql::{
20		statements::{define::config::api::ApiConfig, define::ApiDefinition},
21		Object, Value,
22	},
23};
24
25#[derive(Clone, Debug, Eq, PartialEq)]
26#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
27pub struct ApiInvocation {
28	pub params: Object,
29	pub method: Method,
30	pub query: BTreeMap<String, String>,
31	#[cfg_attr(feature = "arbitrary", arbitrary(value = HeaderMap::new()))]
32	pub headers: HeaderMap,
33}
34
35impl ApiInvocation {
36	pub fn vars(self, body: Value) -> Result<Value, Error> {
37		let obj = map! {
38			"params" => Value::from(self.params),
39			"body" => body,
40			"method" => self.method.to_string().into(),
41			"query" => Value::Object(self.query.into()),
42			"headers" => Value::Object(self.headers.try_into()?),
43		};
44
45		Ok(obj.into())
46	}
47
48	pub async fn invoke_with_transaction(
49		self,
50		tx: Arc<Transaction>,
51		ds: Arc<Datastore>,
52		sess: &Session,
53		api: &ApiDefinition,
54		body: ApiBody,
55	) -> Result<Option<(ApiResponse, ResponseInstruction)>, Error> {
56		let opt = ds.setup_options(sess);
57
58		let mut ctx = ds.setup_ctx()?;
59		ctx.set_transaction(tx);
60		let ctx = &ctx.freeze();
61
62		let mut stack = TreeStack::new();
63		stack.enter(|stk| self.invoke_with_context(stk, ctx, &opt, api, body)).finish().await
64	}
65
66	// The `invoke` method accepting a parameter like `Option<&mut Stk>`
67	// causes issues with axum, hence the separation
68	pub async fn invoke_with_context(
69		self,
70		stk: &mut Stk,
71		ctx: &Context,
72		opt: &Options,
73		api: &ApiDefinition,
74		body: ApiBody,
75	) -> Result<Option<(ApiResponse, ResponseInstruction)>, Error> {
76		let (action, action_config) =
77			match api.actions.iter().find(|x| x.methods.contains(&self.method)) {
78				Some(v) => (&v.action, &v.config),
79				None => match &api.fallback {
80					Some(v) => (v, &None),
81					None => return Ok(None),
82				},
83			};
84
85		let mut configs: Vec<&ApiConfig> = Vec::new();
86		let global = ctx.tx().get_db_optional_config(opt.ns()?, opt.db()?, "api").await?;
87		configs.extend(global.as_ref().map(|v| v.inner.try_into_api()).transpose()?);
88		configs.extend(api.config.as_ref());
89		configs.extend(action_config);
90
91		let middleware: Vec<&RequestMiddleware> =
92			configs.into_iter().filter_map(|v| v.middleware.as_ref()).collect();
93		let builtin = middleware.collect()?;
94
95		let mut inv_ctx = InvocationContext::default();
96		inv_ctx.apply_middleware(builtin)?;
97
98		// Prepare the response headers and conversion
99		let res_instruction = if body.is_native() {
100			ResponseInstruction::Native
101		} else if inv_ctx.response_body_raw {
102			ResponseInstruction::Raw
103		} else {
104			ResponseInstruction::for_format(&self)?
105		};
106
107		let body = body.process(&inv_ctx, &self).await?;
108
109		// Edit the options
110		let opt = opt.new_with_perms(false);
111
112		// Edit the context
113		let mut ctx = MutableContext::new_isolated(ctx);
114
115		// Set the request variable
116		let vars = self.vars(body)?;
117		ctx.add_value("request", vars.into());
118
119		// Possibly set the timeout
120		if let Some(timeout) = inv_ctx.timeout {
121			ctx.add_timeout(*timeout)?
122		}
123
124		// Freeze the context
125		let ctx = ctx.freeze();
126
127		// Compute the action
128
129		let res = action.compute(stk, &ctx, &opt, None).await?;
130
131		let mut res = ApiResponse::try_from(res)?;
132		if let Some(headers) = inv_ctx.response_headers {
133			let mut headers = headers;
134			headers.extend(res.headers);
135			res.headers = headers;
136		}
137
138		Ok(Some((res, res_instruction)))
139	}
140}