libikarus 0.1.14

The core functionality of Ikarus wrapped neatly in a rust library
Documentation
use crate::objects::id::TypedId;
use crate::objects::object::ObjectScope;
use sqrite::connection::Connection;

pub mod blueprint;
pub mod blueprint_folder;
pub mod entity;
pub mod entity_folder;
pub mod id;
pub mod property;
pub mod property_folder;
pub mod property_settings;

pub(crate) mod folder;
pub(crate) mod object;

#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct ExpiredConnectionError;

impl std::fmt::Display for ExpiredConnectionError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl std::error::Error for ExpiredConnectionError {}

#[derive(thiserror::Error, Debug)]
pub enum FromObjectScopeError {
    #[error("{0} scope cannot be constructed from scope {1}")]
    InvalidScope(&'static str, ObjectScope),
}

#[derive(thiserror::Error, Debug)]
pub enum FromIdError {
    #[error("{0} cannot be constructed from id {1}")]
    InvalidId(&'static str, TypedId),
}

pub trait FromId {
    fn from_id(conn: std::rc::Rc<Connection>, id: TypedId) -> Result<Self, FromIdError>
    where
        Self: Sized;
}

pub trait ToObject {
    fn to_object(&self) -> object::Object
    where
        Self: Sized;
}

#[macro_export]
macro_rules! define_object {
    ($name:ident, $folder:ident, $scope:ident, $err:ident) => {
        #[derive(Clone)]
        pub struct $name {
            pub(crate) conn: std::rc::Weak<Connection>,
            pub(crate) id: TypedId,
        }

        impl std::cmp::PartialOrd for $name {
            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
                self.id.partial_cmp(&other.id)
            }
        }

        impl std::cmp::Ord for $name {
            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
                self.id.cmp(&other.id)
            }
        }

        impl std::hash::Hash for $name {
            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
                std::hash::Hash::hash(&self.id, state)
            }
        }

        impl std::fmt::Debug for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "{}", self.id)
            }
        }

        impl std::fmt::Display for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "{}", self.id)
            }
        }

        impl std::cmp::PartialEq<$name> for $name {
            fn eq(&self, other: &$name) -> bool {
                // type information is already present in the id
                other.id() == self.id()
            }
        }

        impl std::cmp::PartialEq<$name> for &$name {
            fn eq(&self, other: &$name) -> bool {
                // type information is already present in the id
                other.id() == self.id()
            }
        }

        impl std::cmp::PartialEq<$name> for Object {
            fn eq(&self, other: &$name) -> bool {
                // type information is already present in the id
                other.id() == self.id()
            }
        }

        impl std::cmp::PartialEq<$name> for &Object {
            fn eq(&self, other: &$name) -> bool {
                // type information is already present in the id
                other.id() == self.id()
            }
        }

        impl std::cmp::Eq for $name {}

        impl crate::objects::FromId for $name {
            fn from_id(
                conn: std::rc::Rc<Connection>,
                id: TypedId,
            ) -> Result<Self, crate::objects::FromIdError> {
                if id.get_object_type() != ObjectType::$name {
                    Err(crate::objects::FromIdError::InvalidId(
                        stringify!($name),
                        id,
                    ))
                } else {
                    Ok(Self {
                        id,
                        conn: std::rc::Rc::downgrade(&conn),
                    })
                }
            }
        }

        impl crate::objects::ToObject for $name {
            fn to_object(&self) -> crate::objects::object::Object {
                Object::$name(self.clone())
            }
        }

        impl $name {
            pub fn to_object(&self) -> Object {
                Object::$name(self.clone())
            }

            pub(crate) fn id(&self) -> TypedId {
                self.id
            }

            pub fn conn(
                &self,
            ) -> Result<std::rc::Rc<Connection>, crate::objects::ExpiredConnectionError> {
                self.conn
                    .upgrade()
                    .ok_or(crate::objects::ExpiredConnectionError)
            }
        }

        impl $name {
            #[instrument(level = Level::INFO)]
            pub fn delete(self) -> Result<(), $err> {
                crate::args::validate::validate_object_exists(self.to_object())?;

                Ok(object_delete(self.to_object())?)
            }

            #[instrument(level = Level::DEBUG)]
            pub fn exists(&self) -> Result<bool, $err> {
                // explicitly not validating that the object exists

                Ok(object_exists(self.to_object())?)
            }

            #[instrument(level = Level::DEBUG)]
            pub fn location(&self) -> Result<crate::types::location::Location<$folder>, $err> {
                crate::args::validate::validate_object_exists(self.to_object())?;

                Ok(object_location::<$folder>(self.to_object())?)
            }

            #[instrument(level = Level::INFO)]
            pub fn set_location(
                &mut self,
                target_folder: Option<$folder>,
                target_position: FolderPosition,
            ) -> Result<(), $err> {
                crate::args::validate::validate_object_exists(self.to_object())?;

                Ok(object_set_location(
                    self.to_object(),
                    target_folder.map(|v| v.to_folder()),
                    target_position,
                )?)
            }

            #[instrument(level = Level::DEBUG)]
            pub fn name(&self) -> Result<String, $err> {
                crate::args::validate::validate_object_exists(self.to_object())?;

                Ok(object_name(self.to_object())?)
            }

            #[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<(), $err> {
                crate::args::validate::validate_object_exists(self.to_object())?;
                crate::args::validate::validate_string_not_blank(&new_name)?;

                Ok(object_set_name(self.to_object(), new_name)?)
            }

            #[instrument(level = Level::DEBUG)]
            pub fn information(&self) -> Result<String, $err> {
                crate::args::validate::validate_object_exists(self.to_object())?;

                Ok(object_information(self.to_object())?)
            }

            #[instrument(level = Level::INFO, skip(new_information))]
            pub fn set_information(
                &mut self,
                new_information: impl AsRef<str>,
            ) -> Result<(), $err> {
                crate::args::validate::validate_object_exists(self.to_object())?;

                Ok(object_set_information(self.to_object(), new_information)?)
            }

            #[instrument(level = Level::DEBUG)]
            pub fn scope(&self) -> Result<$scope, $err> {
                crate::args::validate::validate_object_exists(self.to_object())?;

                Ok($scope::from_object_scope(object_get_scope(
                    self.to_object(),
                )?)?)
            }
        }
    };
}

