use std::sync::Arc;
use surrealdb_core::dbs::Session;
use surrealdb_core::iam::{Auth, Level};
use surrealdb_core::kvs::Datastore;
use surrealdb_core::rpc::{
DbResult, Method, RpcProtocol, method_not_allowed, method_not_found, session_exists,
session_not_found,
};
use surrealdb_types::{Array, Error as TypesError, HashMap, Value};
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::cnf::{HTTP_MAX_ATTACHED_SESSIONS, PKG_NAME, PKG_VERSION};
pub struct Http {
kvs: Arc<Datastore>,
sessions: HashMap<Uuid, Arc<RwLock<Session>>>,
ephemeral_sessions: HashMap<Uuid, ()>,
}
impl Http {
pub fn new(kvs: Arc<Datastore>) -> Self {
Self {
kvs,
sessions: HashMap::new(),
ephemeral_sessions: HashMap::new(),
}
}
pub(crate) fn register_ephemeral_session(&self, id: Uuid, session: Arc<RwLock<Session>>) {
self.ephemeral_sessions.insert(id, ());
self.sessions.insert(id, session);
}
pub(crate) fn remove_ephemeral_session(&self, id: &Uuid) {
if self.ephemeral_sessions.contains_key(id) {
self.sessions.remove(id);
self.ephemeral_sessions.remove(id);
}
}
fn attached_session_count(&self) -> usize {
self.sessions.len().saturating_sub(self.ephemeral_sessions.len())
}
pub(crate) async fn verify_caller_for_session(
&self,
session_id: &Uuid,
caller_au: &Auth,
) -> Result<(), TypesError> {
if self.ephemeral_sessions.contains_key(session_id) {
return Err(session_not_found(*session_id));
}
let session_lock = self.get_session(session_id)?;
let session_guard = session_lock.read().await;
let session_au = session_guard.au.as_ref();
if caller_may_use_session(session_au, caller_au) {
Ok(())
} else {
Err(session_not_found(*session_id))
}
}
}
fn caller_may_use_session(session_au: &Auth, caller_au: &Auth) -> bool {
match session_au.level() {
Level::No => true,
_ => session_au.id() == caller_au.id() && session_au.level() == caller_au.level(),
}
}
impl RpcProtocol for Http {
fn kvs(&self) -> &Datastore {
&self.kvs
}
fn version_data(&self) -> DbResult {
let value = Value::String(format!("{PKG_NAME}-{}", *PKG_VERSION));
DbResult::Other(value)
}
fn session_map(&self) -> &HashMap<Uuid, Arc<RwLock<Session>>> {
&self.sessions
}
async fn sessions(&self) -> Result<DbResult, TypesError> {
Err(method_not_allowed(Method::Sessions.to_string()))
}
async fn attach(&self, session_id: Uuid) -> Result<DbResult, TypesError> {
if self.session_map().contains_key(&session_id) {
return Err(session_exists(session_id));
}
if self.attached_session_count() >= *HTTP_MAX_ATTACHED_SESSIONS {
return Err(method_not_allowed(Method::Attach.to_string()));
}
let mut session = Session::default().with_rt(Self::LQ_SUPPORT);
session.id = Some(session_id);
self.session_map().insert(session_id, Arc::new(RwLock::new(session)));
Ok(DbResult::Other(Value::None))
}
const LQ_SUPPORT: bool = false;
async fn cleanup_lqs(&self, _session_id: &Uuid) {
}
async fn cleanup_all_lqs(&self) {
}
async fn begin(&self, _txn: Option<Uuid>, _session_id: Uuid) -> Result<DbResult, TypesError> {
Err(method_not_found(Method::Begin.to_string()))
}
async fn commit(
&self,
_txn: Option<Uuid>,
_session_id: Uuid,
_params: Array,
) -> Result<DbResult, TypesError> {
Err(method_not_found(Method::Commit.to_string()))
}
async fn cancel(
&self,
_txn: Option<Uuid>,
_session_id: Uuid,
_params: Array,
) -> Result<DbResult, TypesError> {
Err(method_not_found(Method::Cancel.to_string()))
}
}