Skip to content

Commit c5da9f7

Browse files
committed
refactor: improve error handling
Still work to do, but incrementally improve error-handling to reduce usage of unwrap and expect. Also, consolidate file parsing around a Parsable trait. Signed-off-by: Bruce D'Arcus <bdarcus@gmail.com>
1 parent 2c7a5e4 commit c5da9f7

File tree

13 files changed

+89
-108
lines changed

13 files changed

+89
-108
lines changed

cli/Cargo.toml

+1-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ schemars = "0.8"
2020
serde_json = "1.0"
2121
csln = { path = "../csln", package = "csln" }
2222
processor = { path = "../processor", package = "csln-processor" }
23+
anyhow = "1.0.79"
2324

24-
[lints]
25-
workspace = true
2625

cli/src/main.rs

+22-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
use anyhow::Context;
12
use clap::Parser;
2-
use csln::bibliography::InputBibliography as Bibliography;
33
use csln::citation::Citations;
4-
use csln::style::Style;
5-
use csln::HasFile;
6-
use processor::Processor;
4+
use csln::from_file;
5+
use processor::{ProcReferences, Processor};
76

87
#[derive(Parser, Default, Debug)]
98
#[clap(author = "Bruce D'Arcus", version, about = "A CLI for CSLN")]
@@ -16,24 +15,34 @@ pub struct Opts {
1615
bibliography: String,
1716
#[clap(short, long)]
1817
/// The optional path to the CSLN citation file
19-
citation: Option<String>,
18+
citations: Option<String>,
2019
#[clap(short, long)]
2120
/// The path to the CSLN locale file
2221
locale: String,
2322
}
2423

