surrealdb_core/api/
invocation.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use std::{collections::BTreeMap, sync::Arc};

use http::HeaderMap;
use reblessive::{tree::Stk, TreeStack};

use super::{
	body::ApiBody,
	context::InvocationContext,
	method::Method,
	middleware::CollectMiddleware,
	response::{ApiResponse, ResponseInstruction},
};
use crate::{
	api::middleware::RequestMiddleware,
	ctx::{Context, MutableContext},
	dbs::{Options, Session},
	err::Error,
	kvs::{Datastore, Transaction},
	sql::{
		statements::{define::config::api::ApiConfig, define::ApiDefinition},
		Object, Value,
	},
};

#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct ApiInvocation {
	pub params: Object,
	pub method: Method,
	pub query: BTreeMap<String, String>,
	#[cfg_attr(feature = "arbitrary", arbitrary(value = HeaderMap::new()))]
	pub headers: HeaderMap,
}

impl ApiInvocation {
	pub fn vars(self, body: Value) -> Result<Value, Error> {
		let obj = map! {
			"params" => Value::from(self.params),
			"body" => body,
			"method" => self.method.to_string().into(),
			"query" => Value::Object(self.query.into()),
			"headers" => Value::Object(self.headers.try_into()?),
		};

		Ok(obj.into())
	}

	pub async fn invoke_with_transaction(
		self,
		tx: Arc<Transaction>,
		ds: Arc<Datastore>,
		sess: &Session,
		api: &ApiDefinition,
		body: ApiBody,
	) -> Result<Option<(ApiResponse, ResponseInstruction)>, Error> {
		let opt = ds.setup_options(sess);

		let mut ctx = ds.setup_ctx()?;
		ctx.set_transaction(tx);
		let ctx = &ctx.freeze();

		let mut stack = TreeStack::new();
		stack.enter(|stk| self.invoke_with_context(stk, ctx, &opt, api, body)).finish().await
	}

	// The `invoke` method accepting a parameter like `Option<&mut Stk>`
	// causes issues with axum, hence the separation
	pub async fn invoke_with_context(
		self,
		stk: &mut Stk,
		ctx: &Context,
		opt: &Options,
		api: &ApiDefinition,
		body: ApiBody,
	) -> Result<Option<(ApiResponse, ResponseInstruction)>, Error> {
		let (action, action_config) =
			match api.actions.iter().find(|x| x.methods.contains(&self.method)) {
				Some(v) => (&v.action, &v.config),
				None => match &api.fallback {
					Some(v) => (v, &None),
					None => return Ok(None),
				},
			};

		let mut configs: Vec<&ApiConfig> = Vec::new();
		let global = ctx.tx().get_db_optional_config(opt.ns()?, opt.db()?, "api").await?;
		configs.extend(global.as_ref().map(|v| v.inner.try_into_api()).transpose()?);
		configs.extend(api.config.as_ref());
		configs.extend(action_config);

		let middleware: Vec<&RequestMiddleware> =
			configs.into_iter().filter_map(|v| v.middleware.as_ref()).collect();
		let builtin = middleware.collect()?;

		let mut inv_ctx = InvocationContext::default();
		inv_ctx.apply_middleware(builtin)?;

		// Prepare the response headers and conversion
		let res_instruction = if body.is_native() {
			ResponseInstruction::Native
		} else if inv_ctx.response_body_raw {
			ResponseInstruction::Raw
		} else {
			ResponseInstruction::for_format(&self)?
		};

		let body = body.process(&inv_ctx, &self).await?;

		// Edit the options
		let opt = opt.new_with_perms(false);

		// Edit the context
		let mut ctx = MutableContext::new_isolated(ctx);

		// Set the request variable
		let vars = self.vars(body)?;
		ctx.add_value("request", vars.into());

		// Possibly set the timeout
		if let Some(timeout) = inv_ctx.timeout {
			ctx.add_timeout(*timeout)?
		}

		// Freeze the context
		let ctx = ctx.freeze();

		// Compute the action

		let res = action.compute(stk, &ctx, &opt, None).await?;

		let mut res = ApiResponse::try_from(res)?;
		if let Some(headers) = inv_ctx.response_headers {
			let mut headers = headers;
			headers.extend(res.headers);
			res.headers = headers;
		}

		Ok(Some((res, res_instruction)))
	}
}