use crate::query::persistence_query_specification::PersistenceQuerySpecification;
use bevy::prelude::Resource;
use futures::future::BoxFuture;
use mockall::automock;
use serde_json::Value;
use std::fmt;
use std::sync::Arc;
pub const BEVY_PERSISTENCE_DATABASE_METADATA_FIELD: &str = "bevy_persistence_database_metadata";
pub const BEVY_PERSISTENCE_DATABASE_VERSION_FIELD: &str = "bevy_persistence_version";
pub const BEVY_PERSISTENCE_DATABASE_BEVY_TYPE_FIELD: &str = "bevy_type";
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
pub enum DocumentKind {
Entity,
Resource,
}
impl DocumentKind {
pub fn as_str(&self) -> &'static str {
match self {
DocumentKind::Entity => "entity",
DocumentKind::Resource => "resource",
}
}
pub fn from_str(value: &str) -> Option<Self> {
match value {
"entity" => Some(DocumentKind::Entity),
"resource" => Some(DocumentKind::Resource),
_ => None,
}
}
}
pub fn read_version(doc: &Value) -> Option<u64> {
doc.get(BEVY_PERSISTENCE_DATABASE_METADATA_FIELD)
.and_then(|meta| meta.get(BEVY_PERSISTENCE_DATABASE_VERSION_FIELD))
.and_then(Value::as_u64)
}
pub fn read_kind(doc: &Value) -> Option<DocumentKind> {
doc.get(BEVY_PERSISTENCE_DATABASE_METADATA_FIELD)
.and_then(|meta| meta.get(BEVY_PERSISTENCE_DATABASE_BEVY_TYPE_FIELD))
.and_then(Value::as_str)
.and_then(DocumentKind::from_str)
}
#[derive(Debug, Clone)]
pub enum PersistenceError {
General(String),
Conflict { key: String },
}
impl fmt::Display for PersistenceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PersistenceError::General(msg) => write!(f, "Persistence Error: {}", msg),
PersistenceError::Conflict { key } => write!(f, "Version conflict for key: {}", key),
}
}
}
impl std::error::Error for PersistenceError {}
impl PersistenceError {
pub fn new(msg: impl Into<String>) -> Self {
PersistenceError::General(msg.into())
}
}
#[derive(serde::Serialize, Debug, Clone)]
pub enum TransactionOperation {
CreateDocument {
store: String,
kind: DocumentKind,
data: Value,
},
UpdateDocument {
store: String,
kind: DocumentKind,
key: String,
expected_current_version: u64,
patch: Value,
},
DeleteDocument {
store: String,
kind: DocumentKind,
key: String,
expected_current_version: u64,
},
}
impl TransactionOperation {
pub fn store(&self) -> &str {
match self {
TransactionOperation::CreateDocument { store, .. }
| TransactionOperation::UpdateDocument { store, .. }
| TransactionOperation::DeleteDocument { store, .. } => store,
}
}
}
#[automock]
pub trait DatabaseConnection: Send + Sync + std::fmt::Debug {
fn document_key_field(&self) -> &'static str;
fn execute_keys(
&self,
spec: &PersistenceQuerySpecification,
) -> BoxFuture<'static, Result<Vec<String>, PersistenceError>>;
fn execute_documents(
&self,
spec: &PersistenceQuerySpecification,
) -> BoxFuture<'static, Result<Vec<Value>, PersistenceError>>;
fn execute_documents_sync(
&self,
_spec: &PersistenceQuerySpecification,
) -> Result<Vec<Value>, PersistenceError> {
panic!("execute_documents_sync not implemented");
}
fn execute_transaction(
&self,
operations: Vec<TransactionOperation>,
) -> BoxFuture<'static, Result<Vec<String>, PersistenceError>>;
fn fetch_document(
&self,
store: &str,
entity_key: &str,
) -> BoxFuture<'static, Result<Option<(Value, u64)>, PersistenceError>>;
fn fetch_component(
&self,
store: &str,
entity_key: &str,
comp_name: &str,
) -> BoxFuture<'static, Result<Option<Value>, PersistenceError>>;
fn fetch_resource(
&self,
store: &str,
resource_name: &str,
) -> BoxFuture<'static, Result<Option<(Value, u64)>, PersistenceError>>;
fn clear_store(
&self,
store: &str,
kind: DocumentKind,
) -> BoxFuture<'static, Result<(), PersistenceError>>;
fn count_documents(
&self,
spec: &PersistenceQuerySpecification,
) -> BoxFuture<'static, Result<usize, PersistenceError>>;
}
#[derive(Resource)]
pub struct DatabaseConnectionResource {
pub connection: Arc<dyn DatabaseConnection>,
}
impl std::ops::Deref for DatabaseConnectionResource {
type Target = Arc<dyn DatabaseConnection>;
fn deref(&self) -> &Self::Target {
&self.connection
}
}