Skip to content

Commit

Permalink
Finished converting auxiliary commands to thiserror
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholaslyang authored and nicholaslyang committed Oct 23, 2023
1 parent b6a6458 commit 8348a64
Show file tree
Hide file tree
Showing 15 changed files with 223 additions and 157 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/turborepo-auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ turborepo-api-client = { workspace = true }
turborepo-ui.workspace = true
turborepo-vercel-api = { workspace = true }
turborepo-vercel-api-mock = { workspace = true }
url = { workspace = true }
webbrowser = { workspace = true }

[dev-dependencies]
Expand Down
7 changes: 5 additions & 2 deletions crates/turborepo-auth/src/auth/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub async fn login<'a>(
ui: &UI,
existing_token: Option<&'a str>,
login_url_configuration: &str,
login_server: &impl LoginServer,
login_server: impl LoginServer,
) -> Result<Cow<'a, str>, Error> {
// Check if token exists first.
if let Some(token) = existing_token {
Expand Down Expand Up @@ -67,7 +67,10 @@ pub async fn login<'a>(
let token = token_cell.get().ok_or(Error::FailedToGetToken)?;

// TODO: make this a request to /teams endpoint instead?
let user_response = api_client.get_user(token.as_str()).await?;
let user_response = api_client
.get_user(token.as_str())
.await
.map_err(Error::FailedToFetchUser)?;

ui::print_cli_authorized(&user_response.user.email, ui);

Expand Down
15 changes: 0 additions & 15 deletions crates/turborepo-auth/src/auth/logout.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1 @@
use anyhow::Result;
use tracing::error;
use turborepo_ui::{GREY, UI};

pub fn logout<F>(ui: &UI, mut set_token: F) -> Result<()>
where
F: FnMut() -> Result<()>,
{
if let Err(err) = set_token() {
error!("could not logout. Something went wrong: {}", err);
return Err(err);
}

println!("{}", ui.apply(GREY.apply_to(">>> Logged out")));
Ok(())
}
2 changes: 0 additions & 2 deletions crates/turborepo-auth/src/auth/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
mod login;
mod logout;
mod sso;

pub use login::*;
pub use logout::*;
pub use sso::*;
28 changes: 16 additions & 12 deletions crates/turborepo-auth/src/auth/sso.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
use std::{borrow::Cow, sync::Arc};

use anyhow::{anyhow, Context, Result};
use reqwest::Url;
use tokio::sync::OnceCell;
use tracing::warn;
use turborepo_api_client::Client;
use turborepo_ui::{start_spinner, BOLD, UI};

use crate::{error, server, ui};
use crate::{error, server, ui, Error, Error::FailedToMakeSSOTokenName};

const DEFAULT_HOST_NAME: &str = "127.0.0.1";
const DEFAULT_PORT: u16 = 9789;
const DEFAULT_SSO_PROVIDER: &str = "SAML/OIDC Single Sign-On";

fn make_token_name() -> Result<String> {
let host = hostname::get()?;
fn make_token_name() -> Result<String, Error> {
let host = hostname::get().map_err(FailedToMakeSSOTokenName)?;

Ok(format!(
"Turbo CLI on {} via {DEFAULT_SSO_PROVIDER}",
Expand All @@ -30,8 +29,8 @@ pub async fn sso_login<'a>(
existing_token: Option<&'a str>,
login_url_configuration: &str,
sso_team: &str,
login_server: &impl server::SSOLoginServer,
) -> Result<Cow<'a, str>> {
login_server: impl server::SSOLoginServer,
) -> Result<Cow<'a, str>, Error> {
// Check if token exists first. Must be there for the user and contain the
// sso_team passed into this function.
if let Some(token) = existing_token {
Expand Down Expand Up @@ -80,14 +79,19 @@ pub async fn sso_login<'a>(
login_server.run(DEFAULT_PORT, token_cell.clone()).await?;
spinner.finish_and_clear();

let token = token_cell
.get()
.ok_or_else(|| anyhow!("no token auth token found"))?;
let token = token_cell.get().ok_or(Error::FailedToGetToken)?;

let token_name = make_token_name().context("failed to make sso token name")?;
let token_name = make_token_name()?;

let verified_user = api_client.verify_sso_token(token, &token_name).await?;
let user_response = api_client.get_user(&verified_user.token).await?;
let verified_user = api_client
.verify_sso_token(token, &token_name)
.await
.map_err(Error::FailedToValidateSSOToken)?;

let user_response = api_client
.get_user(&verified_user.token)
.await
.map_err(Error::FailedToFetchUser)?;

ui::print_cli_authorized(&user_response.user.email, ui);

Expand Down
13 changes: 10 additions & 3 deletions crates/turborepo-auth/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::io;

use thiserror::Error;

#[derive(Debug, Error)]
Expand All @@ -7,9 +9,14 @@ pub enum Error {
situations like using a `data:` URL."
)]
LoginUrlCannotBeABase { value: String },

#[error(transparent)]
SetTokenFailed(Box<dyn std::error::Error>),
#[error("failed to get token")]
FailedToGetToken,
#[error("failed to fetch user: {0}")]
FailedToFetchUser(turborepo_api_client::Error),
#[error("url is invalid: {0}")]
InvalidUrl(#[from] url::ParseError),
#[error("failed to validate sso token")]
FailedToValidateSSOToken(turborepo_api_client::Error),
#[error("failed to make sso token name")]
FailedToMakeSSOTokenName(io::Error),
}
16 changes: 6 additions & 10 deletions crates/turborepo-auth/src/server/login_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use axum::{extract::Query, response::Redirect, routing::get, Router};
use serde::Deserialize;
use tokio::sync::OnceCell;

use crate::Error;

#[derive(Debug, Clone, Deserialize)]
struct LoginPayload {
token: String,
Expand All @@ -18,27 +20,20 @@ pub trait LoginServer {
port: u16,
login_url_base: String,
login_token: Arc<OnceCell<String>>,
) -> Result<()>;
) -> Result<(), Error>;
}

/// TODO: Document this.
#[derive(Default)]
pub struct DefaultLoginServer;

impl DefaultLoginServer {
pub fn new() -> Self {
DefaultLoginServer {}
}
}

#[async_trait]
impl LoginServer for DefaultLoginServer {
async fn run(
&self,
port: u16,
login_url_base: String,
login_token: Arc<OnceCell<String>>,
) -> Result<()> {
) -> Result<(), Error> {
let handle = axum_server::Handle::new();
let route_handle = handle.clone();
let app = Router::new()
Expand All @@ -56,6 +51,7 @@ impl LoginServer for DefaultLoginServer {
Ok(axum_server::bind(addr)
.handle(handle)
.serve(app.into_make_service())
.await?)
.await
.expect("failed to start one-shot server"))
}
}
19 changes: 7 additions & 12 deletions crates/turborepo-auth/src/server/sso_server.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::{net::SocketAddr, sync::Arc};

use anyhow::Result;
use async_trait::async_trait;
use axum::{extract::Query, response::Redirect, routing::get, Router};
use reqwest::Url;
use serde::Deserialize;
use tokio::sync::OnceCell;

use crate::Error;

#[derive(Debug, Default, Clone, Deserialize)]
#[allow(dead_code)]
pub struct SsoPayload {
Expand All @@ -20,22 +21,15 @@ pub struct SsoPayload {

#[async_trait]
pub trait SSOLoginServer {
async fn run(&self, port: u16, verification_token: Arc<OnceCell<String>>) -> Result<()>;
async fn run(&self, port: u16, verification_token: Arc<OnceCell<String>>) -> Result<(), Error>;
}

/// TODO: Document this.
#[derive(Default)]
pub struct DefaultSSOLoginServer;

impl DefaultSSOLoginServer {
pub fn new() -> Self {
DefaultSSOLoginServer {}
}
}

#[async_trait]
impl SSOLoginServer for DefaultSSOLoginServer {
async fn run(&self, port: u16, verification_token: Arc<OnceCell<String>>) -> Result<()> {
async fn run(&self, port: u16, verification_token: Arc<OnceCell<String>>) -> Result<(), Error> {
let handle = axum_server::Handle::new();
let route_handle = handle.clone();
let app = Router::new()
Expand All @@ -57,11 +51,12 @@ impl SSOLoginServer for DefaultSSOLoginServer {
Ok(axum_server::bind(addr)
.handle(handle)
.serve(app.into_make_service())
.await?)
.await
.expect("failed to start one-shot server"))
}
}

fn get_token_and_redirect(payload: SsoPayload) -> Result<(Option<String>, Url)> {
fn get_token_and_redirect(payload: SsoPayload) -> Result<(Option<String>, Url), Error> {
let location_stub = "https://vercel.com/notifications/cli-login/turbo/";
if let Some(login_error) = payload.login_error {
let mut url = Url::parse(&format!("{}failed", location_stub))?;
Expand Down
47 changes: 47 additions & 0 deletions crates/turborepo-lib/src/cli/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::{backtrace, io};

use thiserror::Error;
use turbopath::AbsoluteSystemPathBuf;

use crate::{
commands::{bin, prune},
daemon::DaemonError,
rewrite_json::RewriteError,
};

#[derive(Debug, Error)]
pub enum Error {
#[error("No command specified")]
NoCommand(#[backtrace] backtrace::Backtrace),
#[error("{0}")]
Bin(#[from] bin::Error, #[backtrace] backtrace::Backtrace),
#[error(transparent)]
Path(#[from] turbopath::Error),
#[error("at least one task must be specified")]
NoTasks(#[backtrace] backtrace::Backtrace),
#[error(transparent)]
Config(#[from] crate::config::Error),
#[error(transparent)]
ChromeTracing(#[from] crate::tracing::Error),
#[error("Encountered an IO error while attempting to read {config_path}: {error}")]
FailedToReadConfig {
config_path: AbsoluteSystemPathBuf,
error: io::Error,
},
#[error("Encountered an IO error while attempting to set {config_path}: {error}")]
FailedToSetConfig {
config_path: AbsoluteSystemPathBuf,
error: io::Error,
},
#[error(transparent)]
Rewrite(#[from] RewriteError),
#[error(transparent)]
Auth(#[from] turborepo_auth::Error),
#[error(transparent)]
Daemon(#[from] DaemonError),
#[error(transparent)]
Prune(#[from] prune::Error),
// Temporary to prevent having to move all of the errors from anyhow to thiserror at once.
#[error(transparent)]
Anyhow(#[from] anyhow::Error),
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::{backtrace, env, io, mem, process};
use std::{backtrace, backtrace::Backtrace, env, io, mem, process};

use camino::{Utf8Path, Utf8PathBuf};
use clap::{ArgAction, CommandFactory, Parser, Subcommand, ValueEnum};
use clap_complete::{generate, Shell};
pub use error::Error;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::{debug, error};
use turbopath::AbsoluteSystemPathBuf;
use turborepo_repository::inference::{RepoMode, RepoState};
Expand All @@ -13,11 +13,12 @@ use turborepo_ui::UI;
use crate::{
commands::{bin, daemon, generate, info, link, login, logout, prune, unlink, CommandBase},
get_version,
rewrite_json::RewriteError,
tracing::TurboSubscriber,
Payload,
};

mod error;

// Global turbo sets this environment variable to its cwd so that local
// turbo can use it for package inference.
pub const INVOCATION_DIR_ENV_VAR: &str = "TURBO_INVOCATION_DIR";
Expand Down Expand Up @@ -201,27 +202,6 @@ pub enum LinkTarget {
Spaces,
}

#[derive(Debug, Error)]
pub enum Error {
#[error("No command specified")]
NoCommand(#[backtrace] backtrace::Backtrace),
#[error("{0}")]
Bin(bin::Error, #[backtrace] backtrace::Backtrace),
#[error(transparent)]
Path(turbopath::Error),
#[error("at least one task must be specified")]
NoTasks(#[backtrace] backtrace::Backtrace),
#[error(transparent)]
ChromeTracing(#[from] crate::tracing::Error),
#[error("Encountered an IO error while attempting to read {config_path}: {error}")]
FailedToReadConfig {
config_path: AbsoluteSystemPathBuf,
error: io::Error,
},
#[error(transparent)]
Rewrite(RewriteError),
}

impl Args {
pub fn new() -> Self {
// We always pass --single-package in from the shim.
Expand Down Expand Up @@ -659,7 +639,8 @@ pub async fn run(
let mut command = if let Some(command) = mem::take(&mut cli_args.command) {
command
} else {
let run_args = mem::take(&mut cli_args.run_args).ok_or(Error::NoCommand)?;
let run_args = mem::take(&mut cli_args.run_args)
.ok_or_else(|| Error::NoCommand(Backtrace::capture()))?;
if run_args.tasks.is_empty() {
let mut cmd = <Args as CommandFactory>::command();
let _ = cmd.print_help();
Expand Down
Loading

0 comments on commit 8348a64

Please sign in to comment.