2524
fn main() {
2625
let opts = Opts::parse();
27-
let style: Style = Style::from_file(&opts.style);
28-
let bibliography: Bibliography = Bibliography::from_file(&opts.bibliography);
29-
let citations: Citations = if opts.citation.is_none() {
26+
let style = from_file(&opts.style).context("Style file?");
27+
let bibliography = from_file(&opts.bibliography).context("Bibliography file?");
28+
let citations: Citations = if opts.citations.is_none() {
3029
Citations::default()
3130
} else {
32-
Citations::from_file(&opts.citation.unwrap_or_default())
31+
from_file(opts.citations.unwrap()).unwrap_or_default()
3332
};
34-
let locale = csln::style::locale::Locale::from_file(&opts.locale);
35-
let processor: Processor = Processor::new(style, bibliography, citations, locale);
36-
let rendered_refs = processor.process_references();
33+
let locale = from_file(&opts.locale).context("Locale file?");
34+
let processor: Processor = Processor::new(
35+
style.expect("msg"), // REVIEW why?
36+
bibliography.expect("msg"),
37+
citations,
38+
locale.expect("msg"),
39+
);
40+
let rendered_refs: ProcReferences = processor.process_references();
41+
let serialized_refs = serde_json::to_string_pretty(&rendered_refs);
3742
//println!("{}", refs_to_string(rendered_refs));
38-
println!("{}", serde_json::to_string_pretty(&rendered_refs).unwrap());
43+
if serialized_refs.is_err() {
44+
println!("Error: {:?}", serialized_refs);
45+
} else {
46+
println!("{}", serialized_refs.unwrap());
47+
}
3948
}

cli/src/makeschemas.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use csln::style::locale::Locale;
99
use csln::style::Style;
1010

1111
fn main() {
12-
fs::create_dir_all("schemas").unwrap();
12+
fs::create_dir_all("schemas").expect("Failed to create directory 'schemas'");
1313

1414
let style_schema = schema_for!(Style);
1515
let citation_schema = schema_for!(CitationList);

csln/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ chrono = { version = "0.4", features = ["unstable-locales"] }
2626
unic-langid = { version = "0.9.1", features = ["serde"] }
2727
itertools = "0.11.0"
2828
rayon = "1.7.0"
29+
anyhow = "1.0.79"
2930
#icu = { version = "1.2.0", features = ["icu_datetime_experimental"] }
3031
#icu_testdata = { version = "1.2.0", features = ["icu_datetime_experimental"] }
3132
#indexmap = { version = "2.0.0", features = ["std"] }

csln/src/bibliography/mod.rs

-17
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,7 @@
1-
use crate::HasFile;
21
use std::collections::HashMap;
3-
use std::fs;
42

53
pub mod reference;
64
pub use reference::InputReference;
75

86
/// A bibliography is a collection of references.
97
pub type InputBibliography = HashMap<String, InputReference>;
10-
11-
impl HasFile for InputBibliography {
12-
/// Load and parse a YAML or JSON bibliography file.
13-
fn from_file(bib_path: &str) -> InputBibliography {
14-
let contents =
15-
fs::read_to_string(bib_path).expect("Failed to read bibliography file");
16-
if bib_path.ends_with(".json") {
17-
serde_json::from_str(&contents).expect("Failed to parse JSON")
18-
} else if bib_path.ends_with(".yaml") || bib_path.ends_with(".yml") {
19-
serde_yaml::from_str(&contents).expect("Failed to parse YAML")
20-
} else {
21-
panic!("Style file must be in YAML or JSON format")
22-
}
23-
}
24-
}

csln/src/citation/mod.rs

-16
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,5 @@
1-
use crate::HasFile;
21
use schemars::JsonSchema;
32
use serde::{Deserialize, Serialize};
4-
use std::fs;
5-
6-
impl HasFile for Citations {
7-
fn from_file(citations_path: &str) -> Citations {
8-
let contents =
9-
fs::read_to_string(citations_path).expect("Failed to read citations file");
10-
if citations_path.ends_with(".json") {
11-
serde_json::from_str(&contents).expect("Failed to parse JSON")
12-
} else if citations_path.ends_with(".yaml") || citations_path.ends_with(".yml") {
13-
serde_yaml::from_str(&contents).expect("Failed to parse YAML")
14-
} else {
15-
panic!("Citations file must be in YAML or JSON format")
16-
}
17-
}
18-
}
193

204
pub type Citations = Vec<Citation>;
215

csln/src/lib.rs

+32-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,41 @@
11
pub mod style;
2+
use std::path::Path;
3+
4+
use serde::de::DeserializeOwned;
25
pub use style::Style;
36

7+
use std::fs;
8+
49
pub mod bibliography;
510
pub use bibliography::InputBibliography;
11+
use style::locale::Locale;
12+
13+
use anyhow::{Context, Result};
614

715
pub mod citation;
816

9-
pub trait HasFile {
10-
fn from_file(path: &str) -> Self;
17+
pub trait Parsable: DeserializeOwned {}
18+
impl Parsable for Style {}
19+
impl Parsable for Locale {}
20+
impl Parsable for InputBibliography {}
21+
impl Parsable for citation::Citations {}
22+
23+
pub fn from_file<T: Parsable, P: AsRef<Path>>(path: P) -> Result<T> {
24+
let path = path.as_ref();
25+
let contents = fs::read_to_string(path)
26+
.with_context(|| format!("Failed to read file: {}", path.display()))?;
27+
28+
let value = if path.extension().and_then(|s| s.to_str()) == Some("json") {
29+
serde_json::from_str(&contents).with_context(|| {
30+
format!("Failed to parse JSON from file: {}", path.display())
31+
})?
32+
} else if path.extension().and_then(|s| s.to_str()) == Some("yaml") {
33+
serde_yaml::from_str(&contents).with_context(|| {
34+
format!("Failed to parse YAML from file: {}", path.display())
35+
})?
36+
} else {
37+
return Err(anyhow::anyhow!("Unsupported file extension"));
38+
};
39+
40+
Ok(value)
1141
}

csln/src/style/locale.rs

+1-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use schemars::JsonSchema;
22
use serde::{Deserialize, Serialize};
3-
use std::{collections::HashMap, fs};
3+
use std::collections::HashMap;
44
//use unic_langid::LanguageIdentifier;
55

66
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonSchema)]
@@ -30,20 +30,6 @@ pub struct Terms {
3030
pub ibid: Option<String>,
3131
}
3232

33-
impl Locale {
34-
pub fn from_file(locale_path: &str) -> Locale {
35-
let contents =
36-
fs::read_to_string(locale_path).expect("Failed to read locale file");
37-
if locale_path.ends_with(".json") {
38-
serde_json::from_str(&contents).expect("Failed to parse JSON")
39-
} else if locale_path.ends_with(".yaml") || locale_path.ends_with(".yml") {
40-
serde_yaml::from_str(&contents).expect("Failed to parse YAML")
41-
} else {
42-
panic!("Locale file must be in YAML or JSON format")
43-
}
44-
}
45-
}
46-
4733
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonSchema)]
4834
pub struct AndAs {
4935
pub symbol: String,

csln/src/style/mod.rs

-15
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ SPDX-FileCopyrightText: © 2023 Bruce D'Arcus
66
use schemars::JsonSchema;
77
use serde::{Deserialize, Serialize};
88
use std::collections::HashMap;
9-
use std::fs;
109

1110
pub mod locale;
1211
pub mod options;
@@ -15,20 +14,6 @@ use options::Config;
1514
pub mod template;
1615
use template::TemplateComponent;
1716

18-
impl Style {
19-
/// Load and parse a YAML or JSON style file.
20-
pub fn from_file(style_path: &str) -> Style {
21-
let contents = fs::read_to_string(style_path).expect("Failed to read style file");
22-
if style_path.ends_with(".json") {
23-
serde_json::from_str(&contents).expect("Failed to parse JSON")
24-
} else if style_path.ends_with(".yaml") || style_path.ends_with(".yml") {
25-
serde_yaml::from_str(&contents).expect("Failed to parse YAML")
26-
} else {
27-
panic!("Style file must be in YAML or JSON format")
28-
}
29-
}
30-
}
31-
3217
/// The Style model.
3318
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonSchema)]
3419
pub struct Style {

csln/src/style/options.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ fn author_date_config() {
9999
let sort = config.sort.unwrap_or_default();
100100
assert_eq!(sort.template[0].key, SortKey::Author);
101101
assert_eq!(sort.template[1].key, SortKey::Year);
102-
assert!(config.disambiguate.unwrap().year_suffix);
102+
assert!(config.disambiguate.unwrap_or_default().year_suffix);
103103
}
104104

105105
#[derive(JsonSchema, Debug, PartialEq, Clone, Serialize, Deserialize)]

processor/benches/proc_bench.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
use criterion::{criterion_group, criterion_main, Criterion};
22
use csln::bibliography::InputBibliography as Bibliography;
33
use csln::citation::Citation;
4+
use csln::from_file;
45
use csln::style::Style;
5-
use csln::HasFile;
66
use csln_processor::Processor;
77
use std::time::Duration;
88

99
fn proc_benchmark(c: &mut Criterion) {
10-
let style: Style = Style::from_file("examples/style.csl.yaml");
11-
let bibliography: Bibliography = Bibliography::from_file("examples/ex1.bib.yaml");
12-
let locale = csln::style::locale::Locale::from_file("locales/locale-en.yaml");
10+
let style = match from_file("examples/style.csl.yaml") {
11+
Ok(style) => style,
12+
Err(_) => {
13+
println!("Failed to load style");
14+
return;
15+
}
16+
};
17+
let bibliography: Bibliography = from_file("examples/ex1.bib.yaml").expect("msg");
18+
let locale = from_file("locales/locale-en.yaml");
1319
let citations: Vec<Citation> = Vec::new();
14-
let processor: Processor = Processor::new(style, bibliography, citations, locale);
20+
let processor: Processor =
21+
Processor::new(style, bibliography, citations, locale.expect("msg"));
1522
c.bench_function("sorting references", |b| {
1623
b.iter(|| {
1724
let refs = processor.get_references();

processor/src/lib.rs

+16-15
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use rayon::prelude::*;
2121
use schemars::JsonSchema;
2222
use serde::{Deserialize, Serialize};
2323
//use std::cmp::Ordering;
24+
//use anyhow::Result;
2425
use std::collections::HashMap;
2526
use std::fmt::{self, Debug, Display, Formatter};
2627
use std::option::Option;
@@ -438,7 +439,8 @@ impl ComponentValues for TemplateContributor {
438439
} else {
439440
// TODO generalize the substitution
440441
let add_role_form =
441-
options.global.substitute.clone().unwrap().contributor_role_form;
442+
// REVIEW is this correct?
443+
options.global.substitute.clone()?.contributor_role_form;
442444
let editor = reference.editor()?;
443445
let editor_length = editor.names(options.global.clone(), true).len();
444446
// get the role string; if it's in fact author, it will be None
@@ -449,16 +451,8 @@ impl ComponentValues for TemplateContributor {
449451
role_form,
450452
editor_length,
451453
)
452-
// FIXME
453-
.unwrap()
454454
});
455-
let suffix_padded = suffix.and_then(|s| {
456-
if s.is_empty() {
457-
None
458-
} else {
459-
Some(format!(" {}", s))
460-
}
461-
}); // TODO extract this into separate method
455+
let suffix_padded = suffix.and_then(|s| Some(format!(" {}", s?))); // TODO extract this into separate method
462456
Some(ProcValues {
463457
value: editor.format(options.global.clone(), locale),
464458
prefix: None,
@@ -565,7 +559,11 @@ impl ComponentValues for TemplateDate {
565559

566560
// TODO: implement this along with localized dates
567561
fn _config_fmt(options: &RenderOptions) -> DateTimeFormatterOptions {
568-
match options.global.dates.as_ref().unwrap().month {
562+
let date_options = match options.global.dates.clone() {
563+
Some(dates) => dates,
564+
None => return DateTimeFormatterOptions::default(), // or handle the None case accordingly
565+
};
566+
match date_options.month {
569567
MonthFormat::Long => todo!("long"),
570568
MonthFormat::Short => todo!("short"),
571569
MonthFormat::Numeric => todo!("numeric"),
@@ -584,7 +582,7 @@ impl ComponentValues for TemplateDate {
584582
// TODO need to check form here also
585583
// && self.form == style::template::DateForm::Year
586584
// REVIEW: ugly, and needs to be smarter
587-
&& options.global.processing.clone().unwrap().config().disambiguate.unwrap().year_suffix
585+
&& options.global.processing.clone().unwrap_or_default().config().disambiguate.unwrap_or_default().year_suffix
588586
&& formatted_date.len() == 4
589587
{
590588
int_to_letter((hints.group_index % 26) as u32)
@@ -673,7 +671,10 @@ impl Processor {
673671
) -> Option<ProcCitationItem> {
674672
let citation_style = self.style.citation.clone();
675673
// FIXME below is returning None
676-
let reference = self.get_reference(&citation_item.ref_id).unwrap();
674+
let reference = match self.get_reference(&citation_item.ref_id) {
675+
Ok(reference) => reference,
676+
Err(_) => return None, // or handle the error in a different way
677+
};
677678
let proc_template =
678679
self.process_template(&reference, citation_style?.template.as_slice());
679680
println!("proc_template: {:?}", proc_template);
@@ -685,9 +686,9 @@ impl Processor {
685686
&self,
686687
reference: &InputReference,
687688
) -> Vec<ProcTemplateComponent> {
688-
let bibliography_style = self.style.bibliography.clone();
689+
let bibliography_style = self.style.bibliography.clone().unwrap();
689690
// TODO bibliography should probably be Optional
690-
self.process_template(reference, bibliography_style.unwrap().template.as_slice())
691+
self.process_template(reference, bibliography_style.template.as_slice())
691692
}
692693

693694
fn get_render_options(&self, style: Style, locale: Locale) -> RenderOptions {

processor/tests/processor_test.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,8 @@ mod tests {
2020
csln::bibliography::InputBibliography::from_file("examples/ex1.bib.yaml");
2121
let citations: Citations =
2222
csln::citation::Citations::from_file("examples/citation.yaml");
23-
let processor = csln_processor::Processor::new(
24-
style.clone(),
25-
bibliography.clone(),
26-
citations.clone(),
27-
locale.clone(),
28-
);
23+
let processor =
24+
csln_processor::Processor::new(style, bibliography, citations, locale);
2925

3026
TestFixture { style, locale, bibliography, citations, processor }
3127
}

0 commit comments

Comments
 (0)