To be able to insert Pokemon data we'll need a connection to the database. Inside of our main function, we can use std::env::var
to get the DATABASE_URL
we're already using when we cargo check
or cargo run
.
env::var
returns a Result
because it can fail. We can't continue without a database connection so we can use ?
to unwrap the Result
and return an error immediately if one occurs.
fn main() -> Result<(), csv::Error> {
let database_url = env::var("DATABASE_URL")?;
...
}
Since we've used env::var
in the body of our main function, we'll have to bring the env
module into scope as well.
use std::env;
Running this nets us new errors!
error[E0277]: `?` couldn't convert the error to `csv::Error`
--> crates/upload-pokemon-data/src/main.rs:10:48
|
10 | let database_url = env::var("DATABASE_URL")?;
| ^ the trait `From<VarError>` is not implemented for `csv::Error`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= help: the following implementations were found:
<csv::Error as From<std::io::Error>>
= note: required by `std::convert::From::from`
The type of the return value of our main function is Result<(), csv::Error>
which worked well when we only had csv::Error
s occuring. Now we have std::env::VarError
s as well (and soon we'll have sqlx::Error
s too!).
There are two general approaches we can take here. We can either forward errors to the user, or we can construct a structured error type that contains everything that can go wrong in our program and use that error instead.
Option 2, building up our own error type, makes a lot more sense for a library because people who use our crate would want to do different things in code based on which error was returned.
For us in this application, we don't actually care about handling most of the errors and the most useful thing we can do is report those errors to the person running the CLI. So dealing with the extra machinery of constructing an error type is wasted effort.
There are a few libraries that can help us deal with errors on the application level like this: anyhow
, eyre
, and miette
are all fairly interchangable options with anyhow
being the oldest implementation and miette
being the newest.
We'll use color_eyre
because it allows us to print out colorful reports using the eyre
crate and I like that... but to be clear the libraries I just mentioned are all fairly interchangeable and you can play with a few to see how they work if you want to.
cargo add color_eyre -p upload-pokemon-data
We'll bring eyre
intro scope
use color_eyre::eyre;
Change the return type of our main function to use the new Result
type from eyre
.
fn main() -> eyre::Result<()> {
and at the top of our main function, bootstrap color_eyre using its install method.
fn main() -> eyre::Result<()> {
color_eyre::install()?;
and now if we cargo run
with our DATABASE_URL
set, everything works and our errors get propogated up to the new report.
DATABASE_URL=mysql://127.0.0.1 cargo run
We can confirm that our errors get returned to the user by taking the environment variable away
Note: you must have run cargo build or cargo run with the DATABASE_URL set already because we use the environment variable at compile time to check our queries are valid and this error is a runtime check to make sure someone set the variable. cargo run will run build for you if you haven't run it already and you will see the compilation error, not the runtime error.
cargo run
which returns
Error:
0: environment variable not found
Location:
crates/upload-pokemon-data/src/main.rs:13
Backtrace omitted.
Run with RUST_BACKTRACE=1 environment variable to display it.
Run with RUST_BACKTRACE=full to include source snippets.
We can now add more context to our errors, reminding our future selves how this application is supposed to behave. To add additional error context so that we get the following output.
Error:
0: Must have a DATABASE_URL set
1: environment variable not found
Location:
crates/upload-pokemon-data/src/main.rs:14
Suggestion: Run `pscale connect <database> <branch>` to get a connection
Backtrace omitted.
Run with RUST_BACKTRACE=1 environment variable to display it.
Run with RUST_BACKTRACE=full to include source snippets.
We need to bring in the WrapErr
and Section
traits.
use color_eyre::{eyre, eyre::WrapErr, Section};
and then we can use them on any standard Result
. wrap_err
will wrap the error in more context and suggestion
will offer potentially helpful suggestions to the user.
let database_url = env::var("DATABASE_URL")
.wrap_err("Must have a DATABASE_URL set")
.suggestion("Run `pscale connect <database> <branch>` to get a connection")?;
With the database url in place and error reporting set up we can continue to set up a database connection.