REST servcie restructuring

App devices which implementation will be used.  For now,
the types of the implementations must be defined manually.
This is requied because the State in the REST endpoints must
contain a type which can provide all services.
This commit is contained in:
Simon Goller 2024-04-28 22:30:10 +02:00
parent 2931d37602
commit d45ccf9523
4 changed files with 114 additions and 53 deletions

View file

@ -2,13 +2,28 @@ use std::sync::Arc;
use sqlx::SqlitePool; use sqlx::SqlitePool;
#[tokio::main] type PermissionService =
async fn main() { service_impl::PermissionServiceImpl<dao_impl::PermissionDaoImpl, service_impl::UserServiceDev>;
let pool = Arc::new( type HelloService = service_impl::HelloServiceImpl<dao_impl::HelloDaoImpl, PermissionService>;
SqlitePool::connect("sqlite:./localdb.sqlite3")
.await #[derive(Clone)]
.expect("Could not connect to database"), pub struct RestStateImpl {
); hello_service: Arc<HelloService>,
permission_service: Arc<PermissionService>,
}
impl rest::RestStateDef for RestStateImpl {
type HelloService = HelloService;
type PermissionService = PermissionService;
fn hello_service(&self) -> Arc<Self::HelloService> {
self.hello_service.clone()
}
fn permission_service(&self) -> Arc<Self::PermissionService> {
self.permission_service.clone()
}
}
impl RestStateImpl {
pub fn new(pool: Arc<sqlx::Pool<sqlx::Sqlite>>) -> Self {
let hello_dao = dao_impl::HelloDaoImpl::new(pool.clone()); let hello_dao = dao_impl::HelloDaoImpl::new(pool.clone());
let permission_dao = dao_impl::PermissionDaoImpl::new(pool); let permission_dao = dao_impl::PermissionDaoImpl::new(pool);
@ -19,9 +34,28 @@ async fn main() {
// use differnet implementations on debug then on release. Or control it via a // use differnet implementations on debug then on release. Or control it via a
// feature. // feature.
let user_service = service_impl::UserServiceDev; let user_service = service_impl::UserServiceDev;
let permission_service = let permission_service = Arc::new(service_impl::PermissionServiceImpl::new(
service_impl::PermissionServiceImpl::new(permission_dao.into(), user_service.into()); permission_dao.into(),
let hello_service = user_service.into(),
service_impl::HelloServiceImpl::new(hello_dao.into(), permission_service.into()); ));
rest::start_server(hello_service).await let hello_service = Arc::new(service_impl::HelloServiceImpl::new(
hello_dao.into(),
permission_service.clone(),
));
Self {
hello_service,
permission_service,
}
}
}
#[tokio::main]
async fn main() {
let pool = Arc::new(
SqlitePool::connect("sqlite:./localdb.sqlite3")
.await
.expect("Could not connect to database"),
);
let rest_state = RestStateImpl::new(pool);
rest::start_server(rest_state).await
} }

14
rest/src/hello.rs Normal file
View file

@ -0,0 +1,14 @@
use axum::{extract::State, response::Response};
use crate::{error_handler, RestStateDef, RoString};
use service::HelloService;
pub async fn hello<RestState: RestStateDef>(State(rest_state): State<RestState>) -> Response {
error_handler(
(async {
let string = rest_state.hello_service().hello().await?;
Ok(RoString::from(string).into())
})
.await,
)
}

View file

@ -1,14 +1,9 @@
use serde::{Deserialize, Serialize};
use std::{convert::Infallible, sync::Arc}; use std::{convert::Infallible, sync::Arc};
use uuid::Uuid;
use axum::{ mod hello;
body::Body, mod permission;
extract::State,
response::Response, use axum::{body::Body, response::Response, routing::get, Router};
routing::{get, post},
Json, Router,
};
pub struct RoString(Arc<str>, bool); pub struct RoString(Arc<str>, bool);
impl http_body::Body for RoString { impl http_body::Body for RoString {
@ -57,38 +52,19 @@ fn error_handler(result: Result<Response, service::ServiceError>) -> Response {
} }
} }
async fn root<HelloService: service::HelloService>( pub trait RestStateDef: Clone + Send + Sync + 'static {
State(hello_service): State<Arc<HelloService>>, type HelloService: service::HelloService + Send + Sync + 'static;
) -> Response { type PermissionService: service::PermissionService + Send + Sync + 'static;
error_handler(
(async { fn hello_service(&self) -> Arc<Self::HelloService>;
let string = hello_service.hello().await?; fn permission_service(&self) -> Arc<Self::PermissionService>;
Ok(RoString::from(string).into())
})
.await,
)
} }
#[derive(Debug, Serialize, Deserialize)] pub async fn start_server<RestState: RestStateDef>(rest_state: RestState) {
pub struct User {
#[serde(default)]
pub id: Uuid,
pub name: String,
}
async fn add_user(Json(user): Json<User>) -> Response {
println!("Adding user: {:?}", user);
Response::builder().status(200).body(Body::empty()).unwrap()
}
pub async fn start_server<HelloService>(hello_service: HelloService)
where
HelloService: service::HelloService + Send + Sync + 'static,
{
let app = Router::new() let app = Router::new()
.route("/", get(root)) .route("/", get(hello::hello::<RestState>))
.route("/user", post(add_user)) .nest("/permission", permission::generate_route())
.with_state(Arc::new(hello_service)); .with_state(rest_state);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await .await
.expect("Could not bind server"); .expect("Could not bind server");

37
rest/src/permission.rs Normal file
View file

@ -0,0 +1,37 @@
use axum::{body::Body, extract::State, response::Response, routing::post, Json, Router};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{error_handler, RestStateDef};
use service::PermissionService;
#[derive(Debug, Serialize, Deserialize)]
pub struct User {
#[serde(default)]
pub id: Uuid,
pub name: String,
}
pub fn generate_route<RestState: RestStateDef>() -> Router<RestState> {
Router::new().route("/user/", post(add_user::<RestState>))
}
pub async fn add_user<RestState: RestStateDef>(
rest_state: State<RestState>,
Json(user): Json<User>,
) -> Response {
println!("Adding user: {:?}", user);
error_handler(
(async {
rest_state
.permission_service()
.create_user(user.name.as_str())
.await?;
Ok(Response::builder()
.status(200)
.body(Body::from(""))
.unwrap())
})
.await,
)
}