Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lessen amount of panics #59

Merged
merged 5 commits into from Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 21 additions & 7 deletions Cargo.lock

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

5 changes: 3 additions & 2 deletions Cargo.toml
Expand Up @@ -11,17 +11,18 @@ authors = ["Haris <4259838+Wulf@users.noreply.github.com>"]
edition = "2021"

[features]
default = ["tsync"]
default = ["tsync", "backtrace"]
tsync = []
async = []
backtrace = []

[dependencies]
structopt = "0.3"
syn = { version = "1", features = ["extra-traits", "full"] }
anyhow = "1"
proc-macro2 = "1"
indoc = "2.0.0"
Inflector = { version = "0.11.4" }
thiserror = "1.0"

[lib]
path = "src/lib.rs"
Expand Down
30 changes: 29 additions & 1 deletion src/bin/main.rs
Expand Up @@ -75,6 +75,32 @@ struct Args {
}

fn main() {
let res = actual_main();

if let Err(err) = res {
eprintln!("Error:\n{err}");
#[cfg(feature = "backtrace")]
{
let backtrace = err.backtrace().to_string();

if backtrace == "disabled backtrace" {
eprintln!(
"note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace"
);
} else {
eprintln!("{}", backtrace);
}
}
#[cfg(not(feature = "backtrace"))]
{
eprintln!("backtrace support is disabled, enable feature \"backtrace\"");
}

std::process::exit(1);
}
}

fn actual_main() -> dsync::Result<()> {
let args: Args = Args::from_args();
let cols = args.autogenerated_columns.unwrap_or_default();
let mut default_table_options = TableOptions::default()
Expand Down Expand Up @@ -104,5 +130,7 @@ fn main() {
schema_path: args.schema_path.unwrap_or("crate::schema::".to_owned()),
model_path: args.model_path.unwrap_or("crate::models::".to_owned()),
},
);
)?;

Ok(())
}
169 changes: 169 additions & 0 deletions src/error.rs
@@ -0,0 +1,169 @@
// TODO: change backtrace implementation to be by thiserror, if possible once features become stable
// error_generic_member_access https://github.com/rust-lang/rust/issues/99301
// provide_any https://github.com/rust-lang/rust/issues/96024

use std::{io::Error as ioError, path::Path};
#[cfg(feature = "backtrace")]
use std::backtrace::Backtrace;

pub type Result<T> = std::result::Result<T, Error>;

/// Macro to not repeat having to do multiple implementations of a [ErrorInner] variant with the same string type
macro_rules! fn_string {
($fn_name:ident, $fortype:expr) => {
#[doc = concat!("Create a new [Self] as [", stringify!($fortype), "]")]
pub fn $fn_name<M>(msg: M) -> Self
where
M: Into<String>,
{
return Self::new($fortype(msg.into()));
}
};
}

/// Error type for libytdlr, contains a backtrace, wrapper around [ErrorInner]
#[derive(Debug)]
pub struct Error {
/// The actual error
source: ErrorEnum,
#[cfg(feature = "backtrace")]
/// The backtrace for the error
backtrace: Backtrace,
}

impl Error {
/// Construct a new [Error] instance based on [ErrorInner]
pub fn new(source: ErrorEnum) -> Self {
Self {
source,
#[cfg(feature = "backtrace")]
backtrace: Backtrace::capture(),
}
}

#[cfg(feature = "backtrace")]
/// Get the backtrace that is stored
pub fn backtrace(&self) -> &Backtrace {
&self.backtrace
}

fn_string!(other, ErrorEnum::Other);
fn_string!(
unsupported_schema_format,
ErrorEnum::UnsupportedSchemaFormat
);
fn_string!(unsupported_type, ErrorEnum::UnsupportedType);
fn_string!(no_file_signature, ErrorEnum::NoFileSignature);

/// Create a custom [ioError] with this [Error] wrapped around with a [Path] attached
pub fn custom_ioerror_path<M, P>(kind: std::io::ErrorKind, msg: M, path: P) -> Self
where
M: Into<String>,
P: AsRef<Path>,
{
return Self::new(ErrorEnum::IoError(
ioError::new(kind, msg.into()),
format_path(path.as_ref().to_string_lossy().to_string()),
));
}

pub fn not_a_directory<M, P>(msg: M, path: P) -> Self
where
M: Into<String>,
P: AsRef<Path>,
{
return Self::new(ErrorEnum::NotADirectory(
msg.into(),
path.as_ref().to_string_lossy().to_string(),
));
}
}

impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.source.fmt(f)
}
}

impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
return self.source.source();
}
}

// implement all From<> variants that ErrorInner also implements
impl<T> From<T> for Error
where
T: Into<ErrorEnum>,
{
fn from(value: T) -> Self {
Self::new(value.into())
}
}

