1use std::collections::HashMap;
5
6use reifydb_auth::service::{AuthResponse, AuthService};
7use reifydb_core::{
8 actors::server::{
9 ServerAuthResponse, ServerLogoutResponse, ServerMessage, ServerResponse, ServerSubscribeResponse,
10 },
11 execution::ExecutionResult,
12 interface::catalog::procedure::Procedure,
13};
14use reifydb_engine::{engine::StandardEngine, session::RetryStrategy};
15use reifydb_runtime::{
16 actor::{
17 context::Context,
18 reply::Reply,
19 traits::{Actor, Directive},
20 },
21 context::clock::Clock,
22};
23use reifydb_value::{
24 error::Diagnostic,
25 params::Params,
26 value::{duration::Duration, identity::IdentityId},
27};
28use tracing::error;
29
30pub struct ServerActor {
31 engine: StandardEngine,
32 auth_service: AuthService,
33 clock: Clock,
34 retry: RetryStrategy,
35}
36
37impl ServerActor {
38 pub fn new(engine: StandardEngine, auth_service: AuthService, clock: Clock) -> Self {
39 Self {
40 engine,
41 auth_service,
42 clock,
43 retry: RetryStrategy::default(),
44 }
45 }
46
47 fn dispatch_execute(
48 &self,
49 identity: IdentityId,
50 rql: String,
51 params: Params,
52 reply: Reply<ServerResponse>,
53 execute: impl Fn(&StandardEngine, IdentityId, &str, Params) -> ExecutionResult,
54 ) {
55 let t = self.clock.instant();
56 let result = self
57 .retry
58 .execute(self.engine.rng(), &rql, || execute(&self.engine, identity, &rql, params.clone()));
59 if let Some(err) = result.error {
60 reply.send(ServerResponse::EngineError {
61 diagnostic: Box::new(err.diagnostic()),
62 rql,
63 });
64 } else {
65 reply.send(ServerResponse::Success {
66 frames: result.frames,
67 duration: Duration::from_std(t.elapsed()),
68 metrics: result.metrics,
69 });
70 }
71 }
72
73 #[inline]
74 fn handle_call(&self, identity: IdentityId, name: String, params: Params, reply: Reply<ServerResponse>) {
75 let catalog = self.engine.catalog();
76 let cache = catalog.cache();
77 let binding = match cache.find_ws_binding_by_name(&name) {
78 Some(binding) => binding,
79 None => {
80 return reply.send(call_error(
81 "NOT_FOUND",
82 format!("no WS binding named `{}`", name),
83 name,
84 ));
85 }
86 };
87 let procedure = match cache.find_procedure(binding.procedure_id) {
88 Some(procedure) => procedure,
89 None => {
90 return reply.send(call_error(
91 "INTERNAL_ERROR",
92 "binding references missing procedure".to_string(),
93 name,
94 ));
95 }
96 };
97 let namespace = match cache.find_namespace(binding.namespace) {
98 Some(namespace) => namespace,
99 None => {
100 return reply.send(call_error(
101 "INTERNAL_ERROR",
102 "binding references missing namespace".to_string(),
103 name,
104 ));
105 }
106 };
107 if let Err(diagnostic) = validate_call_params(&procedure, ¶ms) {
108 return reply.send(ServerResponse::EngineError {
109 diagnostic,
110 rql: name,
111 });
112 }
113 let rql = format!("CALL {}::{}()", namespace.name(), procedure.name());
114 self.dispatch_execute(identity, rql, params, reply, |e, id, s, p| e.command_as(id, s, p));
115 }
116
117 #[inline]
118 fn handle_subscribe(
119 &self,
120 identity: IdentityId,
121 rql: String,
122 params: Params,
123 reply: Reply<ServerSubscribeResponse>,
124 ) {
125 let t = self.clock.instant();
126 let result = self.engine.subscribe_as(identity, &rql, params);
127 if let Some(err) = result.error {
128 reply.send(ServerSubscribeResponse::EngineError {
129 diagnostic: Box::new(err.diagnostic()),
130 rql,
131 });
132 } else {
133 reply.send(ServerSubscribeResponse::Subscribed {
134 frames: result.frames,
135 duration: Duration::from_std(t.elapsed()),
136 metrics: result.metrics,
137 });
138 }
139 }
140
141 #[inline]
142 fn handle_authenticate(
143 &self,
144 method: String,
145 credentials: HashMap<String, String>,
146 reply: Reply<ServerAuthResponse>,
147 ) {
148 match self.auth_service.authenticate(&method, credentials) {
149 Ok(AuthResponse::Authenticated {
150 identity,
151 token,
152 }) => reply.send(ServerAuthResponse::Authenticated {
153 identity,
154 token,
155 }),
156 Ok(AuthResponse::Challenge {
157 challenge_id,
158 payload,
159 }) => reply.send(ServerAuthResponse::Challenge {
160 challenge_id,
161 payload,
162 }),
163 Ok(AuthResponse::Failed {
164 reason,
165 }) => reply.send(ServerAuthResponse::Failed {
166 reason,
167 }),
168 Err(e) => reply.send(ServerAuthResponse::Error(e.to_string())),
169 }
170 }
171
172 #[inline]
173 fn handle_logout(&self, token: String, reply: Reply<ServerLogoutResponse>) {
174 match self.auth_service.revoke_token(&token) {
175 Ok(true) => reply.send(ServerLogoutResponse::Ok),
176 Ok(false) => reply.send(ServerLogoutResponse::InvalidToken),
177 Err(e) => {
178 error!("logout could not revoke the session token: {e}");
179 reply.send(ServerLogoutResponse::Error(e.to_string()));
180 }
181 }
182 }
183}
184
185impl Actor for ServerActor {
186 type State = ();
187 type Message = ServerMessage;
188
189 fn init(&self, _ctx: &Context<Self::Message>) -> Self::State {}
190
191 fn handle(&self, _state: &mut (), msg: ServerMessage, _ctx: &Context<ServerMessage>) -> Directive {
192 match msg {
193 ServerMessage::Query {
194 identity,
195 rql,
196 params,
197 reply,
198 } => {
199 self.dispatch_execute(identity, rql, params, reply, |e, id, s, p| e.query_as(id, s, p));
200 }
201 ServerMessage::Command {
202 identity,
203 rql,
204 params,
205 reply,
206 } => {
207 self.dispatch_execute(identity, rql, params, reply, |e, id, s, p| {
208 e.command_as(id, s, p)
209 });
210 }
211 ServerMessage::Call {
212 identity,
213 name,
214 params,
215 reply,
216 } => {
217 self.handle_call(identity, name, params, reply);
218 }
219 ServerMessage::Admin {
220 identity,
221 rql,
222 params,
223 reply,
224 } => {
225 self.dispatch_execute(identity, rql, params, reply, |e, id, s, p| e.admin_as(id, s, p));
226 }
227 ServerMessage::Subscribe {
228 identity,
229 rql,
230 params,
231 reply,
232 } => {
233 self.handle_subscribe(identity, rql, params, reply);
234 }
235 ServerMessage::Authenticate {
236 method,
237 credentials,
238 reply,
239 } => {
240 self.handle_authenticate(method, credentials, reply);
241 }
242 ServerMessage::Logout {
243 token,
244 reply,
245 } => {
246 self.handle_logout(token, reply);
247 }
248 }
249 Directive::Continue
250 }
251}
252
253fn call_error(code: &str, message: String, rql: String) -> ServerResponse {
254 ServerResponse::EngineError {
255 diagnostic: call_diagnostic(code, message),
256 rql,
257 }
258}
259
260fn call_diagnostic(code: &str, message: String) -> Box<Diagnostic> {
261 Box::new(Diagnostic {
262 code: code.to_string(),
263 message,
264 ..Default::default()
265 })
266}
267
268fn validate_call_params(procedure: &Procedure, params: &Params) -> Result<(), Box<Diagnostic>> {
269 match params {
270 Params::None => {
271 if let Some(p) = procedure.params().first() {
272 return Err(call_diagnostic(
273 "INVALID_PARAMS",
274 format!("missing required parameter `{}`", p.name),
275 ));
276 }
277 }
278 Params::Named(map) => {
279 for key in map.keys() {
280 if !procedure.params().iter().any(|p| &p.name == key) {
281 return Err(call_diagnostic(
282 "INVALID_PARAMS",
283 format!("unknown parameter `{}`", key),
284 ));
285 }
286 }
287 for p in procedure.params() {
288 if !map.contains_key(&p.name) {
289 return Err(call_diagnostic(
290 "INVALID_PARAMS",
291 format!("missing required parameter `{}`", p.name),
292 ));
293 }
294 }
295 }
296 Params::Positional(_) => {
297 return Err(call_diagnostic("INVALID_PARAMS", "Call requires named params".to_string()));
298 }
299 }
300 Ok(())
301}