use crate::args::validate::{
validate_folder_or_scope_exists, validate_position_in_bounds, FolderOrScopeValidationError,
FolderPositionValidationError, ObjectValidationError, StringValidationError,
};
use crate::objects::blueprint_folder::BlueprintFolder;
use crate::objects::entity_folder::EntityFolder;
use crate::objects::id::TypedId;
use crate::objects::object::{object_create, Object, ObjectScope, ObjectType};
use crate::objects::property_folder::PropertyFolder;
use crate::objects::{ExpiredConnectionError, FromId, FromIdError};
use crate::persistence::project::ProjectError;
use crate::types::location::FolderPosition;
use sqrite::connection::{Connection, DbError, RcRefCellExtension};
use sqrite::sql_interface::FromSqlError;
use std::fmt::Formatter;
use std::rc::Rc;
use tracing::*;
#[derive(thiserror::Error, Debug)]
pub enum FolderHelperError {
#[error("error interfacing with project: {0}")]
ProjectError(#[from] ProjectError),
#[error("connection has expired")]
ExpiredConnection(#[from] ExpiredConnectionError),
#[error("error interfacing with database: {0}")]
DbError(#[from] DbError),
#[error("unable to validate object: {0}")]
ObjectValidationError(#[from] ObjectValidationError),
#[error("unable to validate string argument: {0}")]
StringValidationError(#[from] StringValidationError),
#[error("unable to validate folder position argument: {0}")]
FolderPositionValidationError(#[from] FolderPositionValidationError),
#[error("unable to validate folder or scope argument: {0}")]
FolderOrScopeValidationError(#[from] FolderOrScopeValidationError),
#[error("unable to construct folder from id {0} with object type {1}")]
InvalidId(TypedId, ObjectType),
}
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum FolderOrScope {
Folder(Folder),
Scope(ObjectScope),
}
impl FolderOrScope {
pub fn from_opt_folder(folder: Option<Folder>, scope: ObjectScope) -> FolderOrScope {
if let Some(folder) = folder {
FolderOrScope::Folder(folder)
} else {
FolderOrScope::Scope(scope)
}
}
}
impl std::fmt::Display for FolderOrScope {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum Folder {
BlueprintFolder(BlueprintFolder),
PropertyFolder(PropertyFolder),
EntityFolder(EntityFolder),
}
impl std::fmt::Display for Folder {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl FromId for Folder {
fn from_id(conn: Rc<Connection>, id: TypedId) -> Result<Self, FromIdError>
where
Self: Sized,
{
match id.get_object_type() {
ObjectType::BlueprintFolder => Ok(Folder::BlueprintFolder(BlueprintFolder {
id,
conn: Rc::downgrade(&conn),
})),
ObjectType::PropertyFolder => Ok(Folder::PropertyFolder(PropertyFolder {
id,
conn: Rc::downgrade(&conn),
})),
ObjectType::EntityFolder => Ok(Folder::EntityFolder(EntityFolder {
id,
conn: Rc::downgrade(&conn),
})),
_ => Err(FromIdError::InvalidId("folder", id)),
}
}
}
impl Folder {
#[instrument(level = Level::TRACE, skip(conn))]
pub(crate) fn from_id(id: TypedId, conn: Rc<Connection>) -> Result<Folder, FolderHelperError> {
Ok(match id.get_object_type() {
ObjectType::BlueprintFolder => Folder::BlueprintFolder(BlueprintFolder {
conn: Rc::downgrade(&conn),
id,
}),
ObjectType::PropertyFolder => Folder::PropertyFolder(PropertyFolder {
conn: Rc::downgrade(&conn),
id,
}),
ObjectType::EntityFolder => Folder::EntityFolder(EntityFolder {
conn: Rc::downgrade(&conn),
id,
}),
v => return Err(FolderHelperError::InvalidId(id, v)),
})
}
pub fn conn(&self) -> Result<Rc<Connection>, ExpiredConnectionError> {
match self {
Folder::BlueprintFolder(BlueprintFolder { conn, .. }) => conn,
Folder::PropertyFolder(PropertyFolder { conn, .. }) => conn,
Folder::EntityFolder(EntityFolder { conn, .. }) => conn,
}
.upgrade()
.ok_or(ExpiredConnectionError)
}
pub(crate) fn id(&self) -> TypedId {
*match self {
Folder::BlueprintFolder(BlueprintFolder { id, .. }) => id,
Folder::PropertyFolder(PropertyFolder { id, .. }) => id,
Folder::EntityFolder(EntityFolder { id, .. }) => id,
}
}
pub fn to_object(&self) -> Object {
match self {
Folder::BlueprintFolder(folder) => Object::BlueprintFolder(folder.clone()),
Folder::PropertyFolder(folder) => Object::PropertyFolder(folder.clone()),
Folder::EntityFolder(folder) => Object::EntityFolder(folder.clone()),
}
}
}
pub(crate) fn folder_create(
conn: Rc<Connection>,
object_scope: ObjectScope,
object_type: ObjectType,
parent_folder: Option<Folder>,
position: FolderPosition,
name: impl AsRef<str>,
information: impl AsRef<str>,
) -> Result<TypedId, FolderHelperError> {
validate_position_in_bounds(
conn.clone(),
position,
FolderOrScope::from_opt_folder(parent_folder.clone(), object_scope.clone()),
true,
)?;
let folder = conn.transaction_rc(|conn| {
let folder = object_create(
conn.clone(),
object_type,
object_scope,
parent_folder,
position,
name,
information,
)
.map_err(|e| DbError::TransactionError(e.to_string()))?;
conn.execute("INSERT INTO `folders`(`id`) VALUES(?)", folder)?;
Ok(folder)
})?;
Ok(folder)
}
#[instrument(level = Level::TRACE, skip(conn))]
pub(crate) fn folder_get_children<T: FromId>(
conn: Rc<Connection>,
folder_or_scope: FolderOrScope,
) -> Result<Vec<T>, FolderHelperError> {
validate_folder_or_scope_exists(&folder_or_scope)?;
Ok(match folder_or_scope {
FolderOrScope::Folder(folder) => conn.query_all(
r#"
SELECT `object`
FROM `object_tree`
WHERE `parent` = ?
"#,
folder.id(),
|row| {
Ok(T::from_id(conn.clone(), row.get::<TypedId>(0)?)
.map_err(|e| FromSqlError::Custom(e.to_string()))?)
},
)?,
FolderOrScope::Scope(scope) => conn.query_all(
r#"
SELECT `tree`.`object`
FROM `object_tree` AS `tree`
INNER JOIN `object_scopes` USING (`object`)
WHERE `scope` = ? AND `parent` IS NULL
"#,
scope,
|row| {
Ok(T::from_id(conn.clone(), row.get::<TypedId>(0)?)
.map_err(|e| FromSqlError::Custom(e.to_string()))?)
},
)?,
})
}
pub(crate) fn folder_get_children_count(
conn: Rc<Connection>,
folder_or_scope: FolderOrScope,
) -> Result<usize, FolderHelperError> {
validate_folder_or_scope_exists(&folder_or_scope)?;
Ok(match folder_or_scope {
FolderOrScope::Folder(folder) => conn.query_one(
r#"
SELECT COUNT(`object`)
FROM `object_tree`
WHERE `parent` = ?
"#,
folder.id(),
|row| row.get::<usize>(0),
)?,
FolderOrScope::Scope(scope) => conn.query_one(
r#"
SELECT COUNT(`tree`.`object`)
FROM `object_tree` AS `tree`
INNER JOIN `object_scopes` USING (`object`)
WHERE `scope` = ? AND `parent` IS NULL
"#,
scope,
|row| row.get::<usize>(0),
)?,
})
}