Skip to content

Commit 424dc64

Browse files
authored
implement some proper error handling
* implement error handling for Coordinates struct * complete rough error handling for config * update reasdme and bump version
1 parent d95f220 commit 424dc64

File tree

12 files changed

+388
-141
lines changed

12 files changed

+388
-141
lines changed

Cargo.lock

Lines changed: 0 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "heliocron"
3-
version = "0.2.2"
3+
version = "0.3.0"
44
authors = ["Michael Freeborn <michaelfreeborn1@gmail.com>"]
55
description = """
66
Heliocron is a command line application written in Rust capable of delaying execution of other
@@ -28,7 +28,6 @@ toml = "0.5"
2828
[dev-dependencies]
2929
assert_cmd = "0.12"
3030
criterion = "0.3"
31-
guerrilla = "0.1"
3231
predicates = "1"
3332

3433
[profile.release]

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ $ cargo install heliocron
1313
.
1414
.
1515
$ heliocron --version
16-
heliocron 0.1.3
16+
heliocron 0.3.0
1717
```
1818

1919
#### 3. Build from source
@@ -22,7 +22,7 @@ $ git clone https://github.com/mfreeborn/heliocron
2222
$ cd heliocron
2323
$ cargo build --release
2424
$ ./target/release/heliocron --version
25-
heliocron 0.1.3
25+
heliocron 0.3.0
2626
```
2727

2828
## Usage Examples
@@ -45,8 +45,8 @@ Ever wondered what time sunrise is in Edinburgh on 7th May 2065?
4545
$ heliocron -d "7 May 2065" -f "%e %B %Y" -l 55.9533N -o 3.1883W report
4646
LOCATION
4747
--------
48-
Latitude: 55.9533
49-
Longitude: -3.1883
48+
Latitude: 55.9533N
49+
Longitude: 3.1883W
5050
5151
DATE
5252
----
@@ -72,8 +72,8 @@ Now, using Heliocron without providing specific coordinates will yield the follo
7272
$ heliocron -d 2020-03-08 report
7373
LOCATION
7474
--------
75-
Latitude: 51.5014
76-
Longitude: -0.1419
75+
Latitude: 51.5014N
76+
Longitude: 0.1419W
7777
7878
DATE
7979
----
@@ -92,8 +92,8 @@ Arguments passed in via the command line will override those set in the configur
9292
$ heliocron -d 2020-03-08 -l 51.4839N -o 0.6044W report
9393
LOCATION
9494
--------
95-
Latitude: 51.4839
96-
Longitude: -0.6044
95+
Latitude: 51.4839N
96+
Longitude: 0.6044W
9797
9898
DATE
9999
----

benches/benches.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ fn criterion_benchmark(c: &mut Criterion) {
1111
let date = FixedOffset::east(0).ymd(2020, 2, 25).and_hms(12, 0, 0);
1212

1313
let coordinates = structs::Coordinates {
14-
latitude: 51.0,
15-
longitude: 4.0,
14+
latitude: structs::Latitude { value: 51.0 },
15+
longitude: structs::Longitude { value: 4.0 },
1616
};
1717

1818
c.bench_function("run_report", |b| b.iter(|| run_report(date, coordinates)));

src/bin/heliocron.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::process;
22

33
use chrono::{Duration, FixedOffset, Local, TimeZone};
44

5-
use heliocron::{config, enums, report, utils};
5+
use heliocron::{config, enums, errors, report, utils};
66

77
fn wait(offset: Duration, report: report::SolarReport, event: enums::Event) {
88
let event_time = match event {
@@ -20,18 +20,26 @@ fn wait(offset: Duration, report: report::SolarReport, event: enums::Event) {
2020
utils::sleep(duration_to_sleep, sleep_until);
2121
}
2222

23-
fn main() {
24-
let config = config::get_config();
23+
fn run_heliocron() -> Result<(), errors::HeliocronError> {
24+
let config = config::get_config()?;
2525

2626
let report = report::SolarReport::new(config.date, config.coordinates);
2727

2828
match config.subcommand {
2929
Some(config::Subcommand::Report {}) => println!("{}", report),
30-
Some(config::Subcommand::Wait { offset, event }) => wait(offset, report, event),
30+
Some(config::Subcommand::Wait { offset, event }) => wait(offset?, report, event?),
3131
// will never match None as this is caught earlier by StructOpt
32-
None => {
33-
println!("No subcommand provided!");
34-
process::exit(1)
35-
}
32+
None => println!("No subcommand provided!"),
3633
}
34+
Ok(())
35+
}
36+
37+
fn main() {
38+
process::exit(match run_heliocron() {
39+
Ok(_) => 0,
40+
Err(err) => {
41+
eprintln!("{}", err);
42+
1
43+
}
44+
});
3745
}

src/config.rs

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ use dirs;
55
use serde::Deserialize;
66
use structopt::clap::AppSettings;
77
use structopt::StructOpt;
8-
use toml;
98

10-
use super::{enums, parsers, structs};
9+
use super::{
10+
enums,
11+
errors::{ConfigErrorKind, HeliocronError},
12+
parsers, structs,
13+
};
14+
15+
type Result<T> = std::result::Result<T, HeliocronError>;
1116

1217
#[derive(Debug, StructOpt)]
1318
#[structopt(
@@ -50,7 +55,7 @@ pub enum Subcommand {
5055
default_value = "00:00:00",
5156
parse(from_str=parsers::parse_offset),
5257
)]
53-
offset: Duration,
58+
offset: Result<Duration>,
5459

5560
#[structopt(
5661
help = "Choose one of {sunrise | sunset} from which to base your delay.",
@@ -59,7 +64,7 @@ pub enum Subcommand {
5964
parse(from_str=parsers::parse_event),
6065
possible_values = &["sunrise", "sunset"]
6166
)]
62-
event: enums::Event,
67+
event: Result<enums::Event>,
6368
},
6469
}
6570

@@ -89,7 +94,7 @@ impl TomlConfig {
8994
}
9095
}
9196

92-
fn from_toml(config: Result<TomlConfig, toml::de::Error>) -> TomlConfig {
97+
fn from_toml(config: std::result::Result<TomlConfig, toml::de::Error>) -> TomlConfig {
9398
match config {
9499
Ok(conf) => conf,
95100
_ => TomlConfig::new(),
@@ -106,67 +111,71 @@ pub struct Config {
106111
}
107112

108113
impl Config {
109-
fn merge_toml(mut self, toml_config: TomlConfig) -> Self {
114+
fn merge_toml(mut self, toml_config: TomlConfig) -> Result<Config> {
110115
if let (Some(latitude), Some(longitude)) = (toml_config.latitude, toml_config.longitude) {
111-
self.coordinates = structs::Coordinates::from_decimal_degrees(&latitude, &longitude)
116+
self.coordinates = structs::Coordinates::from_decimal_degrees(&latitude, &longitude)?
112117
}
113-
self
118+
Ok(self)
114119
}
115120

116-
fn merge_cli_args(mut self, cli_args: Cli) -> Self {
121+
fn merge_cli_args(mut self, cli_args: Cli) -> Result<Config> {
117122
// merge in location if set. Structopt requires either both or neither of lat and long to be set
118123
if let (Some(latitude), Some(longitude)) = (cli_args.latitude, cli_args.longitude) {
119-
self.coordinates = structs::Coordinates::from_decimal_degrees(&latitude, &longitude)
124+
self.coordinates = structs::Coordinates::from_decimal_degrees(&latitude, &longitude)?
120125
}
121126

122127
// set the date
123128
let date_args = cli_args.date_args;
124129
if let Some(date) = date_args.date {
125130
self.date = parsers::parse_date(
126-
Some(&date),
131+
&date,
127132
&date_args.date_format,
128133
date_args.time_zone.as_deref(),
129-
);
134+
)?;
130135
}
131136

132137
// set the subcommand to execute
133138
self.subcommand = Some(cli_args.subcommand);
134139

135-
self
140+
Ok(self)
136141
}
137142
}
138143

139-
pub fn get_config() -> Config {
144+
pub fn get_config() -> Result<Config> {
140145
// master function for collecting all config variables and returning a single runtime configuration
141146

142147
// 0. Set up default config
143148
let default_config = Config {
144-
coordinates: structs::Coordinates::from_decimal_degrees("51.4769N", "0.0005W"),
149+
coordinates: structs::Coordinates::from_decimal_degrees("51.4769N", "0.0005W")?,
145150
date: Local::today()
146151
.and_hms(12, 0, 0)
147152
.with_timezone(&FixedOffset::from_offset(Local::today().offset())),
148153
subcommand: None,
149154
event: None,
150155
};
151156

152-
// 1. Overwrite defaults with config from ~/.config/heliocron.toml
157+
// 1. Overwrite defaults with config from ~/.config/heliocron.toml if present
153158

154159
let path = dirs::config_dir()
155-
.unwrap()
160+
.unwrap() // this shouldn't ever really be None?
156161
.join(Path::new("heliocron.toml"));
157162

158163
let file = fs::read_to_string(path);
159164

160165
let config: Config = match file {
161-
Ok(f) => default_config.merge_toml(TomlConfig::from_toml(toml::from_str(&f))),
162-
// any problems with the config file and we just continue on with the default configuration
163-
_ => default_config,
164-
};
165-
166-
// 2. Add/overwrite any currently set config from CLI arguments
166+
Ok(f) => match default_config.merge_toml(TomlConfig::from_toml(toml::from_str(&f))) {
167+
Ok(merged_config) => Ok(merged_config),
168+
// any errors parsing the .toml raise an error
169+
Err(_) => Err(HeliocronError::Config(ConfigErrorKind::InvalidTomlFile)),
170+
},
171+
// any problems opening the .toml file and we just continue on with the default configuration
172+
Err(_) => Ok(default_config),
173+
}?;
174+
175+
// 2. Overwrite any currently set config with CLI arguments
167176
let cli_args = Cli::from_args();
168177

169-
let config = config.merge_cli_args(cli_args);
178+
let config = config.merge_cli_args(cli_args)?;
170179

171-
config
180+
Ok(config)
172181
}

src/enums.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
use std::str::FromStr;
1+
use std::result;
2+
3+
use super::errors::{ConfigErrorKind, HeliocronError};
4+
5+
type Result<T> = result::Result<T, HeliocronError>;
26

37
#[derive(Debug, PartialEq)]
48
pub enum Event {
59
Sunrise,
610
Sunset,
711
}
812

9-
impl FromStr for Event {
10-
type Err = ();
11-
12-
fn from_str(s: &str) -> Result<Self, Self::Err> {
13-
let s: &str = &s.trim().to_lowercase();
14-
15-
match s {
16-
"sunrise" => Ok(Self::Sunrise),
17-
"sunset" => Ok(Self::Sunset),
18-
_ => Err(()),
13+
impl Event {
14+
pub fn new(event: &str) -> Result<Event> {
15+
let event = event.trim().to_lowercase();
16+
match event.as_str() {
17+
"sunrise" => Ok(Event::Sunrise),
18+
"sunset" => Ok(Event::Sunset),
19+
_ => Err(HeliocronError::Config(ConfigErrorKind::InvalidEvent)),
1920
}
2021
}
2122
}

src/errors.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use std::error;
2+
3+
use chrono;
4+
5+
#[derive(Debug)]
6+
pub enum HeliocronError {
7+
Config(ConfigErrorKind),
8+
}
9+
10+
#[derive(Debug)]
11+
pub enum ConfigErrorKind {
12+
InvalidCoordindates(&'static str),
13+
InvalidTomlFile,
14+
ParseDate,
15+
InvalidEvent,
16+
}
17+
18+
impl ConfigErrorKind {
19+
fn as_str(&self) -> &str {
20+
match *self {
21+
ConfigErrorKind::InvalidCoordindates(msg) => msg,
22+
ConfigErrorKind::InvalidTomlFile => {
23+
"Error parsing .toml file. Ensure that it is of the correct format."
24+
}
25+
ConfigErrorKind::ParseDate => {
26+
"Error parsing date. Ensure the date and timezone formats are correct."
27+
}
28+
ConfigErrorKind::InvalidEvent => {
29+
"Error parsing event. Expected one of {'sunrise' | 'sunset'}"
30+
}
31+
}
32+
}
33+
}
34+
35+
impl std::fmt::Display for HeliocronError {
36+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37+
match *self {
38+
HeliocronError::Config(ref err) => write!(
39+
f,
40+
"Config error: {}",
41+
match err {
42+
ConfigErrorKind::InvalidCoordindates(msg) =>
43+
format!("Invalid coordinates - {}", msg),
44+
ConfigErrorKind::InvalidTomlFile => err.as_str().to_string(),
45+
ConfigErrorKind::ParseDate => err.as_str().to_string(),
46+
ConfigErrorKind::InvalidEvent => err.as_str().to_string(),
47+
}
48+
),
49+
}
50+
}
51+
}
52+
53+
impl error::Error for HeliocronError {
54+
fn description(&self) -> &str {
55+
match *self {
56+
HeliocronError::Config(ref err) => err.as_str(),
57+
}
58+
}
59+
}
60+
61+
impl From<chrono::ParseError> for HeliocronError {
62+
fn from(err: chrono::ParseError) -> Self {
63+
match err {
64+
_err => HeliocronError::Config(ConfigErrorKind::ParseDate),
65+
}
66+
}
67+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod config;
22
pub mod enums;
3+
pub mod errors;
34
pub mod parsers;
45
pub mod report;
56
pub mod structs;

0 commit comments

Comments
 (0)