Skip to main content

okf_core/
error.rs

1//! Error types for the crate.
2
3use crate::concept_id::ConceptIdError;
4use crate::yaml::YamlError;
5use std::fmt;
6
7/// Errors raised when parsing or validating a single OKF concept document.
8#[derive(Clone, Debug, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum DocumentError {
11    /// The frontmatter opened with `---` but no closing `---` was found.
12    UnterminatedFrontmatter,
13    /// The frontmatter block did not contain a YAML mapping.
14    FrontmatterNotMapping,
15    /// The YAML frontmatter could not be parsed.
16    InvalidYaml(YamlError),
17    /// Required frontmatter keys are missing or empty.
18    MissingKeys(Vec<String>),
19    /// The file's path could not be turned into a valid concept id.
20    InvalidConceptId(ConceptIdError),
21}
22
23impl fmt::Display for DocumentError {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Self::UnterminatedFrontmatter => {
27                write!(f, "Unterminated YAML frontmatter block")
28            }
29            Self::FrontmatterNotMapping => {
30                write!(f, "Frontmatter must be a YAML mapping")
31            }
32            Self::InvalidYaml(e) => write!(f, "Invalid YAML in frontmatter: {e}"),
33            Self::MissingKeys(keys) => {
34                write!(f, "Missing required frontmatter keys: {}", keys.join(", "))
35            }
36            Self::InvalidConceptId(e) => write!(f, "Invalid concept id: {e}"),
37        }
38    }
39}
40
41impl std::error::Error for DocumentError {
42    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
43        match self {
44            Self::InvalidYaml(e) => Some(e),
45            Self::InvalidConceptId(e) => Some(e),
46            _ => None,
47        }
48    }
49}
50
51impl From<YamlError> for DocumentError {
52    fn from(e: YamlError) -> Self {
53        Self::InvalidYaml(e)
54    }
55}
56
57impl From<ConceptIdError> for DocumentError {
58    fn from(e: ConceptIdError) -> Self {
59        Self::InvalidConceptId(e)
60    }
61}
62
63/// Errors raised when loading or operating on a bundle on disk.
64#[derive(Clone, Debug)]
65#[non_exhaustive]
66pub enum BundleError {
67    /// An I/O error occurred while reading the bundle.
68    ///
69    /// The original [`std::io::Error`] is not `Clone`, so the kind and the
70    /// rendered message are stored instead. The [`std::error::Error::source`]
71    /// chain is therefore unavailable for this variant; use
72    /// [`BundleError::io_kind`] to inspect the failure category.
73    Io {
74        /// The kind of I/O failure (`NotFound`, `PermissionDenied`, ...).
75        kind: std::io::ErrorKind,
76        /// The rendered message of the original error, which preserves any
77        /// path context the OS or stdlib attached.
78        message: String,
79    },
80    /// The bundle root does not exist or is not a directory.
81    NotADirectory(std::path::PathBuf),
82    /// A concept document failed to parse.
83    Document {
84        /// Path to the offending file.
85        path: std::path::PathBuf,
86        /// The underlying document error.
87        error: DocumentError,
88    },
89}
90
91impl BundleError {
92    /// The [`std::io::ErrorKind`] for an [`BundleError::Io`] variant, or `None`
93    /// for any other variant. Convenient for matching on the failure category
94    /// without downcasting.
95    #[must_use]
96    pub const fn io_kind(&self) -> Option<std::io::ErrorKind> {
97        match self {
98            Self::Io { kind, .. } => Some(*kind),
99            _ => None,
100        }
101    }
102}
103
104impl fmt::Display for BundleError {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        match self {
107            Self::Io { message, .. } => write!(f, "I/O error: {message}"),
108            Self::NotADirectory(p) => {
109                write!(f, "bundle root is not a directory: {}", p.display())
110            }
111            Self::Document { path, error } => {
112                write!(f, "{}: {error}", path.display())
113            }
114        }
115    }
116}
117
118impl std::error::Error for BundleError {
119    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
120        match self {
121            Self::Document { error, .. } => Some(error),
122            _ => None,
123        }
124    }
125}
126
127impl From<std::io::Error> for BundleError {
128    fn from(e: std::io::Error) -> Self {
129        Self::Io {
130            kind: e.kind(),
131            message: e.to_string(),
132        }
133    }
134}