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

Improve Errors using miette #103

Merged
merged 60 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
8e46782
use thiserror for granular errors
asmello Sep 29, 2023
e4f3f92
uncomment local registry module
asmello Sep 29, 2023
7910a2a
add doc-comments
asmello Sep 29, 2023
8a329af
Merge branch 'main' into amello/thiserror
asmello Sep 29, 2023
bf8ef05
make changes to generate command stuff
asmello Sep 29, 2023
b8371ee
fix typo
asmello Sep 29, 2023
0bdfc2f
fix typo
asmello Sep 29, 2023
d35e251
fix inverted test
asmello Sep 29, 2023
4257574
rework errors
asmello Oct 3, 2023
bebba3d
some leftover cleanup
asmello Oct 3, 2023
70f79d6
more tweaks
asmello Oct 3, 2023
144138f
Merge branch 'main' into amello/thiserror
asmello Oct 3, 2023
1ea79de
Merge branch 'main' into amello/thiserror
asmello Oct 4, 2023
ef92502
Merge branch 'main' into amello/thiserror
asmello Oct 4, 2023
d9643b1
improve some error messages
asmello Oct 4, 2023
c542618
MVP
asmello Oct 6, 2023
c81ec9f
remove displaydoc
asmello Oct 6, 2023
91b2532
remove http errors
asmello Oct 6, 2023
e4a57cf
Merge branch 'main' into amello/alternative
asmello Oct 6, 2023
df74971
remove contextcompat from command mod
asmello Oct 6, 2023
c0585a8
undo unintentional feature flag condition change
asmello Oct 6, 2023
4cf861e
some tweaks to minimise changes
asmello Oct 6, 2023
9d692fc
add FileExistsError
asmello Oct 6, 2023
2d25120
more tweaks to minimise changes
asmello Oct 6, 2023
437e086
more tweaks
asmello Oct 6, 2023
9cac469
switch a bunch of bails to ensures
asmello Oct 6, 2023
6078c5b
last tweaks for today
asmello Oct 6, 2023
16f5554
use imports
asmello Oct 10, 2023
ab06839
Merge branch 'main' into amello/alternative
asmello Oct 10, 2023
0e4a10e
remove eyre prefix in bail
asmello Oct 10, 2023
da7fa15
switch to miette
asmello Oct 10, 2023
d5a1d1d
Apply suggestions from code review
asmello Oct 10, 2023
b14bc4d
Apply suggestions from code review
asmello Oct 10, 2023
68f0e10
avoid expect
asmello Oct 10, 2023
101b04e
Apply suggestions from code review
asmello Oct 10, 2023
ec3b963
copy changes to local.rs
asmello Oct 10, 2023
f16515b
remove unecessary clones
asmello Oct 10, 2023
3352d6c
derive diagnostic for downloaderror
asmello Oct 10, 2023
8d27b1d
introduce enum for ser/de errors
asmello Oct 10, 2023
2c9bd5f
remove some redundant error wrappers
asmello Oct 10, 2023
75ce67b
add some miette
asmello Oct 10, 2023
4e766ac
Merge branch 'main' into amello/alternative
mara-schulke Oct 11, 2023
6b52a65
fmt
asmello Oct 11, 2023
1ceaa04
Apply suggestions from code review
asmello Oct 12, 2023
f393b47
no expectations
asmello Oct 12, 2023
c98d88c
miette everywhere
asmello Oct 12, 2023
8c7f536
move BUFFRS_SUITE to const
asmello Oct 12, 2023
fb914c1
more miette
asmello Oct 12, 2023
75db404
fix credentials::write and nits
asmello Oct 12, 2023
4a2ddad
refactor request code
asmello Oct 12, 2023
e0a97a7
Merge branch 'main' into amello/alternative
asmello Oct 13, 2023
74dfc3d
Update src/package.rs
asmello Oct 13, 2023
ffe17a1
nits
asmello Oct 13, 2023
d49b5c5
revert to inline code in download
asmello Oct 13, 2023
6b73ab9
remove todo
asmello Oct 13, 2023
495b956
add doc-comment
asmello Oct 13, 2023
ab2b147
move ManagedFile to lib
asmello Oct 13, 2023
0e7f2e2
Merge branch 'main' into amello/alternative
mara-schulke Oct 13, 2023
1de378b
Merge branch 'main' into amello/alternative
mara-schulke Oct 13, 2023
7332c20
Merge branch 'main' into amello/alternative
mara-schulke Oct 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
MVP
  • Loading branch information
asmello committed Oct 6, 2023
commit c542618351cd4f41de1a758ca7ec5d2e1adcd52b
296 changes: 89 additions & 207 deletions src/command.rs

Large diffs are not rendered by default.

85 changes: 27 additions & 58 deletions src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use displaydoc::Display;
use eyre::Context;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, env, path::PathBuf};
use thiserror::Error;
use tokio::fs;

