feat: initial base implementation
This commit is contained in:
parent
b3e1badef2
commit
92799df15e
|
@ -1 +1,4 @@
|
|||
/target
|
||||
config.toml
|
||||
test.sql
|
||||
Cargo.lock
|
13
Cargo.toml
13
Cargo.toml
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "MS3"
|
||||
name = "ms3"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
@ -7,4 +7,13 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
sibyl = "0.6.16"
|
||||
sibyl = { version = "0.6", features = ["nonblocking", "tokio"] }
|
||||
axum = { version = "0.2.8", features = ["headers", "multipart"] }
|
||||
hyper = "0.14.16"
|
||||
tower = { version = "0.4", features = ["util", "timeout"] }
|
||||
tower-http = { version = "0.1", features = ["add-extension", "trace", "fs", "set-header"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.51"
|
||||
structopt = "0.3.22"
|
||||
toml = "0.5.8"
|
||||
thiserror = "1.0.30"
|
|
@ -0,0 +1,11 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
pub addr: SocketAddr,
|
||||
pub db_name: String,
|
||||
pub db_username: String,
|
||||
pub db_password: String,
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
use std::convert::Infallible;
|
||||
|
||||
use axum::{response::IntoResponse, body::{Full, Bytes}, Json};
|
||||
use hyper::StatusCode;
|
||||
use serde::{Serializer, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ApplicationError {
|
||||
#[error("File read error: {0}")]
|
||||
Read(#[from] std::io::Error),
|
||||
#[error("Deserialize error: {0}")]
|
||||
Deserialize(#[from] toml::de::Error),
|
||||
#[error("Database connection error: {0}")]
|
||||
Database(#[from] sibyl::Error),
|
||||
#[error("Axum error: {0}")]
|
||||
Axum(#[from] hyper::Error),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ServiceError {
|
||||
#[error("SQL error: {0}")]
|
||||
Database(#[from] sibyl::Error),
|
||||
#[error("Response code: {0}")]
|
||||
ErrorResponse(StatusCode, Option<String>),
|
||||
}
|
||||
|
||||
fn serialize_status<S>(x: &StatusCode, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
s.serialize_u16(x.as_u16())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ErrorResponse {
|
||||
#[serde(serialize_with = "serialize_status")]
|
||||
pub status: StatusCode,
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
impl IntoResponse for ServiceError {
|
||||
type Body = Full<Bytes>;
|
||||
|
||||
type BodyError = Infallible;
|
||||
|
||||
fn into_response(self) -> axum::http::Response<Self::Body> {
|
||||
let res = match self {
|
||||
ServiceError::Database(err) => ErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Some(err.to_string())),
|
||||
ServiceError::ErrorResponse(code, reason) => ErrorResponse::new(code, reason),
|
||||
};
|
||||
let status = res.status;
|
||||
(status, Json(res)).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorResponse {
|
||||
fn new(status: StatusCode, message: Option<String>) -> Self {
|
||||
let reason = status.canonical_reason().unwrap_or_default();
|
||||
Self {
|
||||
status,
|
||||
error: message.unwrap_or_else(|| reason.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
62
src/main.rs
62
src/main.rs
|
@ -1,5 +1,61 @@
|
|||
#[tokio::main]
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
async fn main() {
|
||||
println!("Hello, world!");
|
||||
use axum::{Router, AddExtensionLayer, http::{HeaderValue, header}};
|
||||
use error::ApplicationError;
|
||||
use hyper::{Request, Body};
|
||||
use sibyl::{Environment, SessionPool};
|
||||
use structopt::{StructOpt, lazy_static::lazy_static};
|
||||
|
||||
use config::Config;
|
||||
use tower_http::set_header::SetResponseHeaderLayer;
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
mod routes;
|
||||
mod sql;
|
||||
mod model;
|
||||
|
||||
#[derive(StructOpt)]
|
||||
struct Opt {
|
||||
#[structopt(
|
||||
short,
|
||||
long,
|
||||
help = "config file to use",
|
||||
default_value = "./config.toml"
|
||||
)]
|
||||
config: PathBuf,
|
||||
}
|
||||
|
||||
pub struct ServiceInner {
|
||||
pool: SessionPool<'static>
|
||||
}
|
||||
|
||||
pub type Service = Arc<ServiceInner>;
|
||||
|
||||
lazy_static!{
|
||||
pub static ref ORACLE: Environment = sibyl::env().expect("Sibyl error");
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), ApplicationError> {
|
||||
let opt = Opt::from_args();
|
||||
let config = std::fs::read(&opt.config)?;
|
||||
let config = toml::from_slice::<Config>(&config)?;
|
||||
|
||||
let pool = ORACLE.create_session_pool(&config.db_name, &config.db_username, &config.db_password, 0, 1, 10).await?;
|
||||
let service: Service = Arc::new(ServiceInner{ pool });
|
||||
|
||||
let app = Router::new()
|
||||
.nest("/api", routes::routes())
|
||||
.layer(AddExtensionLayer::new(service))
|
||||
.layer(SetResponseHeaderLayer::<_, Request<Body>>::if_not_present(
|
||||
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||
HeaderValue::from_static("*"),
|
||||
));
|
||||
|
||||
axum::Server::bind(&config.addr)
|
||||
.serve(app.into_make_service())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Room {
|
||||
pub room_number: i32,
|
||||
pub floor: i32,
|
||||
pub size: i32,
|
||||
pub room_type: String,
|
||||
pub beds: i32,
|
||||
pub accessibility: bool,
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
use axum::{Router, routing::BoxRoute, extract::Extension, response::IntoResponse, Json, handler::get};
|
||||
|
||||
use crate::{Service, error::ServiceError};
|
||||
|
||||
async fn rooms(
|
||||
Extension(service): Extension<Service>,
|
||||
) -> Result<impl IntoResponse, ServiceError> {
|
||||
let rooms = service.get_rooms().await?;
|
||||
Ok(Json(rooms))
|
||||
}
|
||||
|
||||
|
||||
pub fn routes() -> Router<BoxRoute> {
|
||||
Router::new()
|
||||
.route("/rooms", get(rooms))
|
||||
.boxed()
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
use crate::{ServiceInner, model::Room, error::ServiceError};
|
||||
|
||||
impl ServiceInner {
|
||||
|
||||
pub async fn get_rooms(&self) -> Result<Vec<Room>, ServiceError> {
|
||||
let session = self.pool.get_session().await?;
|
||||
let stmt = session.prepare("
|
||||
SELECT
|
||||
roomNumber,
|
||||
floor,
|
||||
roomTyp,
|
||||
\"size\",
|
||||
accessibility,
|
||||
beds
|
||||
FROM room
|
||||
").await?;
|
||||
|
||||
let rows = stmt.query("").await?;
|
||||
|
||||
let mut rooms: Vec<Room> = vec![];
|
||||
|
||||
while let Some(row) = rows.next().await? {
|
||||
|
||||
let acc: i32 = row.get(5)?;
|
||||
let room = Room {
|
||||
room_number: row.get(0)?,
|
||||
floor: row.get(1)?,
|
||||
size: row.get(3)?,
|
||||
room_type: row.get(2)?,
|
||||
beds: row.get(4)?,
|
||||
accessibility: acc != 0,
|
||||
};
|
||||
rooms.push(room);
|
||||
}
|
||||
|
||||
Ok(rooms)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue