From 0d18b1a59be351b5e184b6546915104864d08ff9 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 15 Mar 2024 16:19:24 +0800 Subject: [PATCH] add luna types --- Cargo.toml | 4 + README.md | 1 + luna-orm-axum/Cargo.toml | 1 + luna-orm-axum/src/handler.rs | 36 ++++----- luna-orm-axum/src/lib.rs | 1 + luna-orm-axum/src/middleware.rs | 108 +++++++++++++++++++++++++++ luna-orm-axum/src/request.rs | 13 ++++ luna-orm-axum/src/router.rs | 17 +++-- luna-orm-axum/tests/axum_router.rs | 4 +- luna-orm/src/error.rs | 3 + luna-types/src/constraint/integer.rs | 11 +++ luna-types/src/constraint/mod.rs | 1 + luna-types/src/constraint/string.rs | 51 +++++++++++++ 13 files changed, 225 insertions(+), 26 deletions(-) create mode 100644 luna-orm-axum/src/middleware.rs diff --git a/Cargo.toml b/Cargo.toml index b4a497d..7c53f03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,10 @@ axum = {version = "0.7.4"} axum-macros = {version = "0.4.1"} http-body-util = {version = "0.1.0"} +tower = { version = "0.4.13", default-features = false, features = ["util"] } +tower-layer = "0.3.2" +tower-service = "0.3" + case = {version = "1.0"} rust-format = {version = "0.3.4"} tera = {version = "1.19"} diff --git a/README.md b/README.md index 8b5de2c..4ee7185 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ # :warning: LUNA-ORM is under rapid development, api may change, you should not use it in PRODUCTION env. For now, just have a basic taste, waiting for the version 1.0 At that time, the api will be stable, and backward compatible will be promised. + ![Building](https://github.com/thegenius/luna-orm/actions/workflows/rust.yml/badge.svg) [![Version](https://img.shields.io/badge/crates-0.3.6-green)](https://crates.io/crates/luna-orm) diff --git a/luna-orm-axum/Cargo.toml b/luna-orm-axum/Cargo.toml index b1c1d1e..51f3c32 100644 --- a/luna-orm-axum/Cargo.toml +++ b/luna-orm-axum/Cargo.toml @@ -8,6 +8,7 @@ version.workspace = true [dependencies] #sqlx = {workspace = true} axum = {workspace = true} +tower = {workspace = true} axum-macros = {workspace = true} tokio = {workspace = true} serde = {workspace = true} diff --git a/luna-orm-axum/src/handler.rs b/luna-orm-axum/src/handler.rs index 73d098e..d9b4f24 100644 --- a/luna-orm-axum/src/handler.rs +++ b/luna-orm-axum/src/handler.rs @@ -92,29 +92,29 @@ where pub struct PostHandler where D: Database + Clone + Send + 'static, - T: DeserializeOwned + Entity + Send + Clone + 'static, + T: DeserializeOwned + Serialize + Entity + Send + Clone + 'static, { - db: PhantomData, + db: DB, data: PhantomData, } -impl Default for PostHandler +impl PostHandler where D: Database + Clone + Send + 'static, - T: DeserializeOwned + Entity + Send + Clone + 'static, + T: DeserializeOwned + Serialize + Entity + Send + Clone + 'static, { - fn default() -> Self { + pub fn new(db: &DB) -> Self { Self { - db: PhantomData, + db: db.clone(), data: PhantomData, } } } - +/* impl Handler<((),), S> for PostHandler where S: Database + Clone + Send + 'static, - T: DeserializeOwned + Entity + Send + Clone + 'static, + T: DeserializeOwned + Serialize + Entity + Send + Clone + 'static, { type Future = Pin + Send>>; @@ -123,37 +123,37 @@ where 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; + handle_post(&mut self.db, payload).await; let response: Response = StatusCode::NOT_FOUND.into_response(); response }) } } +*/ -pub async fn post_test(payload: PostRequest) -> bool +pub async fn handle_post(db: &mut DB, payload: PostRequest) -> Result> where - T: Entity + Send + Sync, + T: Entity + Send + Sync + Serialize, + D: Database, { - true - /* - match payload { + match payload { PostRequest::Create { mut entity } => { db.create(&mut entity).await.unwrap(); let response = PostResponse::Create { entity }; - Ok(Json(response)) + Ok(response) } PostRequest::Insert { entity } => { db.insert(&entity).await.unwrap(); - Ok(Json(PostResponse::Insert)) + Ok(PostResponse::Insert) } PostRequest::Upsert { entity } => { db.upsert(&entity).await.unwrap(); - Ok(Json(PostResponse::Insert)) + Ok(PostResponse::Insert) } } - */ } + pub async fn put( State(mut db): State>, Json(payload): Json>, diff --git a/luna-orm-axum/src/lib.rs b/luna-orm-axum/src/lib.rs index eb59190..94a7474 100644 --- a/luna-orm-axum/src/lib.rs +++ b/luna-orm-axum/src/lib.rs @@ -1,6 +1,7 @@ mod field_type; mod generator; pub mod handler; +mod middleware; pub mod request; pub mod response; pub mod router; diff --git a/luna-orm-axum/src/middleware.rs b/luna-orm-axum/src/middleware.rs new file mode 100644 index 0000000..4afcba6 --- /dev/null +++ b/luna-orm-axum/src/middleware.rs @@ -0,0 +1,108 @@ +use std::future::Future; + +use axum::body::Body; +use axum::extract::Request; +use axum::extract::State; +use axum::http::header; +use axum::middleware::from_fn_with_state; +use axum::middleware::Next; +use axum::response::Response; +use axum::Json; +use axum::RequestExt; +use axum::RequestPartsExt; + +use axum::body; +use axum::middleware::FromFnLayer; +use luna_orm::prelude::*; +use luna_orm::LunaOrmResult; +use serde_json::Value; +use std::task::{Context, Poll}; +use tower::{Layer, Service}; + +#[derive(Debug, Clone)] +pub struct OrmLayer +where + D: Database + Clone, +{ + db: DB, +} + +impl Layer for OrmLayer +where + D: Database + Clone, +{ + type Service = OrmService; + + fn layer(&self, inner: S) -> Self::Service { + OrmService { + inner, + db: self.db.clone(), + } + } +} + +#[derive(Debug, Clone)] +pub struct OrmService +where + D: Database, +{ + inner: S, + db: DB, +} + +struct OrmRequest { + url: String, + command: String, + payload: Value, +} + +async fn extract_orm_request(req: Request) -> LunaOrmResult { + // get url path + let uri = req.uri(); + let path = uri.path(); + + // get method + let method = req.method(); + + // get sub method in header + let command = req.headers().get("x-method"); + + // get content type + let content_type: String = req + .headers() + .get(header::CONTENT_TYPE) + .unwrap() + .to_str() + .unwrap() + .to_string(); + + // get json payload + if content_type.starts_with("application/json") {} + let body = req.into_body(); + let bytes = body::to_bytes(body, 1024 * 1024).await.unwrap(); + let payload: Value = serde_json::from_slice(&bytes).unwrap(); + if !payload.is_object() {} + + return OrmRequest {}; +} + +impl Service for OrmService +where + S: Service, + D: Database + Clone, +{ + type Response = S::Response; + type Error = S::Error; + type Future = S::Future; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: Request) -> Self::Future { + let (mut parts, body) = req.into_parts(); + let data = body.collect().await.unwrap().to_bytes(); + let payload: PostRequest = serde_json::from_slice(&data).unwrap(); + self.inner.call(req) + } +} diff --git a/luna-orm-axum/src/request.rs b/luna-orm-axum/src/request.rs index 40bd61a..ae70ba7 100644 --- a/luna-orm-axum/src/request.rs +++ b/luna-orm-axum/src/request.rs @@ -45,6 +45,19 @@ where Purify { location: L }, } +pub enum DynamicRequest +where + T: Entity + Send + Sync, + M: Serialize + Mutation, + P: Serialize + Primary, + L: Serialize + Location, + S: Serialize + Selection, +{ + Post(PostRequest), + Put(PutRequest), + Delete(DeleteRequest), +} + #[cfg(test)] mod test { use super::PostRequest; diff --git a/luna-orm-axum/src/router.rs b/luna-orm-axum/src/router.rs index 08c2a18..d513f0e 100644 --- a/luna-orm-axum/src/router.rs +++ b/luna-orm-axum/src/router.rs @@ -1,16 +1,21 @@ +use crate::handler; use crate::handler::PostHandler; use axum::routing::post; use axum::Router; use luna_orm::prelude::*; use serde::de::DeserializeOwned; +use serde::Serialize; -pub fn get_post_router(db: &D, path: impl AsRef) -> Router +/* +pub fn get_post_router(db: &DB, path: impl AsRef) -> Router where - T: DeserializeOwned + Entity + Send + Clone + 'static, + T: DeserializeOwned + Serialize + 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()) + let post_handler = PostHandler::::new(db); + //Router::new() + // .route(path.as_ref(), post(post_handler)) + // .with_state(db.clone()); + Router::new().route(path.as_ref(), post(post_handler)) } +*/ diff --git a/luna-orm-axum/tests/axum_router.rs b/luna-orm-axum/tests/axum_router.rs index 0edccfd..e8c0534 100644 --- a/luna-orm-axum/tests/axum_router.rs +++ b/luna-orm-axum/tests/axum_router.rs @@ -4,7 +4,7 @@ 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 luna_orm_axum::router::get_post_router; use serde::{Deserialize, Serialize}; use sqlx::SqliteConnection; @@ -36,5 +36,5 @@ struct CreateUser { 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"); + // let router = get_post_router::(&database, "/user"); } diff --git a/luna-orm/src/error.rs b/luna-orm/src/error.rs index 9316faa..5ca40f8 100644 --- a/luna-orm/src/error.rs +++ b/luna-orm/src/error.rs @@ -16,4 +16,7 @@ pub enum LunaOrmError { #[error("paged template sql can't execute with no count sql")] PagedTemplateHasNoCountSql, + + #[error("dynamic request parse error: {0}")] + DynamicRequestParseError(String), } diff --git a/luna-types/src/constraint/integer.rs b/luna-types/src/constraint/integer.rs index cdd148b..1c0c9bc 100644 --- a/luna-types/src/constraint/integer.rs +++ b/luna-types/src/constraint/integer.rs @@ -12,8 +12,15 @@ use std::fmt::Debug; #[builder(setter(into))] pub struct IntegerConstraint { #[builder(default = "None")] + #[serde(default)] + is_option: Option, + + #[builder(default = "None")] + #[serde(default)] min: Option, + #[builder(default = "None")] + #[serde(default)] max: Option, } @@ -64,6 +71,10 @@ impl IntegerConstraint { impl Constraint for IntegerConstraint { type ValueType = T; + fn is_option(&self) -> bool { + return self.is_option.unwrap_or(false); + } + fn is_valid_json(&self, value: &Value) -> bool { let data_opt: Option = value_to_primitive_int(value); if data_opt.is_none() { diff --git a/luna-types/src/constraint/mod.rs b/luna-types/src/constraint/mod.rs index f251d92..fdf2212 100644 --- a/luna-types/src/constraint/mod.rs +++ b/luna-types/src/constraint/mod.rs @@ -40,6 +40,7 @@ impl<'a> Error for ConstraintError<'a> {} pub trait Constraint: Debug + Serialize { type ValueType; + fn is_option(&self) -> bool; fn is_valid_json(&self, value: &Value) -> bool; fn is_valid(&self, value: &Self::ValueType) -> bool; } diff --git a/luna-types/src/constraint/string.rs b/luna-types/src/constraint/string.rs index fed7327..4f6505a 100644 --- a/luna-types/src/constraint/string.rs +++ b/luna-types/src/constraint/string.rs @@ -10,20 +10,29 @@ use std::{fmt::Debug, marker::PhantomData}; #[builder(setter(into))] pub struct StringConstraint<'a> { #[builder(default = "None")] + #[serde(default)] + is_option: Option, + + #[builder(default = "None")] + #[serde(default)] min_len: Option, #[builder(default = "None")] + #[serde(default)] max_len: Option, #[builder(default = "false")] + #[serde(default = "bool::default")] deny_blank: bool, #[builder(setter(custom))] #[serde(with = "serde_regex")] #[builder(default = "None")] + #[serde(default)] regex: Option, #[builder(setter(skip))] + #[serde(default)] phantom: PhantomData<&'a ()>, } @@ -66,6 +75,10 @@ impl<'a> PartialEq for StringConstraint<'a> { impl<'a> Constraint for StringConstraint<'a> { type ValueType = Cow<'a, str>; + fn is_option(&self) -> bool { + return self.is_option.unwrap_or(false); + } + fn is_valid_json(&self, value: &Value) -> bool { let value = value.as_str(); if value.is_none() { @@ -103,3 +116,41 @@ impl<'a> Constraint for StringConstraint<'a> { return true; } } + +#[cfg(test)] +mod test { + use super::*; + use serde_json; + + #[test] + fn test_default() { + let constraint_str = r#" {} "#; + let string_constrait: StringConstraint = serde_json::from_str(constraint_str).unwrap(); + let json_value_str = r#" "" "#; + let json_value: serde_json::Value = serde_json::from_str(json_value_str).unwrap(); + let is_option = string_constrait.is_option(); + assert_eq!(is_option, false); + let is_valid = string_constrait.is_valid_json(&json_value); + assert_eq!(is_valid, true); + } + + #[test] + fn test_is_option() { + let constraint_str = r#" { "is_option": true } "#; + let string_constrait: StringConstraint = serde_json::from_str(constraint_str).unwrap(); + let is_option = string_constrait.is_option(); + assert_eq!(is_option, true); + } + + #[test] + fn test_min_length() { + let constraint_str = r#" { "min_len": 10 } "#; + let string_constrait: StringConstraint = serde_json::from_str(constraint_str).unwrap(); + let json_value_str = r#" "test" "#; + let json_value: serde_json::Value = serde_json::from_str(json_value_str).unwrap(); + let is_option = string_constrait.is_option(); + assert_eq!(is_option, false); + let is_valid = string_constrait.is_valid_json(&json_value); + assert_eq!(is_valid, false); + } +}