Skip to content

Commit

Permalink
feat: improve error struct (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret authored Oct 19, 2024
1 parent 3c9eab0 commit 438b362
Show file tree
Hide file tree
Showing 13 changed files with 250 additions and 98 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ jobs:
run: |
cargo test --features serde
cargo test --features preserve_order
cargo test --verbose --all-features
cargo test --all-features
- name: Test release
if: matrix.config.kind == 'test_release'
run: cargo test --release --verbose --all-features
run: cargo test --release --all-features

# CARGO PUBLISH
- name: Cargo login
Expand All @@ -48,14 +48,14 @@ jobs:
name: Benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install latest nightly
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
- name: Cache cargo
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ jobs:

steps:
- name: Clone repository
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_DPRINTBOT_PAT }}

- uses: denoland/setup-deno@v1
- uses: denoland/setup-deno@v2
- uses: dsherret/rust-toolchain-file@v1

- name: Bump version and tag
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ all-features = true
[dependencies]
indexmap = { version = "2.2.6", optional = true }
serde_json = { version = "1.0", optional = true }
unicode-width = { version = "0.2.0", optional = true }

[features]
cst = []
preserve_order = ["indexmap", "serde_json/preserve_order"]
serde = ["serde_json"]
error_unicode_width = ["unicode-width"]

