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 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 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 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 let opt = opt.new_with_perms(false);
111
112 let mut ctx = MutableContext::new_isolated(ctx);
114
115 let vars = self.vars(body)?;
117 ctx.add_value("request", vars.into());
118
119 if let Some(timeout) = inv_ctx.timeout {
121 ctx.add_timeout(*timeout)?
122 }
123
124 let ctx = ctx.freeze();
126
127 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}