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};
28
29pub struct ServerActor {
30 engine: StandardEngine,
31 auth_service: AuthService,
32 clock: Clock,
33 retry: RetryStrategy,
34}
35
36impl ServerActor {
37 pub fn new(engine: StandardEngine, auth_service: AuthService, clock: Clock) -> Self {
38 Self {
39 engine,
40 auth_service,
41 clock,
42 retry: RetryStrategy::default(),
43 }
44 }
45
46 fn dispatch_execute(
47 &self,
48 identity: IdentityId,
49 rql: String,
50 params: Params,
51 reply: Reply<ServerResponse>,
52 execute: impl Fn(&StandardEngine, IdentityId, &str, Params) -> ExecutionResult,
53 ) {
54 let t = self.clock.instant();
55 let result = self
56 .retry
57 .execute(self.engine.rng(), &rql, || execute(&self.engine, identity, &rql, params.clone()));
58 if let Some(err) = result.error {
59 reply.send(ServerResponse::EngineError {
60 diagnostic: Box::new(err.diagnostic()),
61 rql,
62 });
63 } else {
64 reply.send(ServerResponse::Success {
65 frames: result.frames,
66 duration: Duration::from_std(t.elapsed()),
67 metrics: result.metrics,
68 });
69 }
70 }
71
72 #[inline]
73 fn handle_call(&self, identity: IdentityId, name: String, params: Params, reply: Reply<ServerResponse>) {
74 let catalog = self.engine.catalog();
75 let cache = catalog.cache();
76 let binding = match cache.find_ws_binding_by_name(&name) {
77 Some(binding) => binding,
78 None => {
79 return reply.send(call_error(
80 "NOT_FOUND",
81 format!("no WS binding named `{}`", name),
82 name,
83 ));
84 }
85 };
86 let procedure = match cache.find_procedure(binding.procedure_id) {
87 Some(procedure) => procedure,
88 None => {
89 return reply.send(call_error(
90 "INTERNAL_ERROR",
91 "binding references missing procedure".to_string(),
92 name,
93 ));
94 }
95 };
96 let namespace = match cache.find_namespace(binding.namespace) {
97 Some(namespace) => namespace,
98 None => {
99 return reply.send(call_error(
100 "INTERNAL_ERROR",
101 "binding references missing namespace".to_string(),
102 name,
103 ));
104 }
105 };
106 if let Err(diagnostic) = validate_call_params(&procedure, ¶ms) {
107 return reply.send(ServerResponse::EngineError {
108 diagnostic,
109 rql: name,
110 });
111 }
112 let rql = format!("CALL {}::{}()", namespace.name(), procedure.name());
113 self.dispatch_execute(identity, rql, params, reply, |e, id, s, p| e.command_as(id, s, p));
114 }
115
116 #[inline]
117 fn handle_subscribe(
118 &self,
119 identity: IdentityId,
120 rql: String,
121 params: Params,
122 reply: Reply<ServerSubscribeResponse>,
123 ) {
124 let t = self.clock.instant();
125 let result = self.engine.subscribe_as(identity, &rql, params);
126 if let Some(err) = result.error {
127 reply.send(ServerSubscribeResponse::EngineError {
128 diagnostic: Box::new(err.diagnostic()),
129 rql,
130 });
131 } else {
132 reply.send(ServerSubscribeResponse::Subscribed {
133 frames: result.frames,
134 duration: Duration::from_std(t.elapsed()),
135 metrics: result.metrics,
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 if self.auth_service.revoke_token(&token) {
174 reply.send(ServerLogoutResponse::Ok);
175 } else {
176 reply.send(ServerLogoutResponse::InvalidToken);
177 }
178 }
179}
180
181impl Actor for ServerActor {
182 type State = ();
183 type Message = ServerMessage;
184
185 fn init(&self, _ctx: &Context<Self::Message>) -> Self::State {}
186
187 fn handle(&self, _state: &mut (), msg: ServerMessage, _ctx: &Context<ServerMessage>) -> Directive {
188 match msg {
189 ServerMessage::Query {
190 identity,
191 rql,
192 params,
193 reply,
194 } => {
195 self.dispatch_execute(identity, rql, params, reply, |e, id, s, p| e.query_as(id, s, p));
196 }
197 ServerMessage::Command {
198 identity,
199 rql,
200 params,
201 reply,
202 } => {
203 self.dispatch_execute(identity, rql, params, reply, |e, id, s, p| {
204 e.command_as(id, s, p)
205 });
206 }
207 ServerMessage::Call {
208 identity,
209 name,
210 params,
211 reply,
212 } => {
213 self.handle_call(identity, name, params, reply);
214 }
215 ServerMessage::Admin {
216 identity,
217 rql,
218 params,
219 reply,
220 } => {
221 self.dispatch_execute(identity, rql, params, reply, |e, id, s, p| e.admin_as(id, s, p));
222 }
223 ServerMessage::Subscribe {
224 identity,
225 rql,
226 params,
227 reply,
228 } => {
229 self.handle_subscribe(identity, rql, params, reply);
230 }
231 ServerMessage::Authenticate {
232 method,
233 credentials,
234 reply,
235 } => {
236 self.handle_authenticate(method, credentials, reply);
237 }
238 ServerMessage::Logout {
239 token,
240 reply,
241 } => {
242 self.handle_logout(token, reply);
243 }
244 }
245 Directive::Continue
246 }
247}
248
249fn call_error(code: &str, message: String, rql: String) -> ServerResponse {
250 ServerResponse::EngineError {
251 diagnostic: call_diagnostic(code, message),
252 rql,
253 }
254}
255
256fn call_diagnostic(code: &str, message: String) -> Box<Diagnostic> {
257 Box::new(Diagnostic {
258 code: code.to_string(),
259 message,
260 ..Default::default()
261 })
262}
263
264fn validate_call_params(procedure: &Procedure, params: &Params) -> Result<(), Box<Diagnostic>> {
265 match params {
266 Params::None => {
267 if let Some(p) = procedure.params().first() {
268 return Err(call_diagnostic(
269 "INVALID_PARAMS",
270 format!("missing required parameter `{}`", p.name),
271 ));
272 }
273 }
274 Params::Named(map) => {
275 for key in map.keys() {
276 if !procedure.params().iter().any(|p| &p.name == key) {
277 return Err(call_diagnostic(
278 "INVALID_PARAMS",
279 format!("unknown parameter `{}`", key),
280 ));
281 }
282 }
283 for p in procedure.params() {
284 if !map.contains_key(&p.name) {
285 return Err(call_diagnostic(
286 "INVALID_PARAMS",
287 format!("missing required parameter `{}`", p.name),
288 ));
289 }
290 }
291 }
292 Params::Positional(_) => {
293 return Err(call_diagnostic("INVALID_PARAMS", "Call requires named params".to_string()));
294 }
295 }
296 Ok(())
297}