Use thiserror and anyhow for command-level error types

This commit is contained in:
Lucien Greathouse
2020-03-16 21:13:38 -07:00
parent 363f95ba14
commit f69096dadb
7 changed files with 69 additions and 132 deletions

View File

@@ -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<OutputKind> {
}
}
#[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<rbx_binary::EncodeError> 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(())
}

View File

@@ -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<OpenError> 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(())
}

View File

@@ -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<io::Error> 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()),
}
}

View File

@@ -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()));

View File

@@ -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(())