use std::fmt;
use std::str::FromStr;
use std::sync::Arc;
use chrono::Utc;
use surrealdb_types::ToSql;
use uuid::Uuid;
use crate::iam::{Auth, Level, Role};
use crate::types::{PublicValue, PublicVariables};
use crate::val::Value;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Session {
pub au: Arc<Auth>,
pub rt: bool,
pub ip: Option<String>,
pub or: Option<String>,
pub id: Option<Uuid>,
pub ns: Option<String>,
pub db: Option<String>,
pub ac: Option<String>,
pub tk: Option<PublicValue>,
pub rd: Option<PublicValue>,
pub exp: Option<i64>,
pub variables: PublicVariables,
pub new_planner_strategy: NewPlannerStrategy,
pub redact_volatile_explain_attrs: bool,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
pub enum NewPlannerStrategy {
#[default]
BestEffortReadOnlyStatements,
ComputeOnly,
AllReadOnlyStatements,
}
impl fmt::Display for NewPlannerStrategy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BestEffortReadOnlyStatements => f.write_str("best-effort"),
Self::ComputeOnly => f.write_str("compute-only"),
Self::AllReadOnlyStatements => f.write_str("all-read-only"),
}
}
}
impl FromStr for NewPlannerStrategy {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"best-effort" => Ok(Self::BestEffortReadOnlyStatements),
"compute-only" => Ok(Self::ComputeOnly),
"all-read-only" => Ok(Self::AllReadOnlyStatements),
_ => Err(format!(
"unknown planner strategy: '{s}' (expected 'best-effort', 'compute-only', or 'all-read-only')"
)),
}
}
}
impl Session {
pub fn with_ns(mut self, ns: &str) -> Session {
self.ns = Some(ns.to_owned());
self
}
pub fn with_db(mut self, db: &str) -> Session {
self.db = Some(db.to_owned());
self
}
pub fn with_ac(mut self, ac: &str) -> Session {
self.ac = Some(ac.to_owned());
self
}
pub fn with_rt(mut self, rt: bool) -> Session {
self.rt = rt;
self
}
pub fn new_planner_strategy(mut self, strategy: NewPlannerStrategy) -> Session {
self.new_planner_strategy = strategy;
self
}
pub(crate) fn ns(&self) -> Option<Arc<str>> {
self.ns.as_deref().map(Into::into)
}
pub(crate) fn db(&self) -> Option<Arc<str>> {
self.db.as_deref().map(Into::into)
}
pub(crate) fn live(&self) -> bool {
self.rt
}
pub(crate) fn expired(&self) -> bool {
match self.exp {
Some(exp) => Utc::now().timestamp() > exp,
None => false,
}
}
pub(crate) fn values(&self) -> Vec<(&'static str, Value)> {
use crate::sql::expression::convert_public_value_to_internal;
let access = self.ac.as_deref().map(Value::from).unwrap_or(Value::None);
let auth = self.rd.clone().map(convert_public_value_to_internal).unwrap_or(Value::None);
let token = self.tk.clone().map(convert_public_value_to_internal).unwrap_or(Value::None);
let session = Value::from(map! {
"ac" => access.clone(),
"exp" => self.exp.map(Value::from).unwrap_or(Value::None),
"db" => self.db.as_deref().map(Value::from).unwrap_or(Value::None),
"id" => self.id.map(Value::from).unwrap_or(Value::None),
"ip" => self.ip.as_deref().map(Value::from).unwrap_or(Value::None),
"ns" => self.ns.as_deref().map(Value::from).unwrap_or(Value::None),
"or" => self.or.as_deref().map(Value::from).unwrap_or(Value::None),
"rd" => auth.clone(),
"tk" => token.clone(),
});
vec![("access", access), ("auth", auth), ("token", token), ("session", session)]
}
pub fn for_level(level: Level, role: Role) -> Session {
let mut sess = Session::default();
match level {
Level::Root => {
sess.au = Arc::new(Auth::for_root(role));
}
Level::Namespace(ns) => {
sess.au = Arc::new(Auth::for_ns(role, &ns));
sess.ns = Some(ns);
}
Level::Database(ns, db) => {
sess.au = Arc::new(Auth::for_db(role, &ns, &db));
sess.ns = Some(ns);
sess.db = Some(db);
}
_ => {}
}
sess
}
pub fn for_record(ns: &str, db: &str, ac: &str, rid: PublicValue) -> Session {
Session {
ac: Some(ac.to_owned()),
au: Arc::new(Auth::for_record(rid.to_sql(), ns, db, ac)),
rt: false,
ip: None,
or: None,
id: None,
ns: Some(ns.to_owned()),
db: Some(db.to_owned()),
tk: None,
rd: Some(rid),
exp: None,
variables: Default::default(),
new_planner_strategy: NewPlannerStrategy::default(),
redact_volatile_explain_attrs: false,
}
}
pub fn owner() -> Session {
Session::for_level(Level::Root, Role::Owner)
}
pub fn editor() -> Session {
Session::for_level(Level::Root, Role::Editor)
}
pub fn viewer() -> Session {
Session::for_level(Level::Root, Role::Viewer)
}
}