axum sqlx

use sqlx::postgres::PgPoolOptions;

#[async_std::main]
async fn main() -> Result<(), sqlx::Error> {

	let pool = PgPoolOptions::new()
		.max_connections(5)
		.connect("postgres://postgres:password@localhost/test").await?;

	let row: (i64,) = sqlx::query_as("SELECT $1")
		.bind(150_i64)
		.fetch_one(&pool).await?;

	assert_eq!(row.0, 150);
	Ok(())
}


pub async fn startup(modules: Arc<Modules>){
	let hc_router = Router::new()
		.route("/", get(hc))
		.route("/postgres", get(hc_postgres));

	let todo_router = Router::new()
		.route("/", get(find_todo).post(create_todo))
		.route(
			"/:id",
			get(get_todo)
				.patch(update_todo)
				.put(upsert_todo)
				.delete(delte_todo),
		);

	let app = Router::new()
		.nest("/v1/hc", hc_router)
		.nest("/v1/todos", todo_router)
		.layer(Extension(modules));

	axum::Server::bind(&&addr)
		.server(app.into_mmake_service())
		.await
		.unwrap_or_else(|_| panic!("server cannot launch."));
}

use std::sync::Arc;
use todo_adapter::modules::{RepositoriesModule, RepositoriesModuleExt};
use todo_adapber::persistence::postgres::Db;
use todo_adapter::repository::health_check::HealthCheckRepository;
use todo_app::usecase::health_check::HealthCechkUseCase;
use todo_app::usecase::todo::TodoUseCase;

pub struct Modules {
	health_check_use_case:: HealthCheckUseCase,
	todo_use_case: TodoUseCase<RepositoriesModule>,
}

pub trait ModulesExt {
	type RepositoriesModule: RepositoriesModuleExt;

	fn health_check_use_case(&self) -> &HealthCheckUseCase;
	fn todo_use_case(&self) -> &TodoUseCase<Self::RepositoriesModule>;
}

immpl ModulesExt for Modules {
	type RepositoriesModule = RepositoriesModule;

	fn health_check_use_case(&self) -> &HealthCheckUseCase {
		&self.health_check_use_case
	}

	fn todo_use_case(&self) -> &TodoUseCase<Self::RepositoriesModule> {
        &self.todo_use_case
    }
}

impl Modules {
	pub async fn new() -> Self {
		pub async fn new() -> Self {
			let db = Db::new().await;

			let repositories_module = Arc::new(RepositoriesModule::new(db.clone()));

			let health_check_use_case = HealthCheckUseCase::new(HealthCheckRepository::new(db));
			let todo_use_case = TodoUseCase::new(repositories_module.clone());

			Self {
				health_check_use_case,
				todo_use_case,
			}
		}
	}
}


#[derive(Debug, Deserialize)]
#[serde(rename_all) = "camelCase"]
pub struct TodoQuery {
	pub status: Option<String>,
}

immpl From<TodoQuery> for SearchTodoCondition {
	fn from(tq: TodoQuery) -> Self {
		Self {
			status_code: tq.status,
		}
	}
}

pub async fn update_todo(
	Path(id): Path<String>,
	ValidatedRequest(source): ValidatedRequest<JsonUpdateTodoContents>,
	Extension(modules): Extension<Arc<Modules>>,
) -> Result<impl IntoResponse, impl IntoResponse> {
	match source.validate(id) {
		Ok(todo) => {
			let resp = modules.todo_use_case().update_todo(todo).await;

			resp.map(|tv| {
				info!("updated todo {}", tv.id);
				let json: JsonTodo = tv.info();
				(StatusCode::OK, Json(json))
			})
			.map_err(|err|{
				error!("{:?}", err);

                if err.to_string() == *"`statusCode` is invalid." {
                    let json = JsonErrorResponse::new(
                        "invalid_request".to_string(),
                        vec![err.to_string()],
                    );
                    (StatusCode::BAD_REQUEST, Json(json))
                } else {
                    let json = JsonErrorResponse::new(
                        "server_error".to_string(),
                        vec!["INTERNAL SERVER ERROR".to_string()],
                    );
                    (StatusCode::INTERNAL_SERVER_ERROR, Json(json))
                }
			})
		}
		Err(errors) => {
			let json = JsonErrorResponse::new("invalid_request".to_string(), errors);
			Err((Statuscode::BAD_REQUEST, Json(json)))
		}
	}
}

