(event: E) -> Self
where
E: Eq + Hash + Debug + Clone + Display,
{
Self {
- payload,
+ payload: Payload::None,
event: event.into(),
id: uuid::Uuid::new_v4().to_string(),
callback: None,
}
}
+ pub fn payload(mut self, payload: P) -> Self
+ where
+ P: TryInto,
+ {
+ let payload = match payload.try_into() {
+ Ok(payload) => payload,
+ Err(e) => {
+ log::error!("{}", e);
+ Payload::None
+ },
+ };
+ self.payload = payload;
+ self
+ }
+
pub fn callback(mut self, callback: BoxFutureCallback) -> Self {
self.callback = Some(callback);
self
diff --git a/rust-lib/flowy-sys/src/error/mod.rs b/rust-lib/flowy-sys/src/error/mod.rs
index feb53da209..3925f93ff7 100644
--- a/rust-lib/flowy-sys/src/error/mod.rs
+++ b/rust-lib/flowy-sys/src/error/mod.rs
@@ -1,5 +1,3 @@
mod error;
-pub type ResponseResult = std::result::Result, E>;
-
pub use error::*;
diff --git a/rust-lib/flowy-sys/src/lib.rs b/rust-lib/flowy-sys/src/lib.rs
index a89876bb3e..71cdcaa15a 100644
--- a/rust-lib/flowy-sys/src/lib.rs
+++ b/rust-lib/flowy-sys/src/lib.rs
@@ -8,9 +8,10 @@ mod rt;
mod service;
mod util;
+mod data;
mod dispatch;
mod system;
pub mod prelude {
- pub use crate::{dispatch::*, error::*, module::*, request::*, response::*, rt::*};
+ pub use crate::{data::*, dispatch::*, error::*, module::*, request::*, response::*, rt::*};
}
diff --git a/rust-lib/flowy-sys/src/request/payload.rs b/rust-lib/flowy-sys/src/request/payload.rs
index 19b3ffdf5b..318897f669 100644
--- a/rust-lib/flowy-sys/src/request/payload.rs
+++ b/rust-lib/flowy-sys/src/request/payload.rs
@@ -38,6 +38,16 @@ impl std::convert::Into for Vec {
fn into(self) -> Payload { Payload::Bytes(self) }
}
+// = note: conflicting implementation in crate `core`:
+// - impl TryInto for T where U: TryFrom;
+//
+// impl std::convert::TryInto for Vec {
+// type Error = String;
+// fn try_into(self) -> Result {
+// Ok(Payload::Bytes(self))
+// }
+// }
+
impl std::convert::Into for &str {
fn into(self) -> Payload { self.to_string().into() }
}
diff --git a/rust-lib/flowy-sys/src/request/request.rs b/rust-lib/flowy-sys/src/request/request.rs
index 0da8823c40..6f1d82ac89 100644
--- a/rust-lib/flowy-sys/src/request/request.rs
+++ b/rust-lib/flowy-sys/src/request/request.rs
@@ -10,7 +10,6 @@ use crate::{
use futures_core::ready;
use std::{
fmt::Debug,
- ops,
pin::Pin,
task::{Context, Poll},
};
@@ -61,7 +60,7 @@ impl FromRequest for String {
}
}
-fn unexpected_none_payload(request: &EventRequest) -> SystemError {
+pub fn unexpected_none_payload(request: &EventRequest) -> SystemError {
log::warn!("{:?} expected payload", &request.event);
InternalError::new("Expected payload").into()
}
@@ -99,65 +98,3 @@ where
Poll::Ready(Ok(res))
}
}
-
-pub struct Data(pub T);
-
-impl Data {
- pub fn into_inner(self) -> T { self.0 }
-}
-
-impl ops::Deref for Data {
- type Target = T;
-
- fn deref(&self) -> &T { &self.0 }
-}
-
-impl ops::DerefMut for Data {
- fn deref_mut(&mut self) -> &mut T { &mut self.0 }
-}
-
-pub trait FromBytes: Sized {
- fn parse_from_bytes(bytes: &Vec) -> Result;
-}
-
-#[cfg(feature = "use_protobuf")]
-impl FromBytes for T
-where
- // https://stackoverflow.com/questions/62871045/tryfromu8-trait-bound-in-trait
- T: for<'a> std::convert::TryFrom<&'a Vec, Error = String>,
-{
- fn parse_from_bytes(bytes: &Vec) -> Result { T::try_from(bytes) }
-}
-
-#[cfg(feature = "use_serde")]
-impl FromBytes for T
-where
- T: serde::de::DeserializeOwned + 'static,
-{
- fn parse_from_bytes(bytes: &Vec) -> Result {
- let s = String::from_utf8_lossy(bytes);
- match serde_json::from_str::(s.as_ref()) {
- Ok(data) => Ok(data),
- Err(e) => Err(format!("{:?}", e)),
- }
- }
-}
-
-impl FromRequest for Data
-where
- T: FromBytes + 'static,
-{
- type Error = SystemError;
- type Future = Ready>;
-
- #[inline]
- fn from_request(req: &EventRequest, payload: &mut Payload) -> Self::Future {
- match payload {
- Payload::None => ready(Err(unexpected_none_payload(req))),
- Payload::Bytes(bytes) => match T::parse_from_bytes(bytes) {
- Ok(data) => ready(Ok(Data(data))),
- Err(e) => ready(Err(InternalError::new(format!("{:?}", e)).into())),
- },
- }
- }
-}
diff --git a/rust-lib/flowy-sys/src/response/responder.rs b/rust-lib/flowy-sys/src/response/responder.rs
index b397b4342f..7acbba0266 100644
--- a/rust-lib/flowy-sys/src/response/responder.rs
+++ b/rust-lib/flowy-sys/src/response/responder.rs
@@ -1,7 +1,7 @@
#[allow(unused_imports)]
use crate::error::{InternalError, SystemError};
use crate::{
- request::{Data, EventRequest},
+ request::EventRequest,
response::{EventResponse, ResponseBuilder},
};
use bytes::Bytes;
@@ -62,25 +62,3 @@ where
}
}
}
-
-impl Responder for Data
-where
- T: ToBytes,
-{
- fn respond_to(self, _request: &EventRequest) -> EventResponse {
- match self.into_inner().into_bytes() {
- Ok(bytes) => ResponseBuilder::Ok().data(bytes.to_vec()).build(),
- Err(e) => {
- let system_err: SystemError = InternalError::new(format!("{:?}", e)).into();
- system_err.into()
- },
- }
- }
-}
-
-impl std::convert::From for Data
-where
- T: ToBytes,
-{
- fn from(val: T) -> Self { Data(val) }
-}
diff --git a/rust-lib/flowy-sys/src/response/response.rs b/rust-lib/flowy-sys/src/response/response.rs
index 3b9ef88fd3..c3c9a2e035 100644
--- a/rust-lib/flowy-sys/src/response/response.rs
+++ b/rust-lib/flowy-sys/src/response/response.rs
@@ -1,6 +1,7 @@
use crate::{
+ data::Data,
error::SystemError,
- request::{Data, EventRequest, Payload},
+ request::{EventRequest, Payload},
response::Responder,
};
use std::{fmt, fmt::Formatter};
@@ -51,6 +52,8 @@ impl Responder for EventResponse {
fn respond_to(self, _: &EventRequest) -> EventResponse { self }
}
+pub type ResponseResult = std::result::Result, E>;
+
pub fn response_ok(data: T) -> Result, E>
where
E: Into,
diff --git a/rust-lib/flowy-sys/tests/api/module.rs b/rust-lib/flowy-sys/tests/api/module.rs
index 015ac65b38..b5d7ecf066 100644
--- a/rust-lib/flowy-sys/tests/api/module.rs
+++ b/rust-lib/flowy-sys/tests/api/module.rs
@@ -9,7 +9,7 @@ async fn test_init() {
let event = "1";
init_dispatch(|| vec![Module::new().event(event, hello)]);
- let request = DispatchRequest::new(event, Payload::None);
+ let request = DispatchRequest::new(event);
let resp = async_send(request).await;
dbg!(&resp);
}
diff --git a/rust-lib/flowy-test/Cargo.toml b/rust-lib/flowy-test/Cargo.toml
new file mode 100644
index 0000000000..d0d1a6f7f3
--- /dev/null
+++ b/rust-lib/flowy-test/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "flowy-test"
+version = "0.1.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+flowy-sdk = { path = "../flowy-sdk"}
+flowy-sys = { path = "../flowy-sys"}
+
+serde = { version = "1.0", features = ["derive"] }
+bincode = { version = "1.3"}
+protobuf = {version = "2.24.1"}
+claim = "0.5.0"
+tokio = { version = "1", features = ["full"]}
+futures-util = "0.3.15"
\ No newline at end of file
diff --git a/rust-lib/flowy-test/src/lib.rs b/rust-lib/flowy-test/src/lib.rs
new file mode 100644
index 0000000000..92c5162ac9
--- /dev/null
+++ b/rust-lib/flowy-test/src/lib.rs
@@ -0,0 +1,105 @@
+pub use flowy_sdk::*;
+use flowy_sys::prelude::*;
+use std::{
+ convert::TryFrom,
+ fmt::{Debug, Display},
+ fs,
+ hash::Hash,
+ path::PathBuf,
+ sync::Once,
+};
+
+pub mod prelude {
+ pub use crate::EventTester;
+ pub use flowy_sys::prelude::*;
+ pub use std::convert::TryFrom;
+}
+
+static INIT: Once = Once::new();
+pub fn init_sdk() {
+ let root_dir = root_dir();
+
+ INIT.call_once(|| {
+ FlowySDK::init_log(&root_dir);
+ });
+ FlowySDK::init(&root_dir);
+}
+
+fn root_dir() -> String {
+ // https://doc.rust-lang.org/cargo/reference/environment-variables.html
+ let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or("./".to_owned());
+ let mut path_buf = fs::canonicalize(&PathBuf::from(&manifest_dir)).unwrap();
+ path_buf.pop(); // rust-lib
+ path_buf.push("flowy-test");
+ path_buf.push("temp");
+ path_buf.push("flowy");
+
+ let root_dir = path_buf.to_str().unwrap().to_string();
+ if !std::path::Path::new(&root_dir).exists() {
+ std::fs::create_dir_all(&root_dir).unwrap();
+ }
+ root_dir
+}
+
+pub struct EventTester {
+ request: DispatchRequest,
+ assert_status_code: Option,
+}
+
+impl EventTester {
+ pub fn new(event: E) -> Self
+ where
+ E: Eq + Hash + Debug + Clone + Display,
+ {
+ init_sdk();
+ let request = DispatchRequest::new(event);
+ Self {
+ request,
+ assert_status_code: None,
+ }
+ }
+
+ pub fn payload(mut self, payload: P) -> Self
+ where
+ P: ToBytes,
+ {
+ self.request = self.request.payload(Data(payload));
+ self
+ }
+
+ pub fn assert_status_code(mut self, status_code: StatusCode) -> Self {
+ self.assert_status_code = Some(status_code);
+ self
+ }
+
+ #[allow(dead_code)]
+ pub async fn async_send(self) -> R
+ where
+ R: FromBytes,
+ {
+ let resp = async_send(self.request).await;
+ dbg!(&resp);
+ data_from_response(&resp)
+ }
+
+ pub fn sync_send(self) -> R
+ where
+ R: FromBytes,
+ {
+ let resp = sync_send(self.request);
+ if let Some(status_code) = self.assert_status_code {
+ assert_eq!(resp.status_code, status_code)
+ }
+
+ dbg!(&resp);
+ data_from_response(&resp)
+ }
+}
+
+fn data_from_response(response: &EventResponse) -> R
+where
+ R: FromBytes,
+{
+ let result = >::try_from(&response.payload).unwrap().into_inner();
+ result
+}
diff --git a/rust-lib/flowy-user/Cargo.toml b/rust-lib/flowy-user/Cargo.toml
index 04e23fdb60..4510fadd60 100644
--- a/rust-lib/flowy-user/Cargo.toml
+++ b/rust-lib/flowy-user/Cargo.toml
@@ -18,9 +18,12 @@ rand = { version = "0.8", features=["std_rng"] }
unicode-segmentation = "1.7.1"
log = "0.4.14"
protobuf = {version = "2.18.0"}
+lazy_static = "1.4.0"
+fancy-regex = "0.5.0"
[dev-dependencies]
quickcheck = "0.9.2"
quickcheck_macros = "0.9.1"
fake = "~2.3.0"
-claim = "0.4.0"
\ No newline at end of file
+claim = "0.4.0"
+flowy-test = { path = "../flowy-test" }
\ No newline at end of file
diff --git a/rust-lib/flowy-user/src/domain/user/user.rs b/rust-lib/flowy-user/src/domain/user/user.rs
index 8198426f94..b2aaead93d 100644
--- a/rust-lib/flowy-user/src/domain/user/user.rs
+++ b/rust-lib/flowy-user/src/domain/user/user.rs
@@ -1,6 +1,5 @@
use crate::domain::{UserEmail, UserName, UserPassword};
use flowy_derive::ProtoBuf;
-use std::convert::TryInto;
#[derive(ProtoBuf, Default)]
pub struct User {
diff --git a/rust-lib/flowy-user/src/domain/user/user_email.rs b/rust-lib/flowy-user/src/domain/user/user_email.rs
index 67806fed76..c75d438a30 100644
--- a/rust-lib/flowy-user/src/domain/user/user_email.rs
+++ b/rust-lib/flowy-user/src/domain/user/user_email.rs
@@ -5,10 +5,14 @@ pub struct UserEmail(pub String);
impl UserEmail {
pub fn parse(s: String) -> Result {
+ if s.trim().is_empty() {
+ return Err(format!("Email can not be empty or whitespace"));
+ }
+
if validate_email(&s) {
Ok(Self(s))
} else {
- Err(format!("{} is not a valid subscriber email.", s))
+ Err(format!("{} is not a valid email.", s))
}
}
}
diff --git a/rust-lib/flowy-user/src/domain/user/user_name.rs b/rust-lib/flowy-user/src/domain/user/user_name.rs
index 77dcd669f7..01970de4d0 100644
--- a/rust-lib/flowy-user/src/domain/user/user_name.rs
+++ b/rust-lib/flowy-user/src/domain/user/user_name.rs
@@ -19,7 +19,7 @@ impl UserName {
let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g));
if is_empty_or_whitespace || is_too_long || contains_forbidden_characters {
- Err(format!("{} is not a valid subscriber name.", s))
+ Err(format!("{} is not a valid name.", s))
} else {
Ok(Self(s))
}
diff --git a/rust-lib/flowy-user/src/domain/user/user_password.rs b/rust-lib/flowy-user/src/domain/user/user_password.rs
index b1d2b6e400..e29b167978 100644
--- a/rust-lib/flowy-user/src/domain/user/user_password.rs
+++ b/rust-lib/flowy-user/src/domain/user/user_password.rs
@@ -1,6 +1,43 @@
+use fancy_regex::Regex;
+use lazy_static::lazy_static;
+use unicode_segmentation::UnicodeSegmentation;
#[derive(Debug)]
pub struct UserPassword(pub String);
impl UserPassword {
- pub fn parse(s: String) -> Result { Ok(Self(s)) }
+ pub fn parse(s: String) -> Result {
+ let is_empty_or_whitespace = s.trim().is_empty();
+ if is_empty_or_whitespace {
+ return Err(format!("Password can not be empty or whitespace."));
+ }
+ let is_too_long = s.graphemes(true).count() > 100;
+ let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}'];
+ let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g));
+ let is_invalid_password = !validate_password(&s);
+
+ if is_too_long || contains_forbidden_characters || is_invalid_password {
+ Err(format!("{} is not a valid password.", s))
+ } else {
+ Ok(Self(s))
+ }
+ }
}
+
+lazy_static! {
+ // Test it in https://regex101.com/
+ // https://stackoverflow.com/questions/2370015/regular-expression-for-password-validation/2370045
+ // Hell1!
+ // [invalid, less than 6]
+ // Hel1!
+ //
+ // Hello1!
+ // [invalid, must include number]
+ // Hello!
+ //
+ // Hello12!
+ // [invalid must include upper case]
+ // hello12!
+ static ref PASSWORD: Regex = Regex::new("((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\\W]).{6,20})").unwrap();
+}
+
+pub fn validate_password(password: &str) -> bool { PASSWORD.is_match(password).is_ok() }
diff --git a/rust-lib/flowy-user/src/event.rs b/rust-lib/flowy-user/src/event.rs
deleted file mode 100644
index 97e0dea582..0000000000
--- a/rust-lib/flowy-user/src/event.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-use derive_more::Display;
-
-#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash)]
-pub enum UserEvent {
- #[display(fmt = "AuthCheck")]
- AuthCheck = 0,
- #[display(fmt = "SignIn")]
- SignIn = 1,
- #[display(fmt = "SignUp")]
- SignUp = 2,
- #[display(fmt = "SignOut")]
- SignOut = 3,
-}
diff --git a/rust-lib/flowy-user/src/lib.rs b/rust-lib/flowy-user/src/lib.rs
index 498e7e3ebd..f975fd7947 100644
--- a/rust-lib/flowy-user/src/lib.rs
+++ b/rust-lib/flowy-user/src/lib.rs
@@ -1,14 +1,9 @@
mod domain;
mod error;
-pub mod event;
mod handlers;
pub mod module;
mod protobuf;
pub mod prelude {
- pub use crate::{
- domain::*,
- event::{UserEvent::*, *},
- handlers::auth::*,
- };
+ pub use crate::{domain::*, handlers::auth::*, module::UserEvent::*};
}
diff --git a/rust-lib/flowy-user/src/module.rs b/rust-lib/flowy-user/src/module.rs
index 7eed0568dd..4d4863ec54 100644
--- a/rust-lib/flowy-user/src/module.rs
+++ b/rust-lib/flowy-user/src/module.rs
@@ -1,9 +1,23 @@
-use crate::{event::UserEvent::*, handlers::*};
+use crate::handlers::*;
use flowy_sys::prelude::*;
+use derive_more::Display;
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash)]
+pub enum UserEvent {
+ #[display(fmt = "AuthCheck")]
+ AuthCheck = 0,
+ #[display(fmt = "SignIn")]
+ SignIn = 1,
+ #[display(fmt = "SignUp")]
+ SignUp = 2,
+ #[display(fmt = "SignOut")]
+ SignOut = 3,
+}
+
pub fn create() -> Module {
Module::new()
.name("Flowy-User")
- .event(SignIn, user_sign_in)
- .event(SignUp, user_sign_up)
+ .event(UserEvent::SignIn, user_sign_in)
+ .event(UserEvent::SignUp, user_sign_up)
}
diff --git a/rust-lib/flowy-sdk/tests/sdk/user/mod.rs b/rust-lib/flowy-user/tests/main.rs
similarity index 100%
rename from rust-lib/flowy-sdk/tests/sdk/user/mod.rs
rename to rust-lib/flowy-user/tests/main.rs
diff --git a/rust-lib/flowy-user/tests/sign_in.rs b/rust-lib/flowy-user/tests/sign_in.rs
new file mode 100644
index 0000000000..c625114b44
--- /dev/null
+++ b/rust-lib/flowy-user/tests/sign_in.rs
@@ -0,0 +1,32 @@
+use flowy_test::prelude::*;
+use flowy_user::prelude::*;
+
+#[test]
+#[should_panic]
+fn sign_in_without_password() {
+ let params = UserSignInParams {
+ email: "annie@appflowy.io".to_string(),
+ password: "".to_string(),
+ };
+
+ let result = EventTester::new(SignIn)
+ .payload(params)
+ .assert_status_code(StatusCode::Err)
+ .sync_send::();
+ dbg!(&result);
+}
+
+#[test]
+#[should_panic]
+fn sign_in_without_email() {
+ let params = UserSignInParams {
+ email: "".to_string(),
+ password: "HelloWorld!123".to_string(),
+ };
+
+ let result = EventTester::new(SignIn)
+ .payload(params)
+ .assert_status_code(StatusCode::Err)
+ .sync_send::();
+ dbg!(&result);
+}