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, .. } | HirError::Invalid { code, .. } => code,
32            HirError::IncompatibleProtocol { .. } => "incompatible-protocol",
33            HirError::UnsupportedNode { .. } => "unsupported-node",
34        }
35    }
36
37    /// Human-readable message.
38    pub fn message(&self) -> String {
39        match self {
40            HirError::Malformed { message, .. } | HirError::Invalid { message, .. } => {
41                message.clone()
42            }
43            HirError::IncompatibleProtocol { expected, received } => {
44                format!("incompatible protocol: expected {expected}, received {received}")
45            }
46            HirError::UnsupportedNode { kind, .. } => {
47                format!("unsupported node kind '{kind}'")
48            }
49        }
50    }
51
52    /// The offending source span, when known.
53    pub fn span(&self) -> Option<&Span> {
54        match self {
55            HirError::UnsupportedNode { span, .. } | HirError::Invalid { span, .. } => {
56                span.as_ref()
57            }
58            _ => None,
59        }
60    }
61}
62
63impl std::fmt::Display for HirError {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        write!(f, "{}: {}", self.code(), self.message())
66    }
67}
68
69impl std::error::Error for HirError {}
70
71impl From<serde_json::Error> for HirError {
72    fn from(error: serde_json::Error) -> Self {
73        HirError::Malformed {
74            code: "malformed-payload",
75            message: error.to_string(),
76        }
77    }
78}
79
80/// Shorthand for an invalid-payload error with a stable code.
81pub(crate) fn invalid(
82    code: &'static str,
83    message: impl Into<String>,
84    span: Option<Span>,
85) -> HirError {
86    HirError::Invalid {
87        code,
88        message: message.into(),
89        span,
90    }
91}