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		sess.context(&mut ctx);
61		let ctx = &ctx.freeze();
62
63		let mut stack = TreeStack::new();
64		stack.enter(|stk| self.invoke_with_context(stk, ctx, &opt, api, body)).finish().await
65	}
66
67	// The `invoke` method accepting a parameter like `Option<&mut Stk>`
68	// causes issues with axum, hence the separation
69	pub async fn invoke_with_context(
70		self,
71		stk: &mut Stk,
72		ctx: &Context,
73		opt: &Options,
74		api: &ApiDefinition,
75		body: ApiBody,
76	) -> Result<Option<(ApiResponse, ResponseInstruction)>, Error> {
77		let (action, action_config) =
78			match api.actions.iter().find(|x| x.methods.contains(&self.method)) {
79				Some(v) => (&v.action, &v.config),
80				None => match &api.fallback {
81					Some(v) => (v, &None),
82					None => return Ok(None),
83				},
84			};
85
86		let mut configs: Vec<&ApiConfig> = Vec::new();
87		let global = ctx.tx().get_db_optional_config(opt.ns()?, opt.db()?, "api").await?;
88		configs.extend(global.as_ref().map(|v| v.inner.try_into_api()).transpose()?);
89		configs.extend(api.config.as_ref());
90		configs.extend(action_config);
91
92		let middleware: Vec<&RequestMiddleware> =
93			configs.into_iter().filter_map(|v| v.middleware.as_ref()).collect();
94		let builtin = middleware.collect()?;
95
96		let mut inv_ctx = InvocationContext::default();
97		inv_ctx.apply_middleware(builtin)?;
98
99		// Prepare the response headers and conversion
100		let res_instruction = if body.is_native() {
101			ResponseInstruction::Native
102		} else if inv_ctx.response_body_raw {
103			ResponseInstruction::Raw
104		} else {
105			ResponseInstruction::for_format(&self)?
106		};
107
108		let body = body.process(&inv_ctx, &self).await?;
109
110		// Edit the options
111		let opt = opt.new_with_perms(false);
112
113		// Edit the context
114		let mut ctx = MutableContext::new_isolated(ctx);
115
116		// Set the request variable
117		let vars = self.vars(body)?;
118		ctx.add_value("request", vars.into());
119
120		// Possibly set the timeout
121		if let Some(timeout) = inv_ctx.timeout {
122			ctx.add_timeout(*timeout)?
123		}
124
125		// Freeze the context
126		let ctx = ctx.freeze();
127
128		// Compute the action
129
130		let res = action.compute(stk, &ctx, &opt, None).await?;
131
132		let mut res = ApiResponse::try_from(res)?;
133		if let Some(headers) = inv_ctx.response_headers {
134			let mut headers = headers;
135			headers.extend(res.headers);
136			res.headers = headers;
137		}
138
139		Ok(Some((res, res_instruction)))
140	}
141}