Skip to main content

opy_rs/hir/
error.rs

1//! Structured errors for Opy HIR v2 ingestion.
2//!
3//! Every failure carries a stable code, a message, and — when the offending
4//! source position is known — a span. Human-readable wording is not part of
5//! the stable contract; `code` is.
6
7use crate::hir::types::Span;
8
9/// A structured Opy HIR v2 ingestion error.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum HirError {
12    /// The payload is not valid JSON or does not fit the protocol envelope.
13    Malformed { code: &'static str, message: String },
14    /// Protocol identity or major version is not supported. Reported before
15    /// any program-body inspection.
16    IncompatibleProtocol { expected: String, received: String },
17    /// A node kind the consumer does not understand.
18    UnsupportedNode { kind: String, span: Option<Span> },
19    /// A structural, provenance, identifier, or reference invariant failed.
20    Invalid {
21        code: &'static str,
22        message: String,
23        span: Option<Span>,
24    },
25}
26
27impl HirError {
28    /// Stable machine-readable code for this error.
29    pub fn code(&self) -> &'static str {
30        match self {
31            HirError::Malformed { code, .. } => code,
32            HirError::IncompatibleProtocol { .. } => "incompatible-protocol",
33            HirError::UnsupportedNode { .. } => "unsupported-node",
34            HirError::Invalid { code, .. } => code,
35        }
36    }
37
38    /// Human-readable message.
39    pub fn message(&self) -> String {
40        match self {
41            HirError::Malformed { message, .. } => message.clone(),
42            HirError::IncompatibleProtocol { expected, received } => {
43                format!("incompatible protocol: expected {expected}, received {received}")
44            }
45            HirError::UnsupportedNode { kind, .. } => {
46                format!("unsupported node kind '{kind}'")
47            }
48            HirError::Invalid { message, .. } => message.clone(),
49        }
50    }
51
52    /// The offending source span, when known.
53    pub fn span(&self) -> Option<&Span> {
54        match self {
55            HirError::UnsupportedNode { span, .. } => span.as_ref(),
56            HirError::Invalid { span, .. } => span.as_ref(),
57            _ => None,
58        }
59    }
60}
61
62impl std::fmt::Display for HirError {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        write!(f, "{}: {}", self.code(), self.message())
65    }
66}
67
68impl std::error::Error for HirError {}
69
70impl From<serde_json::Error> for HirError {
71    fn from(error: serde_json::Error) -> Self {
72        HirError::Malformed {
73            code: "malformed-payload",
74            message: error.to_string(),
75        }
76    }
77}
78
79/// Shorthand for an invalid-payload error with a stable code.
80pub(crate) fn invalid(
81    code: &'static str,
82    message: impl Into<String>,
83    span: Option<Span>,
84) -> HirError {
85    HirError::Invalid {
86        code,
87        message: message.into(),
88        span,
89    }
90}