use crate::store::ReadValue;
use crate::store::StorageError;
use crate::store::places::Taken;
use crate::store::rule::Invalid;
use amethystate_core::failure::{Because, spelled};
use amethystate_core::path::{StorePath, StorePathError};
use error_stack::Report;
use std::fmt;
use std::sync::Arc;
pub enum OpenStruct {
Refused { at: StorePath, said: Arc<str> },
WillNotRead { at: StorePath, why: Because },
Taken(Box<Taken>),
NotAPath(StorePathError),
Store(Because),
Declined(Invalid),
}
pub trait Schema: Sized {
fn open(store: &crate::Store) -> Result<Self, OpenStruct>;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is opened by hand, and nothing implements `Open` for it",
label = "`open = manual` leaves `Open` to the struct's author",
note = "write `impl Open for {Self}`, starting from `<Self as Schema>::open(store)`"
)]
pub trait Open: Sized {
fn new_with(store: &crate::Store) -> Result<Self, OpenStruct>;
}
impl OpenStruct {
pub fn explain(&self) -> String {
match self {
Self::Store(why) | Self::WillNotRead { why, .. } => why.explain(),
other => other.to_string(),
}
}
}
impl fmt::Display for OpenStruct {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Refused { at, said } => {
write!(f, "a declared rule refused what is stored at {at}: {said}")
}
Self::WillNotRead { at, .. } => {
write!(f, "what is stored at {at} will not read back")
}
Self::Taken(taken) => write!(f, "{taken}"),
Self::NotAPath(why) => write!(f, "the field was given no path to sit at: {why}"),
Self::Store(why) => write!(f, "{}", why.current_context()),
Self::Declined(said) => {
write!(
f,
"the struct's own constructor declined to open it: {said}"
)
}
}
}
}
impl fmt::Debug for OpenStruct {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
spelled(self, f)
}
}
impl std::error::Error for OpenStruct {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::NotAPath(why) => Some(why),
Self::Store(why) | Self::WillNotRead { why, .. } => {
why.caused().map(|under| under as &dyn std::error::Error)
}
Self::Refused { .. } | Self::Taken(_) | Self::Declined(_) => None,
}
}
}
impl From<Invalid> for OpenStruct {
fn from(said: Invalid) -> Self {
Self::Declined(said)
}
}
impl From<Box<Taken>> for OpenStruct {
fn from(taken: Box<Taken>) -> Self {
Self::Taken(taken)
}
}
impl From<StorePathError> for OpenStruct {
fn from(why: StorePathError) -> Self {
Self::NotAPath(why)
}
}
impl From<Report<StorageError>> for OpenStruct {
fn from(why: Report<StorageError>) -> Self {
Self::Store(why.into())
}
}
impl From<ReadValue> for OpenStruct {
fn from(why: ReadValue) -> Self {
match why {
ReadValue::NotAPath(why) => Self::NotAPath(why),
ReadValue::WillNotRead { at, why } => Self::WillNotRead { at, why },
ReadValue::Store(why) => Self::Store(why),
ReadValue::Closed { at } => Self::Store(
Report::new(StorageError::Closed)
.attach(amethystate_core::facts::Key(at))
.into(),
),
}
}
}
impl From<amethystate_core::primitives::error::WriteValue> for OpenStruct {
fn from(why: amethystate_core::primitives::error::WriteValue) -> Self {
Self::Store(Report::<StorageError>::from(why).into())
}
}
impl From<OpenStruct> for Report<StorageError> {
fn from(why: OpenStruct) -> Self {
match why {
OpenStruct::Store(report) | OpenStruct::WillNotRead { why: report, .. } => {
report.into_report()
}
OpenStruct::NotAPath(why) => Report::new(why).change_context(StorageError::Path),
OpenStruct::Refused { at, said } => Report::new(StorageError::Read)
.attach(amethystate_core::facts::Key(at))
.attach(amethystate_core::facts::Refused(said.to_string())),
OpenStruct::Taken(taken) => crate::store::places::refused(&taken),
OpenStruct::Declined(said) => Report::new(StorageError::Read)
.attach(amethystate_core::facts::Refused(said.reason().to_string())),
}
}
}
pub enum OpenStore {
WouldNotOpen { why: Because },
Migrating {
why: Because,
report: Option<crate::MigrationReport>,
},
Store(Because),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WillNotOpen {
#[default]
Refuse,
StartFresh,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WhenItWillNotRead {
TryAgainFor(std::time::Duration),
SetAside,
Refuse,
Overwrite,
}
impl Default for WhenItWillNotRead {
fn default() -> Self {
Self::TryAgainFor(std::time::Duration::from_secs(5))
}
}
impl OpenStore {
pub fn from_store(why: Report<StorageError>) -> Self {
match *why.current_context() {
StorageError::Open => Self::WouldNotOpen { why: why.into() },
StorageError::Migrate => Self::Migrating {
why: why.into(),
report: None,
},
_ => Self::Store(why.into()),
}
}
pub(crate) fn migrating(report: crate::MigrationReport) -> Self {
let why = report
.failures()
.next()
.map(told)
.unwrap_or_else(|| Report::new(StorageError::Migrate));
Self::Migrating {
why: why.into(),
report: Some(report),
}
}
pub fn explain(&self) -> String {
self.why().explain()
}
fn why(&self) -> &Because {
let (Self::WouldNotOpen { why } | Self::Migrating { why, .. } | Self::Store(why)) = self;
why
}
}
fn told(failed: &Report<StorageError>) -> Report<StorageError> {
use error_stack::{AttachmentKind, FrameKind};
failed.frames().fold(
Report::new(StorageError::Migrate),
|told, frame| match frame.kind() {
FrameKind::Context(context) => told.attach(context.to_string()),
FrameKind::Attachment(AttachmentKind::Printable(said)) => told.attach(said.to_string()),
FrameKind::Attachment(_) => told,
},
)
}
impl fmt::Display for OpenStore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::WouldNotOpen { .. } => f.write_str("the store would not open"),
Self::Migrating { .. } => f.write_str(
"the store opened, and the data would not come up to the declared schema",
),
Self::Store(why) => write!(f, "{}", why.current_context()),
}
}
}
impl fmt::Debug for OpenStore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
spelled(self, f)
}
}
impl std::error::Error for OpenStore {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.why()
.caused()
.map(|under| under as &dyn std::error::Error)
}
}
impl From<Report<StorageError>> for OpenStore {
fn from(why: Report<StorageError>) -> Self {
Self::from_store(why)
}
}
impl From<OpenStore> for Report<StorageError> {
fn from(why: OpenStore) -> Self {
let (OpenStore::WouldNotOpen { why }
| OpenStore::Migrating { why, .. }
| OpenStore::Store(why)) = why;
why.into_report()
}
}