openbim_ifcx/
json.rs

1//! Reading and writing IFCX files as JSON.
2
3use std::fmt;
4use std::io::{Read, Write};
5
6use serde_json::error::Category;
7
8use crate::model::IfcxFile;
9
10/// Why a file could not be read.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ReadErrorKind {
13    /// Reading the underlying stream failed.
14    Io,
15    /// The input is not valid JSON.
16    Syntax,
17    /// The input is JSON but does not match the IFCX file shape.
18    Data,
19    /// The input ended early.
20    Eof,
21}
22
23/// A file that could not be read, with the 1-based location of the problem.
24#[derive(Debug)]
25pub struct ReadError {
26    kind: ReadErrorKind,
27    line: usize,
28    column: usize,
29    source: serde_json::Error,
30}
31
32impl ReadError {
33    pub fn kind(&self) -> ReadErrorKind {
34        self.kind
35    }
36
37    /// 1-based line of the problem, or 0 for an I/O error.
38    pub fn line(&self) -> usize {
39        self.line
40    }
41
42    /// 1-based column of the problem, or 0 for an I/O error.
43    pub fn column(&self) -> usize {
44        self.column
45    }
46}
47
48impl From<serde_json::Error> for ReadError {
49    fn from(source: serde_json::Error) -> Self {
50        let kind = match source.classify() {
51            Category::Io => ReadErrorKind::Io,
52            Category::Syntax => ReadErrorKind::Syntax,
53            Category::Data => ReadErrorKind::Data,
54            Category::Eof => ReadErrorKind::Eof,
55        };
56        Self {
57            kind,
58            line: source.line(),
59            column: source.column(),
60            source,
61        }
62    }
63}
64
65impl fmt::Display for ReadError {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        write!(f, "invalid IFCX file: {}", self.source)
68    }
69}
70
71impl std::error::Error for ReadError {
72    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
73        Some(&self.source)
74    }
75}
76
77/// A file that could not be written.
78#[derive(Debug)]
79pub struct WriteError(serde_json::Error);
80
81impl fmt::Display for WriteError {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        write!(f, "could not write IFCX file: {}", self.0)
84    }
85}
86
87impl std::error::Error for WriteError {
88    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
89        Some(&self.0)
90    }
91}
92
93impl IfcxFile {
94    pub fn from_json_str(input: &str) -> Result<Self, ReadError> {
95        Ok(serde_json::from_str(input)?)
96    }
97
98    pub fn from_json_slice(input: &[u8]) -> Result<Self, ReadError> {
99        Ok(serde_json::from_slice(input)?)
100    }
101
102    /// Reads from a stream. Wrap unbuffered sources in a `BufReader`.
103    pub fn from_json_reader(reader: impl Read) -> Result<Self, ReadError> {
104        Ok(serde_json::from_reader(reader)?)
105    }
106
107    pub fn to_json_string(&self) -> Result<String, WriteError> {
108        serde_json::to_string(self).map_err(WriteError)
109    }
110
111    pub fn to_json_string_pretty(&self) -> Result<String, WriteError> {
112        serde_json::to_string_pretty(self).map_err(WriteError)
113    }
114
115    pub fn to_json_writer(&self, writer: impl Write) -> Result<(), WriteError> {
116        serde_json::to_writer(writer, self).map_err(WriteError)
117    }
118}