use crate::{
errors::{DeserializationError, SerializationError},
errors::{DeserializationError, ReadError, SerializationError, WriteError},
registry::RegistryUri,
};

Expand All @@ -35,99 +35,68 @@ pub struct Credentials {
pub registry_tokens: HashMap<RegistryUri, String>,
}

#[derive(Error, Display, Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum LocateError {
/// the home folder could not be determined
MissingHome,
}

#[derive(Error, Display, Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ExistsError {
/// could not locate the credentials file
Locate(#[from] LocateError),
/// could not access the filesystem
Io(#[from] std::io::Error),
}
const BUFFRS_HOME_VAR: &str = "BUFFRS_HOME";

#[derive(Error, Display, Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ReadError {
/// could not locate the credentials file
Locate(#[from] LocateError),
/// could not read the file
Io(#[from] std::io::Error),
/// could not deserialize the credentials
Deserialize(#[from] DeserializationError),
}

#[derive(Error, Display, Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum WriteError {
/// could not locate the credentials file
Locate(#[from] LocateError),
/// could not write to the file
Io(#[from] std::io::Error),
/// could not serialize the credentials
Serialize(#[from] SerializationError),
}
#[derive(Error, Debug)]
#[error("could not determine credentials location")]
struct LocateError(#[from] eyre::Report);

impl Credentials {
fn location() -> Result<PathBuf, LocateError> {
env::var("BUFFRS_HOME")
env::var(BUFFRS_HOME_VAR)
.map(PathBuf::from)
.or(home::home_dir().ok_or(LocateError::MissingHome))
.map(|home| home.join(BUFFRS_HOME).join(CREDENTIALS_FILE))
.or_else(|_| {
home::home_dir()
.ok_or_else(|| eyre::eyre!("{BUFFRS_HOME_VAR} is not set and the user's home folder could not be determined"))
})
.map(|home| home.join(BUFFRS_HOME).join(CREDENTIALS_FILE)).map_err(LocateError::from)
}

/// Checks if the credentials exists
pub async fn exists() -> Result<bool, ExistsError> {
pub async fn exists() -> eyre::Result<bool> {
fs::try_exists(Self::location()?)
.await
.map_err(ExistsError::from)
.wrap_err_with(|| format!("failed to determine if {CREDENTIALS_FILE} file exists"))
}

/// Reads the credentials from the file system
pub async fn read() -> Result<Self, ReadError> {
let raw: RawCredentialCollection =
toml::from_str(&fs::read_to_string(Self::location()?).await?)
.map_err(DeserializationError::from)?;
pub async fn read() -> eyre::Result<Self> {
let raw: RawCredentialCollection = toml::from_str(
&fs::read_to_string(Self::location()?)
.await
.wrap_err_with(|| ReadError(CREDENTIALS_FILE))?,
)
.wrap_err_with(|| DeserializationError("credentials"))?;

Ok(raw.into())
}

/// Writes the credentials to the file system
pub async fn write(&self) -> Result<(), WriteError> {
pub async fn write(&self) -> eyre::Result<()> {
fs::create_dir(
Self::location()
.map_err(WriteError::Locate)?
Self::location()?
.parent()
asmello marked this conversation as resolved.
Show resolved Hide resolved
.expect("unexpected error: resolved credentials path has no parent"),
asmello marked this conversation as resolved.
Show resolved Hide resolved
)
.await
.ok();
.ok(); // ok to ignore if parent already exists (other failure modes covered by expect above)
asmello marked this conversation as resolved.
Show resolved Hide resolved

let data: RawCredentialCollection = self.clone().into();

fs::write(
Self::location()?,
toml::to_string(&data)
.map_err(SerializationError::from)?
.wrap_err_with(|| SerializationError("credentials"))?
.into_bytes(),
)
.await
.map_err(WriteError::from)
.wrap_err_with(|| WriteError(CREDENTIALS_FILE))
}

/// Loads the credentials from the file system
///
/// Note: Initializes the credential file if its not present
pub async fn load() -> Result<Self, WriteError> {
pub async fn load() -> eyre::Result<Self> {
let Ok(credentials) = Self::read().await else {
let credentials = Credentials::default();
credentials.write().await?;
Expand Down
55 changes: 30 additions & 25 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
use displaydoc::Display;
use url::Url;

/// Wrapper for a generic serialization error
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub struct DeserializationError(#[from] toml::de::Error);

/// Wrapper for a generic serialization error
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub struct SerializationError(#[from] toml::ser::Error);

/// Request failure context
#[derive(thiserror::Error, Debug)]
#[error("{method} request to {url}")]
Expand Down Expand Up @@ -88,22 +77,38 @@ impl ResponseError {
}
}

#[derive(thiserror::Error, Display, Debug)]
#[allow(missing_docs)]
pub enum ConfigError {
/// unknown error
Unknown,
}

#[derive(thiserror::Error, Display, Debug)]
#[allow(missing_docs)]
pub enum HttpError {
/// failed to make an http request
#[derive(thiserror::Error, Debug)]
pub(crate) enum HttpError {
#[error("failed to make an http request")]
Request(#[from] RequestError),
/// remote produced an error response
#[error("remote produced an error response")]
Response(#[from] ResponseError),
/// unauthorized - please provide registry credentials with `buffrs login`
#[error("unauthorized - please provide registry credentials with `buffrs login`")]
Unauthorized,
/// {0}
#[error("{0}")]
Other(String),
}

#[derive(thiserror::Error, Debug)]
#[error("environment variable {0} is not set")]
pub(crate) struct EnvVarNotSet(pub &'static str);

#[derive(thiserror::Error, Debug)]
#[error("could not write to {0} file")]
pub(crate) struct WriteError(pub &'static str);

#[derive(thiserror::Error, Debug)]
#[error("could not read from {0} file")]
pub(crate) struct ReadError(pub &'static str);

#[derive(thiserror::Error, Debug)]
#[error("could not deserialize {0}")]
pub(crate) struct DeserializationError(pub &'static str);

#[derive(thiserror::Error, Debug)]
#[error("could not serialize {0}")]
pub(crate) struct SerializationError(pub &'static str);

#[derive(thiserror::Error, Debug)]
#[error("file `{0}` is missing")]
pub(crate) struct FileNotFound(pub String);
53 changes: 12 additions & 41 deletions src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,19 @@ use std::{
path::{Path, PathBuf},
};

use displaydoc::Display;
use eyre::Context;
use protoc_bin_vendored::protoc_bin_path;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::{
manifest::{self, Manifest},
package::PackageStore,
};
use crate::{manifest::Manifest, package::PackageStore};

/// The language used for code generation
#[derive(
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, clap::ValueEnum,
)]
#[serde(rename_all = "kebab-case")]
#[allow(missing_docs)] // trivial enum
pub enum Language {
/// The Python language
Python,
}

Expand All @@ -57,26 +53,13 @@ pub enum Generator {
},
}

#[derive(Error, Display, Debug)]
#[allow(missing_docs)]
pub enum RunError {
/// unable to locate vendored protoc
Locate,
/// io error
Io(#[from] std::io::Error),
/// a signal interrupted the protoc subprocess before it could complete
Interrupted,
/// the protoc subprocess terminated with an error: {code}
NonZeroExitCode { code: i32, stderr: Vec<u8> },
}

impl Generator {
/// Tonic include file name
pub const TONIC_INCLUDE_FILE: &str = "buffrs.rs";

/// Run the generator for a dependency and output files at the provided path
pub async fn run(&self) -> Result<(), RunError> {
let protoc = protoc_bin_path().map_err(|_| RunError::Locate)?;
pub async fn run(&self) -> eyre::Result<()> {
let protoc = protoc_bin_path().wrap_err("unable to locate vendored protoc")?;

std::env::set_var("PROTOC", protoc.clone());

Expand Down Expand Up @@ -117,13 +100,12 @@ impl Generator {

let output = protoc_cmd.output().await?;

let exit = output.status.code().ok_or(RunError::Interrupted)?;
let exit = output.status.code().ok_or(eyre::eyre!(
"a signal interrupted the protoc subprocess before it could complete"
))?;

if exit != 0 {
return Err(RunError::NonZeroExitCode {
code: exit,
stderr: output.stderr,
});
eyre::bail!("the protoc subprocess terminated with an error: {exit}");
}

tracing::info!(":: {language} code generated successfully");
Expand All @@ -134,29 +116,18 @@ impl Generator {
}
}

#[derive(Error, Display, Debug)]
#[allow(missing_docs)]
pub enum GenerateError {
/// failed to read the manifest
ManifestRead(#[from] manifest::ReadError),
/// either a compilable package (library or api) or at least one dependency is needed to generate code bindings
NothingToGenerate,
/// failed to generate bindings
RunFailed(#[from] RunError),
}

impl Generator {
/// Execute code generation with pre-configured parameters
pub async fn generate(&self) -> Result<(), GenerateError> {
pub async fn generate(&self) -> eyre::Result<()> {
let manifest = Manifest::read().await?;

tracing::info!(":: initializing code generator");

if !manifest.package.kind.compilable() && manifest.dependencies.is_empty() {
return Err(GenerateError::NothingToGenerate);
eyre::bail!("either a compilable package (library or api) or at least one dependency is needed to generate code bindings");
}

self.run().await?;
self.run().await.wrap_err("failed to generate bindings")?;

if manifest.package.kind.compilable() {
let location = Path::new(PackageStore::PROTO_PATH);
Expand Down
Loading