mirror of
https://github.com/LukeMathWalker/zero-to-production.git
synced 2024-09-29 23:02:03 +00:00
Layering.
This commit is contained in:
parent
5f89afe095
commit
f0aee87a00
1 changed files with 132 additions and 20 deletions
|
@ -1,7 +1,8 @@
|
||||||
use crate::domain::{NewSubscriber, SubscriberEmail, SubscriberName};
|
use crate::domain::{NewSubscriber, SubscriberEmail, SubscriberName};
|
||||||
use crate::email_client::EmailClient;
|
use crate::email_client::EmailClient;
|
||||||
use crate::startup::ApplicationBaseUrl;
|
use crate::startup::ApplicationBaseUrl;
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::{web, HttpResponse, ResponseError};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use rand::distributions::Alphanumeric;
|
use rand::distributions::Alphanumeric;
|
||||||
use rand::{thread_rng, Rng};
|
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(
|
#[tracing::instrument(
|
||||||
name = "Adding a new subscriber",
|
name = "Adding a new subscriber",
|
||||||
skip(form, pool, email_client, base_url),
|
skip(form, pool, email_client, base_url),
|
||||||
|
@ -38,35 +124,25 @@ pub async fn subscribe(
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
email_client: web::Data<EmailClient>,
|
email_client: web::Data<EmailClient>,
|
||||||
base_url: web::Data<ApplicationBaseUrl>,
|
base_url: web::Data<ApplicationBaseUrl>,
|
||||||
) -> Result<HttpResponse, HttpResponse> {
|
) -> Result<HttpResponse, SubscribeError> {
|
||||||
let new_subscriber = form
|
let new_subscriber = form.0.try_into()?;
|
||||||
.0
|
let mut transaction = pool.begin().await.map_err(SubscribeError::PoolError)?;
|
||||||
.try_into()
|
|
||||||
.map_err(|_| HttpResponse::BadRequest().finish())?;
|
|
||||||
let mut transaction = pool
|
|
||||||
.begin()
|
|
||||||
.await
|
|
||||||
.map_err(|_| HttpResponse::InternalServerError().finish())?;
|
|
||||||
let subscriber_id = insert_subscriber(&mut transaction, &new_subscriber)
|
let subscriber_id = insert_subscriber(&mut transaction, &new_subscriber)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| HttpResponse::InternalServerError().finish())?;
|
.map_err(SubscribeError::InsertSubscriberError)?;
|
||||||
// We are swallowing the error for the time being.
|
|
||||||
let subscription_token = generate_subscription_token();
|
let subscription_token = generate_subscription_token();
|
||||||
store_token(&mut transaction, subscriber_id, &subscription_token)
|
store_token(&mut transaction, subscriber_id, &subscription_token).await?;
|
||||||
.await
|
|
||||||
.map_err(|_| HttpResponse::InternalServerError().finish())?;
|
|
||||||
transaction
|
transaction
|
||||||
.commit()
|
.commit()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| HttpResponse::InternalServerError().finish())?;
|
.map_err(SubscribeError::TransactionCommitError)?;
|
||||||
send_confirmation_email(
|
send_confirmation_email(
|
||||||
&email_client,
|
&email_client,
|
||||||
new_subscriber,
|
new_subscriber,
|
||||||
&base_url.0,
|
&base_url.0,
|
||||||
&subscription_token,
|
&subscription_token,
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|_| HttpResponse::InternalServerError().finish())?;
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +217,7 @@ pub async fn store_token(
|
||||||
transaction: &mut Transaction<'_, Postgres>,
|
transaction: &mut Transaction<'_, Postgres>,
|
||||||
subscriber_id: Uuid,
|
subscriber_id: Uuid,
|
||||||
subscription_token: &str,
|
subscription_token: &str,
|
||||||
) -> Result<(), sqlx::Error> {
|
) -> Result<(), StoreTokenError> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO subscription_tokens (subscription_token, subscriber_id)
|
INSERT INTO subscription_tokens (subscription_token, subscriber_id)
|
||||||
|
@ -154,7 +230,43 @@ pub async fn store_token(
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::error!("Failed to execute query: {:?}", e);
|
tracing::error!("Failed to execute query: {:?}", e);
|
||||||
e
|
StoreTokenError(e)
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue