ソースを参照

consume reqwest status code

appflowy 3 年 前
コミット
49e5f38406
29 ファイル変更501 行追加254 行削除
  1. 24 10
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbenum.dart
  2. 15 8
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbjson.dart
  3. 13 7
      backend/src/routers/helper.rs
  4. 5 5
      backend/src/startup.rs
  5. 3 5
      backend/src/user_service/auth.rs
  6. 3 3
      backend/src/ws_service/entities/connect.rs
  7. 3 3
      backend/src/ws_service/ws_server.rs
  8. 4 4
      rust-lib/dart-ffi/Cargo.toml
  9. 1 0
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  10. 46 0
      rust-lib/flowy-infra/src/errors/builder.rs
  11. 3 0
      rust-lib/flowy-infra/src/errors/mod.rs
  12. 4 1
      rust-lib/flowy-net/Cargo.toml
  13. 0 40
      rust-lib/flowy-net/src/errors.rs
  14. 45 0
      rust-lib/flowy-net/src/errors/errors.rs
  15. 3 0
      rust-lib/flowy-net/src/errors/mod.rs
  16. 17 14
      rust-lib/flowy-net/src/request/request.rs
  17. 98 3
      rust-lib/flowy-net/src/response/response.rs
  18. 10 19
      rust-lib/flowy-net/src/response/response_http.rs
  19. 4 3
      rust-lib/flowy-user/src/entities/parser/user_email.rs
  20. 12 5
      rust-lib/flowy-user/src/entities/parser/user_name.rs
  21. 7 5
      rust-lib/flowy-user/src/entities/parser/user_password.rs
  22. 3 7
      rust-lib/flowy-user/src/entities/sign_in.rs
  23. 4 13
      rust-lib/flowy-user/src/entities/sign_up.rs
  24. 3 11
      rust-lib/flowy-user/src/entities/user_update.rs
  25. 36 13
      rust-lib/flowy-user/src/errors.rs
  26. 107 68
      rust-lib/flowy-user/src/protobuf/model/errors.rs
  27. 14 7
      rust-lib/flowy-user/src/protobuf/proto/errors.proto
  28. 1 0
      rust-lib/flowy-user/tests/server/main.rs
  29. 13 0
      rust-lib/flowy-user/tests/server/user_test.rs

+ 24 - 10
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbenum.dart

