surrealdb_core/api/
invocation.rs1use 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 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 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 let opt = opt.new_with_perms(false);
112
113 let mut ctx = MutableContext::new_isolated(ctx);
115
116 let vars = self.vars(body)?;
118 ctx.add_value("request", vars.into());
119
120 if let Some(timeout) = inv_ctx.timeout {
122 ctx.add_timeout(*timeout)?
123 }
124
125 let ctx = ctx.freeze();
127
128 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}