Layering.

This commit is contained in:
LukeMathWalker 2021-05-09 18:36:31 +01:00
parent 5f89afe095
commit f0aee87a00

View file

@ -1,7 +1,8 @@
use crate::domain::{NewSubscriber, SubscriberEmail, SubscriberName};
use crate::email_client::EmailClient;
use crate::startup::ApplicationBaseUrl;
use actix_web::{web, HttpResponse};
use actix_web::http::StatusCode;
use actix_web::{web, HttpResponse, ResponseError};
use chrono::Utc;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
@ -25,6 +26,91 @@ impl TryInto<NewSubscriber> for FormData {
}
}
pub enum SubscribeError {
ValidationError(String),
PoolError(sqlx::Error),
InsertSubscriberError(sqlx::Error),
StoreTokenError(StoreTokenError),
TransactionCommitError(sqlx::Error),
SendEmailError(reqwest::Error),
}
impl From<reqwest::Error> for SubscribeError {
fn from(e: reqwest::Error) -> Self {
Self::SendEmailError(e)
}
}
impl From<StoreTokenError> for SubscribeError {
fn from(e: StoreTokenError) -> Self {
Self::StoreTokenError(e)
}
}
impl From<String> for SubscribeError {
fn from(e: String) -> Self {
Self::ValidationError(e)
}
}
impl std::fmt::Display for SubscribeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SubscribeError::ValidationError(e) => write!(f, "{}", e),
SubscribeError::PoolError(_) => {
write!(f, "Failed to acquire a Postgres connection from the pool")
}
SubscribeError::InsertSubscriberError(_) => {
write!(f, "Failed to insert new subscriber in the database.")
}
SubscribeError::StoreTokenError(_) => write!(
f,
"Failed to store the confirmation token for a new subscriber."
),
SubscribeError::TransactionCommitError(_) => {
write!(
f,
"Failed to commit SQL transaction to store a new subscriber."
)
}
SubscribeError::SendEmailError(_) => write!(f, "Failed to send a confirmation email."),
}
}
}
impl std::fmt::Debug for SubscribeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
error_chain_fmt(self, f)
}
}
impl std::error::Error for SubscribeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
// &str does not implement `Error` - we consider it the root cause
SubscribeError::ValidationError(_) => None,
SubscribeError::PoolError(e) => Some(e),
SubscribeError::InsertSubscriberError(e) => Some(e),
SubscribeError::StoreTokenError(e) => Some(e),
SubscribeError::TransactionCommitError(e) => Some(e),
SubscribeError::SendEmailError(e) => Some(e),
}
}
}
impl ResponseError for SubscribeError {
fn status_code(&self) -> StatusCode {
match self {
SubscribeError::ValidationError(_) => StatusCode::BAD_REQUEST,
SubscribeError::PoolError(_)
| SubscribeError::TransactionCommitError(_)
| SubscribeError::InsertSubscriberError(_)
| SubscribeError::StoreTokenError(_)
| SubscribeError::SendEmailError(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
#[tracing::instrument(
name = "Adding a new subscriber",
skip(form, pool, email_client, base_url),
@ -38,35 +124,25 @@ pub async fn subscribe(
pool: web::Data<PgPool>,
email_client: web::Data<EmailClient>,
base_url: web::Data<ApplicationBaseUrl>,
) -> Result<HttpResponse, HttpResponse> {
let new_subscriber = form
.0
.try_into()
.map_err(|_| HttpResponse::BadRequest().finish())?;
let mut transaction = pool
.begin()
.await
.map_err(|_| HttpResponse::InternalServerError().finish())?;
) -> Result<HttpResponse, SubscribeError> {
let new_subscriber = form.0.try_into()?;
let mut transaction = pool.begin().await.map_err(SubscribeError::PoolError)?;
let subscriber_id = insert_subscriber(&mut transaction, &new_subscriber)
.await
.map_err(|_| HttpResponse::InternalServerError().finish())?;
// We are swallowing the error for the time being.
.map_err(SubscribeError::InsertSubscriberError)?;
let subscription_token = generate_subscription_token();
store_token(&mut transaction, subscriber_id, &subscription_token)
.await
.map_err(|_| HttpResponse::InternalServerError().finish())?;
store_token(&mut transaction, subscriber_id, &subscription_token).await?;
transaction
.commit()
.await
.map_err(|_| HttpResponse::InternalServerError().finish())?;
.map_err(SubscribeError::TransactionCommitError)?;
send_confirmation_email(
&email_client,
new_subscriber,
&base_url.0,
&subscription_token,
)
.await
.map_err(|_| HttpResponse::InternalServerError().finish())?;
.await?;
Ok(HttpResponse::Ok().finish())
}
@ -141,7 +217,7 @@ pub async fn store_token(
transaction: &mut Transaction<'_, Postgres>,
subscriber_id: Uuid,
subscription_token: &str,
) -> Result<(), sqlx::Error> {
) -> Result<(), StoreTokenError> {
sqlx::query!(
r#"
INSERT INTO subscription_tokens (subscription_token, subscriber_id)
@ -154,7 +230,43 @@ pub async fn store_token(
.await
.map_err(|e| {
tracing::error!("Failed to execute query: {:?}", e);
e
StoreTokenError(e)
})?;
Ok(())
}
pub struct StoreTokenError(sqlx::Error);
impl std::error::Error for StoreTokenError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
impl std::fmt::Debug for StoreTokenError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
error_chain_fmt(self, f)
}
}
impl std::fmt::Display for StoreTokenError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"A database failure was encountered while trying to store a subscription token."
)
}
}
fn error_chain_fmt(
e: &impl std::error::Error,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
writeln!(f, "{}\n", e)?;
let mut current = e.source();
while let Some(cause) = current {
writeln!(f, "Caused by:\n\t{}", cause)?;
current = cause.source();
}
Ok(())
}