use crate::addons::bool::BoolToResult;
use crate::args::validate::{validate_object_exists, ObjectValidationError, StringValidationError};
use crate::objects::blueprint::{Blueprint, BlueprintError};
use crate::objects::blueprint_folder::{BlueprintFolder, BlueprintFolderError};
use crate::objects::entity::{Entity, EntityError};
use crate::objects::entity_folder::{EntityFolder, EntityFolderError};
use crate::objects::folder::{Folder, FolderHelperError};
use crate::objects::id::TypedId;
use crate::objects::property::{Property, PropertyError, PropertyScope};
use crate::objects::property_folder::{PropertyFolder, PropertyFolderError};
use crate::objects::{ExpiredConnectionError, FromId, FromIdError};
use crate::persistence::project::ProjectError;
use crate::types::location::{FolderPosition, Location};
use bitflags::bitflags;
use sqrite::connection::DbError;
use sqrite::connection::{Connection, RcRefCellExtension};
use sqrite::sql_interface::{FromSql, FromSqlError, ToSql, ToSqlError};
use sqrite::value::Value;
use std::rc::Rc;
use tracing::*;
#[derive(thiserror::Error, Debug)]
pub enum ObjectHelperError {
#[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 interface with folder: {0}")]
FolderError(#[from] FolderHelperError),
#[error("attempted to treat object of type {0} as type {1}")]
InvalidObjectType(ObjectType, ObjectType),
#[error("object {0} has invalid scope type {1}")]
InvalidObjectScope(Object, ObjectType),
#[error("invalid id: {0}")]
InvalidId(#[from] FromIdError),
#[error("attempted to downcast object {0} to {1}")]
InvalidDowncast(Object, &'static str),
}
#[derive(thiserror::Error, Debug)]
pub enum ObjectError {
#[error("error interfacing with blueprint: {0}")]
BlueprintError(#[from] BlueprintError),
#[error("error interfacing with blueprint folder: {0}")]
BlueprintFolderError(#[from] BlueprintFolderError),
#[error("error interfacing with property: {0}")]
PropertyError(#[from] PropertyError),
#[error("error interfacing with property folder: {0}")]
PropertyFolderError(#[from] PropertyFolderError),
#[error("error interfacing with entity: {0}")]
EntityError(#[from] EntityError),
#[error("error interfacing with entity folder: {0}")]
EntityFolderError(#[from] EntityFolderError),
}
#[repr(i8)]
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum ObjectType {
Blueprint = 1,
BlueprintFolder = 2,
Property = 3,
PropertyFolder = 4,
Entity = 5,
EntityFolder = 6,
}
impl ObjectType {
pub fn get_values() -> [ObjectType; 6] {
[
ObjectType::Blueprint,
ObjectType::BlueprintFolder,
ObjectType::Property,
ObjectType::PropertyFolder,
ObjectType::Entity,
ObjectType::EntityFolder,
]
}
pub fn is_folder_type(&self) -> bool {
match self {
ObjectType::BlueprintFolder | ObjectType::PropertyFolder | ObjectType::EntityFolder => {
true
}
_ => false,
}
}
pub fn folder_type(&self) -> ObjectType {
match self {
ObjectType::Blueprint | ObjectType::BlueprintFolder => ObjectType::BlueprintFolder,
ObjectType::Property | ObjectType::PropertyFolder => ObjectType::PropertyFolder,
ObjectType::Entity | ObjectType::EntityFolder => ObjectType::EntityFolder,
}
}
pub fn to_bitset(&self) -> ObjectTypes {
unsafe { ObjectTypes::from_bits(1 << (*self as u64 - 1)).unwrap_unchecked() }
}
pub fn to_int(&self) -> i8 {
match self {
ObjectType::Blueprint => 1,
ObjectType::BlueprintFolder => 2,
ObjectType::Property => 3,
ObjectType::PropertyFolder => 4,
ObjectType::Entity => 5,
ObjectType::EntityFolder => 6,
}
}
pub fn from_int(value: i8) -> Result<ObjectType, i8> {
match value {
1 => Ok(ObjectType::Blueprint),
2 => Ok(ObjectType::BlueprintFolder),
3 => Ok(ObjectType::Property),
4 => Ok(ObjectType::PropertyFolder),
5 => Ok(ObjectType::Entity),
6 => Ok(ObjectType::EntityFolder),
v => Err(v),
}
}
}
impl std::fmt::Display for ObjectType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl FromSql for ObjectType {
#[instrument(level = Level::TRACE)]
fn from_sql(value: Value) -> Result<Self, FromSqlError> {
ObjectType::from_int(<i8 as FromSql>::from_sql(value)?)
.map_err(|v| FromSqlError::Custom(format!("invalid object type: {v}")))
}
}
impl ToSql for ObjectType {
#[instrument(level = Level::TRACE)]
fn to_sql(&self) -> Result<Value, ToSqlError> {
Ok(Value::Integer(self.to_int().into()))
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ObjectTypes : u64 {
const None = 0;
const Blueprint = 1 << (ObjectType::Blueprint as u64 - 1);
const BlueprintFolder = 1 << (ObjectType::BlueprintFolder as u64 - 1);
const Property = 1 << (ObjectType::Property as u64 - 1);
const PropertyFolder = 1 << (ObjectType::PropertyFolder as u64 - 1);
const Entity = 1 << (ObjectType::Entity as u64 - 1);
const EntityFolder = 1 << (ObjectType::EntityFolder as u64 - 1);
const Folder = Self::BlueprintFolder.bits() | Self::PropertyFolder.bits() | Self::EntityFolder.bits();
}
}
impl std::fmt::Display for ObjectTypes {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Debug::fmt(&self, f)
}
}
#[derive(thiserror::Error, Debug)]
pub enum ObjectTypeValidationError {
#[error("{0} has invalid type: expected one of {1} found {2}")]
InvalidType(Object, ObjectTypes, ObjectType),
}
impl ObjectTypes {
#[instrument(level = Level::TRACE)]
pub fn validate(&self, object: Object) -> Result<(), ObjectTypeValidationError> {
let object_type = object.object_type();
self.iter()
.any(|t| t.bits().trailing_zeros() + 1 == object_type as u32)
.true_or(ObjectTypeValidationError::InvalidType(
object,
*self,
object_type,
))?;
Ok(())
}
}
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum ObjectScope {
Blueprints,
Property(PropertyScope),
Entities,
}
impl std::fmt::Display for ObjectScope {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Debug::fmt(&self, f)
}
}
impl ToSql for ObjectScope {
#[instrument(level = Level::TRACE)]
fn to_sql(&self) -> Result<Value, ToSqlError> {
Ok(match self {
ObjectScope::Blueprints => Value::Text(String::from("blueprints")),
ObjectScope::Property(scope) => scope.to_sql()?,
ObjectScope::Entities => Value::Text(String::from("entities")),
})
}
}
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum Object {
Blueprint(Blueprint),
BlueprintFolder(BlueprintFolder),
Property(Property),
PropertyFolder(PropertyFolder),
Entity(Entity),
EntityFolder(EntityFolder),
}
impl std::fmt::Display for Object {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl FromId for Object {
fn from_id(conn: Rc<Connection>, id: TypedId) -> Result<Self, FromIdError>
where
Self: Sized,
{
Ok(match id.get_object_type() {
ObjectType::Blueprint => Object::Blueprint(Blueprint {
conn: Rc::downgrade(&conn),
id,
}),
ObjectType::BlueprintFolder => Object::BlueprintFolder(BlueprintFolder {
conn: Rc::downgrade(&conn),
id,
}),
ObjectType::Property => Object::Property(Property {
conn: Rc::downgrade(&conn),
id,
}),
ObjectType::PropertyFolder => Object::PropertyFolder(PropertyFolder {
conn: Rc::downgrade(&conn),
id,
}),
ObjectType::Entity => Object::Entity(Entity {
conn: Rc::downgrade(&conn),
id,
}),
ObjectType::EntityFolder => Object::EntityFolder(EntityFolder {
conn: Rc::downgrade(&conn),
id,
}),
})
}
}
impl Object {
#[instrument(level = Level::TRACE)]
pub fn conn(&self) -> Result<Rc<Connection>, ExpiredConnectionError> {
match self {
Object::Blueprint(Blueprint { conn, .. }) => conn,
Object::BlueprintFolder(BlueprintFolder { conn, .. }) => conn,
Object::Property(Property { conn, .. }) => conn,
Object::PropertyFolder(PropertyFolder { conn, .. }) => conn,
Object::Entity(Entity { conn, .. }) => conn,
Object::EntityFolder(EntityFolder { conn, .. }) => conn,
}
.upgrade()
.ok_or(ExpiredConnectionError)
}
#[instrument(level = Level::TRACE)]
pub(crate) fn id(&self) -> TypedId {
*match self {
Object::Blueprint(Blueprint { id, .. }) => id,
Object::BlueprintFolder(BlueprintFolder { id, .. }) => id,
Object::Property(Property { id, .. }) => id,
Object::PropertyFolder(PropertyFolder { id, .. }) => id,
Object::Entity(Entity { id, .. }) => id,
Object::EntityFolder(EntityFolder { id, .. }) => id,
}
}
pub fn object_type(&self) -> ObjectType {
match self {
Object::Blueprint(_) => ObjectType::Blueprint,
Object::BlueprintFolder(_) => ObjectType::BlueprintFolder,
Object::Property(_) => ObjectType::Property,
Object::PropertyFolder(_) => ObjectType::PropertyFolder,
Object::Entity(_) => ObjectType::Entity,
Object::EntityFolder(_) => ObjectType::EntityFolder,
}
}
}
macro_rules! object_cast_fn {
($name:ident, $t:ident) => {
pub fn $name(&self) -> Result<$t, ObjectHelperError> {
match self {
Object::$t(v) => Ok(v.clone()),
v => Err(ObjectHelperError::InvalidDowncast(
v.clone(),
stringify!($t),
)),
}
}
};
}
impl Object {
object_cast_fn!(to_blueprint, Blueprint);
object_cast_fn!(to_blueprint_folder, BlueprintFolder);
object_cast_fn!(to_property, Property);
object_cast_fn!(to_property_folder, PropertyFolder);
object_cast_fn!(to_entity, Entity);
object_cast_fn!(to_entity_folder, EntityFolder);
pub fn to_folder(&self) -> Result<Folder, ObjectHelperError> {
match self {
Object::BlueprintFolder(v) => Ok(v.to_folder()),
Object::PropertyFolder(v) => Ok(v.to_folder()),
Object::EntityFolder(v) => Ok(v.to_folder()),
v => Err(ObjectHelperError::InvalidDowncast(
v.clone(),
stringify!($t),
)),
}
}
}
impl Object {
#[instrument(level = Level::INFO)]
pub fn delete(self) -> Result<(), ObjectError> {
Ok(match self {
Object::Blueprint(blueprint) => blueprint.delete()?,
Object::BlueprintFolder(folder) => folder.delete()?,
Object::Property(property) => property.delete()?,
Object::PropertyFolder(folder) => folder.delete()?,
Object::Entity(entity) => entity.delete()?,
Object::EntityFolder(folder) => folder.delete()?,
})
}
#[instrument(level = Level::DEBUG)]
pub fn exists(&self) -> Result<bool, ObjectError> {
Ok(match self {
Object::Blueprint(blueprint) => blueprint.exists()?,
Object::BlueprintFolder(folder) => folder.exists()?,
Object::Property(property) => property.exists()?,
Object::PropertyFolder(folder) => folder.exists()?,
Object::Entity(entity) => entity.exists()?,
Object::EntityFolder(folder) => folder.exists()?,
})
}
#[instrument(level = Level::DEBUG)]
pub fn name(&self) -> Result<String, ObjectError> {
Ok(match self {
Object::Blueprint(blueprint) => blueprint.name()?,
Object::BlueprintFolder(folder) => folder.name()?,
Object::Property(property) => property.name()?,
Object::PropertyFolder(folder) => folder.name()?,
Object::Entity(entity) => entity.name()?,
Object::EntityFolder(folder) => folder.name()?,
})
}
#[instrument(level = Level::INFO)]
pub fn location(&self) -> Result<Location<Folder>, ObjectError> {
Ok(match self {
Object::Blueprint(blueprint) => blueprint.location().map(|v| Location {
parent: v.parent.map(|v| v.to_folder()),
position: v.position,
})?,
Object::BlueprintFolder(folder) => folder.location().map(|v| Location {
parent: v.parent.map(|v| v.to_folder()),
position: v.position,
})?,
Object::Property(property) => property.location().map(|v| Location {
parent: v.parent.map(|v| v.to_folder()),
position: v.position,
})?,
Object::PropertyFolder(folder) => folder.location().map(|v| Location {
parent: v.parent.map(|v| v.to_folder()),
position: v.position,
})?,
Object::Entity(entity) => entity.location().map(|v| Location {
parent: v.parent.map(|v| v.to_folder()),
position: v.position,
})?,
Object::EntityFolder(folder) => folder.location().map(|v| Location {
parent: v.parent.map(|v| v.to_folder()),
position: v.position,
})?,
})
}
#[instrument(level = Level::INFO)]
pub fn set_location(
&mut self,
target_folder: Option<Folder>,
target_position: FolderPosition,
) -> Result<(), ObjectError> {
Ok(match self {
Object::Blueprint(blueprint) => blueprint.set_location(
target_folder
.as_ref()
.map(BlueprintFolder::from_folder)
.transpose()?,
target_position,
)?,
Object::BlueprintFolder(folder) => folder.set_location(
target_folder
.as_ref()
.map(BlueprintFolder::from_folder)
.transpose()?,
target_position,
)?,
Object::Property(property) => property.set_location(
target_folder
.as_ref()
.map(PropertyFolder::from_folder)
.transpose()?,
target_position,
)?,
Object::PropertyFolder(folder) => folder.set_location(
target_folder
.as_ref()
.map(PropertyFolder::from_folder)
.transpose()?,
target_position,
)?,
Object::Entity(entity) => entity.set_location(
target_folder
.as_ref()
.map(EntityFolder::from_folder)
.transpose()?,
target_position,
)?,
Object::EntityFolder(folder) => folder.set_location(
target_folder
.as_ref()
.map(EntityFolder::from_folder)
.transpose()?,
target_position,
)?,
})
}
#[instrument(level = Level::INFO, skip(new_name), fields(new_name = new_name.as_ref()))]
pub fn set_name(&mut self, new_name: impl AsRef<str>) -> Result<(), ObjectError> {
Ok(match self {
Object::Blueprint(blueprint) => blueprint.set_name(new_name)?,
Object::BlueprintFolder(folder) => folder.set_name(new_name)?,
Object::Property(property) => property.set_name(new_name)?,
Object::PropertyFolder(folder) => folder.set_name(new_name)?,
Object::Entity(entity) => entity.set_name(new_name)?,
Object::EntityFolder(folder) => folder.set_name(new_name)?,
})
}
#[instrument(level = Level::DEBUG)]
pub fn information(&self) -> Result<String, ObjectError> {
Ok(match self {
Object::Blueprint(blueprint) => blueprint.information()?,
Object::BlueprintFolder(folder) => folder.information()?,
Object::Property(property) => property.information()?,
Object::PropertyFolder(folder) => folder.information()?,
Object::Entity(entity) => entity.information()?,
Object::EntityFolder(folder) => folder.information()?,
})
}
#[instrument(level = Level::INFO, skip(new_information))]
pub fn set_information(&mut self, new_information: impl AsRef<str>) -> Result<(), ObjectError> {
Ok(match self {
Object::Blueprint(blueprint) => blueprint.set_information(new_information)?,
Object::BlueprintFolder(folder) => folder.set_information(new_information)?,
Object::Property(property) => property.set_information(new_information)?,
Object::PropertyFolder(folder) => folder.set_information(new_information)?,
Object::Entity(entity) => entity.set_information(new_information)?,
Object::EntityFolder(folder) => folder.set_information(new_information)?,
})
}
#[instrument(level = Level::DEBUG)]
pub fn scope(&self) -> Result<ObjectScope, ObjectError> {
Ok(match self {
Object::Blueprint(blueprint) => blueprint.scope()?.to_object_scope(),
Object::BlueprintFolder(folder) => folder.scope()?.to_object_scope(),
Object::Property(property) => property.scope()?.to_object_scope(),
Object::PropertyFolder(folder) => folder.scope()?.to_object_scope(),
Object::Entity(entity) => entity.scope()?.to_object_scope(),
Object::EntityFolder(folder) => folder.scope()?.to_object_scope(),
})
}
}
#[instrument(level = Level::INFO, skip(conn, name, information), fields(name = name.as_ref()))]
pub(crate) fn object_create(
conn: Rc<Connection>,
object_type: ObjectType,
scope: ObjectScope,
parent: Option<Folder>,
position: FolderPosition,
name: impl AsRef<str>,
information: impl AsRef<str>,
) -> Result<TypedId, ObjectHelperError> {
let position = position.resolve(conn.clone(), scope.clone(), parent.clone())?;
let parent_id = parent.map(|v| v.id());
let object = conn.transaction_rc(|conn| {
debug!("incrementing sibling indices");
conn.execute(
r#"
UPDATE `object_tree`
SET `position` = `position` + 1
FROM `object_scopes`
WHERE
`object_tree`.`object` = `object_scopes`.`object` AND
`scope` = ? AND
`parent` IS ? AND
`position` >= ?
"#,
(scope.clone(), parent_id, position),
)?;
debug!("inserting object into objects table");
conn.execute(
"INSERT INTO `objects`(`object_type`, `name`, `information`) VALUES(?, ?, ?)",
(object_type, name.as_ref(), information.as_ref()),
)?;
let last_insert_rowid = conn.last_insert_rowid();
debug!("fetching generated typed id");
let object_id = conn.query_one(
"SELECT `id` FROM `objects` WHERE `rowid` = ?",
last_insert_rowid,
|row| row.get::<TypedId>(0),
)?;
debug!("inserting object into object_scopes table");
conn.execute(
"INSERT INTO `object_scopes`(`object`, `scope`) VALUES(?, ?)",
(object_id, scope.clone()),
)?;
debug!("inserting object into object_tree table");
conn.execute(
"INSERT INTO `object_tree`(`object`, `parent`, `position`) VALUES(?, ?, ?)",
(object_id, parent_id, position),
)?;
Ok(object_id)
})?;
Ok(object)
}
#[instrument(level = Level::INFO)]
pub(crate) fn object_delete(object: Object) -> Result<(), ObjectHelperError> {
validate_object_exists(object.clone())?;
object
.conn()?
.execute("DELETE FROM `objects` WHERE `id` = ?", object.id())?;
Ok(())
}
#[instrument(level = Level::DEBUG)]
pub(crate) fn object_exists(object: Object) -> Result<bool, ObjectHelperError> {
Ok(object.conn()?.query_one(
"SELECT EXISTS(SELECT 1 FROM `objects` WHERE `id` = ?)",
object.id(),
|row| row.get::<bool>(0),
)?)
}
#[instrument(level = Level::DEBUG)]
pub(crate) fn object_location<F: FromId + std::fmt::Debug>(
object: Object,
) -> Result<Location<F>, ObjectHelperError> {
validate_object_exists(object.clone())?;
let conn = object.conn()?;
let data = conn.query_one(
"SELECT `parent`, `position` FROM `object_tree` WHERE `object` = ?",
object.id(),
|row| Ok((row.get::<Option<TypedId>>(0)?, row.get::<i64>(1)? as usize)),
)?;
Ok(Location {
parent: data.0.map(|v| F::from_id(conn, v)).transpose()?,
position: data.1,
})
}
#[instrument(level = Level::INFO)]
pub(crate) fn object_set_location(
object: Object,
target_folder: Option<Folder>,
target_position: FolderPosition,
) -> Result<(), ObjectHelperError> {
validate_object_exists(object.clone())?;
let scope = object_get_scope(object.clone())?;
let target_position =
target_position.resolve(object.conn()?, scope.clone(), target_folder.clone())?;
let target_folder_id = target_folder.map(|v| v.id());
object.conn()?.transaction_rc(|conn| {
let current_location: Location<Folder> = object_location(object.clone())
.map_err(|e| DbError::TransactionError(e.to_string()))?;
conn.execute(
r#"
UPDATE `object_tree`
SET `position` = `position` + 1
FROM `object_scopes`
WHERE
`object_tree`.`object` = `object_scopes`.`object` AND
`scope` = ? AND
`parent` IS ? AND
`position` >= ?
"#,
(scope.clone(), target_folder_id, target_position),
)?;
conn.execute(
r#"
UPDATE `object_tree`
SET `parent` = ?, `position` = ?
WHERE `object` = ?
"#,
(target_folder_id, target_position, object.id()),
)?;
conn.execute(
r#"
UPDATE `object_tree`
SET `position` = `position` - 1
FROM `object_scopes`
WHERE
`object_tree`.`object` = `object_scopes`.`object` AND
`scope` = ? AND
`parent` IS ? AND
`position` > ?
"#,
(
scope.clone(),
current_location.parent.map(|v| v.id()),
current_location.position,
),
)?;
Ok(())
})?;
Ok(())
}
#[instrument(level = Level::DEBUG)]
pub(crate) fn object_name(object: Object) -> Result<String, ObjectHelperError> {
validate_object_exists(object.clone())?;
Ok(object.conn()?.query_one(
"SELECT `name` FROM `objects` WHERE `id` = ?",
object.id(),
|row| row.get(0),
)?)
}
#[instrument(level = Level::INFO, skip(new_name), fields(new_name = new_name.as_ref()))]
pub(crate) fn object_set_name(
object: Object,
new_name: impl AsRef<str>,
) -> Result<(), ObjectHelperError> {
validate_object_exists(object.clone())?;
object.conn()?.execute(
"UPDATE `objects` SET `name` = ? WHERE `id` = ?",
(new_name.as_ref(), object.id()),
)?;
Ok(())
}
#[instrument(level = Level::DEBUG)]
pub(crate) fn object_information(object: Object) -> Result<String, ObjectHelperError> {
validate_object_exists(object.clone())?;
Ok(object.conn()?.query_one(
"SELECT `information` FROM `objects` WHERE `id` = ?",
object.id(),
|row| row.get(0),
)?)
}
#[instrument(level = Level::INFO, skip(new_information))]
pub(crate) fn object_set_information(
object: Object,
new_information: impl AsRef<str>,
) -> Result<(), ObjectHelperError> {
validate_object_exists(object.clone())?;
object.conn()?.execute(
"UPDATE `objects` SET `information` = ? WHERE `id` = ?",
(new_information.as_ref(), object.id()),
)?;
Ok(())
}
#[instrument(level = Level::DEBUG)]
pub(crate) fn object_get_scope(object: Object) -> Result<ObjectScope, ObjectHelperError> {
validate_object_exists(object.clone())?;
Ok(match object.id().get_object_type() {
ObjectType::Blueprint | ObjectType::BlueprintFolder => ObjectScope::Blueprints,
ObjectType::Property | ObjectType::PropertyFolder => {
let scope = object.conn()?.query_one(
"SELECT `scope` FROM `object_scopes` WHERE `object` = ?",
object.id(),
|row| row.get::<TypedId>(0),
)?;
match scope.get_object_type() {
ObjectType::Blueprint => {
ObjectScope::Property(PropertyScope::Blueprint(Blueprint {
conn: Rc::downgrade(&object.conn()?),
id: scope,
}))
}
ObjectType::Entity => ObjectScope::Property(PropertyScope::Entity(Entity {
conn: Rc::downgrade(&object.conn()?),
id: scope,
})),
v => return Err(ObjectHelperError::InvalidObjectScope(object, v)),
}
}
ObjectType::Entity | ObjectType::EntityFolder => ObjectScope::Entities,
})
}