#[macro_export]
macro_rules! define_folder_funcs_impl {
    ($name:ident, $obj:ident, $err:ident) => {
        impl $name {
            pub fn to_folder(&self) -> Folder {
                Folder::$name(self.clone())
            }

            pub fn from_folder(folder: &Folder) -> Result<$name, $err> {
                match &folder {
                    Folder::$name(v) => Ok(v.clone()),
                    _ => Err($err::InvalidFolder(folder.clone())),
                }
            }
        }

        impl $name {
            #[instrument(level = Level::DEBUG)]
            pub fn get_children(&self) -> Result<Vec<$obj>, $err> {
                crate::args::validate::validate_object_exists(self.to_object())?;

                Ok(folder_get_children(
                    self.conn()?.clone(),
                    FolderOrScope::Folder(self.to_folder()),
                )?)
            }

            #[instrument(level = Level::DEBUG)]
            pub fn get_children_count(&self) -> Result<usize, $err> {
                crate::args::validate::validate_object_exists(self.to_object())?;

                Ok(folder_get_children_count(
                    self.conn()?.clone(),
                    FolderOrScope::Folder(self.to_folder()),
                )?)
            }
        }
    };
}

#[macro_export]
macro_rules! define_folder_funcs {
    ($name:ident, $obj:ident, Value $scope:expr, $err:ident) => {
        impl $name {
            #[instrument(level = Level::INFO, skip(conn, name, information), fields(name = name.as_ref()))]
            pub fn new(
                conn: Rc<Connection>,
                parent_folder: Option<&$name>,
                position: FolderPosition,
                name: impl AsRef<str>,
                information: impl AsRef<str>,
            ) -> Result<Self, $err> {
                crate::args::validate::validate_string_not_blank(&name)?;

                let folder = folder_create(
                    conn.clone(),
                    $scope,
                    ObjectType::$name,
                    parent_folder.map(|v| v.to_folder()),
                    position,
                    name,
                    information,
                )?;

                Ok($name {
                    id: folder,
                    conn: Rc::downgrade(&conn),
                })
            }
        }

        crate::define_folder_funcs_impl!($name, $obj, $err);
    };
    ($name:ident, $obj:ident, Type $scope:ty, $err:ident) => {
        impl $name {
            #[instrument(level = Level::INFO, skip(conn, name, information), fields(name = name.as_ref()))]
            pub fn new(
                conn: Rc<Connection>,
                scope: $scope,
                parent_folder: Option<&$name>,
                position: FolderPosition,
                name: impl AsRef<str>,
                information: impl AsRef<str>,
            ) -> Result<Self, $err> {
                crate::args::validate::validate_string_not_blank(&name)?;

                let folder = folder_create(
                    conn.clone(),
                    scope.to_object_scope(),
                    ObjectType::$name,
                    parent_folder.map(|v| v.to_folder()),
                    position,
                    name,
                    information,
                )?;

                Ok($name {
                    id: folder,
                    conn: Rc::downgrade(&conn),
                })
            }
        }

        crate::define_folder_funcs_impl!($name, $obj, $err);
    };
}