diff --git a/Cargo.toml b/Cargo.toml index 400337e..cf13386 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,15 @@ serde_with = { version = "3.4.0" } serde_json = "1.0" serde_yaml = "0.9.27" +axum = {version = "0.7.4"} +axum-macros = {version = "0.4.1"} +http-body-util = {version = "0.1.0"} + +case = {version = "1.0"} +rust-format = {version = "0.3.4"} +tera = {version = "1.19"} + + thiserror = {version = "1.0"} path-absolutize = { version = "3.1.1"} typetag = { version= "0.2"} diff --git a/luna-orm-axum/Cargo.toml b/luna-orm-axum/Cargo.toml index cdcaf33..b1c1d1e 100644 --- a/luna-orm-axum/Cargo.toml +++ b/luna-orm-axum/Cargo.toml @@ -6,4 +6,17 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axum = {version = "0.7.4"} +#sqlx = {workspace = true} +axum = {workspace = true} +axum-macros = {workspace = true} +tokio = {workspace = true} +serde = {workspace = true} +serde_yaml = {workspace = true} +serde_json = {workspace = true} +case = {workspace = true} +rust-format = {workspace = true} +tera = {workspace = true} +http-body-util = {workspace = true} + +sqlx = {version = "0.7.3", features = ["runtime-tokio", "mysql", "sqlite", "macros", "any"]} +luna-orm = { path = "../luna-orm", version = "0.3.6" } diff --git a/luna-orm-axum/src/field_type.rs b/luna-orm-axum/src/field_type.rs new file mode 100644 index 0000000..d24089e --- /dev/null +++ b/luna-orm-axum/src/field_type.rs @@ -0,0 +1,68 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum NumericType { + #[serde(alias = "short")] + Short, + #[serde(alias = "int")] + Integer, + #[serde(alias = "long")] + Long, + + #[serde(alias = "ushort")] + UShort, + #[serde(alias = "uint")] + UInteger, + #[serde(alias = "ulong")] + ULong, + + #[serde(alias = "float")] + Float, + #[serde(alias = "double")] + Double, + #[serde(alias = "decimal")] + Decimal, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum FieldType { + #[serde(alias = "boolean")] + Boolean, + + #[serde(alias = "timestamp")] + Timestamp, + + #[serde(alias = "datetime")] + DateTime, + + #[serde(alias = "uuid")] + Uuid, + + #[serde(alias = "id")] + Id, + + #[serde(alias = "string")] + String, + + #[serde(alias = "cellphone")] + Cellphone, + + #[serde(alias = "email")] + Email, + + #[serde(untagged)] + NumericType(NumericType), +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_deserialize() { + let field_type: FieldType = serde_yaml::from_str("ushort").unwrap(); + let expect_type: FieldType = FieldType::NumericType(NumericType::UShort); + assert_eq!(field_type, expect_type); + } +} diff --git a/luna-orm-axum/src/generator.rs b/luna-orm-axum/src/generator.rs new file mode 100644 index 0000000..74764c8 --- /dev/null +++ b/luna-orm-axum/src/generator.rs @@ -0,0 +1,20 @@ +use crate::Schema; + +pub struct Generator { + schema: Schema, +} + +impl Generator { + pub fn new(schema: Schema) -> Self { + Self { schema } + } + + pub fn from_yaml>(value: T) -> serde_yaml::Result { + let schema = serde_yaml::from_str(value.as_ref())?; + Ok(Generator::new(schema)) + } + + pub fn generate_axum_router() -> String { + "".to_string() + } +} diff --git a/luna-orm-axum/src/handler.rs b/luna-orm-axum/src/handler.rs new file mode 100644 index 0000000..73d098e --- /dev/null +++ b/luna-orm-axum/src/handler.rs @@ -0,0 +1,220 @@ +use std::future::Future; + +use crate::request::*; +use crate::response::*; +use axum::extract::FromRequest; +use axum::extract::Request; +use axum::extract::State; +use axum::handler::Handler; +use axum::http::Result; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use axum::response::Response; +use axum::Json; +use axum::Router; +use axum_macros::debug_handler; +use http_body_util::BodyExt; +use luna_orm::prelude::*; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; +use std::pin::Pin; + +/* +struct ResponseSuccess { + success: bool, + data: T +} +struct ResponseFailure { + success: bool, + err_code: i16, + err_msg: String +} + +enum Response { + Success(ResultSuccess), + Failure(ResultFailure) +} +type Result = std::result::Result, thiserror::Error> + +enum InsertRequest { + +} + +// POST /users +insert(State(database), Json(entity)) -> Result<()> +// POST /users +create(State(database), Json(entity)) -> Result +// POST /users +upsert(State(database), Json(entity)) -> Result<()> + +// PUT /users +update(State(database), Json(UpdateRequest)) -> Result<()> +// PUT /users +change(State(database), Json(ChangeRequest)) -> Result + +// DELETE /users +delete(State(database), Json(primary)) -> Result<()> +// DELETE /users +remove(State(database), Json(primary)) -> Result +// DELETE /users +purify(State(database), Json(location)) -> Result + +*/ + +//#[debug_handler] +pub async fn post( + State(mut db): State>, + Json(payload): Json>, +) -> Result>> +where + T: Serialize + Entity + Send + Sync, + D: Database, +{ + match payload { + PostRequest::Create { mut entity } => { + db.create(&mut entity).await.unwrap(); + let response = PostResponse::Create { entity }; + Ok(Json(response)) + } + PostRequest::Insert { entity } => { + db.insert(&entity).await.unwrap(); + Ok(Json(PostResponse::Insert)) + } + PostRequest::Upsert { entity } => { + db.upsert(&entity).await.unwrap(); + Ok(Json(PostResponse::Insert)) + } + } +} + +#[derive(Clone, Debug)] +pub struct PostHandler +where + D: Database + Clone + Send + 'static, + T: DeserializeOwned + Entity + Send + Clone + 'static, +{ + db: PhantomData, + data: PhantomData, +} + +impl Default for PostHandler +where + D: Database + Clone + Send + 'static, + T: DeserializeOwned + Entity + Send + Clone + 'static, +{ + fn default() -> Self { + Self { + db: PhantomData, + data: PhantomData, + } + } +} + +impl Handler<((),), S> for PostHandler +where + S: Database + Clone + Send + 'static, + T: DeserializeOwned + Entity + Send + Clone + 'static, +{ + type Future = Pin + Send>>; + + fn call(self, req: Request, state: S) -> Self::Future { + Box::pin(async move { + let (mut parts, body) = req.into_parts(); + let data = body.collect().await.unwrap().to_bytes(); + let payload: PostRequest = serde_json::from_slice(&data).unwrap(); + post_test(payload).await; + + let response: Response = StatusCode::NOT_FOUND.into_response(); + response + }) + } +} + +pub async fn post_test(payload: PostRequest) -> bool +where + T: Entity + Send + Sync, +{ + true + /* + match payload { + PostRequest::Create { mut entity } => { + db.create(&mut entity).await.unwrap(); + let response = PostResponse::Create { entity }; + Ok(Json(response)) + } + PostRequest::Insert { entity } => { + db.insert(&entity).await.unwrap(); + Ok(Json(PostResponse::Insert)) + } + PostRequest::Upsert { entity } => { + db.upsert(&entity).await.unwrap(); + Ok(Json(PostResponse::Insert)) + } + } + */ +} +pub async fn put( + State(mut db): State>, + Json(payload): Json>, +) -> Result> +where + M: Serialize + Mutation, + P: Serialize + Primary, + L: Serialize + Location, + D: Database, +{ + match payload { + PutRequest::Update { mutation, primary } => { + db.update(&mutation, &primary).await.unwrap(); + let response = PutResponse::Update; + Ok(Json(response)) + } + PutRequest::Change { mutation, location } => { + let count = db.change(&mutation, &location).await.unwrap(); + Ok(Json(PutResponse::Change { count })) + } + } +} + +pub async fn delete( + State(mut db): State>, + Json(payload): Json>, +) -> Result>> +where + P: Serialize + Primary, + L: Serialize + Location, + S: Serialize + Selection, + SE: Serialize + SelectedEntity + Send + Unpin, + D: Database, +{ + match payload { + DeleteRequest::Delete { primary } => { + db.delete(&primary).await.unwrap(); + let response = DeleteResponse::Delete; + Ok(Json(response)) + } + DeleteRequest::Remove { primary, selection } => { + let entity: Option = db.remove(&primary, &selection).await.unwrap(); + Ok(Json(DeleteResponse::Remove { entity })) + } + DeleteRequest::Purify { location } => { + let count = db.purify(&location).await.unwrap(); + Ok(Json(DeleteResponse::Purify { count })) + } + } +} + +pub fn generate_router(path: P) +where + P: AsRef, + D: Database, + E: Entity + Serialize, +{ + /* + let router = Router::new().route( + path.as_ref(), + axum::routing::post(post::), + ); + */ +} diff --git a/luna-orm-axum/src/lib.rs b/luna-orm-axum/src/lib.rs new file mode 100644 index 0000000..eb59190 --- /dev/null +++ b/luna-orm-axum/src/lib.rs @@ -0,0 +1,10 @@ +mod field_type; +mod generator; +pub mod handler; +pub mod request; +pub mod response; +pub mod router; +mod schema; + +pub use field_type::FieldType; +pub use schema::*; diff --git a/luna-orm-axum/src/main.rs b/luna-orm-axum/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/luna-orm-axum/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/luna-orm-axum/src/request.rs b/luna-orm-axum/src/request.rs new file mode 100644 index 0000000..40bd61a --- /dev/null +++ b/luna-orm-axum/src/request.rs @@ -0,0 +1,87 @@ +use luna_orm::prelude::*; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(tag = "cmd")] +pub enum PostRequest +where + T: Entity + Send + Sync, +{ + #[serde(rename = "create")] + Create { entity: T }, + #[serde(rename = "insert")] + Insert { entity: T }, + #[serde(rename = "upsert")] + Upsert { entity: T }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(tag = "cmd")] +pub enum PutRequest +where + M: Serialize + Mutation, + P: Serialize + Primary, + L: Serialize + Location, +{ + #[serde(rename = "update")] + Update { mutation: M, primary: P }, + #[serde(rename = "change")] + Change { mutation: M, location: L }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(tag = "cmd")] +pub enum DeleteRequest +where + P: Serialize + Primary, + L: Serialize + Location, + S: Serialize + Selection, +{ + #[serde(rename = "create")] + Delete { primary: P }, + #[serde(rename = "insert")] + Remove { primary: P, selection: S }, + #[serde(rename = "upsert")] + Purify { location: L }, +} + +#[cfg(test)] +mod test { + use super::PostRequest; + use luna_orm::prelude::*; + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Schema, Serialize, Deserialize, PartialEq, Eq)] + pub struct User { + #[PrimaryKey] + name: String, + } + + #[test] + fn test_insert_request() { + let user = User { + name: "test".to_string(), + }; + let insert_req = PostRequest::Create { entity: user }; + let value = serde_yaml::to_string(&insert_req).unwrap(); + let expect_str = r#"cmd: create +entity: + name: test +"#; + assert_eq!(value, expect_str); + } + + #[test] + fn test_insert_request_deserialize() { + let user = User { + name: "test".to_string(), + }; + let insert_req = PostRequest::Create { entity: user }; + let expect_str = r#"cmd: create +entity: + name: test +"#; + let value: PostRequest = serde_yaml::from_str(expect_str).unwrap(); + assert_eq!(value, insert_req); + } +} diff --git a/luna-orm-axum/src/response.rs b/luna-orm-axum/src/response.rs new file mode 100644 index 0000000..7437b3a --- /dev/null +++ b/luna-orm-axum/src/response.rs @@ -0,0 +1,38 @@ +use luna_orm::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum PostResponse +where + T: Serialize + Entity + Send + Sync, +{ + #[serde(rename = "create")] + Create { entity: T }, + #[serde(rename = "insert")] + Insert, + #[serde(rename = "upsert")] + Upsert, +} +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum PutResponse { + #[serde(rename = "update")] + Update, + #[serde(rename = "change")] + Change { count: usize }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum DeleteResponse +where + SE: Serialize + SelectedEntity, +{ + #[serde(rename = "delete")] + Delete, + #[serde(rename = "remove")] + Remove { entity: Option }, + #[serde(rename = "purify")] + Purify { count: usize }, +} diff --git a/luna-orm-axum/src/router.rs b/luna-orm-axum/src/router.rs new file mode 100644 index 0000000..08c2a18 --- /dev/null +++ b/luna-orm-axum/src/router.rs @@ -0,0 +1,16 @@ +use crate::handler::PostHandler; +use axum::routing::post; +use axum::Router; +use luna_orm::prelude::*; +use serde::de::DeserializeOwned; + +pub fn get_post_router(db: &D, path: impl AsRef) -> Router +where + T: DeserializeOwned + Entity + Send + Clone + 'static, + D: Database + Clone + Send + Sync + 'static, +{ + let post_handler = PostHandler::::default(); + Router::new() + .route(path.as_ref(), post(post_handler)) + .with_state(db.clone()) +} diff --git a/luna-orm-axum/src/schema.rs b/luna-orm-axum/src/schema.rs new file mode 100644 index 0000000..44f70b4 --- /dev/null +++ b/luna-orm-axum/src/schema.rs @@ -0,0 +1,32 @@ +use crate::FieldType; +use case::CaseExt; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Field { + pub f_name: String, + pub f_type: FieldType, + pub f_constraint: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum IndexType { + Normal, + Primary, + Unique, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Index { + pub i_name: String, + pub i_type: IndexType, + pub f_names: Vec, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Schema { + pub name: String, + pub fields: Vec, + pub indexes: Vec, +} diff --git a/luna-orm-axum/tests/axum_router.rs b/luna-orm-axum/tests/axum_router.rs new file mode 100644 index 0000000..0edccfd --- /dev/null +++ b/luna-orm-axum/tests/axum_router.rs @@ -0,0 +1,40 @@ +use axum::http::StatusCode; +use axum::routing::{get, post}; +use axum::Json; +use axum::Router; +use luna_orm::prelude::*; +use luna_orm_axum::handler::PostHandler; +use luna_orm_axum::router::get_post_router; +use serde::{Deserialize, Serialize}; +use sqlx::SqliteConnection; + +#[derive(Debug, Clone, Serialize, Deserialize, Entity)] +struct User { + #[PrimaryKey] + id: i64, + username: String, +} +async fn create_user(Json(payload): Json) -> (StatusCode, Json) { + // insert your application logic here + let user = User { + id: 1337, + username: payload.username, + }; + + // this will be converted into a JSON response + // with a status code of `201 Created` + (StatusCode::CREATED, Json(user)) +} + +// the input to our `create_user` handler +#[derive(Deserialize)] +struct CreateUser { + username: String, +} + +#[tokio::test] +async fn test_router() { + let config = SqliteLocalConfig::new("workspace", "test.db"); + let database = SqliteDatabase::build(config).await.unwrap(); + let router = get_post_router::(&database, "/user"); +} diff --git a/luna-orm-axum/tests/schema.rs b/luna-orm-axum/tests/schema.rs new file mode 100644 index 0000000..eb1306c --- /dev/null +++ b/luna-orm-axum/tests/schema.rs @@ -0,0 +1,60 @@ +use luna_orm_axum::Field; +use luna_orm_axum::FieldType; +use luna_orm_axum::Index; +use luna_orm_axum::IndexType; +use luna_orm_axum::Schema; + +#[test] +pub fn test_serailize_index() { + let index_type = IndexType::Normal; + let serailized = serde_yaml::to_string(&index_type).unwrap(); + assert_eq!(serailized, "normal\n"); +} + +#[test] +pub fn test_serailize() { + let schema = Schema { + name: "article".to_string(), + fields: vec![Field { + f_name: "a".to_string(), + f_type: FieldType::Cellphone, + f_constraint: "".to_string(), + }], + indexes: vec![Index { + i_name: "i_test".to_string(), + i_type: IndexType::Normal, + f_names: vec!["name".to_string()], + }], + }; + let schema_str: String = serde_yaml::to_string(&schema).unwrap(); + dbg!(&schema_str); + assert_eq!( + schema_str, + r#"name: article +fields: +- f_name: a + f_type: cellphone + f_constraint: '' +indexes: +- i_name: i_test + i_type: normal + f_names: + - name +"# + ); +} + +#[test] +pub fn test_deserialize() { + let schema_str = r#" + name: article + fields: + - f_name: a + f_type: email + f_constraint: '' + indexes: [] + "#; + let schema: Schema = serde_yaml::from_str(&schema_str).unwrap(); + + assert!(true); +}