diff --git a/Cargo.lock b/Cargo.lock index cc087913..1e4dcb5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,11 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "anyhow" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "atty" version = "0.2.14" @@ -1667,11 +1672,13 @@ dependencies = [ name = "rojo" version = "0.6.0-alpha.3" dependencies = [ + "anyhow 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", "criterion 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fs-err 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1702,6 +1709,7 @@ dependencies = [ "structopt 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2052,6 +2060,24 @@ dependencies = [ "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thiserror" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thiserror-impl 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -2531,6 +2557,7 @@ dependencies = [ "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" "checksum aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d5e63fd144e18ba274ae7095c0197a870a7b9468abc801dd62f190d80817d2ec" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum anyhow 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "013a6e0a2cbe3d20f9c60b65458f7a7f7a5e636c5d0f45a5a6aee5d4b1f01785" "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" @@ -2747,6 +2774,8 @@ dependencies = [ "checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" "checksum termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ee14bf8e6767ab4c687c9e8bc003879e042a96fd67a3ba5934eadb6536bef4db" +"checksum thiserror-impl 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "a7b51e1fbc44b5a0840be594fbc0f960be09050f2617e61e6aa43bef97cd3ef4" "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum tinytemplate 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "57a3c6667d3e65eb1bc3aed6fd14011c6cbc3a0665218ab7f5daf040b9ec371a" diff --git a/Cargo.toml b/Cargo.toml index 501e3b8b..ce3d70fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,10 +63,12 @@ harness = false [dependencies] memofs = { version = "0.1.0", path = "memofs" } +anyhow = "1.0.27" backtrace = "0.3" crossbeam-channel = "0.4.0" csv = "1.1.1" env_logger = "0.7.1" +fs-err = "2.2.0" futures = "0.1.29" globset = "0.4.4" humantime = "1.3.0" @@ -90,6 +92,7 @@ serde_json = "1.0" snafu = "0.6.0" structopt = "0.3.5" termcolor = "1.0.5" +thiserror = "1.0.11" tokio = "0.1.22" uuid = { version = "0.8.1", features = ["v4", "serde"] } diff --git a/src/cli/build.rs b/src/cli/build.rs index 11fc7bd7..fbbe4373 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -1,15 +1,13 @@ use std::{ fs::File, - io::{self, BufWriter, Write}, + io::{BufWriter, Write}, }; use memofs::Vfs; -use snafu::{ResultExt, Snafu}; +use thiserror::Error; use tokio::runtime::Runtime; -use crate::{ - cli::BuildCommand, project::ProjectError, serve_session::ServeSession, snapshot::RojoTree, -}; +use crate::{cli::BuildCommand, serve_session::ServeSession, snapshot::RojoTree}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum OutputKind { @@ -31,45 +29,17 @@ fn detect_output_kind(options: &BuildCommand) -> Option { } } -#[derive(Debug, Snafu)] -pub struct BuildError(Error); - -#[derive(Debug, Snafu)] +#[derive(Debug, Error)] enum Error { - #[snafu(display("Could not detect what kind of file to create"))] + #[error("Could not detect what kind of file to build. Expected output file to end in .rbxl, .rbxlx, .rbxm, or .rbxmx.")] UnknownOutputKind, - - #[snafu(display("{}", source))] - Io { source: io::Error }, - - #[snafu(display("{}", source))] - XmlModelEncode { source: rbx_xml::EncodeError }, - - #[snafu(display("Binary model error: {:?}", source))] - BinaryModelEncode { - #[snafu(source(false))] - source: rbx_binary::EncodeError, - }, - - #[snafu(display("{}", source))] - Project { source: ProjectError }, -} - -impl From for Error { - fn from(source: rbx_binary::EncodeError) -> Self { - Error::BinaryModelEncode { source } - } } fn xml_encode_config() -> rbx_xml::EncodeOptions { rbx_xml::EncodeOptions::new().property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown) } -pub fn build(options: BuildCommand) -> Result<(), BuildError> { - Ok(build_inner(options)?) -} - -fn build_inner(options: BuildCommand) -> Result<(), Error> { +pub fn build(options: BuildCommand) -> Result<(), anyhow::Error> { log::trace!("Constructing in-memory filesystem"); let vfs = Vfs::new_default(); @@ -98,14 +68,14 @@ fn build_inner(options: BuildCommand) -> Result<(), Error> { Ok(()) } -fn write_model(tree: &RojoTree, options: &BuildCommand) -> Result<(), Error> { +fn write_model(tree: &RojoTree, options: &BuildCommand) -> Result<(), anyhow::Error> { let output_kind = detect_output_kind(&options).ok_or(Error::UnknownOutputKind)?; log::debug!("Hoping to generate file of type {:?}", output_kind); let root_id = tree.get_root_id(); log::trace!("Opening output file for write"); - let file = File::create(&options.output).context(Io)?; + let file = File::create(&options.output)?; let mut file = BufWriter::new(file); match output_kind { @@ -113,8 +83,7 @@ fn write_model(tree: &RojoTree, options: &BuildCommand) -> Result<(), Error> { // Model files include the root instance of the tree and all its // descendants. - rbx_xml::to_writer(&mut file, tree.inner(), &[root_id], xml_encode_config()) - .context(XmlModelEncode)?; + rbx_xml::to_writer(&mut file, tree.inner(), &[root_id], xml_encode_config())?; } OutputKind::Rbxlx => { // Place files don't contain an entry for the DataModel, but our @@ -123,8 +92,7 @@ fn write_model(tree: &RojoTree, options: &BuildCommand) -> Result<(), Error> { let root_instance = tree.get_instance(root_id).unwrap(); let top_level_ids = root_instance.children(); - rbx_xml::to_writer(&mut file, tree.inner(), top_level_ids, xml_encode_config()) - .context(XmlModelEncode)?; + rbx_xml::to_writer(&mut file, tree.inner(), top_level_ids, xml_encode_config())?; } OutputKind::Rbxm => { rbx_binary::encode(tree.inner(), &[root_id], &mut file)?; @@ -141,7 +109,7 @@ fn write_model(tree: &RojoTree, options: &BuildCommand) -> Result<(), Error> { } } - file.flush().context(Io)?; + file.flush()?; Ok(()) } diff --git a/src/cli/doc.rs b/src/cli/doc.rs index f10cc8f5..3de2b052 100644 --- a/src/cli/doc.rs +++ b/src/cli/doc.rs @@ -1,26 +1,4 @@ -use opener::{open, OpenError}; -use snafu::Snafu; - -#[derive(Debug, Snafu)] -pub struct DocError(Error); - -#[derive(Debug, Snafu)] -enum Error { - Open { source: OpenError }, -} - -impl From for Error { - fn from(source: OpenError) -> Self { - Error::Open { source } - } -} - -pub fn doc() -> Result<(), DocError> { - doc_inner()?; - Ok(()) -} - -fn doc_inner() -> Result<(), Error> { - open("https://rojo.space/docs")?; +pub fn doc() -> Result<(), anyhow::Error> { + opener::open("https://rojo.space/docs")?; Ok(()) } diff --git a/src/cli/init.rs b/src/cli/init.rs index 27aab313..b5ff49dc 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -5,7 +5,7 @@ use std::{ process::{Command, Stdio}, }; -use snafu::Snafu; +use thiserror::Error; use crate::cli::{InitCommand, InitKind}; @@ -20,32 +20,16 @@ static PLACE_PROJECT: &str = static PLACE_README: &str = include_str!("../../assets/default-place-project/README.md"); static PLACE_GIT_IGNORE: &str = include_str!("../../assets/default-place-project/gitignore.txt"); -#[derive(Debug, Snafu)] -pub struct InitError(Error); - -#[derive(Debug, Snafu)] +#[derive(Debug, Error)] enum Error { - #[snafu(display("A project file named default.project.json already exists in this folder"))] + #[error("A project file named default.project.json already exists in this folder")] AlreadyExists, - #[snafu(display("git init failed"))] + #[error("git init failed")] GitInit, - - #[snafu(display("I/O error"))] - Io { source: io::Error }, } -impl From for Error { - fn from(source: io::Error) -> Self { - Self::Io { source } - } -} - -pub fn init(options: InitCommand) -> Result<(), InitError> { - Ok(init_inner(options)?) -} - -fn init_inner(options: InitCommand) -> Result<(), Error> { +pub fn init(options: InitCommand) -> Result<(), anyhow::Error> { let base_path = options.absolute_path(); fs::create_dir_all(&base_path)?; @@ -65,7 +49,7 @@ fn init_inner(options: InitCommand) -> Result<(), Error> { } } -fn init_place(base_path: &Path, project_params: ProjectParams) -> Result<(), Error> { +fn init_place(base_path: &Path, project_params: ProjectParams) -> Result<(), anyhow::Error> { eprintln!("Creating new place project '{}'", project_params.name); let project_file = project_params.render_template(PLACE_PROJECT); @@ -109,7 +93,7 @@ fn init_place(base_path: &Path, project_params: ProjectParams) -> Result<(), Err Ok(()) } -fn init_model(base_path: &Path, project_params: ProjectParams) -> Result<(), Error> { +fn init_model(base_path: &Path, project_params: ProjectParams) -> Result<(), anyhow::Error> { eprintln!("Creating new model project '{}'", project_params.name); let project_file = project_params.render_template(MODEL_PROJECT); @@ -147,14 +131,14 @@ impl ProjectParams { } /// Attempt to initialize a Git repository if necessary, and create .gitignore. -fn try_git_init(path: &Path, git_ignore: &str) -> Result<(), Error> { +fn try_git_init(path: &Path, git_ignore: &str) -> Result<(), anyhow::Error> { if should_git_init(path) { log::debug!("Initializing Git repository..."); let status = Command::new("git").arg("init").current_dir(path).status()?; if !status.success() { - return Err(Error::GitInit); + return Err(Error::GitInit.into()); } } @@ -186,7 +170,7 @@ fn should_git_init(path: &Path) -> bool { } /// Write a file if it does not exist yet, otherwise, leave it alone. -fn write_if_not_exists(path: &Path, contents: &str) -> Result<(), Error> { +fn write_if_not_exists(path: &Path, contents: &str) -> Result<(), anyhow::Error> { let file_res = OpenOptions::new().write(true).create_new(true).open(path); let mut file = match file_res { @@ -205,7 +189,7 @@ fn write_if_not_exists(path: &Path, contents: &str) -> Result<(), Error> { } /// Try to create a project file and fail if it already exists. -fn try_create_project(base_path: &Path, contents: &str) -> Result<(), Error> { +fn try_create_project(base_path: &Path, contents: &str) -> Result<(), anyhow::Error> { let project_path = base_path.join("default.project.json"); let file_res = OpenOptions::new() @@ -217,7 +201,7 @@ fn try_create_project(base_path: &Path, contents: &str) -> Result<(), Error> { Ok(file) => file, Err(err) => { return match err.kind() { - io::ErrorKind::AlreadyExists => Err(Error::AlreadyExists), + io::ErrorKind::AlreadyExists => Err(Error::AlreadyExists.into()), _ => Err(err.into()), } } diff --git a/src/cli/serve.rs b/src/cli/serve.rs index 429ce23b..b9ae48b5 100644 --- a/src/cli/serve.rs +++ b/src/cli/serve.rs @@ -3,25 +3,15 @@ use std::{ sync::Arc, }; +use anyhow::Result; use memofs::Vfs; -use snafu::Snafu; use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; use crate::{cli::ServeCommand, serve_session::ServeSession, web::LiveServer}; const DEFAULT_PORT: u16 = 34872; -#[derive(Debug, Snafu)] -pub struct ServeError(Error); - -#[derive(Debug, Snafu)] -enum Error {} - -pub fn serve(options: ServeCommand) -> Result<(), ServeError> { - Ok(serve_inner(options)?) -} - -fn serve_inner(options: ServeCommand) -> Result<(), Error> { +pub fn serve(options: ServeCommand) -> Result<()> { let vfs = Vfs::new_default(); let session = Arc::new(ServeSession::new(vfs, &options.absolute_project())); diff --git a/src/cli/upload.rs b/src/cli/upload.rs index aed6ba30..2a9b7f7a 100644 --- a/src/cli/upload.rs +++ b/src/cli/upload.rs @@ -1,34 +1,19 @@ use memofs::Vfs; use reqwest::header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT}; -use snafu::{ResultExt, Snafu}; +use thiserror::Error; use crate::{auth_cookie::get_auth_cookie, cli::UploadCommand, serve_session::ServeSession}; -#[derive(Debug, Snafu)] -pub struct UploadError(Error); - -#[derive(Debug, Snafu)] +#[derive(Debug, Error)] enum Error { - #[snafu(display( - "Rojo could not find your Roblox auth cookie. Please pass one via --cookie.", - ))] + #[error("Rojo could not find your Roblox auth cookie. Please pass one via --cookie.")] NeedAuthCookie, - #[snafu(display("XML model file encode error: {}", source))] - XmlModel { source: rbx_xml::EncodeError }, - - #[snafu(display("HTTP error: {}", source))] - Http { source: reqwest::Error }, - - #[snafu(display("Roblox API error: {}", body))] + #[error("The Roblox API returned an unexpected error: {body}")] RobloxApi { body: String }, } -pub fn upload(options: UploadCommand) -> Result<(), UploadError> { - Ok(upload_inner(options)?) -} - -fn upload_inner(options: UploadCommand) -> Result<(), Error> { +pub fn upload(options: UploadCommand) -> Result<(), anyhow::Error> { let cookie = options .cookie .clone() @@ -55,7 +40,7 @@ fn upload_inner(options: UploadCommand) -> Result<(), Error> { let config = rbx_xml::EncodeOptions::new() .property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown); - rbx_xml::to_writer(&mut buffer, tree.inner(), &encode_ids, config).context(XmlModel)?; + rbx_xml::to_writer(&mut buffer, tree.inner(), &encode_ids, config)?; let url = format!( "https://data.roblox.com/Data/Upload.ashx?assetid={}", @@ -72,13 +57,13 @@ fn upload_inner(options: UploadCommand) -> Result<(), Error> { .header(CONTENT_TYPE, "application/xml") .header(ACCEPT, "application/json") .body(buffer) - .send() - .context(Http)?; + .send()?; if !response.status().is_success() { return Err(Error::RobloxApi { - body: response.text().context(Http)?, - }); + body: response.text()?, + } + .into()); } Ok(())