[dev-dependencies]
pretty_assertions = "1.0.0"
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2020-2021 David Sherret
Copyright (c) 2020 David Sherret

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,7 @@ let json_value = parse_to_value(text, &ParseOptions {
allow_trailing_commas: false,
})?;
```

## Error column number with unicode-width

To to get more accurate display column numbers in error messages, enable the `error_unicode_width` cargo feature, which will pull in and use the [unicode-width](https://crates.io/crates/unicode-width) dependency internally. Otherwise it will use the character count, which isn't as accurate of a number, but will probably be good enough in most cases.
3 changes: 2 additions & 1 deletion dprint.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
],
"plugins": [
"https://plugins.dprint.dev/markdown-0.17.8.wasm",
"https://plugins.dprint.dev/exec-0.5.0.json@8d9972eee71fa1590e04873540421f3eda7674d0f1aae3d7c788615e7b7413d0"
"https://plugins.dprint.dev/exec-0.5.0.json@8d9972eee71fa1590e04873540421f3eda7674d0f1aae3d7c788615e7b7413d0",
"https://plugins.dprint.dev/json-0.19.3.wasm"
]
}
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[toolchain]
channel = "1.81.0"
channel = "1.82.0"
components = ["clippy", "rustfmt"]
179 changes: 153 additions & 26 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,52 +1,179 @@
use std::error::Error;
use std::fmt;

use crate::ParseStringErrorKind;

use super::common::Range;

/// Error that could occur while parsing or tokenizing.
#[derive(Debug, PartialEq)]
pub struct ParseError {
/// Start and end position of the error.
pub range: Range,
/// Error message.
pub message: String,
/// Message with the range text.
display_message: String,
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ParseErrorKind {
CommentsNotAllowed,
ExpectedColonAfterObjectKey,
ExpectedObjectValue,
ExpectedDigit,
ExpectedDigitFollowingNegativeSign,
ExpectedPlusMinusOrDigitInNumberLiteral,
ExpectedStringObjectProperty,
MultipleRootJsonValues,
String(ParseStringErrorKind),
TrailingCommasNotAllowed,
UnexpectedCloseBrace,
UnexpectedCloseBracket,
UnexpectedColon,
UnexpectedComma,
UnexpectedToken,
UnexpectedTokenInObject,
UnexpectedWord,
UnterminatedArray,
UnterminatedCommentBlock,
UnterminatedObject,
}

impl ParseError {
pub(crate) fn new(range: Range, message: &str, file_text: &str) -> ParseError {
let display_message = get_message_with_range(range, message, file_text);
ParseError {
message: message.to_string(),
range,
display_message,
impl std::fmt::Display for ParseErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use ParseErrorKind::*;
match self {
CommentsNotAllowed => {
write!(f, "Comments are not allowed")
}
ExpectedColonAfterObjectKey => {
write!(f, "Expected colon after the string or word in object property")
}
ExpectedDigit => {
write!(f, "Expected digit")
}
ExpectedDigitFollowingNegativeSign => {
write!(f, "Expected digit following negative sign")
}
ExpectedPlusMinusOrDigitInNumberLiteral => {
write!(f, "Expected plus, minus, or digit in number literal")
}
ExpectedObjectValue => {
write!(f, "Expected value after colon in object property")
}
ExpectedStringObjectProperty => {
write!(f, "Expected string for object property")
}
MultipleRootJsonValues => {
write!(f, "Text cannot contain more than one JSON value")
}
String(kind) => kind.fmt(f),
TrailingCommasNotAllowed => {
write!(f, "Trailing commas are not allowed")
}
UnexpectedCloseBrace => {
write!(f, "Unexpected close brace")
}
UnexpectedCloseBracket => {
write!(f, "Unexpected close bracket")
}
UnexpectedColon => {
write!(f, "Unexpected colon")
}
UnexpectedComma => {
write!(f, "Unexpected comma")
}
UnexpectedWord => {
write!(f, "Unexpected word")
}
UnexpectedToken => {
write!(f, "Unexpected token")
}
UnexpectedTokenInObject => {
write!(f, "Unexpected token in object")
}
UnterminatedArray => {
write!(f, "Unterminated array")
}
UnterminatedCommentBlock => {
write!(f, "Unterminated comment block")
}
UnterminatedObject => {
write!(f, "Unterminated object")
}
}
}
}

impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.display_message)
#[derive(Debug, Clone, PartialEq)]
struct ParseErrorInner {
range: Range,
line_display: usize,
column_display: usize,
kind: ParseErrorKind,
}

/// Error that could occur while parsing or tokenizing.
#[derive(Debug, Clone, PartialEq)]
pub struct ParseError(Box<ParseErrorInner>);

impl std::error::Error for ParseError {}

impl ParseError {
pub(crate) fn new(range: Range, kind: ParseErrorKind, file_text: &str) -> ParseError {
let (line_display, column_display) = get_line_and_column_display(range, file_text);
ParseError(Box::new(ParseErrorInner {
range,
line_display,
column_display,
kind,
}))
}

/// Start and end position of the error.
pub fn range(&self) -> Range {
self.0.range
}

/// 1-indexed line number the error occurred on.
pub fn line_display(&self) -> usize {
self.0.line_display
}

/// 1-indexed column number the error occurred on.
///
/// Note: Use the `error_unicode_width` feature to get the correct column
/// number for Unicode characters on the line, otherwise this is just the
/// number of characters by default.
pub fn column_display(&self) -> usize {
self.0.column_display
}

/// Error message.
pub fn kind(&self) -> &ParseErrorKind {
&self.0.kind
}
}

impl Error for ParseError {
fn description(&self) -> &str {
&self.display_message
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let inner = &*self.0;
write!(
f,
"{} on line {} column {}",
inner.kind, inner.line_display, inner.column_display
)
}
}

fn get_message_with_range(range: Range, message: &str, file_text: &str) -> String {
fn get_line_and_column_display(range: Range, file_text: &str) -> (usize, usize) {
let mut line_index = 0;
let mut column_index = 0;
for c in file_text[..range.start].chars() {
if c == '\n' {
line_index += 1;
column_index = 0;
} else {
column_index += 1;
#[cfg(feature = "error_unicode_width")]
{
if let Some(width) = unicode_width::UnicodeWidthChar::width_cjk(c) {
column_index += width;
}
}
#[cfg(not(feature = "error_unicode_width"))]
{
column_index += 1;
}
}
}
format!("{} on line {} column {}.", message, line_index + 1, column_index + 1,)
(line_index + 1, column_index + 1)
}
Loading

0 comments on commit 438b362

Please sign in to comment.