Skip to content

Commit

Permalink
Add error message struct
Browse files Browse the repository at this point in the history
Closes GH-108.
Closes GH-114.
Closes: wooorm/mdxjs-rs#42.
Closes: wooorm/mdxjs-rs#43.
  • Loading branch information
wooorm authored Apr 23, 2024
1 parent 71361e4 commit 3dc7f1a
Show file tree
Hide file tree
Showing 63 changed files with 632 additions and 459 deletions.
2 changes: 1 addition & 1 deletion examples/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fn main() -> Result<(), String> {
fn main() -> Result<(), markdown::message::Message> {
// Turn on debugging.
// You can show it with `RUST_LOG=debug cargo run --features log --example lib`
env_logger::init();
Expand Down
4 changes: 2 additions & 2 deletions generate/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ async fn commonmark() {
// > 👉 **Important**: this module is generated by `generate/src/main.rs`.
// > It is generate from the latest CommonMark website.
use markdown::{{to_html_with_options, CompileOptions, Options}};
use markdown::{{message, to_html_with_options, CompileOptions, Options}};
use pretty_assertions::assert_eq;
#[rustfmt::skip]
#[test]
fn commonmark() -> Result<(), String> {{
fn commonmark() -> Result<(), message::Message> {{
let danger = Options {{
compile: CompileOptions {{
allow_dangerous_html: true,
Expand Down
8 changes: 4 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ Yields:
Extensions (in this case GFM):

```rs
fn main() -> Result<(), String> {
fn main() -> Result<(), markdown::message::Message> {
println!(
"{}",
markdown::to_html_with_options(
Expand Down Expand Up @@ -135,7 +135,7 @@ Yields:
Syntax tree ([mdast][]):

```rs
fn main() -> Result<(), String> {
fn main() -> Result<(), markdown::message::Message> {
println!(
"{:?}",
markdown::to_mdast("# Hey, *you*!", &markdown::ParseOptions::default())?
Expand Down Expand Up @@ -294,8 +294,8 @@ user-provided markdown opens you up to XSS attacks.
An aspect related to XSS for security is syntax errors: markdown itself has no
syntax errors.
Some syntax extensions (specifically, only MDX) do include syntax errors.
For that reason, `to_html_with_options` returns `Result<String, String>`, of
which the error is a simple string indicating where the problem happened, what
For that reason, `to_html_with_options` returns `Result<String, Message>`, of
which the error is a struct indicating where the problem happened, what
occurred, and what was expected instead.
Make sure to handle your errors when using MDX.

Expand Down
26 changes: 13 additions & 13 deletions src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ pub struct CompileOptions {
///
/// ```
/// use markdown::{to_html, to_html_with_options, CompileOptions, Options};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // `markdown-rs` is safe by default:
/// assert_eq!(
Expand Down Expand Up @@ -526,7 +526,7 @@ pub struct CompileOptions {
///
/// ```
/// use markdown::{to_html, to_html_with_options, CompileOptions, Options};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // `markdown-rs` is safe by default:
/// assert_eq!(
Expand Down Expand Up @@ -570,7 +570,7 @@ pub struct CompileOptions {
///
/// ```
/// use markdown::{to_html, to_html_with_options, CompileOptions, LineEnding, Options};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // `markdown-rs` uses `\n` by default:
/// assert_eq!(
Expand Down Expand Up @@ -612,7 +612,7 @@ pub struct CompileOptions {
///
/// ```
/// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // `"Footnotes"` is used by default:
/// assert_eq!(
Expand Down Expand Up @@ -657,7 +657,7 @@ pub struct CompileOptions {
///
/// ```
/// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // `"h2"` is used by default:
/// assert_eq!(
Expand Down Expand Up @@ -705,7 +705,7 @@ pub struct CompileOptions {
///
/// ```
/// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // `"class=\"sr-only\""` is used by default:
/// assert_eq!(
Expand Down Expand Up @@ -748,7 +748,7 @@ pub struct CompileOptions {
///
/// ```
/// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // `"Back to content"` is used by default:
/// assert_eq!(
Expand Down Expand Up @@ -804,7 +804,7 @@ pub struct CompileOptions {
///
/// ```
/// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // `"user-content-"` is used by default:
/// assert_eq!(
Expand Down Expand Up @@ -843,7 +843,7 @@ pub struct CompileOptions {
///
/// ```
/// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // With `gfm_task_list_item_checkable`, generated `<input type="checkbox" />`
/// // tags do not contain the attribute `disabled=""` and are thus toggleable by
Expand Down Expand Up @@ -880,7 +880,7 @@ pub struct CompileOptions {
///
/// ```
/// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // With `allow_dangerous_html`, `markdown-rs` passes HTML through untouched:
/// assert_eq!(
Expand Down Expand Up @@ -975,7 +975,7 @@ pub struct ParseOptions {
///
/// ```
/// use markdown::{to_html, to_html_with_options, Constructs, Options, ParseOptions};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // `markdown-rs` follows CommonMark by default:
/// assert_eq!(
Expand Down Expand Up @@ -1020,7 +1020,7 @@ pub struct ParseOptions {
///
/// ```
/// use markdown::{to_html_with_options, Constructs, Options, ParseOptions};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // `markdown-rs` supports single tildes by default:
/// assert_eq!(
Expand Down Expand Up @@ -1075,7 +1075,7 @@ pub struct ParseOptions {
///
/// ```
/// use markdown::{to_html_with_options, Constructs, Options, ParseOptions};
/// # fn main() -> Result<(), String> {
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // `markdown-rs` supports single dollars by default:
/// assert_eq!(
Expand Down
5 changes: 3 additions & 2 deletions src/construct/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@
//! [paragraph]: crate::construct::paragraph
use crate::event::{Content, Kind, Link, Name};
use crate::message;
use crate::resolve::Name as ResolveName;
use crate::state::{Name as StateName, State};
use crate::subtokenize::{subtokenize, Subresult};
use crate::tokenizer::Tokenizer;
use alloc::{string::String, vec};
use alloc::vec;

/// Before a content chunk.
///
Expand Down Expand Up @@ -110,7 +111,7 @@ pub fn definition_after(tokenizer: &mut Tokenizer) -> State {

/// Merge `Content` chunks, which currently span a single line, into actual
/// `Content`s that span multiple lines.
pub fn resolve(tokenizer: &mut Tokenizer) -> Result<Option<Subresult>, String> {
pub fn resolve(tokenizer: &mut Tokenizer) -> Result<Option<Subresult>, message::Message> {
let mut index = 0;

while index < tokenizer.events.len() {
Expand Down
5 changes: 3 additions & 2 deletions src/construct/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
//! * [GFM: Footnote definition][crate::construct::gfm_footnote_definition]
use crate::event::{Content, Event, Kind, Link, Name};
use crate::message;
use crate::state::{Name as StateName, State};
use crate::subtokenize::divide_events;
use crate::tokenizer::{Container, ContainerState, Tokenizer};
use crate::util::skip;
use alloc::{boxed::Box, string::String, vec::Vec};
use alloc::{boxed::Box, vec::Vec};

/// Phases where we can exit containers.
#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -470,7 +471,7 @@ pub fn flow_end(tokenizer: &mut Tokenizer) -> State {
}

/// Close containers (and flow if needed).
fn exit_containers(tokenizer: &mut Tokenizer, phase: &Phase) -> Result<(), String> {
fn exit_containers(tokenizer: &mut Tokenizer, phase: &Phase) -> Result<(), message::Message> {
let mut stack_close = tokenizer
.tokenize_state
.document_container_stack
Expand Down
26 changes: 17 additions & 9 deletions src/construct/mdx_esm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@
//! [parse_options]: crate::ParseOptions
use crate::event::Name;
use crate::message;
use crate::state::{Name as StateName, State};
use crate::tokenizer::Tokenizer;
use crate::util::{mdx_collect::collect, slice::Slice};
use crate::MdxSignal;
use alloc::format;
use alloc::boxed::Box;

/// Start of MDX ESM.
///
Expand Down Expand Up @@ -205,24 +206,31 @@ fn parse_esm(tokenizer: &mut Tokenizer) -> State {
// Parse and handle what was signaled back.
match parse(&result.value) {
MdxSignal::Ok => State::Ok,
MdxSignal::Error(message, relative) => {
MdxSignal::Error(message, relative, source, rule_id) => {
let point = tokenizer
.parse_state
.location
.as_ref()
.expect("expected location index if aware mdx is on")
.relative_to_point(&result.stops, relative)
.expect("expected non-empty string");
State::Error(format!("{}:{}: {}", point.line, point.column, message))
State::Error(message::Message {
place: Some(Box::new(message::Place::Point(point))),
reason: message,
source,
rule_id,
})
}
MdxSignal::Eof(message) => {
MdxSignal::Eof(message, source, rule_id) => {
if tokenizer.current.is_none() {
State::Error(format!(
"{}:{}: {}",
tokenizer.point.line, tokenizer.point.column, message
))
State::Error(message::Message {
place: Some(Box::new(message::Place::Point(tokenizer.point.to_unist()))),
reason: message,
source,
rule_id,
})
} else {
tokenizer.tokenize_state.mdx_last_parse_error = Some(message);
tokenizer.tokenize_state.mdx_last_parse_error = Some((message, *source, *rule_id));
State::Retry(StateName::MdxEsmContinuationStart)
}
}
Expand Down
47 changes: 29 additions & 18 deletions src/construct/partial_mdx_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@
use crate::construct::partial_space_or_tab::space_or_tab_min_max;
use crate::event::Name;
use crate::message;
use crate::state::{Name as StateName, State};
use crate::tokenizer::Tokenizer;
use crate::util::{constant::TAB_SIZE, mdx_collect::collect};
use crate::{MdxExpressionKind, MdxExpressionParse, MdxSignal};
use alloc::format;
use alloc::boxed::Box;

/// Start of an MDX expression.
///
Expand All @@ -89,12 +90,15 @@ pub fn start(tokenizer: &mut Tokenizer) -> State {
pub fn before(tokenizer: &mut Tokenizer) -> State {
match tokenizer.current {
None => {
State::Error(format!(
"{}:{}: {}",
tokenizer.point.line, tokenizer.point.column,
tokenizer.tokenize_state.mdx_last_parse_error.take()
.unwrap_or_else(|| "Unexpected end of file in expression, expected a corresponding closing brace for `{`".into())
))
let problem = tokenizer.tokenize_state.mdx_last_parse_error.take()
.unwrap_or_else(|| ("Unexpected end of file in expression, expected a corresponding closing brace for `{`".into(), "markdown-rs".into(), "unexpected-eof".into()));

State::Error(message::Message {
place: Some(Box::new(message::Place::Point(tokenizer.point.to_unist()))),
reason: problem.0,
rule_id: Box::new(problem.2),
source: Box::new(problem.1),
})
}
Some(b'\n') => {
tokenizer.enter(Name::LineEnding);
Expand Down Expand Up @@ -167,10 +171,14 @@ pub fn eol_after(tokenizer: &mut Tokenizer) -> State {
|| tokenizer.tokenize_state.token_2 == Name::MdxJsxFlowTag)
&& tokenizer.lazy
{
State::Error(format!(
"{}:{}: Unexpected lazy line in expression in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc",
tokenizer.point.line, tokenizer.point.column
))
State::Error(
message::Message {
place: Some(Box::new(message::Place::Point(tokenizer.point.to_unist()))),
reason: "Unexpected lazy line in expression in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc".into(),
source: Box::new("markdown-rs".into()),
rule_id: Box::new("unexpected-lazy".into()),
}
)
} else if matches!(tokenizer.current, Some(b'\t' | b' ')) {
tokenizer.attempt(State::Next(StateName::MdxExpressionBefore), State::Nok);
// Idea: investigate if we’d need to use more complex stripping.
Expand Down Expand Up @@ -220,21 +228,24 @@ fn parse_expression(tokenizer: &mut Tokenizer, parse: &MdxExpressionParse) -> St
// Parse and handle what was signaled back.
match parse(&result.value, &kind) {
MdxSignal::Ok => State::Ok,
MdxSignal::Error(message, relative) => {
MdxSignal::Error(reason, relative, source, rule_id) => {
let point = tokenizer
.parse_state
.location
.as_ref()
.expect("expected location index if aware mdx is on")
.relative_to_point(&result.stops, relative)
.map_or((tokenizer.point.line, tokenizer.point.column), |d| {
(d.line, d.column)
});
.unwrap_or_else(|| tokenizer.point.to_unist());

State::Error(format!("{}:{}: {}", point.0, point.1, message))
State::Error(message::Message {
place: Some(Box::new(message::Place::Point(point))),
reason,
rule_id,
source,
})
}
MdxSignal::Eof(message) => {
tokenizer.tokenize_state.mdx_last_parse_error = Some(message);
MdxSignal::Eof(reason, source, rule_id) => {
tokenizer.tokenize_state.mdx_last_parse_error = Some((reason, *source, *rule_id));
tokenizer.enter(Name::MdxExpressionData);
tokenizer.consume();
State::Next(StateName::MdxExpressionInside)
Expand Down
Loading

0 comments on commit 3dc7f1a

Please sign in to comment.