Skip to content

Commit fe7fc66

Browse files
committed
Refactor: more coherent errors.
1 parent b9bd125 commit fe7fc66

File tree

8 files changed

+281
-222
lines changed

8 files changed

+281
-222
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Changelog
2424

2525
* Block drag and drop of images, which creates a mess.
2626
* Send sync data when doc blocks receive focus.
27+
* Improve error handling.
2728

2829
Version 0.1.41 -- 2025-Nov-17
2930
--------------------------------------------------------------------------------

server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ actix-service = "2.0.3"
6161
actix-web = "4"
6262
actix-web-httpauth = "0.8.2"
6363
actix-ws = "0.3.0"
64+
anyhow = "1.0.100"
6465
# This is used for integration testing. However, since integration tests can't
6566
# access crates in `dev-dependencies`, they're here behind the appropriate
6667
# feature flag.

server/src/ide/filewatcher.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,7 @@ mod tests {
846846

847847
// Check the contents.
848848
let translation_results = source_to_codechat_for_web("", &"py".to_string(), false, false);
849-
let codechat_for_web = cast!(translation_results, TranslationResults::CodeChat);
849+
let codechat_for_web = cast!(cast!(translation_results, Ok), TranslationResults::CodeChat);
850850
assert_eq!(umc.contents, Some(codechat_for_web));
851851

852852
// Report any errors produced when removing the temporary directory.

server/src/processing.rs

Lines changed: 92 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,6 @@ pub enum TranslationResults {
207207
/// This file is unknown to and therefore not supported by the CodeChat
208208
/// Editor.
209209
Unknown,
210-
/// This is a CodeChat Editor file but it contains errors that prevent its
211-
/// translation. The string contains the error message.
212-
Err(String),
213210
/// A CodeChat Editor file; the struct contains the file's contents
214211
/// translated to CodeMirror.
215212
CodeChat(CodeChatForWeb),
@@ -224,9 +221,6 @@ pub enum TranslationResultsString {
224221
/// This file is unknown to the CodeChat Editor. It must be viewed raw or
225222
/// using the simple viewer.
226223
Unknown,
227-
/// This is a CodeChat Editor file but it contains errors that prevent its
228-
/// translation. The string contains the error message.
229-
Err(String),
230224
/// A CodeChat Editor file; the struct contains the file's contents
231225
/// translated to CodeMirror.
232226
CodeChat(CodeChatForWeb),
@@ -388,44 +382,62 @@ pub fn find_path_to_toc(file_path: &Path) -> Option<PathBuf> {
388382
}
389383
}
390384

385+
#[derive(Debug, thiserror::Error)]
386+
pub enum CodechatForWebToSourceError {
387+
#[error("invalid lexer {0}")]
388+
InvalidLexer(String),
389+
#[error("doc blocks not allowed in Markdown documents")]
390+
DocBlocksNotAllowed,
391+
#[error("TODO: diffs not supported")]
392+
TodoDiff,
393+
#[error("unable to convert from HTML to Markdown: {0}")]
394+
HtmlToMarkdownFailed(#[from] HtmlToMarkdownWrappedError),
395+
#[error("unable to translate CodeChat to source: {0}")]
396+
CannotTranslateCodeChat(#[from] CodeDocBlockVecToSourceError),
397+
}
398+
391399
// Transform `CodeChatForWeb` to source code
392400
// -----------------------------------------------------------------------------
393401
/// This function takes in a source file in web-editable format (the
394402
/// `CodeChatForWeb` struct) and transforms it into source code.
395403
pub fn codechat_for_web_to_source(
396404
// The file to save plus metadata, stored in the `LexedSourceFile`
397405
codechat_for_web: &CodeChatForWeb,
398-
) -> Result<String, String> {
406+
) -> Result<String, CodechatForWebToSourceError> {
407+
let lexer_name = &codechat_for_web.metadata.mode;
399408
// Given the mode, find the lexer.
400-
let lexer: &std::sync::Arc<crate::lexer::LanguageLexerCompiled> = match LEXERS
401-
.map_mode_to_lexer
402-
.get(&codechat_for_web.metadata.mode)
403-
{
404-
Some(v) => v,
405-
None => return Err("Invalid mode".to_string()),
406-
};
409+
let lexer: &std::sync::Arc<crate::lexer::LanguageLexerCompiled> =
410+
match LEXERS.map_mode_to_lexer.get(lexer_name) {
411+
Some(v) => v,
412+
None => {
413+
return Err(CodechatForWebToSourceError::InvalidLexer(
414+
lexer_name.clone(),
415+
));
416+
}
417+
};
407418

408419
// Extract the plain (not diffed) CodeMirror contents.
409420
let CodeMirrorDiffable::Plain(ref code_mirror) = codechat_for_web.source else {
410-
panic!("No diff!");
421+
return Err(CodechatForWebToSourceError::TodoDiff);
411422
};
412423

413424
// If this is a Markdown-only document, handle this special case.
414425
if *lexer.language_lexer.lexer_name == "markdown" {
415426
// There should be no doc blocks.
416427
if !code_mirror.doc_blocks.is_empty() {
417-
return Err("Doc blocks not allowed in Markdown documents.".to_string());
428+
return Err(CodechatForWebToSourceError::DocBlocksNotAllowed);
418429
}
419430
// Translate the HTML document to Markdown.
420431
let converter = HtmlToMarkdownWrapped::new();
421432
return converter
422433
.convert(&code_mirror.doc)
423-
.map_err(|e| e.to_string());
434+
.map_err(CodechatForWebToSourceError::HtmlToMarkdownFailed);
424435
}
425436
let code_doc_block_vec_html = code_mirror_to_code_doc_blocks(code_mirror);
426-
let code_doc_block_vec =
427-
doc_block_html_to_markdown(code_doc_block_vec_html).map_err(|e| e.to_string())?;
437+
let code_doc_block_vec = doc_block_html_to_markdown(code_doc_block_vec_html)
438+
.map_err(CodechatForWebToSourceError::HtmlToMarkdownFailed)?;
428439
code_doc_block_vec_to_source(&code_doc_block_vec, lexer)
440+
.map_err(CodechatForWebToSourceError::CannotTranslateCodeChat)
429441
}
430442

431443
/// Return the byte index of `s[u16_16_index]`, where the indexing operation is
@@ -504,6 +516,14 @@ struct HtmlToMarkdownWrapped {
504516
word_wrap_config: Configuration,
505517
}
506518

519+
#[derive(Debug, thiserror::Error)]
520+
pub enum HtmlToMarkdownWrappedError {
521+
#[error("unable to convert from HTML to markdown")]
522+
HtmlToMarkdownFailed(#[from] std::io::Error),
523+
#[error("unable to word wrap Markdown")]
524+
WordWrapFailed(#[from] anyhow::Error),
525+
}
526+
507527
impl HtmlToMarkdownWrapped {
508528
fn new() -> Self {
509529
HtmlToMarkdownWrapped {
@@ -531,11 +551,10 @@ impl HtmlToMarkdownWrapped {
531551
self.word_wrap_config.line_width = line_width as u32;
532552
}
533553

534-
fn convert(&self, html: &str) -> std::io::Result<String> {
554+
fn convert(&self, html: &str) -> Result<String, HtmlToMarkdownWrappedError> {
535555
let converted = self.html_to_markdown.convert(html)?;
536556
Ok(
537-
format_text(&converted, &self.word_wrap_config, |_, _, _| Ok(None))
538-
.map_err(std::io::Error::other)?
557+
format_text(&converted, &self.word_wrap_config, |_, _, _| Ok(None))?
539558
// A return value of `None` means the text was unchanged or
540559
// ignored (by an
541560
// [ignoreFileDirective](https://dprint.dev/plugins/markdown/config/)).
@@ -548,7 +567,7 @@ impl HtmlToMarkdownWrapped {
548567
// Transform HTML in doc blocks to Markdown.
549568
fn doc_block_html_to_markdown(
550569
mut code_doc_block_vec: Vec<CodeDocBlock>,
551-
) -> std::io::Result<Vec<CodeDocBlock>> {
570+
) -> Result<Vec<CodeDocBlock>, HtmlToMarkdownWrappedError> {
552571
let mut converter = HtmlToMarkdownWrapped::new();
553572
for code_doc_block in &mut code_doc_block_vec {
554573
if let CodeDocBlock::DocBlock(doc_block) = code_doc_block {
@@ -566,20 +585,24 @@ fn doc_block_html_to_markdown(
566585
WORD_WRAP_COLUMN,
567586
),
568587
));
569-
doc_block.contents = converter
570-
.convert(&doc_block.contents)
571-
.map_err(std::io::Error::other)?;
588+
doc_block.contents = converter.convert(&doc_block.contents)?;
572589
}
573590
}
574591

575592
Ok(code_doc_block_vec)
576593
}
577594

595+
#[derive(Debug, PartialEq, thiserror::Error)]
596+
pub enum CodeDocBlockVecToSourceError {
597+
#[error("unknown comment opening delimiter '{0}'")]
598+
UnknownCommentOpeningDelimiter(String),
599+
}
600+
578601
// Turn this vec of CodeDocBlocks into a string of source code.
579602
fn code_doc_block_vec_to_source(
580603
code_doc_block_vec: &Vec<CodeDocBlock>,
581604
lexer: &LanguageLexerCompiled,
582-
) -> Result<String, String> {
605+
) -> Result<String, CodeDocBlockVecToSourceError> {
583606
let mut file_contents = String::new();
584607
for code_doc_block in code_doc_block_vec {
585608
match code_doc_block {
@@ -636,10 +659,11 @@ fn code_doc_block_vec_to_source(
636659
{
637660
Some(index) => &lexer.language_lexer.block_comment_delim_arr[index].closing,
638661
None => {
639-
return Err(format!(
640-
"Unknown comment opening delimiter '{}'.",
641-
doc_block.delimiter
642-
));
662+
return Err(
663+
CodeDocBlockVecToSourceError::UnknownCommentOpeningDelimiter(
664+
doc_block.delimiter.clone(),
665+
),
666+
);
643667
}
644668
};
645669

@@ -722,6 +746,12 @@ fn code_doc_block_vec_to_source(
722746
Ok(file_contents)
723747
}
724748

749+
#[derive(Debug, PartialEq, thiserror::Error)]
750+
pub enum SourceToCodeChatForWebError {
751+
#[error("unknown lexer {0}")]
752+
UnknownLexer(String),
753+
}
754+
725755
// Transform from source code to `CodeChatForWeb`
726756
// -----------------------------------------------------------------------------
727757
//
@@ -736,7 +766,7 @@ pub fn source_to_codechat_for_web(
736766
_is_toc: bool,
737767
// True if this file is part of a project.
738768
_is_project: bool,
739-
) -> TranslationResults {
769+
) -> Result<TranslationResults, SourceToCodeChatForWebError> {
740770
// Determine the lexer to use for this file.
741771
let lexer_name;
742772
// First, search for a lexer directive in the file contents.
@@ -745,10 +775,7 @@ pub fn source_to_codechat_for_web(
745775
match LEXERS.map_mode_to_lexer.get(&lexer_name) {
746776
Some(v) => v,
747777
None => {
748-
return TranslationResults::Err(format!(
749-
"<p>Unknown lexer type {}.</p>",
750-
&lexer_name
751-
));
778+
return Err(SourceToCodeChatForWebError::UnknownLexer(lexer_name));
752779
}
753780
}
754781
} else {
@@ -757,7 +784,7 @@ pub fn source_to_codechat_for_web(
757784
Some(llc) => llc.first().unwrap(),
758785
_ => {
759786
// The file type is unknown; treat it as plain text.
760-
return TranslationResults::Unknown;
787+
return Ok(TranslationResults::Unknown);
761788
}
762789
}
763790
};
@@ -861,7 +888,7 @@ pub fn source_to_codechat_for_web(
861888
},
862889
};
863890

864-
TranslationResults::CodeChat(codechat_for_web)
891+
Ok(TranslationResults::CodeChat(codechat_for_web))
865892
}
866893

867894
// Like `source_to_codechat_for_web`, translate a source file to the CodeChat
@@ -875,12 +902,15 @@ pub fn source_to_codechat_for_web_string(
875902
file_path: &Path,
876903
// True if this file is a TOC.
877904
is_toc: bool,
878-
) -> (
879-
// The resulting translation.
880-
TranslationResultsString,
881-
// Path to the TOC, if found; otherwise, None.
882-
Option<PathBuf>,
883-
) {
905+
) -> Result<
906+
(
907+
// The resulting translation.
908+
TranslationResultsString,
909+
// Path to the TOC, if found; otherwise, None.
910+
Option<PathBuf>,
911+
),
912+
SourceToCodeChatForWebError,
913+
> {
884914
// Determine the file's extension, in order to look up a lexer.
885915
let ext = &file_path
886916
.extension()
@@ -893,26 +923,28 @@ pub fn source_to_codechat_for_web_string(
893923
let path_to_toc = find_path_to_toc(file_path);
894924
let is_project = path_to_toc.is_some();
895925

896-
(
926+
Ok((
897927
match source_to_codechat_for_web(file_contents, &ext.to_string(), is_toc, is_project) {
898-
TranslationResults::CodeChat(codechat_for_web) => {
899-
if is_toc {
900-
// For the table of contents sidebar, which is pure
901-
// markdown, just return the resulting HTML, rather than the
902-
// editable CodeChat for web format.
903-
let CodeMirrorDiffable::Plain(plain) = codechat_for_web.source else {
904-
panic!("No diff!");
905-
};
906-
TranslationResultsString::Toc(plain.doc)
907-
} else {
908-
TranslationResultsString::CodeChat(codechat_for_web)
928+
Err(err) => return Err(err),
929+
Ok(translation_results) => match translation_results {
930+
TranslationResults::CodeChat(codechat_for_web) => {
931+
if is_toc {
932+
// For the table of contents sidebar, which is pure
933+
// markdown, just return the resulting HTML, rather than the
934+
// editable CodeChat for web format.
935+
let CodeMirrorDiffable::Plain(plain) = codechat_for_web.source else {
936+
panic!("No diff!");
937+
};
938+
TranslationResultsString::Toc(plain.doc)
939+
} else {
940+
TranslationResultsString::CodeChat(codechat_for_web)
941+
}
909942
}
910-
}
911-
TranslationResults::Unknown => TranslationResultsString::Unknown,
912-
TranslationResults::Err(err) => TranslationResultsString::Err(err),
943+
TranslationResults::Unknown => TranslationResultsString::Unknown,
944+
},
913945
},
914946
path_to_toc,
915-
)
947+
))
916948
}
917949

918950
/// Convert markdown to HTML. (This assumes the Markdown defined in the

0 commit comments

Comments
 (0)