surrealdb_core/sql/statements/define/
api.rs1use 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 opt.is_allowed(Action::Edit, ResourceKind::Api, &Base::Db)?;
40 let txn = ctx.tx();
42 let (ns, db) = (opt.ns()?, opt.db()?);
43 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 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 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 txn.clear();
71 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}