surrealdb_core/sql/statements/define/
api.rs

1use crate::api::method::Method;
2use crate::api::path::Path;
3use crate::dbs::Options;
4use crate::err::Error;
5use crate::iam::{Action, ResourceKind};
6use crate::sql::fmt::{pretty_indent, Fmt};
7use crate::sql::{Base, Object, Value};
8use crate::{ctx::Context, sql::statements::info::InfoStructure};
9use reblessive::tree::Stk;
10use revision::revisioned;
11use serde::{Deserialize, Serialize};
12use std::fmt::{self, Display};
13
14use super::config::api::ApiConfig;
15use super::CursorDoc;
16
17#[revisioned(revision = 1)]
18#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
19#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
20#[non_exhaustive]
21pub struct DefineApiStatement {
22	pub if_not_exists: bool,
23	pub overwrite: bool,
24	pub path: Value,
25	pub actions: Vec<ApiAction>,
26	pub fallback: Option<Value>,
27	pub config: Option<ApiConfig>,
28}
29
30impl DefineApiStatement {
31	pub(crate) async fn compute(
32		&self,
33		stk: &mut Stk,
34		ctx: &Context,
35		opt: &Options,
36		doc: Option<&CursorDoc>,
37	) -> Result<Value, Error> {
38		// Allowed to run?
39		opt.is_allowed(Action::Edit, ResourceKind::Api, &Base::Db)?;
40		// Fetch the transaction
41		let txn = ctx.tx();
42		let (ns, db) = (opt.ns()?, opt.db()?);
43		// Check if the definition exists
44		if txn.get_db_api(ns, db, &self.path.to_string()).await.is_ok() {
45			if self.if_not_exists {
46				return Ok(Value::None);
47			} else if !self.overwrite {
48				return Err(Error::ApAlreadyExists {
49					value: self.path.to_string(),
50				});
51			}
52		}
53		// Process the statement
54		let path: Path =
55			self.path.compute(stk, ctx, opt, doc).await?.coerce_to_string()?.parse()?;
56		let name = path.to_string();
57		let key = crate::key::database::ap::new(ns, db, &name);
58		txn.get_or_add_ns(ns, opt.strict).await?;
59		txn.get_or_add_db(ns, db, opt.strict).await?;
60		let ap = ApiDefinition {
61			// Don't persist the `IF NOT EXISTS` clause to schema
62			path,
63			actions: self.actions.clone(),
64			fallback: self.fallback.clone(),
65			config: self.config.clone(),
66			..Default::default()
67		};
68		txn.set(key, revision::to_vec(&ap)?, None).await?;
69		// Clear the cache
70		txn.clear();
71		// Ok all good
72		Ok(Value::None)
73	}
74}
75
76impl Display for DefineApiStatement {
77	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78		write!(f, "DEFINE API")?;
79		if self.if_not_exists {
80			write!(f, " IF NOT EXISTS")?
81		}
82		if self.overwrite {
83			write!(f, " OVERWRITE")?
84		}
85		write!(f, " {}", self.path)?;
86		let indent = pretty_indent();
87
88		if self.config.is_some() || self.fallback.is_some() {
89			write!(f, "FOR any")?;
90			let indent = pretty_indent();
91
92			if let Some(config) = &self.config {
93				write!(f, "{}", config)?;
94			}
95
96			if let Some(fallback) = &self.fallback {
97				write!(f, "THEN {}", fallback)?;
98			}
99
100			drop(indent);
101		}
102
103		for action in &self.actions {
104			write!(f, "{}", action)?;
105		}
106
107		drop(indent);
108		Ok(())
109	}
110}
111
112impl InfoStructure for DefineApiStatement {
113	fn structure(self) -> Value {
114		Value::from(map! {
115			"path".to_string() => self.path,
116			"config".to_string(), if let Some(config) = self.config => config.structure(),
117			"fallback".to_string(), if let Some(fallback) = self.fallback => fallback.structure(),
118			"actions".to_string() => Value::from(self.actions.into_iter().map(InfoStructure::structure).collect::<Vec<Value>>()),
119		})
120	}
121}
122
123#[revisioned(revision = 1)]
124#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
125#[non_exhaustive]
126pub struct ApiDefinition {
127	pub id: Option<u32>,
128	pub path: Path,
129	pub actions: Vec<ApiAction>,
130	pub fallback: Option<Value>,
131	pub config: Option<ApiConfig>,
132}
133
134impl From<ApiDefinition> for DefineApiStatement {
135	fn from(value: ApiDefinition) -> Self {
136		DefineApiStatement {
137			if_not_exists: false,
138			overwrite: false,
139			path: value.path.to_string().into(),
140			actions: value.actions,
141			fallback: value.fallback,
142			config: value.config,
143		}
144	}
145}
146
147impl InfoStructure for ApiDefinition {
148	fn structure(self) -> Value {
149		let da: DefineApiStatement = self.into();
150		da.structure()
151	}
152}
153
154impl Display for ApiDefinition {
155	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156		let da: DefineApiStatement = self.clone().into();
157		da.fmt(f)
158	}
159}
160
161#[revisioned(revision = 1)]
162#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
163#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
164#[non_exhaustive]
165pub struct ApiAction {
166	pub methods: Vec<Method>,
167	pub action: Value,
168	pub config: Option<ApiConfig>,
169}
170
171impl Display for ApiAction {
172	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
173		write!(f, "FOR {}", Fmt::comma_separated(self.methods.iter()))?;
174		let indent = pretty_indent();
175		if let Some(config) = &self.config {
176			write!(f, "{}", config)?;
177		}
178		write!(f, "THEN {}", self.action)?;
179		drop(indent);
180		Ok(())
181	}
182}
183
184impl InfoStructure for ApiAction {
185	fn structure(self) -> Value {
186		Value::from(map!(
187			"methods" => Value::from(self.methods.into_iter().map(InfoStructure::structure).collect::<Vec<Value>>()),
188			"action" => Value::from(self.action.to_string()),
189			"config", if let Some(config) = self.config => config.structure(),
190		))
191	}
192}
193
194pub trait FindApi<'a> {
195	fn find_api(
196		&'a self,
197		segments: Vec<&'a str>,
198		method: Method,
199	) -> Option<(&'a ApiDefinition, Object)>;
200}
201
202impl<'a> FindApi<'a> for &'a [ApiDefinition] {
203	fn find_api(
204		&'a self,
205		segments: Vec<&'a str>,
206		method: Method,
207	) -> Option<(&'a ApiDefinition, Object)> {
208		let mut specifity = 0_u8;
209		let mut res = None;
210		for api in self.iter() {
211			if let Some(params) = api.path.fit(segments.as_slice()) {
212				if api.fallback.is_some() || api.actions.iter().any(|x| x.methods.contains(&method))
213				{
214					let s = api.path.specifity();
215					if s > specifity {
216						specifity = s;
217						res = Some((api, params));
218					}
219				}
220			}
221		}
222
223		res
224	}
225}