2024-12-01 05:51:08 +00:00
use std ::path ::Path ;
2024-11-30 04:14:52 +00:00
2024-11-30 17:20:38 +00:00
use async_std ::channel ::{ bounded , Receiver , Sender } ;
2024-11-30 16:48:35 +00:00
use async_trait ::async_trait ;
2024-11-30 04:14:52 +00:00
use include_dir ::{ include_dir , Dir } ;
use lazy_static ::lazy_static ;
2024-12-01 05:51:08 +00:00
use result_extended ::{ error , fatal , ok , return_error , ResultExt } ;
2024-12-11 03:43:15 +00:00
use rusqlite ::{ types ::{ FromSql , FromSqlResult , ValueRef } , Connection } ;
2024-11-30 04:14:52 +00:00
use rusqlite_migration ::Migrations ;
use serde ::{ Deserialize , Serialize } ;
use thiserror ::Error ;
use uuid ::Uuid ;
2024-12-01 05:51:08 +00:00
use crate ::types ::FatalError ;
2024-11-30 04:14:52 +00:00
static MIGRATIONS_DIR : Dir = include_dir! ( " $CARGO_MANIFEST_DIR/migrations " ) ;
lazy_static! {
static ref MIGRATIONS : Migrations < 'static > =
Migrations ::from_directory ( & MIGRATIONS_DIR ) . unwrap ( ) ;
}
#[ derive(Debug, Error) ]
pub enum Error {
2024-11-30 16:48:35 +00:00
#[ error( " No response to request " ) ]
2024-11-30 17:20:38 +00:00
NoResponse ,
2024-11-30 16:48:35 +00:00
}
#[ derive(Debug) ]
enum Request {
Charsheet ( CharacterId ) ,
2024-12-11 03:43:15 +00:00
Games ,
2024-12-01 19:07:37 +00:00
Users ,
2024-11-30 16:48:35 +00:00
}
#[ derive(Debug) ]
struct DatabaseRequest {
2024-11-30 17:05:31 +00:00
tx : Sender < DatabaseResponse > ,
2024-11-30 16:48:35 +00:00
req : Request ,
}
#[ derive(Debug) ]
enum DatabaseResponse {
Charsheet ( Option < CharsheetRow > ) ,
2024-12-11 03:43:15 +00:00
Games ( Vec < GameRow > ) ,
Users ( Vec < UserRow > ) ,
2024-11-30 04:14:52 +00:00
}
2024-12-01 04:03:52 +00:00
#[ derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize) ]
pub struct UserId ( String ) ;
impl UserId {
pub fn new ( ) -> Self {
Self ( format! ( " {} " , Uuid ::new_v4 ( ) . hyphenated ( ) ) )
}
pub fn as_str < ' a > ( & ' a self ) -> & ' a str {
& self . 0
}
}
impl From < & str > for UserId {
fn from ( s : & str ) -> Self {
Self ( s . to_owned ( ) )
}
}
impl From < String > for UserId {
fn from ( s : String ) -> Self {
Self ( s )
}
}
2024-12-11 03:43:15 +00:00
impl FromSql for UserId {
fn column_result ( value : ValueRef < '_ > ) -> FromSqlResult < Self > {
match value {
ValueRef ::Text ( text ) = > Ok ( Self ::from ( String ::from_utf8 ( text . to_vec ( ) ) . unwrap ( ) ) ) ,
_ = > unimplemented! ( ) ,
}
}
}
2024-12-01 04:03:52 +00:00
#[ derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize) ]
pub struct GameId ( String ) ;
impl GameId {
pub fn new ( ) -> Self {
Self ( format! ( " {} " , Uuid ::new_v4 ( ) . hyphenated ( ) ) )
}
pub fn as_str < ' a > ( & ' a self ) -> & ' a str {
& self . 0
}
}
impl From < & str > for GameId {
fn from ( s : & str ) -> Self {
Self ( s . to_owned ( ) )
}
}
impl From < String > for GameId {
fn from ( s : String ) -> Self {
Self ( s )
}
}
2024-12-11 03:43:15 +00:00
impl FromSql for GameId {
fn column_result ( value : ValueRef < '_ > ) -> FromSqlResult < Self > {
match value {
ValueRef ::Text ( text ) = > Ok ( Self ::from ( String ::from_utf8 ( text . to_vec ( ) ) . unwrap ( ) ) ) ,
_ = > unimplemented! ( ) ,
}
}
}
2024-11-30 04:14:52 +00:00
#[ derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize) ]
pub struct CharacterId ( String ) ;
impl CharacterId {
pub fn new ( ) -> Self {
2024-12-01 04:03:52 +00:00
Self ( format! ( " {} " , Uuid ::new_v4 ( ) . hyphenated ( ) ) )
2024-11-30 04:14:52 +00:00
}
pub fn as_str < ' a > ( & ' a self ) -> & ' a str {
& self . 0
}
}
impl From < & str > for CharacterId {
fn from ( s : & str ) -> Self {
2024-12-01 04:03:52 +00:00
Self ( s . to_owned ( ) )
2024-11-30 04:14:52 +00:00
}
}
impl From < String > for CharacterId {
fn from ( s : String ) -> Self {
2024-12-01 04:03:52 +00:00
Self ( s )
2024-11-30 04:14:52 +00:00
}
}
2024-12-11 03:43:15 +00:00
impl FromSql for CharacterId {
fn column_result ( value : ValueRef < '_ > ) -> FromSqlResult < Self > {
match value {
ValueRef ::Text ( text ) = > Ok ( Self ::from ( String ::from_utf8 ( text . to_vec ( ) ) . unwrap ( ) ) ) ,
_ = > unimplemented! ( ) ,
}
}
}
2024-12-01 04:03:52 +00:00
#[ derive(Clone, Debug) ]
pub struct UserRow {
2024-12-11 03:43:15 +00:00
pub id : UserId ,
pub name : String ,
pub password : String ,
pub admin : bool ,
pub enabled : bool ,
2024-12-01 04:03:52 +00:00
}
#[ derive(Clone, Debug) ]
pub struct Role {
2024-12-11 03:43:15 +00:00
userid : UserId ,
gameid : GameId ,
2024-12-01 04:03:52 +00:00
role : String ,
}
2024-12-11 03:43:15 +00:00
#[ derive(Clone, Debug) ]
pub struct GameRow {
pub id : UserId ,
pub name : String ,
}
2024-11-30 04:14:52 +00:00
#[ derive(Clone, Debug) ]
pub struct CharsheetRow {
id : String ,
2024-12-11 03:43:15 +00:00
game : GameId ,
2024-11-30 20:24:57 +00:00
pub data : serde_json ::Value ,
2024-11-30 04:14:52 +00:00
}
2024-11-30 16:48:35 +00:00
#[ async_trait ]
pub trait Database : Send + Sync {
2024-12-11 03:43:15 +00:00
async fn users ( & mut self ) -> result_extended ::ResultExt < Vec < UserRow > , Error , FatalError > ;
async fn games ( & mut self ) -> result_extended ::ResultExt < Vec < GameRow > , Error , FatalError > ;
2024-12-01 19:07:37 +00:00
2024-12-01 05:51:08 +00:00
async fn character (
& mut self ,
id : CharacterId ,
) -> result_extended ::ResultExt < Option < CharsheetRow > , Error , FatalError > ;
2024-11-30 04:14:52 +00:00
}
pub struct DiskDb {
conn : Connection ,
}
2024-12-01 05:51:08 +00:00
fn setup_test_database ( conn : & Connection ) -> Result < ( ) , FatalError > {
2024-11-30 23:55:51 +00:00
let mut gamecount_stmt = conn . prepare ( " SELECT count(*) FROM games " ) . unwrap ( ) ;
let mut count = gamecount_stmt . query ( [ ] ) . unwrap ( ) ;
if count . next ( ) . unwrap ( ) . unwrap ( ) . get ::< usize , usize > ( 0 ) = = Ok ( 0 ) {
2024-12-01 04:03:52 +00:00
let admin_id = format! ( " {} " , Uuid ::new_v4 ( ) ) ;
let user_id = format! ( " {} " , Uuid ::new_v4 ( ) ) ;
2024-11-30 23:55:51 +00:00
let game_id = format! ( " {} " , Uuid ::new_v4 ( ) ) ;
let char_id = CharacterId ::new ( ) ;
2024-12-01 05:51:08 +00:00
let mut user_stmt = conn
. prepare ( " INSERT INTO users VALUES (?, ?, ?, ?, ?) " )
. map_err ( | err | FatalError ::ConstructQueryFailure ( format! ( " {} " , err ) ) ) ? ;
user_stmt
. execute ( ( admin_id . clone ( ) , " admin " , " abcdefg " , true , true ) )
. unwrap ( ) ;
user_stmt
. execute ( ( user_id . clone ( ) , " savanni " , " abcdefg " , false , true ) )
. unwrap ( ) ;
2024-12-01 04:03:52 +00:00
2024-12-01 05:51:08 +00:00
let mut game_stmt = conn
. prepare ( " INSERT INTO games VALUES (?, ?) " )
. map_err ( | err | FatalError ::ConstructQueryFailure ( format! ( " {} " , err ) ) ) ? ;
game_stmt
. execute ( ( game_id . clone ( ) , " Circle of Bluest Sky " ) )
. unwrap ( ) ;
2024-12-01 04:03:52 +00:00
2024-12-01 05:51:08 +00:00
let mut role_stmt = conn
. prepare ( " INSERT INTO roles VALUES (?, ?, ?) " )
. map_err ( | err | FatalError ::ConstructQueryFailure ( format! ( " {} " , err ) ) ) ? ;
role_stmt
. execute ( ( user_id . clone ( ) , game_id . clone ( ) , " gm " ) )
. unwrap ( ) ;
2024-11-30 23:55:51 +00:00
let mut sheet_stmt = conn
. prepare ( " INSERT INTO characters VALUES (?, ?, ?) " )
2024-12-01 05:51:08 +00:00
. map_err ( | err | FatalError ::ConstructQueryFailure ( format! ( " {} " , err ) ) ) ? ;
2024-11-30 23:55:51 +00:00
sheet_stmt . execute ( ( char_id . as_str ( ) , game_id , r # "{ "type_": "Candela", "name": "Soren Jensen", "pronouns": "he/him", "circle": "Circle of the Bluest Sky", "style": "dapper gentleman", "catalyst": "a cursed book", "question": "What were the contents of that book?", "nerve": { "type_": "nerve", "drives": { "current": 1, "max": 2 }, "resistances": { "current": 0, "max": 3 }, "move": { "gilded": false, "score": 2 }, "strike": { "gilded": false, "score": 1 }, "control": { "gilded": true, "score": 0 } }, "cunning": { "type_": "cunning", "drives": { "current": 1, "max": 1 }, "resistances": { "current": 0, "max": 3 }, "sway": { "gilded": false, "score": 0 }, "read": { "gilded": false, "score": 0 }, "hide": { "gilded": false, "score": 0 } }, "intuition": { "type_": "intuition", "drives": { "current": 0, "max": 0 }, "resistances": { "current": 0, "max": 3 }, "survey": { "gilded": false, "score": 0 }, "focus": { "gilded": false, "score": 0 }, "sense": { "gilded": false, "score": 0 } }, "role": "Slink", "role_abilities": [ "Scout: If you have time to observe a location, you can spend 1 Intuition to ask a question: What do I notice here that others do not see? What in this place might be of use to us? What path should we follow?" ], "specialty": "Detective", "specialty_abilities": [ "Mind Palace: When you want to figure out how two clues might relate or what path they should point you towards, burn 1 Intution resistance. The GM will give you the information you have deduced." ] }"# ) )
. unwrap ( ) ;
}
2024-12-01 05:51:08 +00:00
Ok ( ( ) )
2024-11-30 23:55:51 +00:00
}
2024-11-30 04:14:52 +00:00
impl DiskDb {
2024-12-01 05:51:08 +00:00
pub fn new < P > ( path : Option < P > ) -> Result < Self , FatalError >
2024-11-30 16:48:35 +00:00
where
P : AsRef < Path > ,
{
2024-11-30 04:14:52 +00:00
let mut conn = match path {
None = > Connection ::open ( " :memory: " ) . expect ( " to create a memory connection " ) ,
Some ( path ) = > Connection ::open ( path ) . expect ( " to create connection " ) ,
} ;
2024-11-30 17:20:38 +00:00
2024-12-01 05:51:08 +00:00
MIGRATIONS
. to_latest ( & mut conn )
. map_err ( | err | FatalError ::DatabaseMigrationFailure ( format! ( " {} " , err ) ) ) ? ;
setup_test_database ( & conn ) ? ;
2024-11-30 17:20:38 +00:00
2024-11-30 04:14:52 +00:00
Ok ( DiskDb { conn } )
}
2024-12-11 03:43:15 +00:00
fn users ( & self ) -> Result < Vec < UserRow > , FatalError > {
let mut stmt = self . conn . prepare ( " SELECT * FROM USERS " )
. map_err ( | err | FatalError ::ConstructQueryFailure ( format! ( " {} " , err ) ) ) ? ;
let items = stmt . query_map ( [ ] , | row | {
Ok ( UserRow {
id : row . get ( 0 ) . unwrap ( ) ,
name : row . get ( 1 ) . unwrap ( ) ,
password : row . get ( 2 ) . unwrap ( ) ,
admin : row . get ( 3 ) . unwrap ( ) ,
enabled : row . get ( 4 ) . unwrap ( ) ,
} )
} ) . unwrap ( ) . collect ::< Result < Vec < UserRow > , rusqlite ::Error > > ( ) . unwrap ( ) ;
Ok ( items )
}
2024-12-01 05:51:08 +00:00
fn user ( & self , id : UserId ) -> Result < Option < UserRow > , FatalError > {
2024-12-01 04:03:52 +00:00
let mut stmt = self
. conn
. prepare ( " SELECT uuid, name, password, admin, enabled WHERE uuid=? " )
2024-12-01 05:51:08 +00:00
. map_err ( | err | FatalError ::ConstructQueryFailure ( format! ( " {} " , err ) ) ) ? ;
2024-12-01 04:03:52 +00:00
let items : Vec < UserRow > = stmt
2024-12-01 05:51:08 +00:00
. query_map ( [ id . as_str ( ) ] , | row | {
Ok ( UserRow {
id : row . get ( 0 ) . unwrap ( ) ,
name : row . get ( 1 ) . unwrap ( ) ,
password : row . get ( 2 ) . unwrap ( ) ,
admin : row . get ( 3 ) . unwrap ( ) ,
enabled : row . get ( 4 ) . unwrap ( ) ,
} )
} )
. unwrap ( )
. collect ::< Result < Vec < UserRow > , rusqlite ::Error > > ( )
. unwrap ( ) ;
2024-12-01 04:03:52 +00:00
match & items [ .. ] {
[ ] = > Ok ( None ) ,
[ item ] = > Ok ( Some ( item . clone ( ) ) ) ,
2024-12-01 05:51:08 +00:00
_ = > Err ( FatalError ::NonUniqueDatabaseKey ( id . as_str ( ) . to_owned ( ) ) ) ,
2024-12-01 04:03:52 +00:00
}
}
2024-12-01 16:14:28 +00:00
fn save_user (
& self ,
user_id : Option < UserId > ,
name : & str ,
password : & str ,
admin : bool ,
enabled : bool ,
) -> Result < UserId , FatalError > {
match user_id {
None = > {
let user_id = UserId ::new ( ) ;
let mut stmt = self
. conn
. prepare ( " INSERT INTO users VALUES (?, ?, ?, ?, ?) " )
. map_err ( | err | FatalError ::ConstructQueryFailure ( format! ( " {} " , err ) ) ) ? ;
stmt . execute ( ( user_id . as_str ( ) , name , password , admin , enabled ) )
. unwrap ( ) ;
Ok ( user_id )
}
Some ( user_id ) = > {
let mut stmt = self
. conn
. prepare (
" UPDATE users SET name=?, password=?, admin=?, enbabled=? WHERE uuid=? " ,
)
. map_err ( | err | FatalError ::ConstructQueryFailure ( format! ( " {} " , err ) ) ) ? ;
stmt . execute ( ( name , password , admin , enabled , user_id . as_str ( ) ) )
. unwrap ( ) ;
Ok ( user_id )
}
}
}
fn save_game (
& self ,
game_id : Option < GameId > ,
name : & str ,
) -> Result < GameId , FatalError > {
match game_id {
None = > {
let game_id = GameId ::new ( ) ;
let mut stmt = self
. conn
. prepare ( " INSERT INTO games VALUES (?, ?) " )
. map_err ( | err | FatalError ::ConstructQueryFailure ( format! ( " {} " , err ) ) ) ? ;
stmt . execute ( ( game_id . as_str ( ) , name ) )
. unwrap ( ) ;
Ok ( game_id )
}
Some ( game_id ) = > {
let mut stmt = self
. conn
. prepare (
" UPDATE games SET name=? WHERE uuid=? " ,
)
. map_err ( | err | FatalError ::ConstructQueryFailure ( format! ( " {} " , err ) ) ) ? ;
stmt . execute ( ( name , game_id . as_str ( ) ) )
. unwrap ( ) ;
Ok ( game_id )
}
}
}
2024-12-01 05:51:08 +00:00
fn character ( & self , id : CharacterId ) -> Result < Option < CharsheetRow > , FatalError > {
2024-11-30 04:14:52 +00:00
let mut stmt = self
. conn
2024-12-01 05:51:08 +00:00
. prepare ( " SELECT uuid, game, data FROM characters WHERE uuid=? " )
. map_err ( | err | FatalError ::ConstructQueryFailure ( format! ( " {} " , err ) ) ) ? ;
2024-11-30 04:14:52 +00:00
let items : Vec < CharsheetRow > = stmt
. query_map ( [ id . as_str ( ) ] , | row | {
let data : String = row . get ( 2 ) . unwrap ( ) ;
Ok ( CharsheetRow {
id : row . get ( 0 ) . unwrap ( ) ,
2024-12-01 04:03:52 +00:00
game : row . get ( 1 ) . unwrap ( ) ,
2024-11-30 04:14:52 +00:00
data : serde_json ::from_str ( & data ) . unwrap ( ) ,
} )
} )
. unwrap ( )
. collect ::< Result < Vec < CharsheetRow > , rusqlite ::Error > > ( )
. unwrap ( ) ;
match & items [ .. ] {
[ ] = > Ok ( None ) ,
[ item ] = > Ok ( Some ( item . clone ( ) ) ) ,
2024-12-01 05:51:08 +00:00
_ = > Err ( FatalError ::NonUniqueDatabaseKey ( id . as_str ( ) . to_owned ( ) ) ) ,
2024-11-30 04:14:52 +00:00
}
}
2024-12-01 05:51:08 +00:00
fn save_character (
2024-11-30 04:14:52 +00:00
& self ,
char_id : Option < CharacterId > ,
2024-12-01 16:14:28 +00:00
game : GameId ,
2024-12-01 05:51:08 +00:00
character : serde_json ::Value ,
) -> std ::result ::Result < CharacterId , Error > {
2024-11-30 04:14:52 +00:00
match char_id {
None = > {
let char_id = CharacterId ::new ( ) ;
let mut stmt = self
. conn
2024-12-01 05:51:08 +00:00
. prepare ( " INSERT INTO characters VALUES (?, ?, ?) " )
2024-11-30 04:14:52 +00:00
. unwrap ( ) ;
2024-12-01 16:14:28 +00:00
stmt . execute ( ( char_id . as_str ( ) , game . as_str ( ) , character . to_string ( ) ) )
2024-11-30 04:14:52 +00:00
. unwrap ( ) ;
Ok ( char_id )
}
Some ( char_id ) = > {
let mut stmt = self
. conn
2024-12-01 05:51:08 +00:00
. prepare ( " UPDATE characters SET data=? WHERE uuid=? " )
2024-11-30 04:14:52 +00:00
. unwrap ( ) ;
2024-12-01 05:51:08 +00:00
stmt . execute ( ( character . to_string ( ) , char_id . as_str ( ) ) )
2024-11-30 04:14:52 +00:00
. unwrap ( ) ;
Ok ( char_id )
}
}
}
}
2024-11-30 20:24:57 +00:00
async fn db_handler ( db : DiskDb , requestor : Receiver < DatabaseRequest > ) {
2024-11-30 17:20:38 +00:00
while let Ok ( DatabaseRequest { tx , req } ) = requestor . recv ( ) . await {
2024-11-30 16:48:35 +00:00
println! ( " Request received: {:?} " , req ) ;
match req {
Request ::Charsheet ( id ) = > {
2024-12-01 05:51:08 +00:00
let sheet = db . character ( id ) ;
2024-11-30 16:48:35 +00:00
println! ( " sheet retrieved: {:?} " , sheet ) ;
match sheet {
2024-11-30 17:05:31 +00:00
Ok ( sheet ) = > {
tx . send ( DatabaseResponse ::Charsheet ( sheet ) ) . await . unwrap ( ) ;
2024-11-30 17:20:38 +00:00
}
2024-11-30 16:48:35 +00:00
_ = > unimplemented! ( ) ,
}
}
2024-12-11 03:43:15 +00:00
Request ::Games = > {
unimplemented! ( ) ;
}
2024-12-01 19:07:37 +00:00
Request ::Users = > {
let users = db . users ( ) ;
match users {
Ok ( users ) = > {
tx . send ( DatabaseResponse ::Users ( users ) ) . await . unwrap ( ) ;
}
_ = > unimplemented! ( ) ,
}
}
2024-11-30 16:48:35 +00:00
}
}
println! ( " ending db_handler " ) ;
2024-11-30 04:14:52 +00:00
}
2024-11-30 16:48:35 +00:00
pub struct DbConn {
2024-11-30 17:05:31 +00:00
conn : Sender < DatabaseRequest > ,
2024-11-30 16:48:35 +00:00
handle : tokio ::task ::JoinHandle < ( ) > ,
2024-11-30 04:14:52 +00:00
}
2024-11-30 16:48:35 +00:00
impl DbConn {
pub fn new < P > ( path : Option < P > ) -> Self
where
P : AsRef < Path > ,
{
2024-11-30 17:05:31 +00:00
let ( tx , rx ) = bounded ::< DatabaseRequest > ( 5 ) ;
2024-11-30 16:48:35 +00:00
let db = DiskDb ::new ( path ) . unwrap ( ) ;
2024-11-30 04:14:52 +00:00
2024-11-30 17:20:38 +00:00
let handle = tokio ::spawn ( async move {
db_handler ( db , rx ) . await ;
} ) ;
2024-11-30 16:48:35 +00:00
Self { conn : tx , handle }
2024-11-30 04:14:52 +00:00
}
}
2024-11-30 16:48:35 +00:00
#[ async_trait ]
impl Database for DbConn {
2024-12-11 03:43:15 +00:00
async fn users ( & mut self ) -> ResultExt < Vec < UserRow > , Error , FatalError > {
2024-12-01 19:07:37 +00:00
let ( tx , rx ) = bounded ::< DatabaseResponse > ( 1 ) ;
let request = DatabaseRequest {
tx ,
req : Request ::Users ,
} ;
match self . conn . send ( request ) . await {
Ok ( ( ) ) = > ( ) ,
Err ( _ ) = > return fatal ( FatalError ::DatabaseConnectionLost ) ,
} ;
match rx . recv ( ) . await {
Ok ( DatabaseResponse ::Users ( lst ) ) = > ok ( lst ) ,
Ok ( _ ) = > fatal ( FatalError ::MessageMismatch ) ,
Err ( _ ) = > error ( Error ::NoResponse ) ,
}
}
2024-12-11 03:43:15 +00:00
async fn games ( & mut self ) -> result_extended ::ResultExt < Vec < GameRow > , Error , FatalError > {
let ( tx , rx ) = bounded ::< DatabaseResponse > ( 1 ) ;
let request = DatabaseRequest {
tx ,
req : Request ::Games ,
} ;
match self . conn . send ( request ) . await {
Ok ( ( ) ) = > ( ) ,
Err ( _ ) = > return fatal ( FatalError ::DatabaseConnectionLost ) ,
} ;
match rx . recv ( ) . await {
Ok ( DatabaseResponse ::Games ( lst ) ) = > ok ( lst ) ,
Ok ( _ ) = > fatal ( FatalError ::MessageMismatch ) ,
Err ( _ ) = > error ( Error ::NoResponse ) ,
}
}
2024-12-01 05:51:08 +00:00
async fn character (
& mut self ,
id : CharacterId ,
) -> ResultExt < Option < CharsheetRow > , Error , FatalError > {
2024-11-30 17:05:31 +00:00
let ( tx , rx ) = bounded ::< DatabaseResponse > ( 1 ) ;
2024-11-30 16:48:35 +00:00
2024-11-30 17:20:38 +00:00
let request = DatabaseRequest {
2024-11-30 16:48:35 +00:00
tx ,
req : Request ::Charsheet ( id ) ,
} ;
2024-12-01 05:51:08 +00:00
match self . conn . send ( request ) . await {
Ok ( ( ) ) = > ( ) ,
Err ( _ ) = > return fatal ( FatalError ::DatabaseConnectionLost ) ,
} ;
2024-11-30 17:05:31 +00:00
match rx . recv ( ) . await {
2024-12-01 05:51:08 +00:00
Ok ( DatabaseResponse ::Charsheet ( row ) ) = > ok ( row ) ,
2024-12-01 19:07:37 +00:00
Ok ( _ ) = > fatal ( FatalError ::MessageMismatch ) ,
2024-12-01 05:51:08 +00:00
Err ( _err ) = > error ( Error ::NoResponse ) ,
2024-11-30 04:14:52 +00:00
}
}
}
#[ cfg(test) ]
mod test {
2024-12-01 05:51:08 +00:00
use std ::path ::PathBuf ;
2024-11-30 04:14:52 +00:00
use cool_asserts ::assert_matches ;
use super ::* ;
const soren : & 'static str = r # "{ "type_": "Candela", "name": "Soren Jensen", "pronouns": "he/him", "circle": "Circle of the Bluest Sky", "style": "dapper gentleman", "catalyst": "a cursed book", "question": "What were the contents of that book?", "nerve": { "type_": "nerve", "drives": { "current": 1, "max": 2 }, "resistances": { "current": 0, "max": 3 }, "move": { "gilded": false, "score": 2 }, "strike": { "gilded": false, "score": 1 }, "control": { "gilded": true, "score": 0 } }, "cunning": { "type_": "cunning", "drives": { "current": 1, "max": 1 }, "resistances": { "current": 0, "max": 3 }, "sway": { "gilded": false, "score": 0 }, "read": { "gilded": false, "score": 0 }, "hide": { "gilded": false, "score": 0 } }, "intuition": { "type_": "intuition", "drives": { "current": 0, "max": 0 }, "resistances": { "current": 0, "max": 3 }, "survey": { "gilded": false, "score": 0 }, "focus": { "gilded": false, "score": 0 }, "sense": { "gilded": false, "score": 0 } }, "role": "Slink", "role_abilities": [ "Scout: If you have time to observe a location, you can spend 1 Intuition to ask a question: What do I notice here that others do not see? What in this place might be of use to us? What path should we follow?" ], "specialty": "Detective", "specialty_abilities": [ "Mind Palace: When you want to figure out how two clues might relate or what path they should point you towards, burn 1 Intution resistance. The GM will give you the information you have deduced." ] }"# ;
2024-12-01 16:14:28 +00:00
fn setup_db ( ) -> ( DiskDb , GameId ) {
2024-11-30 16:48:35 +00:00
let no_path : Option < PathBuf > = None ;
let db = DiskDb ::new ( no_path ) . unwrap ( ) ;
2024-11-30 04:14:52 +00:00
2024-12-01 16:14:28 +00:00
db . save_user ( None , " admin " , " abcdefg " , true , true ) ;
let game_id = db . save_game ( None , " Candela " ) . unwrap ( ) ;
( db , game_id )
}
#[ test ]
fn it_can_retrieve_a_character ( ) {
let ( db , game_id ) = setup_db ( ) ;
2024-12-01 05:51:08 +00:00
assert_matches! ( db . character ( CharacterId ::from ( " 1 " ) ) , Ok ( None ) ) ;
2024-11-30 04:14:52 +00:00
let js : serde_json ::Value = serde_json ::from_str ( soren ) . unwrap ( ) ;
2024-11-30 16:48:35 +00:00
let soren_id = db
2024-12-01 16:14:28 +00:00
. save_character ( None , game_id , js . clone ( ) )
2024-11-30 16:48:35 +00:00
. unwrap ( ) ;
2024-12-01 05:51:08 +00:00
assert_matches! ( db . character ( soren_id ) . unwrap ( ) , Some ( CharsheetRow { data , .. } ) = > assert_eq! ( js , data ) ) ;
2024-11-30 04:14:52 +00:00
}
2024-11-30 16:48:35 +00:00
#[ tokio::test ]
2024-12-01 05:51:08 +00:00
async fn it_can_retrieve_a_character_through_conn ( ) {
2024-11-30 16:48:35 +00:00
let memory_db : Option < PathBuf > = None ;
let mut conn = DbConn ::new ( memory_db ) ;
2024-12-01 16:14:28 +00:00
assert_matches! (
conn . character ( CharacterId ::from ( " 1 " ) ) . await ,
ResultExt ::Ok ( None )
) ;
2024-11-30 16:48:35 +00:00
}
2024-11-30 04:14:52 +00:00
}