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, subscription::SubscribeOptions},
13 retry::RetryStrategy,
14};
15use reifydb_engine::engine::StandardEngine;
16use reifydb_runtime::{
17 actor::{
18 context::Context,
19 reply::Reply,
20 traits::{Actor, Directive},
21 },
22 context::clock::Clock,
23};
24use reifydb_value::{
25 error::Diagnostic,
26 params::Params,
27 value::{duration::Duration, identity::IdentityId},
28};
29use tracing::error;
30
31pub struct ServerActor {
32 engine: StandardEngine,
33 auth_service: AuthService,
34 clock: Clock,
35 retry: RetryStrategy,
36}
37
38impl ServerActor {
39 pub fn new(engine: StandardEngine, auth_service: AuthService, clock: Clock) -> Self {
40 Self {
41 engine,
42 auth_service,
43 clock,
44 retry: RetryStrategy::default(),
45 }
46 }
47
48 fn dispatch_execute(
49 &self,
50 identity: IdentityId,
51 rql: String,
52 params: Params,
53 reply: Reply<ServerResponse>,
54 execute: impl Fn(&StandardEngine, IdentityId, &str, Params) -> ExecutionResult,
55 ) {
56 let t = self.clock.instant();
57 let result = self
58 .retry
59 .execute(self.engine.rng(), &rql, || execute(&self.engine, identity, &rql, params.clone()));
60 if let Some(err) = result.error {
61 reply.send(ServerResponse::EngineError {
62 diagnostic: Box::new(err.diagnostic()),
63 rql,
64 });
65 } else {
66 reply.send(ServerResponse::Success {
67 frames: result.frames,
68 duration: Duration::from_std(t.elapsed()),
69 metrics: result.metrics,
70 });
71 }
72 }
73
74 #[inline]
75 fn handle_call(&self, identity: IdentityId, name: String, params: Params, reply: Reply<ServerResponse>) {
76 let catalog = self.engine.catalog();
77 let cache = catalog.cache();
78 let binding = match cache.find_ws_binding_by_name(&name) {
79 Some(binding) => binding,
80 None => {
81 return reply.send(call_error(
82 "NOT_FOUND",
83 format!("no WS binding named `{}`", name),
84 name,
85 ));
86 }
87 };
88 let procedure = match cache.find_procedure(binding.procedure_id) {
89 Some(procedure) => procedure,
90 None => {
91 return reply.send(call_error(
92 "INTERNAL_ERROR",
93 "binding references missing procedure".to_string(),
94 name,
95 ));
96 }
97 };
98 let namespace = match cache.find_namespace(binding.namespace) {
99 Some(namespace) => namespace,
100 None => {
101 return reply.send(call_error(
102 "INTERNAL_ERROR",
103 "binding references missing namespace".to_string(),
104 name,
105 ));
106 }
107 };
108 if let Err(diagnostic) = validate_call_params(&procedure, ¶ms) {
109 return reply.send(ServerResponse::EngineError {
110 diagnostic,
111 rql: name,
112 });
113 }
114 let rql = format!("CALL {}::{}()", namespace.name(), procedure.name());
115 self.dispatch_execute(identity, rql, params, reply, |e, id, s, p| e.command_as(id, s, p));
116 }
117
118 #[inline]
119 fn handle_subscribe(
120 &self,
121 identity: IdentityId,
122 rql: String,
123 params: Params,
124 options: SubscribeOptions,
125 reply: Reply<ServerSubscribeResponse>,
126 ) {
127 let t = self.clock.instant();
128 match self.engine.subscribe_as(identity, &rql, params, options) {
129 Err(err) => reply.send(ServerSubscribeResponse::EngineError {
130 diagnostic: Box::new(err.diagnostic()),
131 rql,
132 }),
133 Ok(outcome) => reply.send(ServerSubscribeResponse::Subscribed {
134 outcome,
135 duration: Duration::from_std(t.elapsed()),
136 }),
137 }
138 }
139
140 #[inline]
141 fn handle_authenticate(
142 &self,
143 method: String,
144 credentials: HashMap<String, String>,
145 reply: Reply<ServerAuthResponse>,
146 ) {
147 match self.auth_service.authenticate(&method, credentials) {
148 Ok(AuthResponse::Authenticated {
149 identity,
150 token,
151 }) => reply.send(ServerAuthResponse::Authenticated {
152 identity,
153 token,
154 }),
155 Ok(AuthResponse::Challenge {
156 challenge_id,
157 payload,
158 }) => reply.send(ServerAuthResponse::Challenge {
159 challenge_id,
160 payload,
161 }),
162 Ok(AuthResponse::Failed {
163 reason,
164 }) => reply.send(ServerAuthResponse::Failed {
165 reason,
166 }),
167 Err(e) => reply.send(ServerAuthResponse::Error(e.to_string())),
168 }
169 }
170
171 #[inline]
172 fn handle_logout(&self, token: String, reply: Reply<ServerLogoutResponse>) {
173 match self.auth_service.revoke_token(&token) {
174 Ok(true) => reply.send(ServerLogoutResponse::Ok),
175 Ok(false) => reply.send(ServerLogoutResponse::InvalidToken),
176 Err(e) => {
177 error!("logout could not revoke the session token: {e}");
178 reply.send(ServerLogoutResponse::Error(e.to_string()));
179 }
180 }
181 }
182}
183
184impl Actor for ServerActor {
185 type State = ();
186 type Message = ServerMessage;
187
188 fn init(&self, _ctx: &Context<Self::Message>) -> Self::State {}
189
190 fn handle(&self, _state: &mut (), msg: ServerMessage, _ctx: &Context<ServerMessage>) -> Directive {
191 match msg {
192 ServerMessage::Query {
193 identity,
194 rql,
195 params,
196 reply,
197 } => {
198 self.dispatch_execute(identity, rql, params, reply, |e, id, s, p| e.query_as(id, s, p));
199 }
200 ServerMessage::Command {
201 identity,
202 rql,
203 params,
204 reply,
205 } => {
206 self.dispatch_execute(identity, rql, params, reply, |e, id, s, p| {
207 e.command_as(id, s, p)
208 });
209 }
210 ServerMessage::Call {
211 identity,
212 name,
213 params,
214 reply,
215 } => {
216 self.handle_call(identity, name, params, reply);
217 }
218 ServerMessage::Admin {
219 identity,
220 rql,
221 params,
222 reply,
223 } => {
224 self.dispatch_execute(identity, rql, params, reply, |e, id, s, p| e.admin_as(id, s, p));
225 }
226 ServerMessage::Subscribe {
227 identity,
228 rql,
229 params,
230 options,
231 reply,
232 } => {
233 self.handle_subscribe(identity, rql, params, options, 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}