mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
add generic for FromRequest and Responder & config user check
This commit is contained in:
parent
4e45b1bdfe
commit
9371a3d31e
@ -36,6 +36,21 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/flowy_protobuf/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/flowy_protobuf/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/rust-lib/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/path_provider_macos/example/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/path_provider_macos/example/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/path_provider_macos/example/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/path_provider_macos/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/path_provider_macos/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/path_provider_macos/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/flowy_sdk/example/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/flowy_sdk/example/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/flowy_sdk/example/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/flowy_sdk/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/flowy_sdk/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/flowy_sdk/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/window_size/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/window_size/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/window_size/.pub" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
|
@ -17,13 +17,19 @@ tokio = { version = "1", features = ["full"] }
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
log = "0.4.14"
|
||||
env_logger = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_with = "1.9.4"
|
||||
thread-id = "3.3.0"
|
||||
lazy_static = "1.4.0"
|
||||
dyn-clone = "1.0"
|
||||
|
||||
#optional crate
|
||||
bincode = { version = "1.3", optional = true}
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
serde_json = {version = "1.0", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
futures-util = "0.3.15"
|
||||
|
||||
[features]
|
||||
use_serde = ["bincode", "serde", "serde_json"]
|
||||
|
@ -6,6 +6,9 @@ use dyn_clone::DynClone;
|
||||
use std::{fmt, option::NoneError};
|
||||
use tokio::sync::mpsc::error::SendError;
|
||||
|
||||
#[cfg(feature = "use_serde")]
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
pub trait Error: fmt::Debug + fmt::Display + DynClone {
|
||||
fn status_code(&self) -> StatusCode;
|
||||
|
||||
@ -94,3 +97,13 @@ where
|
||||
|
||||
fn as_response(&self) -> EventResponse { EventResponseBuilder::Err().data(format!("{}", self.inner)).build() }
|
||||
}
|
||||
|
||||
#[cfg(feature = "use_serde")]
|
||||
impl Serialize for SystemError {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&format!("{}", self))
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
use std::future::Future;
|
||||
|
||||
use crate::{
|
||||
error::SystemError,
|
||||
error::{InternalError, SystemError},
|
||||
module::Event,
|
||||
request::payload::Payload,
|
||||
util::ready::{ready, Ready},
|
||||
};
|
||||
use futures_core::ready;
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
hash::Hash,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EventRequest {
|
||||
id: String,
|
||||
@ -60,7 +62,46 @@ impl FromRequest for () {
|
||||
#[doc(hidden)]
|
||||
impl FromRequest for String {
|
||||
type Error = SystemError;
|
||||
type Future = Ready<Result<String, SystemError>>;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
|
||||
fn from_request(_req: &EventRequest, _payload: &mut Payload) -> Self::Future { ready(Ok("".to_string())) }
|
||||
fn from_request(req: &EventRequest, _payload: &mut Payload) -> Self::Future {
|
||||
match &req.data {
|
||||
None => ready(Err(InternalError::new("Expected string but request had data").into())),
|
||||
Some(buf) => ready(Ok(String::from_utf8_lossy(buf).into_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl<T> FromRequest for Result<T, T::Error>
|
||||
where
|
||||
T: FromRequest,
|
||||
{
|
||||
type Error = SystemError;
|
||||
type Future = FromRequestFuture<T::Future>;
|
||||
|
||||
fn from_request(req: &EventRequest, payload: &mut Payload) -> Self::Future {
|
||||
FromRequestFuture {
|
||||
fut: T::from_request(req, payload),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub struct FromRequestFuture<Fut> {
|
||||
#[pin]
|
||||
fut: Fut,
|
||||
}
|
||||
|
||||
impl<Fut, T, E> Future for FromRequestFuture<Fut>
|
||||
where
|
||||
Fut: Future<Output = Result<T, E>>,
|
||||
{
|
||||
type Output = Result<Result<T, E>, SystemError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
let res = ready!(this.fut.poll(cx));
|
||||
Poll::Ready(Ok(res))
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
use bytes::{Buf, Bytes};
|
||||
#[cfg(feature = "use_serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt, fmt::Formatter};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
|
||||
pub enum ResponseData {
|
||||
Bytes(Vec<u8>),
|
||||
None,
|
||||
@ -29,6 +31,10 @@ impl std::convert::Into<ResponseData> for Bytes {
|
||||
fn into(self) -> ResponseData { ResponseData::Bytes(self.bytes().to_vec()) }
|
||||
}
|
||||
|
||||
impl std::convert::Into<ResponseData> for Vec<u8> {
|
||||
fn into(self) -> ResponseData { ResponseData::Bytes(self) }
|
||||
}
|
||||
|
||||
impl std::convert::Into<ResponseData> for &str {
|
||||
fn into(self) -> ResponseData { self.to_string().into() }
|
||||
}
|
||||
|
@ -34,3 +34,14 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(feature = "use_serde")]
|
||||
// impl<T> Responder for T
|
||||
// where
|
||||
// T: serde::Serialize,
|
||||
// {
|
||||
// fn respond_to(self, request: &EventRequest) -> EventResponse {
|
||||
// let bytes = bincode::serialize(&self).unwrap();
|
||||
// EventResponseBuilder::Ok().data(bytes).build()
|
||||
// }
|
||||
// }
|
||||
|
@ -3,23 +3,24 @@ use crate::{
|
||||
request::EventRequest,
|
||||
response::{data::ResponseData, Responder},
|
||||
};
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
#[cfg(feature = "use_serde")]
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use std::{fmt, fmt::Formatter};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
|
||||
pub enum StatusCode {
|
||||
Ok,
|
||||
Err,
|
||||
}
|
||||
|
||||
// serde user guide: https://serde.rs/field-attrs.html
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "use_serde", derive(Serialize))]
|
||||
pub struct EventResponse {
|
||||
#[serde(serialize_with = "serialize_data")]
|
||||
pub data: ResponseData,
|
||||
pub status: StatusCode,
|
||||
#[serde(serialize_with = "serialize_error")]
|
||||
pub error: Option<SystemError>,
|
||||
}
|
||||
|
||||
@ -35,10 +36,17 @@ impl EventResponse {
|
||||
|
||||
impl std::fmt::Display for EventResponse {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match serde_json::to_string(self) {
|
||||
Ok(json) => f.write_fmt(format_args!("{:?}", json))?,
|
||||
Err(e) => f.write_fmt(format_args!("{:?}", e))?,
|
||||
f.write_fmt(format_args!("Status_Code: {:?}", self.status))?;
|
||||
|
||||
match &self.data {
|
||||
ResponseData::Bytes(b) => f.write_fmt(format_args!("Data: {} bytes", b.len()))?,
|
||||
ResponseData::None => f.write_fmt(format_args!("Data: Empty"))?,
|
||||
}
|
||||
match &self.error {
|
||||
Some(e) => f.write_fmt(format_args!("Error: {:?}", e))?,
|
||||
None => {},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -48,6 +56,7 @@ impl Responder for EventResponse {
|
||||
fn respond_to(self, _: &EventRequest) -> EventResponse { self }
|
||||
}
|
||||
|
||||
#[cfg(feature = "use_serde")]
|
||||
fn serialize_error<S>(error: &Option<SystemError>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
@ -58,6 +67,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg_attr(feature = "use_serde", #[serde(serialize_with =
|
||||
// "serialize_data")])]
|
||||
#[cfg(feature = "use_serde")]
|
||||
fn serialize_data<S>(data: &ResponseData, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
system::ModuleMap,
|
||||
};
|
||||
use futures_core::{future::LocalBoxFuture, ready, task::Context};
|
||||
use std::{future::Future, hash::Hash};
|
||||
use std::future::Future;
|
||||
use tokio::{
|
||||
macros::support::{Pin, Poll},
|
||||
sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
|
@ -8,4 +8,16 @@ edition = "2018"
|
||||
[dependencies]
|
||||
derive_more = {version = "0.99", features = ["display"]}
|
||||
flowy-sys = { path = "../flowy-sys" }
|
||||
flowy-log = { path = "../flowy-log" }
|
||||
flowy-log = { path = "../flowy-log" }
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
bytes = "0.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
validator = "0.12.0"
|
||||
rand = { version = "0.8", features=["std_rng"] }
|
||||
unicode-segmentation = "1.7.1"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9.2"
|
||||
quickcheck_macros = "0.9.1"
|
||||
fake = "~2.3.0"
|
||||
claim = "0.4.0"
|
7
rust-lib/flowy-user/src/domain/mod.rs
Normal file
7
rust-lib/flowy-user/src/domain/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod user;
|
||||
mod user_email;
|
||||
mod user_name;
|
||||
|
||||
pub use user::*;
|
||||
pub use user_email::*;
|
||||
pub use user_name::*;
|
10
rust-lib/flowy-user/src/domain/user.rs
Normal file
10
rust-lib/flowy-user/src/domain/user.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use crate::domain::{user_email::UserEmail, user_name::UserName};
|
||||
|
||||
pub struct User {
|
||||
name: UserName,
|
||||
email: UserEmail,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn new(name: UserName, email: UserEmail) -> Self { Self { name, email } }
|
||||
}
|
59
rust-lib/flowy-user/src/domain/user_email.rs
Normal file
59
rust-lib/flowy-user/src/domain/user_email.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use validator::validate_email;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserEmail(String);
|
||||
|
||||
impl UserEmail {
|
||||
pub fn parse(s: String) -> Result<UserEmail, String> {
|
||||
if validate_email(&s) {
|
||||
Ok(Self(s))
|
||||
} else {
|
||||
Err(format!("{} is not a valid subscriber email.", s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for UserEmail {
|
||||
fn as_ref(&self) -> &str { &self.0 }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use claim::assert_err;
|
||||
use fake::{faker::internet::en::SafeEmail, Fake};
|
||||
use quickcheck::Gen;
|
||||
|
||||
#[test]
|
||||
fn empty_string_is_rejected() {
|
||||
let email = "".to_string();
|
||||
assert_err!(UserEmail::parse(email));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn email_missing_at_symbol_is_rejected() {
|
||||
let email = "helloworld.com".to_string();
|
||||
assert_err!(UserEmail::parse(email));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn email_missing_subject_is_rejected() {
|
||||
let email = "@domain.com".to_string();
|
||||
assert_err!(UserEmail::parse(email));
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ValidEmailFixture(pub String);
|
||||
|
||||
impl quickcheck::Arbitrary for ValidEmailFixture {
|
||||
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
|
||||
let email = SafeEmail().fake_with_rng(g);
|
||||
Self(email)
|
||||
}
|
||||
}
|
||||
|
||||
#[quickcheck_macros::quickcheck]
|
||||
fn valid_emails_are_parsed_successfully(valid_email: ValidEmailFixture) -> bool {
|
||||
UserEmail::parse(valid_email.0).is_ok()
|
||||
}
|
||||
}
|
75
rust-lib/flowy-user/src/domain/user_name.rs
Normal file
75
rust-lib/flowy-user/src/domain/user_name.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserName(String);
|
||||
|
||||
impl UserName {
|
||||
pub fn parse(s: String) -> Result<UserName, String> {
|
||||
let is_empty_or_whitespace = s.trim().is_empty();
|
||||
// A grapheme is defined by the Unicode standard as a "user-perceived"
|
||||
// character: `å` is a single grapheme, but it is composed of two characters
|
||||
// (`a` and `̊`).
|
||||
//
|
||||
// `graphemes` returns an iterator over the graphemes in the input `s`.
|
||||
// `true` specifies that we want to use the extended grapheme definition set,
|
||||
// the recommended one.
|
||||
let is_too_long = s.graphemes(true).count() > 256;
|
||||
|
||||
let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}'];
|
||||
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))
|
||||
} else {
|
||||
Ok(Self(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for UserName {
|
||||
fn as_ref(&self) -> &str { &self.0 }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::UserName;
|
||||
use claim::{assert_err, assert_ok};
|
||||
|
||||
#[test]
|
||||
fn a_256_grapheme_long_name_is_valid() {
|
||||
let name = "a̐".repeat(256);
|
||||
assert_ok!(UserName::parse(name));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_name_longer_than_256_graphemes_is_rejected() {
|
||||
let name = "a".repeat(257);
|
||||
assert_err!(UserName::parse(name));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whitespace_only_names_are_rejected() {
|
||||
let name = " ".to_string();
|
||||
assert_err!(UserName::parse(name));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_string_is_rejected() {
|
||||
let name = "".to_string();
|
||||
assert_err!(UserName::parse(name));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn names_containing_an_invalid_character_are_rejected() {
|
||||
for name in vec!['/', '(', ')', '"', '<', '>', '\\', '{', '}'] {
|
||||
let name = name.to_string();
|
||||
assert_err!(UserName::parse(name));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_valid_name_is_parsed_successfully() {
|
||||
let name = "nathan".to_string();
|
||||
assert_ok!(UserName::parse(name));
|
||||
}
|
||||
}
|
@ -1,2 +1,21 @@
|
||||
// #[tracing::instrument(name = "Adding a new subscriber")]
|
||||
pub async fn user_check() -> String { "".to_owned() }
|
||||
use crate::domain::{User, UserEmail, UserName};
|
||||
use bytes::Bytes;
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub struct UserData {
|
||||
name: String,
|
||||
email: String,
|
||||
}
|
||||
|
||||
impl TryInto<User> for UserData {
|
||||
type Error = String;
|
||||
|
||||
fn try_into(self) -> Result<User, Self::Error> {
|
||||
let name = UserName::parse(self.name)?;
|
||||
let email = UserEmail::parse(self.email)?;
|
||||
Ok(User::new(name, email))
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "User check")]
|
||||
pub async fn user_check(user_name: String) -> Bytes { unimplemented!() }
|
||||
|
@ -1,3 +1,4 @@
|
||||
mod domain;
|
||||
mod error;
|
||||
mod event;
|
||||
mod handlers;
|
||||
|
Loading…
Reference in New Issue
Block a user