/// Error type for "yt-downloader-rust", implements all Error types that could happen in this lib
#[derive(thiserror::Error, Debug)]
pub enum ErrorEnum {
/// Wrapper Variant for [`std::io::Error`]
/// Argument 1 (String) is up to the implementation to set, commonly the path
#[error("IoError: {0}; {1}")]
IoError(std::io::Error, String),
/// Variant for when a directory path was expected but did not exist yet or was not a directory
/// TODO: replace with io::ErrorKind::NotADirectory once stable <https://github.com/rust-lang/rust/issues/86442>
#[error("NotADirectory: {0}; Path: \"{1}\"")]
NotADirectory(String, String),
/// Variant for unsupported diesel schema formats
#[error("UnsupportedSchemaFormat: {0}")]
UnsupportedSchemaFormat(String),
/// Variant for unsupported sql types
#[error("UnsupportedType: {0}")]
UnsupportedType(String),
/// Variant for when "has_file_signature" is `false`
#[error("NoFileSignature: {0}")]
NoFileSignature(String),

/// Variant for Other messages
#[error("Other: {0}")]
Other(String),
}

/// Helper function to keep consistent formatting
#[inline]
fn format_path(msg: String) -> String {
format!("Path \"{}\"", msg)
}

/// Trait to map [std::io::Error] into [Error]
pub trait IOErrorToError<T> {
/// Map a [std::io::Error] to [Error] with a [std::path::Path] attached
fn attach_path_err<P: AsRef<Path>>(self, path: P) -> Result<T>;

/// Map a [std::io::Error] to [Error] with a [std::path::Path] and message attached
fn attach_path_msg<P: AsRef<Path>, M: AsRef<str>>(self, path: P, msg: M) -> Result<T>;
}

impl<T> IOErrorToError<T> for std::result::Result<T, std::io::Error> {
fn attach_path_err<P: AsRef<Path>>(self, path: P) -> Result<T> {
return match self {
Ok(v) => Ok(v),
Err(e) => Err(crate::Error::new(ErrorEnum::IoError(
e,
format_path(path.as_ref().to_string_lossy().to_string()),
))),
};
}

fn attach_path_msg<P: AsRef<Path>, M: AsRef<str>>(self, path: P, msg: M) -> Result<T> {
match self {
Ok(v) => Ok(v),
Err(e) => Err(crate::Error::new(ErrorEnum::IoError(
e,
format!(
"{msg} {path}",
msg = msg.as_ref(),
path = format_path(path.as_ref().to_string_lossy().to_string())
),
))),
}
}
}
29 changes: 14 additions & 15 deletions src/file.rs
@@ -1,23 +1,22 @@
use std::path::PathBuf;
use crate::{Result, IOErrorToError, Error};

pub struct MarkedFile {
pub file_contents: String,
pub path: PathBuf,
}

impl MarkedFile {
pub fn new(path: PathBuf) -> MarkedFile {
MarkedFile {
pub fn new(path: PathBuf) -> Result<MarkedFile> {
Ok(MarkedFile {
path: path.clone(),
file_contents: if !path.exists() {
std::fs::write(&path, "")
.unwrap_or_else(|_| panic!("Could not write to '{path:#?}'"));
std::fs::write(&path, "").attach_path_err(&path)?;
"".to_string()
} else {
std::fs::read_to_string(&path)
.unwrap_or_else(|_| panic!("Could not read '{path:#?}'"))
std::fs::read_to_string(&path).attach_path_err(&path)?
},
}
})
}

pub fn has_use_stmt(&self, use_name: &str) -> bool {
Expand Down Expand Up @@ -91,19 +90,19 @@ impl MarkedFile {
.starts_with(crate::parser::FILE_SIGNATURE)
}

pub fn ensure_file_signature(&self) {
pub fn ensure_file_signature(&self) -> Result<()> {
if !self.has_file_signature() {
panic!("Expected file '{path:#?}' to have file signature ('{sig}') -- you might be accidentally overwriting files that weren't generated!", path=self.path, sig=crate::parser::FILE_SIGNATURE)
return Err(Error::no_file_signature(format!("Expected file '{path:#?}' to have file signature ('{sig}') -- you might be accidentally overwriting files that weren't generated!", path=self.path, sig=crate::parser::FILE_SIGNATURE)));
}

Ok(())
}

pub fn write(&self) {
std::fs::write(&self.path, &self.file_contents)
.unwrap_or_else(|_| panic!("Could not write to file '{:#?}'", self.path));
pub fn write(&self) -> Result<()> {
std::fs::write(&self.path, &self.file_contents).attach_path_err(&self.path)
}

pub fn delete(self) {
std::fs::remove_file(&self.path)
.unwrap_or_else(|_| panic!("Could not delete redundant file '{:#?}'", self.path));
pub fn delete(self) -> Result<()> {
std::fs::remove_file(&self.path).attach_path_err(&self.path)
}
}