diff options
Diffstat (limited to 'plugins/warp/src/container.rs')
| -rw-r--r-- | plugins/warp/src/container.rs | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/plugins/warp/src/container.rs b/plugins/warp/src/container.rs new file mode 100644 index 00000000..8d720021 --- /dev/null +++ b/plugins/warp/src/container.rs @@ -0,0 +1,266 @@ +use crate::container::disk::NAMESPACE_DISK_SOURCE; +use std::collections::HashMap; +use std::fmt::{Debug, Display}; +use std::hash::Hash; +use std::io; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use thiserror::Error; +use uuid::Uuid; +use warp::r#type::guid::TypeGUID; +use warp::r#type::{ComputedType, Type}; +use warp::signature::function::{Function, FunctionGUID}; +use warp::target::Target; + +pub mod disk; +pub mod memory; +pub mod network; + +pub type ContainerResult<T> = Result<T, ContainerError>; + +#[derive(Debug, Error, PartialEq, Eq, Hash)] +pub enum ContainerError { + #[error("source {0} was not found")] + SourceNotFound(SourceId), + #[error("source {0} is not writable")] + SourceNotWritable(SourceId), + #[error("source with path {0} already exists")] + SourceAlreadyExists(SourcePath), + #[error("source with path {0} cannot be created in container")] + CannotCreateSource(SourcePath), + #[error("operation failed due to corrupted data: {0}")] + CorruptedData(&'static str), + #[error("failed io operation: {0}")] + FailedIO(io::ErrorKind), + #[error("source {0} does not have an available path")] + SourcePathUnavailable(SourceId), +} + +/// Represents the ID for a single container source. +/// +/// A [`SourceId`] can be used in multiple separate containers, but **must** be unique in a container. +/// +/// A source is used to relate types and functions separate from the container. This allows +/// type name lookups and for containers which are bandwidth sensitive to exist. +/// +/// An example of a bandwidth-sensitive container would be a container that pulls functions over +/// the network instead of from memory. +/// +/// This type is marked `repr(transparent)` to the underlying `[u8; 16]` type, so it is safe to use in FFI. +#[repr(transparent)] +#[derive(Clone, Debug, Eq, PartialEq, Hash, Copy)] +pub struct SourceId(Uuid); + +impl SourceId { + pub fn new() -> Self { + Self(Uuid::new_v4()) + } +} + +impl From<Uuid> for SourceId { + fn from(value: Uuid) -> Self { + Self(value) + } +} + +impl FromStr for SourceId { + type Err = uuid::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Uuid::parse_str(s).map(Into::into) + } +} + +impl Display for SourceId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Represents a unique path to a source. +/// +/// This is used when first creating a source for a container, the path is given to the container +/// as otherwise the user has no control over source creation and where the source is ultimately located. +/// +/// While the underlying type is a [`PathBuf`], a source path can be really anything, the [`PathBuf`] +/// just provides an easier way to join segments for nested source locations. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct SourcePath(PathBuf); + +impl SourcePath { + pub fn new(path: PathBuf) -> Self { + Self(path) + } + + pub fn new_with_str(value: &str) -> Self { + Self(PathBuf::from(value)) + } + + pub fn to_source_id(&self) -> SourceId { + // TODO: This path is not relative to the disk container is it? + // TODO: The path here should be relative to the container I think? + // TODO: The above is important so that the id is the same across users. + let value: Vec<u8> = self.to_string().into_bytes(); + SourceId(Uuid::new_v5(&NAMESPACE_DISK_SOURCE, &value)) + } +} + +impl AsRef<PathBuf> for SourcePath { + fn as_ref(&self) -> &PathBuf { + &self.0 + } +} + +impl AsRef<Path> for SourcePath { + fn as_ref(&self) -> &Path { + &self.0 + } +} + +impl Display for SourcePath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.display()) + } +} + +/// Storage for WARP information. +/// +/// Containers are made up of sources, see [`SourceId`] for more details. +pub trait Container: Send + Sync + Display + Debug { + /// Available container sources. + /// + /// NOTE: Due to the nature of some containers, this list of sources may be incomplete. Do not + /// rely on this list to retrieve data, instead prefer: + /// - [Container::sources_with_type_guid] + /// - [Container::sources_with_type_guids] + /// - [Container::sources_with_function_guid] + /// - [Container::sources_with_function_guids] + fn sources(&self) -> ContainerResult<Vec<SourceId>>; + + /// Create a new source in the container or add the existing source at the given path to known sources. + /// + /// The returned [`SourceId`] can be used to add, query and remove information from the source. + /// + /// NOTE: Adding a source does **NOT** mean that it, and the data associated with it, has been + /// persisted, you **MUST** call [`Container::commit_source`] to persist the created source. + /// + /// NOTE: Adding a source does **NOT** mean that you can write to it, use [`Container::is_source_writable`] + /// to verify the permissions of the source. + fn add_source(&mut self, path: SourcePath) -> ContainerResult<SourceId>; + + /// Flush changes made to a source. + /// + /// Because writing to a source can require file or network operations, we let the container + /// offer the ability to hold off performing that operation until the data needs to be committed. + fn commit_source(&mut self, source: &SourceId) -> ContainerResult<bool>; + + /// Whether the source can be written to. + /// + /// The source must be mutable to perform the following: + /// - [Container::add_types] + /// - [Container::add_computed_types] + /// - [Container::remove_types] + /// - [Container::add_functions] + /// - [Container::remove_functions] + fn is_source_writable(&self, source: &SourceId) -> ContainerResult<bool>; + + /// Whether the source has uncommitted changes or not. + /// + /// NOTE: This is **NOT** whether the source has been committed at all, rather a flag to indicate + /// that a source has uncommitted changes. + fn is_source_uncommitted(&self, source: &SourceId) -> ContainerResult<bool>; + + /// Retrieve the [`SourcePath`] for the given source. + /// + /// NOTE: This does not have to be a filesystem path, its representation is dictated + /// by the implementation. + fn source_path(&self, source: &SourceId) -> ContainerResult<SourcePath>; + + // TODO: Note about commit_source + fn add_types(&mut self, source: &SourceId, types: &[Type]) -> ContainerResult<()> { + let computed_types: Vec<_> = types.iter().cloned().map(ComputedType::new).collect(); + self.add_computed_types(source, &computed_types) + } + + // TODO: Note about commit_source + fn add_computed_types( + &mut self, + source: &SourceId, + types: &[ComputedType], + ) -> ContainerResult<()>; + + // TODO: Note about commit_source + fn remove_types(&mut self, source: &SourceId, guids: &[TypeGUID]) -> ContainerResult<()>; + + // TODO: Note about commit_source + fn add_functions( + &mut self, + target: &Target, + source: &SourceId, + functions: &[Function], + ) -> ContainerResult<()>; + + // TODO: Note about commit_source + fn remove_functions( + &mut self, + target: &Target, + source: &SourceId, + functions: &[Function], + ) -> ContainerResult<()>; + + /// Get the sources that contain a type with the given [`TypeGUID`]. + fn sources_with_type_guid(&self, guid: &TypeGUID) -> ContainerResult<Vec<SourceId>>; + + /// Plural version of [`Container::sources_with_type_guid`]. + /// + /// Each source will have a list of the containing GUID's so that when looking up a source, you give + /// it only the GUID's that it knows about, for networking this means cutting down traffic significantly. + fn sources_with_type_guids( + &self, + guids: &[TypeGUID], + ) -> ContainerResult<HashMap<TypeGUID, Vec<SourceId>>>; + + /// Retrieve all [`TypeGUID`]'s with the given name. + fn type_guids_with_name(&self, source: &SourceId, name: &str) + -> ContainerResult<Vec<TypeGUID>>; + + fn type_with_guid(&self, source: &SourceId, guid: &TypeGUID) -> ContainerResult<Option<Type>>; + + fn has_type_with_guid(&self, source: &SourceId, guid: &TypeGUID) -> ContainerResult<bool> { + Ok(self.type_with_guid(source, guid)?.is_some()) + } + + /// Get the sources that contain functions with the given [`FunctionGUID`]. + fn sources_with_function_guid( + &self, + target: &Target, + guid: &FunctionGUID, + ) -> ContainerResult<Vec<SourceId>>; + + // TODO: Allocating with Vec is not good. + /// Plural version of [`Container::sources_with_function_guid`]. + /// + /// Each source will have a list of the containing GUID's so that when looking up a source you give + /// it only the GUID's that it knows about, for networking this means cutting down traffic significantly. + fn sources_with_function_guids( + &self, + target: &Target, + guids: &[FunctionGUID], + ) -> ContainerResult<HashMap<FunctionGUID, Vec<SourceId>>>; + + fn functions_with_guid( + &self, + target: &Target, + source: &SourceId, + guid: &FunctionGUID, + ) -> ContainerResult<Vec<Function>>; + + fn has_function_with_guid( + &self, + target: &Target, + source: &SourceId, + guid: &FunctionGUID, + ) -> ContainerResult<bool> { + Ok(!self.functions_with_guid(target, source, guid)?.is_empty()) + } +} |