@@ -20,13 +20,20 @@ class UserErrCode extends $pb.ProtobufEnum {
   static const UserErrCode UserNotLoginYet = UserErrCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotLoginYet');
   static const UserErrCode ReadCurrentIdFailed = UserErrCode._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadCurrentIdFailed');
   static const UserErrCode WriteCurrentIdFailed = UserErrCode._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WriteCurrentIdFailed');
-  static const UserErrCode EmailInvalid = UserErrCode._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EmailInvalid');
-  static const UserErrCode PasswordInvalid = UserErrCode._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PasswordInvalid');
-  static const UserErrCode UserNameInvalid = UserErrCode._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNameInvalid');
-  static const UserErrCode UserWorkspaceInvalid = UserErrCode._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserWorkspaceInvalid');
-  static const UserErrCode UserIdInvalid = UserErrCode._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserIdInvalid');
-  static const UserErrCode CreateDefaultWorkspaceFailed = UserErrCode._(25, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateDefaultWorkspaceFailed');
-  static const UserErrCode DefaultWorkspaceAlreadyExist = UserErrCode._(26, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DefaultWorkspaceAlreadyExist');
+  static const UserErrCode EmailIsEmpty = UserErrCode._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EmailIsEmpty');
+  static const UserErrCode EmailFormatInvalid = UserErrCode._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EmailFormatInvalid');
+  static const UserErrCode EmailAlreadyExists = UserErrCode._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EmailAlreadyExists');
+  static const UserErrCode PasswordIsEmpty = UserErrCode._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PasswordIsEmpty');
+  static const UserErrCode PasswordTooLong = UserErrCode._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PasswordTooLong');
+  static const UserErrCode PasswordContainsForbidCharacters = UserErrCode._(32, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PasswordContainsForbidCharacters');
+  static const UserErrCode PasswordFormatInvalid = UserErrCode._(33, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PasswordFormatInvalid');
+  static const UserErrCode UserNameTooLong = UserErrCode._(40, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNameTooLong');
+  static const UserErrCode UserNameContainsForbiddenCharacters = UserErrCode._(41, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNameContainsForbiddenCharacters');
+  static const UserErrCode UserNameIsEmpty = UserErrCode._(42, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNameIsEmpty');
+  static const UserErrCode UserWorkspaceInvalid = UserErrCode._(50, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserWorkspaceInvalid');
+  static const UserErrCode UserIdInvalid = UserErrCode._(51, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserIdInvalid');
+  static const UserErrCode CreateDefaultWorkspaceFailed = UserErrCode._(52, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateDefaultWorkspaceFailed');
+  static const UserErrCode DefaultWorkspaceAlreadyExist = UserErrCode._(53, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DefaultWorkspaceAlreadyExist');
   static const UserErrCode NetworkError = UserErrCode._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NetworkError');
 
   static const $core.List<UserErrCode> values = <UserErrCode> [
@@ -40,9 +47,16 @@ class UserErrCode extends $pb.ProtobufEnum {
     UserNotLoginYet,
     ReadCurrentIdFailed,
     WriteCurrentIdFailed,
-    EmailInvalid,
-    PasswordInvalid,
-    UserNameInvalid,
+    EmailIsEmpty,
+    EmailFormatInvalid,
+    EmailAlreadyExists,
+    PasswordIsEmpty,
+    PasswordTooLong,
+    PasswordContainsForbidCharacters,
+    PasswordFormatInvalid,
+    UserNameTooLong,
+    UserNameContainsForbiddenCharacters,
+    UserNameIsEmpty,
     UserWorkspaceInvalid,
     UserIdInvalid,
     CreateDefaultWorkspaceFailed,

+ 15 - 8
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbjson.dart

@@ -22,19 +22,26 @@ const UserErrCode$json = const {
     const {'1': 'UserNotLoginYet', '2': 10},
     const {'1': 'ReadCurrentIdFailed', '2': 11},
     const {'1': 'WriteCurrentIdFailed', '2': 12},
-    const {'1': 'EmailInvalid', '2': 20},
-    const {'1': 'PasswordInvalid', '2': 21},
-    const {'1': 'UserNameInvalid', '2': 22},
-    const {'1': 'UserWorkspaceInvalid', '2': 23},
-    const {'1': 'UserIdInvalid', '2': 24},
-    const {'1': 'CreateDefaultWorkspaceFailed', '2': 25},
-    const {'1': 'DefaultWorkspaceAlreadyExist', '2': 26},
+    const {'1': 'EmailIsEmpty', '2': 20},
+    const {'1': 'EmailFormatInvalid', '2': 21},
+    const {'1': 'EmailAlreadyExists', '2': 22},
+    const {'1': 'PasswordIsEmpty', '2': 30},
+    const {'1': 'PasswordTooLong', '2': 31},
+    const {'1': 'PasswordContainsForbidCharacters', '2': 32},
+    const {'1': 'PasswordFormatInvalid', '2': 33},
+    const {'1': 'UserNameTooLong', '2': 40},
+    const {'1': 'UserNameContainsForbiddenCharacters', '2': 41},
+    const {'1': 'UserNameIsEmpty', '2': 42},
+    const {'1': 'UserWorkspaceInvalid', '2': 50},
+    const {'1': 'UserIdInvalid', '2': 51},
+    const {'1': 'CreateDefaultWorkspaceFailed', '2': 52},
+    const {'1': 'DefaultWorkspaceAlreadyExist', '2': 53},
     const {'1': 'NetworkError', '2': 100},
   ],
 };
 
 /// Descriptor for `UserErrCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List userErrCodeDescriptor = $convert.base64Decode('CgtVc2VyRXJyQ29kZRILCgdVbmtub3duEAASGgoWVXNlckRhdGFiYXNlSW5pdEZhaWxlZBABEhsKF1VzZXJEYXRhYmFzZVdyaXRlTG9ja2VkEAISGgoWVXNlckRhdGFiYXNlUmVhZExvY2tlZBADEhsKF1VzZXJEYXRhYmFzZURpZE5vdE1hdGNoEAQSHQoZVXNlckRhdGFiYXNlSW50ZXJuYWxFcnJvchAFEhQKEFNxbEludGVybmFsRXJyb3IQBhITCg9Vc2VyTm90TG9naW5ZZXQQChIXChNSZWFkQ3VycmVudElkRmFpbGVkEAsSGAoUV3JpdGVDdXJyZW50SWRGYWlsZWQQDBIQCgxFbWFpbEludmFsaWQQFBITCg9QYXNzd29yZEludmFsaWQQFRITCg9Vc2VyTmFtZUludmFsaWQQFhIYChRVc2VyV29ya3NwYWNlSW52YWxpZBAXEhEKDVVzZXJJZEludmFsaWQQGBIgChxDcmVhdGVEZWZhdWx0V29ya3NwYWNlRmFpbGVkEBkSIAocRGVmYXVsdFdvcmtzcGFjZUFscmVhZHlFeGlzdBAaEhAKDE5ldHdvcmtFcnJvchBk');
+final $typed_data.Uint8List userErrCodeDescriptor = $convert.base64Decode('CgtVc2VyRXJyQ29kZRILCgdVbmtub3duEAASGgoWVXNlckRhdGFiYXNlSW5pdEZhaWxlZBABEhsKF1VzZXJEYXRhYmFzZVdyaXRlTG9ja2VkEAISGgoWVXNlckRhdGFiYXNlUmVhZExvY2tlZBADEhsKF1VzZXJEYXRhYmFzZURpZE5vdE1hdGNoEAQSHQoZVXNlckRhdGFiYXNlSW50ZXJuYWxFcnJvchAFEhQKEFNxbEludGVybmFsRXJyb3IQBhITCg9Vc2VyTm90TG9naW5ZZXQQChIXChNSZWFkQ3VycmVudElkRmFpbGVkEAsSGAoUV3JpdGVDdXJyZW50SWRGYWlsZWQQDBIQCgxFbWFpbElzRW1wdHkQFBIWChJFbWFpbEZvcm1hdEludmFsaWQQFRIWChJFbWFpbEFscmVhZHlFeGlzdHMQFhITCg9QYXNzd29yZElzRW1wdHkQHhITCg9QYXNzd29yZFRvb0xvbmcQHxIkCiBQYXNzd29yZENvbnRhaW5zRm9yYmlkQ2hhcmFjdGVycxAgEhkKFVBhc3N3b3JkRm9ybWF0SW52YWxpZBAhEhMKD1VzZXJOYW1lVG9vTG9uZxAoEicKI1VzZXJOYW1lQ29udGFpbnNGb3JiaWRkZW5DaGFyYWN0ZXJzECkSEwoPVXNlck5hbWVJc0VtcHR5ECoSGAoUVXNlcldvcmtzcGFjZUludmFsaWQQMhIRCg1Vc2VySWRJbnZhbGlkEDMSIAocQ3JlYXRlRGVmYXVsdFdvcmtzcGFjZUZhaWxlZBA0EiAKHERlZmF1bHRXb3Jrc3BhY2VBbHJlYWR5RXhpc3QQNRIQCgxOZXR3b3JrRXJyb3IQZA==');
 @$core.Deprecated('Use userErrorDescriptor instead')
 const UserError$json = const {
   '1': 'UserError',

+ 13 - 7
backend/src/routers/helper.rs

@@ -1,15 +1,15 @@
 use crate::config::MAX_PAYLOAD_SIZE;
 use actix_web::web;
-use flowy_net::{errors::NetworkError, response::*};
+use flowy_net::response::*;
 use futures::StreamExt;
 use protobuf::{Message, ProtobufResult};
 
-pub async fn parse_from_payload<T: Message>(payload: web::Payload) -> Result<T, NetworkError> {
+pub async fn parse_from_payload<T: Message>(payload: web::Payload) -> Result<T, ServerError> {
     let bytes = poll_payload(payload).await?;
     parse_from_bytes(&bytes)
 }
 
-pub fn parse_from_bytes<T: Message>(bytes: &[u8]) -> Result<T, NetworkError> {
+pub fn parse_from_bytes<T: Message>(bytes: &[u8]) -> Result<T, ServerError> {
     let result: ProtobufResult<T> = Message::parse_from_bytes(&bytes);
     match result {
         Ok(data) => Ok(data),
@@ -17,13 +17,19 @@ pub fn parse_from_bytes<T: Message>(bytes: &[u8]) -> Result<T, NetworkError> {
     }
 }
 
-pub async fn poll_payload(mut payload: web::Payload) -> Result<web::BytesMut, NetworkError> {
+pub async fn poll_payload(mut payload: web::Payload) -> Result<web::BytesMut, ServerError> {
     let mut body = web::BytesMut::new();
     while let Some(chunk) = payload.next().await {
-        let chunk = chunk.map_err(|e| NetworkError::InternalError(format!("{:?}", e)))?;
+        let chunk = chunk.map_err(|e| ServerError {
+            code: ServerCode::InternalError,
+            msg: format!("{:?}", e),
+        })?;
+
         if (body.len() + chunk.len()) > MAX_PAYLOAD_SIZE {
-            let resp = FlowyResponse::from_msg("Payload overflow", ServerCode::PayloadOverflow);
-            return Err(NetworkError::BadRequest(resp));
+            return Err(ServerError {
+                code: ServerCode::PayloadOverflow,
+                msg: "Payload overflow".to_string(),
+            });
         }
         body.extend_from_slice(&chunk);
     }

+ 5 - 5
backend/src/startup.rs

@@ -6,7 +6,7 @@ use crate::{
     ws_service::WSServer,
 };
 use actix::Actor;
-use actix_web::{dev::Server, middleware, web, App, HttpServer, Scope};
+use actix_web::{dev::Server, middleware, web, web::Data, App, HttpServer, Scope};
 use sqlx::PgPool;
 use std::{net::TcpListener, sync::Arc};
 
@@ -14,12 +14,12 @@ pub fn run(app_ctx: Arc<AppContext>, listener: TcpListener) -> Result<Server, st
     let server = HttpServer::new(move || {
         App::new()
             .wrap(middleware::Logger::default())
-            .data(web::JsonConfig::default().limit(4096))
+            .app_data(web::JsonConfig::default().limit(4096))
             .service(ws_scope())
             .service(user_scope())
-            .data(app_ctx.ws_server.clone())
-            .data(app_ctx.db_pool.clone())
-            .data(app_ctx.auth.clone())
+            .app_data(Data::new(app_ctx.ws_server.clone()))
+            .app_data(Data::new(app_ctx.db_pool.clone()))
+            .app_data(Data::new(app_ctx.auth.clone()))
     })
     .listen(listener)?
     .run();

+ 3 - 5
backend/src/user_service/auth.rs

@@ -1,4 +1,4 @@
-use flowy_net::errors::NetworkError;
+use flowy_net::response::{ServerCode, ServerError};
 use flowy_user::{entities::SignUpResponse, protobuf::SignUpParams};
 use sqlx::PgPool;
 use std::sync::Arc;
@@ -10,15 +10,13 @@ pub struct Auth {
 impl Auth {
     pub fn new(db_pool: Arc<PgPool>) -> Self { Self { db_pool } }
 
-    pub fn sign_up(&self, params: SignUpParams) -> Result<SignUpResponse, NetworkError> {
+    pub fn sign_up(&self, params: SignUpParams) -> Result<SignUpResponse, ServerError> {
         // email exist?
 
         // generate user id
 
-        //
-
         unimplemented!()
     }
 
-    pub fn is_email_exist(&self, email: &str) -> bool {}
+    pub fn is_email_exist(&self, email: &str) -> bool { true }
 }

+ 3 - 3
backend/src/ws_service/entities/connect.rs

@@ -1,6 +1,6 @@
 use crate::ws_service::ClientMessage;
 use actix::{Message, Recipient};
-use flowy_net::errors::NetworkError;
+use flowy_net::response::ServerError;
 use serde::{Deserialize, Serialize};
 use std::fmt::Formatter;
 
@@ -37,14 +37,14 @@ impl std::fmt::Display for SessionId {
 }
 
 #[derive(Debug, Message, Clone)]
-#[rtype(result = "Result<(), NetworkError>")]
+#[rtype(result = "Result<(), ServerError>")]
 pub struct Connect {
     pub socket: Socket,
     pub sid: SessionId,
 }
 
 #[derive(Debug, Message, Clone)]
-#[rtype(result = "Result<(), NetworkError>")]
+#[rtype(result = "Result<(), ServerError>")]
 pub struct Disconnect {
     pub sid: SessionId,
 }

+ 3 - 3
backend/src/ws_service/ws_server.rs

@@ -4,7 +4,7 @@ use crate::ws_service::{
 };
 use actix::{Actor, Context, Handler};
 use dashmap::DashMap;
-use flowy_net::errors::NetworkError;
+use flowy_net::response::ServerError;
 
 pub struct WSServer {
     sessions: DashMap<SessionId, Session>,
@@ -26,7 +26,7 @@ impl Actor for WSServer {
 }
 
 impl Handler<Connect> for WSServer {
-    type Result = Result<(), NetworkError>;
+    type Result = Result<(), ServerError>;
     fn handle(&mut self, msg: Connect, _ctx: &mut Context<Self>) -> Self::Result {
         let session: Session = msg.into();
         self.sessions.insert(session.id.clone(), session);
@@ -36,7 +36,7 @@ impl Handler<Connect> for WSServer {
 }
 
 impl Handler<Disconnect> for WSServer {
-    type Result = Result<(), NetworkError>;
+    type Result = Result<(), ServerError>;
     fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) -> Self::Result {
         self.sessions.remove(&msg.sid);
         Ok(())

+ 4 - 4
rust-lib/dart-ffi/Cargo.toml

@@ -7,11 +7,11 @@ edition = "2018"
 [lib]
 name = "dart_ffi"
 # this value will change depending on the target os
-# for iOS it would be `rlib`
-# for Macos it would be `rlib`
+# for iOS it would be `cdylib`
+# for Macos it would be `cdylib`
 # for android it would be `c-dylib`
-# default rlib
-crate-type = ["rlib"]
+# default cdylib
+crate-type = ["cdylib"]
 
 
 [dependencies]

+ 1 - 0
rust-lib/flowy-derive/src/derive_cache/derive_cache.rs

@@ -64,6 +64,7 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "EditorEvent"
         | "DocErrorCode"
         | "FFIStatusCode"
+        | "UserServerError"
         | "UserStatus"
         | "UserEvent"
         | "UserErrCode"

+ 46 - 0
rust-lib/flowy-infra/src/errors/builder.rs

@@ -0,0 +1,46 @@
+use std::{fmt::Debug, marker::PhantomData};
+
+pub trait Build<C> {
+    fn build(code: C, msg: String) -> Self;
+}
+
+pub struct Builder<C, O> {
+    pub code: C,
+    pub msg: Option<String>,
+    phantom: PhantomData<O>,
+}
+
+impl<C, O> Builder<C, O>
+where
+    C: Debug,
+    O: Build<C> + Build<C>,
+{
+    pub fn new(code: C) -> Self {
+        Builder {
+            code,
+            msg: None,
+            phantom: PhantomData,
+        }
+    }
+
+    pub fn msg<M>(mut self, msg: M) -> Self
+    where
+        M: Into<String>,
+    {
+        self.msg = Some(msg.into());
+        self
+    }
+
+    pub fn error<Err>(mut self, err: Err) -> Self
+    where
+        Err: std::fmt::Debug,
+    {
+        self.msg = Some(format!("{:?}", err));
+        self
+    }
+
+    pub fn build(mut self) -> O {
+        let msg = self.msg.take().unwrap_or("".to_owned());
+        O::build(self.code, msg)
+    }
+}

+ 3 - 0
rust-lib/flowy-infra/src/errors/mod.rs

@@ -0,0 +1,3 @@
+pub mod builder;
+
+pub use builder::*;

+ 4 - 1
rust-lib/flowy-net/Cargo.toml

@@ -7,6 +7,7 @@ edition = "2018"
 
 [dependencies]
 reqwest = "0.11"
+hyper = "0.14"
 protobuf = {version = "2.18.0"}
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
@@ -17,8 +18,10 @@ log = "0.4"
 bytes = "1.0"
 lazy_static = "1.4.0"
 tokio = { version = "1", features = ["full"] }
-
 actix-web = {version = "4.0.0-beta.8", optional = true}
+derive_more = {version = "0.99", features = ["display"]}
+flowy-derive = { path = "../flowy-derive" }
+
 
 [features]
 http = ["actix-web"]

+ 0 - 40
rust-lib/flowy-net/src/errors.rs

@@ -1,40 +0,0 @@
-use crate::response::FlowyResponse;
-use protobuf::ProtobufError;
-use std::fmt::{Formatter, Write};
-
-#[derive(Debug)]
-pub enum NetworkError {
-    InternalError(String),
-    ProtobufError(ProtobufError),
-    BadRequest(FlowyResponse<String>),
-    Unauthorized,
-}
-
-impl std::fmt::Display for NetworkError {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        match self {
-            NetworkError::InternalError(_) => f.write_str("Internal Server Error"),
-            NetworkError::ProtobufError(err) => f.write_str(&format!("protobuf error: {}", err)),
-            NetworkError::BadRequest(request) => {
-                let msg = format!("Bad Request: {:?}", request);
-                f.write_str(&msg)
-            },
-            NetworkError::Unauthorized => f.write_str("Unauthorized"),
-        }
-    }
-}
-
-impl std::convert::From<ProtobufError> for NetworkError {
-    fn from(err: ProtobufError) -> Self { NetworkError::ProtobufError(err) }
-}
-
-impl std::convert::From<reqwest::Error> for NetworkError {
-    fn from(error: reqwest::Error) -> Self {
-        let msg = format!("{:?}", error);
-        NetworkError::InternalError(msg)
-    }
-}
-
-impl std::convert::From<String> for NetworkError {
-    fn from(error: String) -> Self { NetworkError::InternalError(error) }
-}

+ 45 - 0
rust-lib/flowy-net/src/errors/errors.rs

@@ -0,0 +1,45 @@
+use crate::response::FlowyResponse;
+use protobuf::ProtobufError;
+use std::fmt::{Formatter, Write};
+
+// #[derive(Debug)]
+// pub struct ServerError {
+//     code: ErrorCode
+// }
+//
+// pub enum ErrorCode {
+//     InternalError(String),
+//     ProtobufError(ProtobufError),
+//     BadRequest(FlowyResponse<String>),
+//     Unauthorized,
+// }
+//
+//
+// impl std::fmt::Display for ErrorCode {
+//     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+//         match self {
+//             ErrorCode::InternalError(_) => f.write_str("Internal Server
+// Error"),             ErrorCode::ProtobufError(err) =>
+// f.write_str(&format!("protobuf error: {}", err)),             
+// ErrorCode::BadRequest(request) => {                 let msg = format!("Bad
+// Request: {:?}", request);                 f.write_str(&msg)
+//             },
+//             ErrorCode::Unauthorized => f.write_str("Unauthorized"),
+//         }
+//     }
+// }
+
+// impl std::convert::From<ProtobufError> for ServerCode {
+//     fn from(err: ProtobufError) -> Self { ServerCode::ProtobufError(err) }
+// }
+//
+// impl std::convert::From<reqwest::Error> for ServerError {
+//     fn from(error: reqwest::Error) -> Self {
+//         let msg = format!("{:?}", error);
+//         ServerError::InternalError(msg)
+//     }
+// }
+//
+// impl std::convert::From<String> for ServerError {
+//     fn from(error: String) -> Self { ServerError::InternalError(error) }
+// }

+ 3 - 0
rust-lib/flowy-net/src/errors/mod.rs

@@ -0,0 +1,3 @@
+mod errors;
+
+pub use errors::*;

+ 17 - 14
rust-lib/flowy-net/src/request/request.rs

@@ -1,4 +1,4 @@
-use crate::{errors::NetworkError, future::ResultFuture};
+use crate::{future::ResultFuture, response::ServerError};
 use bytes::Bytes;
 use protobuf::{Message, ProtobufError};
 use reqwest::{Client, Error, Response};
@@ -6,7 +6,9 @@ use std::{
     convert::{TryFrom, TryInto},
     time::Duration,
 };
+use hyper::{StatusCode, http};
 use tokio::sync::{oneshot, oneshot::error::RecvError};
+use crate::response::ServerCode;
 
 // pub async fn http_post<T1, T2>(url: &str, data: T1) -> ResultFuture<T2,
 // NetworkError> where
@@ -17,7 +19,7 @@ use tokio::sync::{oneshot, oneshot::error::RecvError};
 //     ResultFuture::new(async move { post(url, data).await })
 // }
 
-pub async fn http_post<T1, T2>(url: &str, data: T1) -> Result<T2, NetworkError>
+pub async fn http_post<T1, T2>(url: &str, data: T1) -> Result<T2, ServerError>
 where
     T1: TryInto<Bytes, Error = ProtobufError>,
     T2: TryFrom<Bytes, Error = ProtobufError>,
@@ -32,20 +34,21 @@ where
         tx.send(response);
     });
 
-    match rx.await {
-        Ok(response) => {
-            let response = response?;
-            let response_bytes = response.bytes().await?;
-            let data = T2::try_from(response_bytes)?;
-            Ok(data)
-        },
-        Err(e) => {
-            unimplemented!()
-        },
+    let response = rx.await??;
+    if response.status() == http::StatusCode::OK {
+        let response_bytes = response.bytes().await?;
+        let data = T2::try_from(response_bytes)?;
+        Ok(data)
+
+    } else {
+        Err(ServerError {
+            code: ServerCode::InternalError,
+            msg: format!("{:?}", response),
+        })
     }
 }
 
-async fn parse_response<T>(response: Response) -> Result<T, NetworkError>
+async fn parse_response<T>(response: Response) -> Result<T, ServerError>
 where
     T: Message,
 {
@@ -53,7 +56,7 @@ where
     parse_bytes(bytes)
 }
 
-fn parse_bytes<T>(bytes: Bytes) -> Result<T, NetworkError>
+fn parse_bytes<T>(bytes: Bytes) -> Result<T, ServerError>
 where
     T: Message,
 {

+ 98 - 3
rust-lib/flowy-net/src/response/response.rs

@@ -1,8 +1,32 @@
-use serde::Serialize;
-
+use serde::{Serialize, __private::Formatter};
 use serde_repr::*;
+use std::{error::Error, fmt};
+use tokio::sync::oneshot::error::RecvError;
+
+#[derive(Debug)]
+pub struct ServerError {
+    pub code: ServerCode,
+    pub msg: String,
+}
 
-#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)]
+impl std::fmt::Display for ServerError {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        let msg = format!("{:?}:{}", self.code, self.msg);
+        f.write_str(&msg)
+    }
+}
+
+impl std::convert::From<&ServerError> for FlowyResponse<String> {
+    fn from(error: &ServerError) -> Self {
+        FlowyResponse {
+            msg: error.msg.clone(),
+            data: None,
+            code: error.code.clone(),
+        }
+    }
+}
+
+#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Clone)]
 #[repr(u16)]
 pub enum ServerCode {
     Success          = 0,
@@ -12,6 +36,11 @@ pub enum ServerCode {
     PayloadOverflow  = 4,
     PayloadSerdeFail = 5,
     ProtobufError    = 6,
+    SerdeError       = 7,
+    ConnectRefused   = 8,
+    ConnectTimeout   = 9,
+    ConnectClose     = 10,
+    ConnectCancel    = 11,
 }
 
 #[derive(Debug, Serialize)]
@@ -42,3 +71,69 @@ impl FlowyResponse<String> {
         Self::new(Some("".to_owned()), msg, code)
     }
 }
+
+impl std::convert::From<protobuf::ProtobufError> for ServerError {
+    fn from(err: protobuf::ProtobufError) -> Self {
+        ServerError {
+            code: ServerCode::ProtobufError,
+            msg: format!("{}", err),
+        }
+    }
+}
+
+impl std::convert::From<RecvError> for ServerError {
+    fn from(error: RecvError) -> Self {
+        ServerError {
+            code: ServerCode::InternalError,
+            msg: format!("{:?}", error),
+        }
+    }
+}
+
+impl std::convert::From<reqwest::Error> for ServerError {
+    fn from(error: reqwest::Error) -> Self {
+        if error.is_timeout() {
+            return ServerError {
+                code: ServerCode::ConnectTimeout,
+                msg: format!("{}", error),
+            };
+        }
+
+        if error.is_request() {
+            let hyper_error: Option<&hyper::Error> = error.source().unwrap().downcast_ref();
+            return match hyper_error {
+                None => ServerError {
+                    code: ServerCode::ConnectRefused,
+                    msg: format!("{:?}", error),
+                },
+                Some(hyper_error) => {
+                    let mut code = ServerCode::InternalError;
+                    let msg = format!("{}", error);
+                    if hyper_error.is_closed() {
+                        code = ServerCode::ConnectClose;
+                    }
+
+                    if hyper_error.is_connect() {
+                        code = ServerCode::ConnectRefused;
+                    }
+
+                    if hyper_error.is_canceled() {
+                        code = ServerCode::ConnectCancel;
+                    }
+
+                    if hyper_error.is_timeout() {
+
+                    }
+
+                    ServerError { code, msg }
+                },
+            };
+        }
+
+        let msg = format!("{:?}", error);
+        ServerError {
+            code: ServerCode::ProtobufError,
+            msg,
+        }
+    }
+}

+ 10 - 19
rust-lib/flowy-net/src/response/response_http.rs

@@ -1,28 +1,15 @@
-use crate::{errors::NetworkError, response::*};
+use crate::response::*;
 use actix_web::{body::Body, error::ResponseError, BaseHttpResponse, HttpResponse};
 use serde::Serialize;
 
-impl NetworkError {
+impl ServerError {
     fn http_response(&self) -> HttpResponse {
-        match self {
-            NetworkError::InternalError(msg) => {
-                let resp = FlowyResponse::from_msg(&msg, ServerCode::InternalError);
-                HttpResponse::InternalServerError().json(resp)
-            },
-            NetworkError::ProtobufError(err) => {
-                let resp = FlowyResponse::from_msg(&format!("{}", err), ServerCode::ProtobufError);
-                HttpResponse::InternalServerError().json(resp)
-            },
-            NetworkError::BadRequest(ref resp) => HttpResponse::BadRequest().json(resp),
-            NetworkError::Unauthorized => {
-                let resp = FlowyResponse::from_msg("Unauthorized", ServerCode::Unauthorized);
-                HttpResponse::Unauthorized().json(resp)
-            },
-        }
+        let resp: FlowyResponse<String> = self.into();
+        HttpResponse::Ok().json(resp)
     }
 }
 
-impl ResponseError for NetworkError {
+impl ResponseError for ServerError {
     fn error_response(&self) -> HttpResponse { self.http_response().into() }
 }
 
@@ -32,7 +19,11 @@ impl<T: Serialize> std::convert::Into<HttpResponse> for FlowyResponse<T> {
             Ok(body) => HttpResponse::Ok().body(Body::from(body)),
             Err(e) => {
                 let msg = format!("Serial error: {:?}", e);
-                NetworkError::InternalError(msg).error_response()
+                ServerError {
+                    code: ServerCode::SerdeError,
+                    msg,
+                }
+                .error_response()
             },
         }
     }

+ 4 - 3
rust-lib/flowy-user/src/entities/parser/user_email.rs

@@ -1,18 +1,19 @@
+use crate::errors::UserErrCode;
 use validator::validate_email;
 
 #[derive(Debug)]
 pub struct UserEmail(pub String);
 
 impl UserEmail {
-    pub fn parse(s: String) -> Result<UserEmail, String> {
+    pub fn parse(s: String) -> Result<UserEmail, UserErrCode> {
         if s.trim().is_empty() {
-            return Err(format!("Email can not be empty or whitespace"));
+            return Err(UserErrCode::EmailIsEmpty);
         }
 
         if validate_email(&s) {
             Ok(Self(s))
         } else {
-            Err(format!("{} is not a valid email.", s))
+            Err(UserErrCode::EmailFormatInvalid)
         }
     }
 }

+ 12 - 5
rust-lib/flowy-user/src/entities/parser/user_name.rs

@@ -1,11 +1,15 @@
+use crate::errors::UserErrCode;
 use unicode_segmentation::UnicodeSegmentation;
 
 #[derive(Debug)]
 pub struct UserName(pub String);
 
 impl UserName {
-    pub fn parse(s: String) -> Result<UserName, String> {
+    pub fn parse(s: String) -> Result<UserName, UserErrCode> {
         let is_empty_or_whitespace = s.trim().is_empty();
+        if is_empty_or_whitespace {
+            return Err(UserErrCode::UserNameIsEmpty);
+        }
         // 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 `̊`).
@@ -14,15 +18,18 @@ impl UserName {
         // `true` specifies that we want to use the extended grapheme definition set,
         // the recommended one.
         let is_too_long = s.graphemes(true).count() > 256;
+        if is_too_long {
+            return Err(UserErrCode::UserNameTooLong);
+        }
 
         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 name.", s))
-        } else {
-            Ok(Self(s))
+        if contains_forbidden_characters {
+            return Err(UserErrCode::UserNameContainsForbiddenCharacters);
         }
+
+        Ok(Self(s))
     }
 }
 

+ 7 - 5
rust-lib/flowy-user/src/entities/parser/user_password.rs

@@ -1,27 +1,29 @@
+use crate::errors::UserErrCode;
 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<UserPassword, String> {
+    pub fn parse(s: String) -> Result<UserPassword, UserErrCode> {
         if s.trim().is_empty() {
-            return Err(format!("Password can not be empty or whitespace."));
+            return Err(UserErrCode::PasswordIsEmpty);
         }
 
         if s.graphemes(true).count() > 100 {
-            return Err(format!("Password too long."));
+            return Err(UserErrCode::PasswordTooLong);
         }
 
         let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}'];
         let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g));
         if contains_forbidden_characters {
-            return Err(format!("Password contains forbidden characters."));
+            return Err(UserErrCode::PasswordContainsForbidCharacters);
         }
 
         if !validate_password(&s) {
-            return Err(format!("Password should contain a minimum of 6 characters with 1 special 1 letter and 1 numeric"));
+            return Err(UserErrCode::PasswordFormatInvalid);
         }
 
         Ok(Self(s))

+ 3 - 7
rust-lib/flowy-user/src/entities/sign_in.rs

@@ -36,13 +36,9 @@ impl TryInto<SignInParams> for SignInRequest {
     type Error = UserError;
 
     fn try_into(self) -> Result<SignInParams, Self::Error> {
-        let email = UserEmail::parse(self.email)
-            .map_err(|e| ErrorBuilder::new(UserErrCode::EmailInvalid).msg(e).build())?;
-        let password = UserPassword::parse(self.password).map_err(|e| {
-            ErrorBuilder::new(UserErrCode::PasswordInvalid)
-                .msg(e)
-                .build()
-        })?;
+        let email = UserEmail::parse(self.email).map_err(|e| ErrorBuilder::new(e).build())?;
+        let password =
+            UserPassword::parse(self.password).map_err(|e| ErrorBuilder::new(e).build())?;
 
         Ok(SignInParams {
             email: email.0,

+ 4 - 13
rust-lib/flowy-user/src/entities/sign_up.rs

@@ -20,19 +20,10 @@ impl TryInto<SignUpParams> for SignUpRequest {
     type Error = UserError;
 
     fn try_into(self) -> Result<SignUpParams, Self::Error> {
-        let email = UserEmail::parse(self.email)
-            .map_err(|e| ErrorBuilder::new(UserErrCode::EmailInvalid).msg(e).build())?;
-        let password = UserPassword::parse(self.password).map_err(|e| {
-            ErrorBuilder::new(UserErrCode::PasswordInvalid)
-                .msg(e)
-                .build()
-        })?;
-
-        let name = UserName::parse(self.name).map_err(|e| {
-            ErrorBuilder::new(UserErrCode::UserNameInvalid)
-                .msg(e)
-                .build()
-        })?;
+        let email = UserEmail::parse(self.email).map_err(|e| ErrorBuilder::new(e).build())?;
+        let password =
+            UserPassword::parse(self.password).map_err(|e| ErrorBuilder::new(e).build())?;
+        let name = UserName::parse(self.name).map_err(|e| ErrorBuilder::new(e).build())?;
 
         Ok(SignUpParams {
             email: email.0,

+ 3 - 11
rust-lib/flowy-user/src/entities/user_update.rs

@@ -72,11 +72,7 @@ impl TryInto<UpdateUserParams> for UpdateUserRequest {
             None => None,
             Some(name) => Some(
                 UserName::parse(name)
-                    .map_err(|e| {
-                        ErrorBuilder::new(UserErrCode::UserNameInvalid)
-                            .msg(e)
-                            .build()
-                    })?
+                    .map_err(|e| ErrorBuilder::new(e).build())?
                     .0,
             ),
         };
@@ -85,7 +81,7 @@ impl TryInto<UpdateUserParams> for UpdateUserRequest {
             None => None,
             Some(email) => Some(
                 UserEmail::parse(email)
-                    .map_err(|e| ErrorBuilder::new(UserErrCode::EmailInvalid).msg(e).build())?
+                    .map_err(|e| ErrorBuilder::new(e).build())?
                     .0,
             ),
         };
@@ -107,11 +103,7 @@ impl TryInto<UpdateUserParams> for UpdateUserRequest {
             None => None,
             Some(password) => Some(
                 UserPassword::parse(password)
-                    .map_err(|e| {
-                        ErrorBuilder::new(UserErrCode::PasswordInvalid)
-                            .msg(e)
-                            .build()
-                    })?
+                    .map_err(|e| ErrorBuilder::new(e).build())?
                     .0,
             ),
         };

+ 36 - 13
rust-lib/flowy-user/src/errors.rs

@@ -47,21 +47,37 @@ pub enum UserErrCode {
     #[display(fmt = "Get current id write lock failed")]
     WriteCurrentIdFailed = 12,
 
-    #[display(fmt = "Email format is not correct")]
-    EmailInvalid         = 20,
-    #[display(fmt = "Password format is not correct")]
-    PasswordInvalid      = 21,
-    #[display(fmt = "User name is invalid")]
-    UserNameInvalid      = 22,
+    #[display(fmt = "Email can not be empty or whitespace")]
+    EmailIsEmpty         = 20,
+    #[display(fmt = "Email format is not valid")]
+    EmailFormatInvalid   = 21,
+    #[display(fmt = "Email already exists")]
+    EmailAlreadyExists   = 22,
+    #[display(fmt = "Password can not be empty or whitespace")]
+    PasswordIsEmpty      = 30,
+    #[display(fmt = "Password format too long")]
+    PasswordTooLong      = 31,
+    #[display(fmt = "Password contains forbidden characters.")]
+    PasswordContainsForbidCharacters = 32,
+    #[display(
+        fmt = "Password should contain a minimum of 6 characters with 1 special 1 letter and 1 numeric"
+    )]
+    PasswordFormatInvalid = 33,
+    #[display(fmt = "User name is too long")]
+    UserNameTooLong      = 40,
+    #[display(fmt = "User name contain forbidden characters")]
+    UserNameContainsForbiddenCharacters = 41,
+    #[display(fmt = "User name can not be empty or whitespace")]
+    UserNameIsEmpty      = 42,
     #[display(fmt = "User workspace is invalid")]
-    UserWorkspaceInvalid = 23,
+    UserWorkspaceInvalid = 50,
     #[display(fmt = "User id is invalid")]
-    UserIdInvalid        = 24,
+    UserIdInvalid        = 51,
     #[display(fmt = "Create user default workspace failed")]
-    CreateDefaultWorkspaceFailed = 25,
+    CreateDefaultWorkspaceFailed = 52,
 
     #[display(fmt = "User default workspace already exists")]
-    DefaultWorkspaceAlreadyExist = 26,
+    DefaultWorkspaceAlreadyExist = 53,
 
     #[display(fmt = "Network error")]
     NetworkError         = 100,
@@ -116,8 +132,8 @@ impl std::convert::From<flowy_sqlite::Error> for UserError {
     }
 }
 
-impl std::convert::From<flowy_net::errors::NetworkError> for UserError {
-    fn from(error: flowy_net::errors::NetworkError) -> Self {
+impl std::convert::From<flowy_net::response::ServerError> for UserError {
+    fn from(error: flowy_net::response::ServerError) -> Self {
         ErrorBuilder::new(UserErrCode::NetworkError)
             .error(error)
             .build()
@@ -134,5 +150,12 @@ impl flowy_dispatch::Error for UserError {
 pub type ErrorBuilder = flowy_infra::errors::Builder<UserErrCode, UserError>;
 
 impl flowy_infra::errors::Build<UserErrCode> for UserError {
-    fn build(code: UserErrCode, msg: String) -> Self { UserError::new(code, &msg) }
+    fn build(code: UserErrCode, msg: String) -> Self {
+        let msg = if msg.is_empty() {
+            format!("{}", code)
+        } else {
+            msg
+        };
+        UserError::new(code, &msg)
+    }
 }

+ 107 - 68
rust-lib/flowy-user/src/protobuf/model/errors.rs

@@ -225,13 +225,20 @@ pub enum UserErrCode {
     UserNotLoginYet = 10,
     ReadCurrentIdFailed = 11,
     WriteCurrentIdFailed = 12,
-    EmailInvalid = 20,
-    PasswordInvalid = 21,
-    UserNameInvalid = 22,
-    UserWorkspaceInvalid = 23,
-    UserIdInvalid = 24,
-    CreateDefaultWorkspaceFailed = 25,
-    DefaultWorkspaceAlreadyExist = 26,
+    EmailIsEmpty = 20,
+    EmailFormatInvalid = 21,
+    EmailAlreadyExists = 22,
+    PasswordIsEmpty = 30,
+    PasswordTooLong = 31,
+    PasswordContainsForbidCharacters = 32,
+    PasswordFormatInvalid = 33,
+    UserNameTooLong = 40,
+    UserNameContainsForbiddenCharacters = 41,
+    UserNameIsEmpty = 42,
+    UserWorkspaceInvalid = 50,
+    UserIdInvalid = 51,
+    CreateDefaultWorkspaceFailed = 52,
+    DefaultWorkspaceAlreadyExist = 53,
     NetworkError = 100,
 }
 
@@ -252,13 +259,20 @@ impl ::protobuf::ProtobufEnum for UserErrCode {
             10 => ::std::option::Option::Some(UserErrCode::UserNotLoginYet),
             11 => ::std::option::Option::Some(UserErrCode::ReadCurrentIdFailed),
             12 => ::std::option::Option::Some(UserErrCode::WriteCurrentIdFailed),
-            20 => ::std::option::Option::Some(UserErrCode::EmailInvalid),
-            21 => ::std::option::Option::Some(UserErrCode::PasswordInvalid),
-            22 => ::std::option::Option::Some(UserErrCode::UserNameInvalid),
-            23 => ::std::option::Option::Some(UserErrCode::UserWorkspaceInvalid),
-            24 => ::std::option::Option::Some(UserErrCode::UserIdInvalid),
-            25 => ::std::option::Option::Some(UserErrCode::CreateDefaultWorkspaceFailed),
-            26 => ::std::option::Option::Some(UserErrCode::DefaultWorkspaceAlreadyExist),
+            20 => ::std::option::Option::Some(UserErrCode::EmailIsEmpty),
+            21 => ::std::option::Option::Some(UserErrCode::EmailFormatInvalid),
+            22 => ::std::option::Option::Some(UserErrCode::EmailAlreadyExists),
+            30 => ::std::option::Option::Some(UserErrCode::PasswordIsEmpty),
+            31 => ::std::option::Option::Some(UserErrCode::PasswordTooLong),
+            32 => ::std::option::Option::Some(UserErrCode::PasswordContainsForbidCharacters),
+            33 => ::std::option::Option::Some(UserErrCode::PasswordFormatInvalid),
+            40 => ::std::option::Option::Some(UserErrCode::UserNameTooLong),
+            41 => ::std::option::Option::Some(UserErrCode::UserNameContainsForbiddenCharacters),
+            42 => ::std::option::Option::Some(UserErrCode::UserNameIsEmpty),
+            50 => ::std::option::Option::Some(UserErrCode::UserWorkspaceInvalid),
+            51 => ::std::option::Option::Some(UserErrCode::UserIdInvalid),
+            52 => ::std::option::Option::Some(UserErrCode::CreateDefaultWorkspaceFailed),
+            53 => ::std::option::Option::Some(UserErrCode::DefaultWorkspaceAlreadyExist),
             100 => ::std::option::Option::Some(UserErrCode::NetworkError),
             _ => ::std::option::Option::None
         }
@@ -276,9 +290,16 @@ impl ::protobuf::ProtobufEnum for UserErrCode {
             UserErrCode::UserNotLoginYet,
             UserErrCode::ReadCurrentIdFailed,
             UserErrCode::WriteCurrentIdFailed,
-            UserErrCode::EmailInvalid,
-            UserErrCode::PasswordInvalid,
-            UserErrCode::UserNameInvalid,
+            UserErrCode::EmailIsEmpty,
+            UserErrCode::EmailFormatInvalid,
+            UserErrCode::EmailAlreadyExists,
+            UserErrCode::PasswordIsEmpty,
+            UserErrCode::PasswordTooLong,
+            UserErrCode::PasswordContainsForbidCharacters,
+            UserErrCode::PasswordFormatInvalid,
+            UserErrCode::UserNameTooLong,
+            UserErrCode::UserNameContainsForbiddenCharacters,
+            UserErrCode::UserNameIsEmpty,
             UserErrCode::UserWorkspaceInvalid,
             UserErrCode::UserIdInvalid,
             UserErrCode::CreateDefaultWorkspaceFailed,
@@ -314,62 +335,80 @@ impl ::protobuf::reflect::ProtobufValue for UserErrCode {
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x0cerrors.proto\"?\n\tUserError\x12\x20\n\x04code\x18\x01\x20\x01(\
     \x0e2\x0c.UserErrCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\tR\x03m\
-    sg*\xc8\x03\n\x0bUserErrCode\x12\x0b\n\x07Unknown\x10\0\x12\x1a\n\x16Use\
+    sg*\x8c\x05\n\x0bUserErrCode\x12\x0b\n\x07Unknown\x10\0\x12\x1a\n\x16Use\
     rDatabaseInitFailed\x10\x01\x12\x1b\n\x17UserDatabaseWriteLocked\x10\x02\
     \x12\x1a\n\x16UserDatabaseReadLocked\x10\x03\x12\x1b\n\x17UserDatabaseDi\
     dNotMatch\x10\x04\x12\x1d\n\x19UserDatabaseInternalError\x10\x05\x12\x14\
     \n\x10SqlInternalError\x10\x06\x12\x13\n\x0fUserNotLoginYet\x10\n\x12\
     \x17\n\x13ReadCurrentIdFailed\x10\x0b\x12\x18\n\x14WriteCurrentIdFailed\
-    \x10\x0c\x12\x10\n\x0cEmailInvalid\x10\x14\x12\x13\n\x0fPasswordInvalid\
-    \x10\x15\x12\x13\n\x0fUserNameInvalid\x10\x16\x12\x18\n\x14UserWorkspace\
-    Invalid\x10\x17\x12\x11\n\rUserIdInvalid\x10\x18\x12\x20\n\x1cCreateDefa\
-    ultWorkspaceFailed\x10\x19\x12\x20\n\x1cDefaultWorkspaceAlreadyExist\x10\
-    \x1a\x12\x10\n\x0cNetworkError\x10dJ\x92\x07\n\x06\x12\x04\0\0\x19\x01\n\
-    \x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\
-    \n\x03\x04\0\x01\x12\x03\x02\x08\x11\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\
-    \x04\x19\n\x0c\n\x05\x04\0\x02\0\x06\x12\x03\x03\x04\x0f\n\x0c\n\x05\x04\
-    \0\x02\0\x01\x12\x03\x03\x10\x14\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\
-    \x17\x18\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x13\n\x0c\n\x05\x04\0\
-    \x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\
-    \x0b\x0e\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x05\
-    \0\x12\x04\x06\0\x19\x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x10\n\x0b\n\
-    \x04\x05\0\x02\0\x12\x03\x07\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\
-    \x07\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\
-    \x05\0\x02\x01\x12\x03\x08\x04\x1f\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\
-    \x08\x04\x1a\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x08\x1d\x1e\n\x0b\n\
-    \x04\x05\0\x02\x02\x12\x03\t\x04\x20\n\x0c\n\x05\x05\0\x02\x02\x01\x12\
-    \x03\t\x04\x1b\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x1e\x1f\n\x0b\n\
-    \x04\x05\0\x02\x03\x12\x03\n\x04\x1f\n\x0c\n\x05\x05\0\x02\x03\x01\x12\
-    \x03\n\x04\x1a\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\n\x1d\x1e\n\x0b\n\
-    \x04\x05\0\x02\x04\x12\x03\x0b\x04\x20\n\x0c\n\x05\x05\0\x02\x04\x01\x12\
-    \x03\x0b\x04\x1b\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x1e\x1f\n\x0b\
-    \n\x04\x05\0\x02\x05\x12\x03\x0c\x04\"\n\x0c\n\x05\x05\0\x02\x05\x01\x12\
-    \x03\x0c\x04\x1d\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x20!\n\x0b\n\
-    \x04\x05\0\x02\x06\x12\x03\r\x04\x19\n\x0c\n\x05\x05\0\x02\x06\x01\x12\
-    \x03\r\x04\x14\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\r\x17\x18\n\x0b\n\
-    \x04\x05\0\x02\x07\x12\x03\x0e\x04\x19\n\x0c\n\x05\x05\0\x02\x07\x01\x12\
-    \x03\x0e\x04\x13\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x16\x18\n\x0b\
-    \n\x04\x05\0\x02\x08\x12\x03\x0f\x04\x1d\n\x0c\n\x05\x05\0\x02\x08\x01\
-    \x12\x03\x0f\x04\x17\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0f\x1a\x1c\n\
-    \x0b\n\x04\x05\0\x02\t\x12\x03\x10\x04\x1e\n\x0c\n\x05\x05\0\x02\t\x01\
-    \x12\x03\x10\x04\x18\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\x10\x1b\x1d\n\
-    \x0b\n\x04\x05\0\x02\n\x12\x03\x11\x04\x16\n\x0c\n\x05\x05\0\x02\n\x01\
-    \x12\x03\x11\x04\x10\n\x0c\n\x05\x05\0\x02\n\x02\x12\x03\x11\x13\x15\n\
-    \x0b\n\x04\x05\0\x02\x0b\x12\x03\x12\x04\x19\n\x0c\n\x05\x05\0\x02\x0b\
-    \x01\x12\x03\x12\x04\x13\n\x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\x12\x16\
-    \x18\n\x0b\n\x04\x05\0\x02\x0c\x12\x03\x13\x04\x19\n\x0c\n\x05\x05\0\x02\
-    \x0c\x01\x12\x03\x13\x04\x13\n\x0c\n\x05\x05\0\x02\x0c\x02\x12\x03\x13\
-    \x16\x18\n\x0b\n\x04\x05\0\x02\r\x12\x03\x14\x04\x1e\n\x0c\n\x05\x05\0\
-    \x02\r\x01\x12\x03\x14\x04\x18\n\x0c\n\x05\x05\0\x02\r\x02\x12\x03\x14\
-    \x1b\x1d\n\x0b\n\x04\x05\0\x02\x0e\x12\x03\x15\x04\x17\n\x0c\n\x05\x05\0\
-    \x02\x0e\x01\x12\x03\x15\x04\x11\n\x0c\n\x05\x05\0\x02\x0e\x02\x12\x03\
-    \x15\x14\x16\n\x0b\n\x04\x05\0\x02\x0f\x12\x03\x16\x04&\n\x0c\n\x05\x05\
-    \0\x02\x0f\x01\x12\x03\x16\x04\x20\n\x0c\n\x05\x05\0\x02\x0f\x02\x12\x03\
-    \x16#%\n\x0b\n\x04\x05\0\x02\x10\x12\x03\x17\x04&\n\x0c\n\x05\x05\0\x02\
-    \x10\x01\x12\x03\x17\x04\x20\n\x0c\n\x05\x05\0\x02\x10\x02\x12\x03\x17#%\
-    \n\x0b\n\x04\x05\0\x02\x11\x12\x03\x18\x04\x17\n\x0c\n\x05\x05\0\x02\x11\
-    \x01\x12\x03\x18\x04\x10\n\x0c\n\x05\x05\0\x02\x11\x02\x12\x03\x18\x13\
-    \x16b\x06proto3\
+    \x10\x0c\x12\x10\n\x0cEmailIsEmpty\x10\x14\x12\x16\n\x12EmailFormatInval\
+    id\x10\x15\x12\x16\n\x12EmailAlreadyExists\x10\x16\x12\x13\n\x0fPassword\
+    IsEmpty\x10\x1e\x12\x13\n\x0fPasswordTooLong\x10\x1f\x12$\n\x20PasswordC\
+    ontainsForbidCharacters\x10\x20\x12\x19\n\x15PasswordFormatInvalid\x10!\
+    \x12\x13\n\x0fUserNameTooLong\x10(\x12'\n#UserNameContainsForbiddenChara\
+    cters\x10)\x12\x13\n\x0fUserNameIsEmpty\x10*\x12\x18\n\x14UserWorkspaceI\
+    nvalid\x102\x12\x11\n\rUserIdInvalid\x103\x12\x20\n\x1cCreateDefaultWork\
+    spaceFailed\x104\x12\x20\n\x1cDefaultWorkspaceAlreadyExist\x105\x12\x10\
+    \n\x0cNetworkError\x10dJ\xb1\t\n\x06\x12\x04\0\0\x20\x01\n\x08\n\x01\x0c\
+    \x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\
+    \x01\x12\x03\x02\x08\x11\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x19\n\
+    \x0c\n\x05\x04\0\x02\0\x06\x12\x03\x03\x04\x0f\n\x0c\n\x05\x04\0\x02\0\
+    \x01\x12\x03\x03\x10\x14\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x17\x18\
+    \n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x13\n\x0c\n\x05\x04\0\x02\x01\
+    \x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x0e\
+    \n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x05\0\x12\
+    \x04\x06\0\x20\x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x10\n\x0b\n\x04\
+    \x05\0\x02\0\x12\x03\x07\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x07\
+    \x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\x05\
+    \0\x02\x01\x12\x03\x08\x04\x1f\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\
+    \x04\x1a\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x08\x1d\x1e\n\x0b\n\x04\
+    \x05\0\x02\x02\x12\x03\t\x04\x20\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\
+    \x04\x1b\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x1e\x1f\n\x0b\n\x04\x05\
+    \0\x02\x03\x12\x03\n\x04\x1f\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\
+    \x1a\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\n\x1d\x1e\n\x0b\n\x04\x05\0\
+    \x02\x04\x12\x03\x0b\x04\x20\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\
+    \x04\x1b\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x1e\x1f\n\x0b\n\x04\
+    \x05\0\x02\x05\x12\x03\x0c\x04\"\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\
+    \x0c\x04\x1d\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x20!\n\x0b\n\x04\
+    \x05\0\x02\x06\x12\x03\r\x04\x19\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\r\
+    \x04\x14\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\r\x17\x18\n\x0b\n\x04\x05\
+    \0\x02\x07\x12\x03\x0e\x04\x19\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\
+    \x04\x13\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x16\x18\n\x0b\n\x04\
+    \x05\0\x02\x08\x12\x03\x0f\x04\x1d\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\
+    \x0f\x04\x17\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0f\x1a\x1c\n\x0b\n\
+    \x04\x05\0\x02\t\x12\x03\x10\x04\x1e\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\
+    \x10\x04\x18\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\x10\x1b\x1d\n\x0b\n\x04\
+    \x05\0\x02\n\x12\x03\x11\x04\x16\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\x11\
+    \x04\x10\n\x0c\n\x05\x05\0\x02\n\x02\x12\x03\x11\x13\x15\n\x0b\n\x04\x05\
+    \0\x02\x0b\x12\x03\x12\x04\x1c\n\x0c\n\x05\x05\0\x02\x0b\x01\x12\x03\x12\
+    \x04\x16\n\x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\x12\x19\x1b\n\x0b\n\x04\
+    \x05\0\x02\x0c\x12\x03\x13\x04\x1c\n\x0c\n\x05\x05\0\x02\x0c\x01\x12\x03\
+    \x13\x04\x16\n\x0c\n\x05\x05\0\x02\x0c\x02\x12\x03\x13\x19\x1b\n\x0b\n\
+    \x04\x05\0\x02\r\x12\x03\x14\x04\x19\n\x0c\n\x05\x05\0\x02\r\x01\x12\x03\
+    \x14\x04\x13\n\x0c\n\x05\x05\0\x02\r\x02\x12\x03\x14\x16\x18\n\x0b\n\x04\
+    \x05\0\x02\x0e\x12\x03\x15\x04\x19\n\x0c\n\x05\x05\0\x02\x0e\x01\x12\x03\
+    \x15\x04\x13\n\x0c\n\x05\x05\0\x02\x0e\x02\x12\x03\x15\x16\x18\n\x0b\n\
+    \x04\x05\0\x02\x0f\x12\x03\x16\x04*\n\x0c\n\x05\x05\0\x02\x0f\x01\x12\
+    \x03\x16\x04$\n\x0c\n\x05\x05\0\x02\x0f\x02\x12\x03\x16')\n\x0b\n\x04\
+    \x05\0\x02\x10\x12\x03\x17\x04\x1f\n\x0c\n\x05\x05\0\x02\x10\x01\x12\x03\
+    \x17\x04\x19\n\x0c\n\x05\x05\0\x02\x10\x02\x12\x03\x17\x1c\x1e\n\x0b\n\
+    \x04\x05\0\x02\x11\x12\x03\x18\x04\x19\n\x0c\n\x05\x05\0\x02\x11\x01\x12\
+    \x03\x18\x04\x13\n\x0c\n\x05\x05\0\x02\x11\x02\x12\x03\x18\x16\x18\n\x0b\
+    \n\x04\x05\0\x02\x12\x12\x03\x19\x04-\n\x0c\n\x05\x05\0\x02\x12\x01\x12\
+    \x03\x19\x04'\n\x0c\n\x05\x05\0\x02\x12\x02\x12\x03\x19*,\n\x0b\n\x04\
+    \x05\0\x02\x13\x12\x03\x1a\x04\x19\n\x0c\n\x05\x05\0\x02\x13\x01\x12\x03\
+    \x1a\x04\x13\n\x0c\n\x05\x05\0\x02\x13\x02\x12\x03\x1a\x16\x18\n\x0b\n\
+    \x04\x05\0\x02\x14\x12\x03\x1b\x04\x1e\n\x0c\n\x05\x05\0\x02\x14\x01\x12\
+    \x03\x1b\x04\x18\n\x0c\n\x05\x05\0\x02\x14\x02\x12\x03\x1b\x1b\x1d\n\x0b\
+    \n\x04\x05\0\x02\x15\x12\x03\x1c\x04\x17\n\x0c\n\x05\x05\0\x02\x15\x01\
+    \x12\x03\x1c\x04\x11\n\x0c\n\x05\x05\0\x02\x15\x02\x12\x03\x1c\x14\x16\n\
+    \x0b\n\x04\x05\0\x02\x16\x12\x03\x1d\x04&\n\x0c\n\x05\x05\0\x02\x16\x01\
+    \x12\x03\x1d\x04\x20\n\x0c\n\x05\x05\0\x02\x16\x02\x12\x03\x1d#%\n\x0b\n\
+    \x04\x05\0\x02\x17\x12\x03\x1e\x04&\n\x0c\n\x05\x05\0\x02\x17\x01\x12\
+    \x03\x1e\x04\x20\n\x0c\n\x05\x05\0\x02\x17\x02\x12\x03\x1e#%\n\x0b\n\x04\
+    \x05\0\x02\x18\x12\x03\x1f\x04\x17\n\x0c\n\x05\x05\0\x02\x18\x01\x12\x03\
+    \x1f\x04\x10\n\x0c\n\x05\x05\0\x02\x18\x02\x12\x03\x1f\x13\x16b\x06proto\
+    3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 14 - 7
rust-lib/flowy-user/src/protobuf/proto/errors.proto

@@ -15,12 +15,19 @@ enum UserErrCode {
     UserNotLoginYet = 10;
     ReadCurrentIdFailed = 11;
     WriteCurrentIdFailed = 12;
-    EmailInvalid = 20;
-    PasswordInvalid = 21;
-    UserNameInvalid = 22;
-    UserWorkspaceInvalid = 23;
-    UserIdInvalid = 24;
-    CreateDefaultWorkspaceFailed = 25;
-    DefaultWorkspaceAlreadyExist = 26;
+    EmailIsEmpty = 20;
+    EmailFormatInvalid = 21;
+    EmailAlreadyExists = 22;
+    PasswordIsEmpty = 30;
+    PasswordTooLong = 31;
+    PasswordContainsForbidCharacters = 32;
+    PasswordFormatInvalid = 33;
+    UserNameTooLong = 40;
+    UserNameContainsForbiddenCharacters = 41;
+    UserNameIsEmpty = 42;
+    UserWorkspaceInvalid = 50;
+    UserIdInvalid = 51;
+    CreateDefaultWorkspaceFailed = 52;
+    DefaultWorkspaceAlreadyExist = 53;
     NetworkError = 100;
 }

+ 1 - 0
rust-lib/flowy-user/tests/server/main.rs

@@ -0,0 +1 @@
+mod user_test;

+ 13 - 0
rust-lib/flowy-user/tests/server/user_test.rs

@@ -0,0 +1,13 @@
+use flowy_user::prelude::*;
+
+#[tokio::test]
+async fn user_register_test() {
+    let server = UserServerImpl {};
+
+    let params = SignUpParams {
+        email: "[email protected]".to_string(),
+        name: "annie".to_string(),
+        password: "123".to_string(),
+    };
+    let result = server.sign_up(params).await.unwrap();
+}