Skip to content

Commit

Permalink
implement some proper error handling
Browse files Browse the repository at this point in the history
* implement error handling for Coordinates struct

* complete rough error handling for config

* update reasdme and bump version
  • Loading branch information
mfreeborn authored Mar 11, 2020
1 parent d95f220 commit 424dc64
Show file tree
Hide file tree
Showing 12 changed files with 388 additions and 141 deletions.
11 changes: 0 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "heliocron"
version = "0.2.2"
version = "0.3.0"
authors = ["Michael Freeborn <michaelfreeborn1@gmail.com>"]
description = """
Heliocron is a command line application written in Rust capable of delaying execution of other
Expand Down Expand Up @@ -28,7 +28,6 @@ toml = "0.5"
[dev-dependencies]
assert_cmd = "0.12"
criterion = "0.3"
guerrilla = "0.1"
predicates = "1"

[profile.release]
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ $ cargo install heliocron
.
.
$ heliocron --version
heliocron 0.1.3
heliocron 0.3.0
```

#### 3. Build from source
Expand All @@ -22,7 +22,7 @@ $ git clone https://github.com/mfreeborn/heliocron
$ cd heliocron
$ cargo build --release
$ ./target/release/heliocron --version
heliocron 0.1.3
heliocron 0.3.0
```

## Usage Examples
Expand All @@ -45,8 +45,8 @@ Ever wondered what time sunrise is in Edinburgh on 7th May 2065?
$ heliocron -d "7 May 2065" -f "%e %B %Y" -l 55.9533N -o 3.1883W report
LOCATION
--------
Latitude: 55.9533
Longitude: -3.1883
Latitude: 55.9533N
Longitude: 3.1883W
DATE
----
Expand All @@ -72,8 +72,8 @@ Now, using Heliocron without providing specific coordinates will yield the follo
$ heliocron -d 2020-03-08 report
LOCATION
--------
Latitude: 51.5014
Longitude: -0.1419
Latitude: 51.5014N
Longitude: 0.1419W
DATE
----
Expand All @@ -92,8 +92,8 @@ Arguments passed in via the command line will override those set in the configur
$ heliocron -d 2020-03-08 -l 51.4839N -o 0.6044W report
LOCATION
--------
Latitude: 51.4839
Longitude: -0.6044
Latitude: 51.4839N
Longitude: 0.6044W
DATE
----
Expand Down
4 changes: 2 additions & 2 deletions benches/benches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ fn criterion_benchmark(c: &mut Criterion) {
let date = FixedOffset::east(0).ymd(2020, 2, 25).and_hms(12, 0, 0);

let coordinates = structs::Coordinates {
latitude: 51.0,
longitude: 4.0,
latitude: structs::Latitude { value: 51.0 },
longitude: structs::Longitude { value: 4.0 },
};

c.bench_function("run_report", |b| b.iter(|| run_report(date, coordinates)));
Expand Down
24 changes: 16 additions & 8 deletions src/bin/heliocron.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::process;

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

use heliocron::{config, enums, report, utils};
use heliocron::{config, enums, errors, report, utils};

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

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

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

match config.subcommand {
Some(config::Subcommand::Report {}) => println!("{}", report),
Some(config::Subcommand::Wait { offset, event }) => wait(offset, report, event),
Some(config::Subcommand::Wait { offset, event }) => wait(offset?, report, event?),
// will never match None as this is caught earlier by StructOpt
None => {
println!("No subcommand provided!");
process::exit(1)
}
None => println!("No subcommand provided!"),
}
Ok(())
}

fn main() {
process::exit(match run_heliocron() {
Ok(_) => 0,
Err(err) => {
eprintln!("{}", err);
1
}
});
}
59 changes: 34 additions & 25 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ use dirs;
use serde::Deserialize;
use structopt::clap::AppSettings;
use structopt::StructOpt;
use toml;

use super::{enums, parsers, structs};
use super::{
enums,
errors::{ConfigErrorKind, HeliocronError},
parsers, structs,
};

type Result<T> = std::result::Result<T, HeliocronError>;

#[derive(Debug, StructOpt)]
#[structopt(
Expand Down Expand Up @@ -50,7 +55,7 @@ pub enum Subcommand {
default_value = "00:00:00",
parse(from_str=parsers::parse_offset),
)]
offset: Duration,
offset: Result<Duration>,

#[structopt(
help = "Choose one of {sunrise | sunset} from which to base your delay.",
Expand All @@ -59,7 +64,7 @@ pub enum Subcommand {
parse(from_str=parsers::parse_event),
possible_values = &["sunrise", "sunset"]
)]
event: enums::Event,
event: Result<enums::Event>,
},
}

Expand Down Expand Up @@ -89,7 +94,7 @@ impl TomlConfig {
}
}

fn from_toml(config: Result<TomlConfig, toml::de::Error>) -> TomlConfig {
fn from_toml(config: std::result::Result<TomlConfig, toml::de::Error>) -> TomlConfig {
match config {
Ok(conf) => conf,
_ => TomlConfig::new(),
Expand All @@ -106,67 +111,71 @@ pub struct Config {
}

impl Config {
fn merge_toml(mut self, toml_config: TomlConfig) -> Self {
fn merge_toml(mut self, toml_config: TomlConfig) -> Result<Config> {
if let (Some(latitude), Some(longitude)) = (toml_config.latitude, toml_config.longitude) {
self.coordinates = structs::Coordinates::from_decimal_degrees(&latitude, &longitude)
self.coordinates = structs::Coordinates::from_decimal_degrees(&latitude, &longitude)?
}
self
Ok(self)
}

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

// set the date
let date_args = cli_args.date_args;
if let Some(date) = date_args.date {
self.date = parsers::parse_date(
Some(&date),
&date,
&date_args.date_format,
date_args.time_zone.as_deref(),
);
)?;
}

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

self
Ok(self)
}
}

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

// 0. Set up default config
let default_config = Config {
coordinates: structs::Coordinates::from_decimal_degrees("51.4769N", "0.0005W"),
coordinates: structs::Coordinates::from_decimal_degrees("51.4769N", "0.0005W")?,
date: Local::today()
.and_hms(12, 0, 0)
.with_timezone(&FixedOffset::from_offset(Local::today().offset())),
subcommand: None,
event: None,
};

// 1. Overwrite defaults with config from ~/.config/heliocron.toml
// 1. Overwrite defaults with config from ~/.config/heliocron.toml if present

let path = dirs::config_dir()
.unwrap()
.unwrap() // this shouldn't ever really be None?
.join(Path::new("heliocron.toml"));

let file = fs::read_to_string(path);

let config: Config = match file {
Ok(f) => default_config.merge_toml(TomlConfig::from_toml(toml::from_str(&f))),
// any problems with the config file and we just continue on with the default configuration
_ => default_config,
};

// 2. Add/overwrite any currently set config from CLI arguments
Ok(f) => match default_config.merge_toml(TomlConfig::from_toml(toml::from_str(&f))) {
Ok(merged_config) => Ok(merged_config),
// any errors parsing the .toml raise an error
Err(_) => Err(HeliocronError::Config(ConfigErrorKind::InvalidTomlFile)),
},
// any problems opening the .toml file and we just continue on with the default configuration
Err(_) => Ok(default_config),
}?;

// 2. Overwrite any currently set config with CLI arguments
let cli_args = Cli::from_args();

let config = config.merge_cli_args(cli_args);
let config = config.merge_cli_args(cli_args)?;

config
Ok(config)
}
23 changes: 12 additions & 11 deletions src/enums.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
use std::str::FromStr;
use std::result;

use super::errors::{ConfigErrorKind, HeliocronError};

type Result<T> = result::Result<T, HeliocronError>;

#[derive(Debug, PartialEq)]
pub enum Event {
Sunrise,
Sunset,
}

impl FromStr for Event {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
let s: &str = &s.trim().to_lowercase();

match s {
"sunrise" => Ok(Self::Sunrise),
"sunset" => Ok(Self::Sunset),
_ => Err(()),
impl Event {
pub fn new(event: &str) -> Result<Event> {
let event = event.trim().to_lowercase();
match event.as_str() {
"sunrise" => Ok(Event::Sunrise),
"sunset" => Ok(Event::Sunset),
_ => Err(HeliocronError::Config(ConfigErrorKind::InvalidEvent)),
}
}
}
67 changes: 67 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use std::error;

use chrono;

#[derive(Debug)]
pub enum HeliocronError {
Config(ConfigErrorKind),
}

#[derive(Debug)]
pub enum ConfigErrorKind {
InvalidCoordindates(&'static str),
InvalidTomlFile,
ParseDate,
InvalidEvent,
}

impl ConfigErrorKind {
fn as_str(&self) -> &str {
match *self {
ConfigErrorKind::InvalidCoordindates(msg) => msg,
ConfigErrorKind::InvalidTomlFile => {
"Error parsing .toml file. Ensure that it is of the correct format."
}
ConfigErrorKind::ParseDate => {
"Error parsing date. Ensure the date and timezone formats are correct."
}
ConfigErrorKind::InvalidEvent => {
"Error parsing event. Expected one of {'sunrise' | 'sunset'}"
}
}
}
}

impl std::fmt::Display for HeliocronError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
HeliocronError::Config(ref err) => write!(
f,
"Config error: {}",
match err {
ConfigErrorKind::InvalidCoordindates(msg) =>
format!("Invalid coordinates - {}", msg),
ConfigErrorKind::InvalidTomlFile => err.as_str().to_string(),
ConfigErrorKind::ParseDate => err.as_str().to_string(),
ConfigErrorKind::InvalidEvent => err.as_str().to_string(),
}
),
}
}
}

impl error::Error for HeliocronError {
fn description(&self) -> &str {
match *self {
HeliocronError::Config(ref err) => err.as_str(),
}
}
}

impl From<chrono::ParseError> for HeliocronError {
fn from(err: chrono::ParseError) -> Self {
match err {
_err => HeliocronError::Config(ConfigErrorKind::ParseDate),
}
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod config;
pub mod enums;
pub mod errors;
pub mod parsers;
pub mod report;
pub mod structs;
Expand Down
Loading

0 comments on commit 424dc64

Please sign in to comment.