use crate::context::errors::AppError;
use crate::context::validate::ValidatedRequest;
use axum::async_trait;
use axum::extract::{FromRequest, RequestParts};
use axum::{BoxError, Json};
use serde::de::DeserializeOwned;
use validator::Validate;

#[async_trait]
impl<T, B> FromRequest<B> for ValidatedRequest<T>
where
    T: DeserializeOwned + Validate,
    B: http_body::Body + Send,
    B::Data: Send,
    B::Error: Into<BoxError>,
{
    type Rejection = AppError;

    async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
        let Json(value) = Json::<T>::from_request(req).await?;
        value.validate()?;
        Ok(ValidatedRequest(value))
    }
}

use crate::context::errors::AppError;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde::Serialize;
use tracing::log::error;

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonErrorResponse {
    error_code: String,
    errors: Vec<String>,
}

impl JsonErrorResponse {
    pub(crate) fn new(error_code: String, errors: Vec<String>) -> Self {
        Self { error_code, errors }
    }
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        match self {
            AppError::Validation(validation_errors) => {
                error!("{:?}", validation_errors);

                let mut messages: Vec<String> = Vec::new();
                let errors = validation_errors.field_errors();
                for (_, v) in errors.into_iter() {
                    for validation_error in v {
                        if let Some(msg) = validation_error.clone().message {
                            messages.push(msg.to_string());
                        }
                    }
                }

                (
                    StatusCode::BAD_REQUEST,
                    Json(JsonErrorResponse::new(
                        "invalid_request".to_string(),
                        messages,
                    )),
                )
            }
            AppError::JsonRejection(rejection) => {
                error!("{:?}", rejection);

                let messages = vec![rejection.to_string()];
                (
                    StatusCode::BAD_REQUEST,
                    Json(JsonErrorResponse::new(
                        "invalid_request".to_string(),
                        messages,
                    )),
                )
            }
        }
        .into_response()
    }
}

#[derive(Deserialize, Debug, Validate)]
#[serde(rename_all = "camelCase")]
pub struct JsonCreateTodo {
    #[validate(
        length(min = 1, message = "`title` is empty."),
        required(message = "`title` is null.")
    )]
    pub title: Option<String>,
    #[validate(required(message = "`description` is null."))]
    pub description: Option<String>,
}

impl From<JsonCreateTodo> for CreateTodo {
    fn from(jc: JsonCreateTodo) -> Self {
        CreateTodo {
            title: jc.title.unwrap(),
            description: jc.description.unwrap(),
        }
    }
}

use sqlx::postgres::PgPoolOptions;
use sqlx::{Pool, Postgres};
use std::env;
use std::sync::Arc;

#[derive(Clone)]
pub struct Db(pub(crate) Arc<Pool<Postgres>>);

impl Db {
	pub async fn new() -> Db {
		let pool = PgPoolOptions::new()
			.max_connections(8)
			.connect(
				&env::var("DATABASE_URL").unwrap_or_else(|_| panic!("DATABASE_URL must be selt!")),
			)
			.await
			.unwrap_or_else(|_| {
				panic!("Cannot connect to the database. Please check your configuration.")
			});

		Db(Arc::new(pool))	
	}
}

async fn get(&self, id: &Id<Todo>) -> anyhow::Result<Option<Todo>> {
	let pool = self.db.0.clone();
	let sql = r#"
		select 
			t.id as id,
			t.title as title,
			t.description as description,
			ts.id as status_id,
			ts.code as status_code,
			ts.name as status_name,
			t.created_at as created_at,
			t.updated_at as updated_at
		from
			todos as t
			inner join
				todo statuses as ts
				on ts.id = t.status_id
		where
			t.id = $1
	"#;
	let stored_todo = query_as::<_, StoredTodo>(sql)
		.bind(id.value.to_string())
		.fetch_one(&*pool)
		.await
		.ok();

	match stored_todo {
		Some(st) => Ok(Some(st.try_into()?)),
		None => Ok(None),
	}

	"
}