hayson-engine 0.22.0

Storage engine for hayson document databases — the embeddable core behind hayson-server, hayson-cli and hayson-browser
Documentation
use std::fmt;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonSchemaError(pub String);

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

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationIssue {
    pub field: String,
    pub reason: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonValidationError {
    pub table: String,
    pub issues: Vec<ValidationIssue>,
}

impl fmt::Display for HaysonValidationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let detail = self
            .issues
            .iter()
            .map(|issue| format!("{}: {}", issue.field, issue.reason))
            .collect::<Vec<_>>()
            .join("; ");
        write!(f, "record rejected by schema of table \"{}\" — {}", self.table, detail)
    }
}

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonUniqueViolationError {
    pub table: String,
    pub field: String,
    pub holder: String,
}

impl fmt::Display for HaysonUniqueViolationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "unique field \"{}\" of table \"{}\" is already taken by key \"{}\"",
            self.field, self.table, self.holder
        )
    }
}

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

/// A write refused because it broke an `enforce: true` relation — `from.field`'s new value has no
/// matching record on the `to` side. `value` is the offending value's JSON text.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonRelationViolationError {
    pub relation: String,
    pub table: String,
    pub field: String,
    pub value: String,
}

impl fmt::Display for HaysonRelationViolationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "relation \"{}\" refuses \"{}\".\"{}\" = {}: no record on the other side of the relation matches it",
            self.relation, self.table, self.field, self.value
        )
    }
}

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

/// A `delete` refused because an `on_delete: 'restrict'` relation still has a live record
/// referencing the key being deleted. `referencing` is the count of records found still pointing
/// at it — capped at the same sample the diagnostic report uses, never a full scan's whole count.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonRelationRestrictError {
    pub relation: String,
    pub table: String,
    pub key: String,
    pub referencing_table: String,
    pub referencing_field: String,
    pub referencing: usize,
}

impl fmt::Display for HaysonRelationRestrictError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "relation \"{}\" refuses deleting \"{}\".\"{}\": {} record(s) of \"{}\" still point at it through \"{}\"",
            self.relation, self.table, self.key, self.referencing, self.referencing_table, self.referencing_field
        )
    }
}

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonPendingConflictError {
    pub table: String,
    pub key: String,
    pub fields: Vec<String>,
}

impl fmt::Display for HaysonPendingConflictError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let list = self
            .fields
            .iter()
            .map(|field| format!("\"{field}\""))
            .collect::<Vec<_>>()
            .join(", ");
        write!(
            f,
            "key \"{}\" of table \"{}\" already has a queued change on {} — pass force to overwrite it",
            self.key, self.table, list
        )
    }
}

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonCorruptLogError {
    pub path: String,
    pub offset: u64,
    pub reason: String,
}

impl fmt::Display for HaysonCorruptLogError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "log \"{}\" is corrupt at offset {}: {}",
            self.path, self.offset, self.reason
        )
    }
}

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonIndexMismatchError {
    pub path: String,
    /// The log the snapshot claimed to describe — on a partitioned table, the one partition whose
    /// fingerprint disagreed, not the table as a whole.
    pub log_path: String,
    /// Byte the snapshot claims to describe `log_path` up to; equal to that log's size, because a
    /// snapshot whose watermark differs is stale rather than mismatched.
    pub watermark: u64,
    pub expected_hash: String,
    pub actual_hash: String,
}

impl fmt::Display for HaysonIndexMismatchError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "snapshot \"{}\" claims watermark and schema match the log, but its content fingerprint ({}) does not match the log's ({}) — left on disk for inspection, not silently discarded",
            self.path, self.expected_hash, self.actual_hash
        )
    }
}

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

/// The log file behind an open handle is no longer the file at its path: something outside this
/// engine unlinked or replaced it — a `git checkout` of a data directory kept under version
/// control, a restore from backup, a stray `mv`. An append would land in the orphaned inode and
/// vanish with it, so the engine refuses the write; the database has to be reopened to attach to
/// the file now at that path.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonLogReplacedError {
    pub path: String,
}

impl fmt::Display for HaysonLogReplacedError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "log file \"{}\" was replaced on disk while this database held it open — reopen the database before writing again",
            self.path
        )
    }
}

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

/// A write carried an image whose bytes differ from the file already stored under the same content
/// hash. The address is a 64-bit FNV-1a, so two different images can collide; the engine refuses the
/// later one instead of letting it resolve to the earlier one's bytes. `physical_path` is the file
/// name the two share.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonImageClashError {
    pub physical_path: String,
}

impl fmt::Display for HaysonImageClashError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "image bytes differ from the stored file \"{}\" that has the same content hash — refusing to replace it",
            self.physical_path
        )
    }
}

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

/// A write carried an image larger than the `max_image_bytes` the database was opened with.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonImageTooLargeError {
    pub bytes: u64,
    pub max_bytes: u64,
}

impl fmt::Display for HaysonImageTooLargeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "image is {} bytes, over the {} byte limit this database was opened with",
            self.bytes, self.max_bytes
        )
    }
}

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonNotFoundError {
    pub table: String,
    pub key: String,
}

impl fmt::Display for HaysonNotFoundError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "key \"{}\" is not in table \"{}\"", self.key, self.table)
    }
}

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonQueryParseError {
    pub position: usize,
    pub reason: String,
}

impl fmt::Display for HaysonQueryParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "query text is invalid at position {}: {}",
            self.position, self.reason
        )
    }
}

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

/// A record cannot be placed in a partition file: its `partitionBy` value cannot name one, or the
/// write would move an existing key out of the partition it already lives in.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonPartitionError {
    pub table: String,
    pub field: String,
    pub reason: String,
}

impl HaysonPartitionError {
    pub fn new(table: impl Into<String>, field: impl Into<String>, reason: impl Into<String>) -> Self {
        HaysonPartitionError {
            table: table.into(),
            field: field.into(),
            reason: reason.into(),
        }
    }
}

impl fmt::Display for HaysonPartitionError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "partition field \"{}\" of table \"{}\": {}",
            self.field, self.table, self.reason
        )
    }
}

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

/// A `cypher` field failed to seal/open, or the keyring passed to `open()` is itself unusable.
/// `table` empty means the second case — the message drops the "of table" clause then.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HaysonCipherError {
    pub table: String,
    pub field: String,
    pub reason: String,
    pub key_id: Option<String>,
}

impl HaysonCipherError {
    pub fn new(table: impl Into<String>, field: impl Into<String>, reason: impl Into<String>) -> Self {
        HaysonCipherError {
            table: table.into(),
            field: field.into(),
            reason: reason.into(),
            key_id: None,
        }
    }

    pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
        self.key_id = Some(key_id.into());
        self
    }
}

impl fmt::Display for HaysonCipherError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.table.is_empty() {
            write!(f, "cypher configuration {}", self.reason)
        } else {
            write!(
                f,
                "encrypted field \"{}\" of table \"{}\" {}",
                self.field, self.table, self.reason
            )
        }
    }
}

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