Skip to main content

jw_hwp_core/
error.rs

1use std::path::PathBuf;
2use thiserror::Error;
3
4#[derive(Debug, Error)]
5pub enum Error {
6    #[error("path not found: {0}")]
7    NotFound(PathBuf),
8    #[error("I/O error: {0}")]
9    Io(#[from] std::io::Error),
10    #[error("container error: {0}")]
11    Container(String),
12    #[error("required stream missing: {0}")]
13    MissingStream(String),
14    #[error("decompression failed in stream {stream}: {source}")]
15    Decompress {
16        stream: String,
17        #[source]
18        source: std::io::Error,
19    },
20    #[error("invalid file header: {0}")]
21    InvalidHeader(String),
22    #[error("record parse error: {0}")]
23    Record(String),
24}
25
26#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
27pub struct Warning {
28    pub code: WarningCode,
29    pub message: String,
30    pub location: Option<Location>,
31}
32
33#[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq)]
34#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
35pub enum WarningCode {
36    UnknownRecordTag,
37    TruncatedRecord,
38    UnsupportedContent,
39    ShapeRefMissing,
40}
41
42#[derive(Debug, Clone, Default, serde::Serialize, PartialEq, Eq)]
43pub struct Location {
44    pub section: Option<usize>,
45    pub paragraph: Option<usize>,
46    pub offset: Option<u64>,
47}
48
49#[derive(Debug, Default)]
50pub struct WarningCollector {
51    pub warnings: Vec<Warning>,
52}
53
54impl WarningCollector {
55    pub fn push(
56        &mut self,
57        code: WarningCode,
58        message: impl Into<String>,
59        location: Option<Location>,
60    ) {
61        self.warnings.push(Warning {
62            code,
63            message: message.into(),
64            location,
65        });
66    }
67    pub fn take(&mut self) -> Vec<Warning> {
68        std::mem::take(&mut self.warnings)
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn collector_accumulates_and_drains() {
78        let mut c = WarningCollector::default();
79        c.push(WarningCode::UnknownRecordTag, "tag=0x999", None);
80        c.push(
81            WarningCode::TruncatedRecord,
82            "ran out at offset 42",
83            Some(Location {
84                section: Some(0),
85                paragraph: Some(3),
86                offset: Some(42),
87            }),
88        );
89        assert_eq!(c.warnings.len(), 2);
90        let taken = c.take();
91        assert_eq!(taken.len(), 2);
92        assert!(c.warnings.is_empty());
93    }
94
95    #[test]
96    fn warning_serializes_with_screaming_code() {
97        let w = Warning {
98            code: WarningCode::UnknownRecordTag,
99            message: "x".into(),
100            location: None,
101        };
102        let s = serde_json::to_string(&w).unwrap();
103        assert!(s.contains("\"UNKNOWN_RECORD_TAG\""));
104    }
105}