Lines
73.08 %
Functions
36.36 %
Branches
100 %
use sqlx::Row;
use sqlx::types::Uuid;
use std::fmt::Debug;
use supp_macro::command;
use super::{CmdError, CmdResult};
use crate::{
config::{ConfigError, ConfigOption},
db::get_connection,
user::User,
};
command! {
GetConfig {
#[required]
user_id: Uuid,
name: String,
} => {
let user = User { id: user_id };
// An absent key is "not set" (Ok(None) → the native emits
// `(:config-value nil)`), not a hard error. This keeps absent
// (nil) distinct from a key holding the empty string ("") while
// letting genuine DB errors still propagate.
match user.config(&name).await {
Ok(opt) => Ok(opt.map(|v| match v {
ConfigOption::String(s) => CmdResult::String(s),
ConfigOption::Blob(b) => CmdResult::Data(b),
})),
Err(ConfigError::NoConfig(_)) => Ok(None),
Err(e) => Err(e.into()),
}
GetVersion {
const HASH: &str = env!("GIT_HASH");
Ok(Some(CmdResult::String(HASH.to_string())))
GetBuildDate {
const BUILD: &str = env!("BUILD_DATE");
Ok(Some(CmdResult::String(BUILD.to_string())))
SetConfig {
value: String,
user.set_config(&name, ConfigOption::String(value)).await?;
Ok(None)
SelectColumn {
field: String,
table: String,
let mut conn = get_connection().await.map_err(|err| {
log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
ConfigError::DB
})?;
// `field`/`table` are SQL identifiers (not bindable as `$n`), so they
// are interpolated. `AssertSqlSafe` is sound ONLY because select-column
// is a trusted-operator escape hatch: it is reachable from the CLI
// `sql selcol` / direct dispatch, never registered as an RPC/script
// native (see rpc::natives::config). Do not expose it to untrusted
// callers without validating/quoting the identifiers first.
let values: Vec<String> = sqlx::query(sqlx::AssertSqlSafe(format!(
"SELECT {field}::text FROM {table}"
)))
.fetch_all(&mut *conn)
.await?
.into_iter()
.map(|row| row.get::<Option<String>, _>(0).unwrap_or_default())
.collect();
Ok(Some(CmdResult::Lines(values)))
#[cfg(test)]
mod command_config_tests {
use super::*;
use crate::{db::DB_POOL, user::User};
use sqlx::{PgPool, types::Uuid};
use supp_macro::local_db_sqlx_test;
use tokio::sync::OnceCell;
/// Context for keeping environment intact
static CONTEXT: OnceCell<()> = OnceCell::const_new();
static USER: OnceCell<User> = OnceCell::const_new();
async fn setup() {
CONTEXT
.get_or_init(|| async {
#[cfg(feature = "testlog")]
let _ = env_logger::builder()
.is_test(true)
.filter_level(log::LevelFilter::Trace)
.try_init();
})
.await;
USER.get_or_init(|| async { User { id: Uuid::new_v4() } })
#[local_db_sqlx_test]
async fn set_then_get_config_round_trips_per_user(pool: PgPool) -> anyhow::Result<()> {
let user_id = Uuid::new_v4();
SetConfig::new()
.user_id(user_id)
.name("testfield".to_string())
.value("testval".to_string())
.run()
.await?;
let val = GetConfig::new()
.unwrap();
assert_eq!(val.to_string(), "testval");