Skip to main content

blockworx/schema/
error.rs

1//! Errors for the schema (de)serialization and conversion layers.
2//!
3//! The parser diagnostic ([`SchemaError::Json`]) carries a byte span into
4//! the source, so a failure points at the offending token. The remaining
5//! variants are conversion failures with no span of their own, each naming
6//! the offending block/pin.
7
8use miette::{Diagnostic, NamedSource, SourceSpan};
9use thiserror::Error;
10
11#[derive(Debug, Error, Diagnostic)]
12pub enum SchemaError {
13    /// A JSON document this build will not read, located in the source.
14    #[error("{message}")]
15    Json {
16        message: String,
17        #[label("here")]
18        span: SourceSpan,
19        #[source_code]
20        src: NamedSource<String>,
21    },
22
23    #[error("invalid {kind} id {value:?}: {reason}")]
24    Id {
25        kind: &'static str,
26        value: String,
27        reason: String,
28    },
29
30    #[error("document `top` id {value:?} is invalid: {reason}")]
31    BadTop { value: String, reason: String },
32
33    #[error("block {block:?}: route anchor {value:?} is invalid: {reason}")]
34    Anchor {
35        block: String,
36        value: String,
37        reason: String,
38    },
39
40    #[error("block {block:?}: pin {pin:?} is missing its `loc`")]
41    MissingPinSide { block: String, pin: String },
42
43    #[error("block {block:?}: pin {pin:?} has invalid `loc` {value:?}: {reason}")]
44    BadLoc {
45        block: String,
46        pin: String,
47        value: String,
48        reason: String,
49    },
50
51    #[error("asset {asset:?} has invalid base64 png: {reason}")]
52    AssetPng { asset: String, reason: String },
53
54    #[error("asset {asset:?} is defined more than once")]
55    DuplicateAsset { asset: String },
56
57    #[error("block {block:?} places image {asset:?}, which no `asset` node defines")]
58    UnknownAsset { block: String, asset: String },
59
60    #[error("block {block:?} lists child {child:?}, which is not present in the document")]
61    DanglingChild { block: String, child: String },
62}
63
64impl SchemaError {
65    /// A `serde_json` failure as a spanned diagnostic. serde reports a line
66    /// and column; the span is that position in bytes, so the rendered
67    /// report underlines the offending token rather than quoting an offset
68    /// the reader has to count to.
69    pub(super) fn json(src: &str, src_name: &str, error: &serde_json::Error) -> Self {
70        let offset = byte_offset(src, error.line(), error.column());
71        SchemaError::Json {
72            message: error.to_string(),
73            span: (offset, 1.min(src.len().saturating_sub(offset))).into(),
74            src: NamedSource::new(src_name, src.to_owned()),
75        }
76    }
77}
78
79/// A 1-based line/column pair as a byte offset. serde reports `(0, 0)` for a
80/// failure with no position (an early EOF), which lands at the start.
81fn byte_offset(src: &str, line: usize, column: usize) -> usize {
82    let start: usize = src
83        .split_inclusive('\n')
84        .take(line.saturating_sub(1))
85        .map(str::len)
86        .sum();
87    (start + column.saturating_sub(1)).min(src.len())
88}