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/.pub" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/flowy_protobuf/build" />
|
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/flowy_protobuf/build" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/rust-lib/target" />
|
<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>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
@ -17,13 +17,19 @@ tokio = { version = "1", features = ["full"] }
|
|||||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_with = "1.9.4"
|
serde_with = "1.9.4"
|
||||||
thread-id = "3.3.0"
|
thread-id = "3.3.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
dyn-clone = "1.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]
|
[dev-dependencies]
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
futures-util = "0.3.15"
|
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 std::{fmt, option::NoneError};
|
||||||
use tokio::sync::mpsc::error::SendError;
|
use tokio::sync::mpsc::error::SendError;
|
||||||
|
|
||||||
|
#[cfg(feature = "use_serde")]
|
||||||
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
|
|
||||||
pub trait Error: fmt::Debug + fmt::Display + DynClone {
|
pub trait Error: fmt::Debug + fmt::Display + DynClone {
|
||||||
fn status_code(&self) -> StatusCode;
|
fn status_code(&self) -> StatusCode;
|
||||||
|
|
||||||
@ -94,3 +97,13 @@ where
|
|||||||
|
|
||||||
fn as_response(&self) -> EventResponse { EventResponseBuilder::Err().data(format!("{}", self.inner)).build() }
|
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 std::future::Future;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::SystemError,
|
error::{InternalError, SystemError},
|
||||||
module::Event,
|
module::Event,
|
||||||
request::payload::Payload,
|
request::payload::Payload,
|
||||||
util::ready::{ready, Ready},
|
util::ready::{ready, Ready},
|
||||||
};
|
};
|
||||||
|
use futures_core::ready;
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct EventRequest {
|
pub struct EventRequest {
|
||||||
id: String,
|
id: String,
|
||||||
@ -60,7 +62,46 @@ impl FromRequest for () {
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
impl FromRequest for String {
|
impl FromRequest for String {
|
||||||
type Error = SystemError;
|
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};
|
use bytes::{Buf, Bytes};
|
||||||
|
#[cfg(feature = "use_serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{fmt, fmt::Formatter};
|
use std::{fmt, fmt::Formatter};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
|
||||||
pub enum ResponseData {
|
pub enum ResponseData {
|
||||||
Bytes(Vec<u8>),
|
Bytes(Vec<u8>),
|
||||||
None,
|
None,
|
||||||
@ -29,6 +31,10 @@ impl std::convert::Into<ResponseData> for Bytes {
|
|||||||
fn into(self) -> ResponseData { ResponseData::Bytes(self.bytes().to_vec()) }
|
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 {
|
impl std::convert::Into<ResponseData> for &str {
|
||||||
fn into(self) -> ResponseData { self.to_string().into() }
|
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,
|
request::EventRequest,
|
||||||
response::{data::ResponseData, Responder},
|
response::{data::ResponseData, Responder},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize, Serializer};
|
|
||||||
|
|
||||||
|
#[cfg(feature = "use_serde")]
|
||||||
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
use std::{fmt, fmt::Formatter};
|
use std::{fmt, fmt::Formatter};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
|
||||||
pub enum StatusCode {
|
pub enum StatusCode {
|
||||||
Ok,
|
Ok,
|
||||||
Err,
|
Err,
|
||||||
}
|
}
|
||||||
|
|
||||||
// serde user guide: https://serde.rs/field-attrs.html
|
// 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 {
|
pub struct EventResponse {
|
||||||
#[serde(serialize_with = "serialize_data")]
|
|
||||||
pub data: ResponseData,
|
pub data: ResponseData,
|
||||||
pub status: StatusCode,
|
pub status: StatusCode,
|
||||||
#[serde(serialize_with = "serialize_error")]
|
|
||||||
pub error: Option<SystemError>,
|
pub error: Option<SystemError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,10 +36,17 @@ impl EventResponse {
|
|||||||
|
|
||||||
impl std::fmt::Display for EventResponse {
|
impl std::fmt::Display for EventResponse {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
match serde_json::to_string(self) {
|
f.write_fmt(format_args!("Status_Code: {:?}", self.status))?;
|
||||||
Ok(json) => f.write_fmt(format_args!("{:?}", json))?,
|
|
||||||
Err(e) => f.write_fmt(format_args!("{:?}", e))?,
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,6 +56,7 @@ impl Responder for EventResponse {
|
|||||||
fn respond_to(self, _: &EventRequest) -> EventResponse { self }
|
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>
|
fn serialize_error<S>(error: &Option<SystemError>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: Serializer,
|
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>
|
fn serialize_data<S>(data: &ResponseData, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
|
@ -6,7 +6,7 @@ use crate::{
|
|||||||
system::ModuleMap,
|
system::ModuleMap,
|
||||||
};
|
};
|
||||||
use futures_core::{future::LocalBoxFuture, ready, task::Context};
|
use futures_core::{future::LocalBoxFuture, ready, task::Context};
|
||||||
use std::{future::Future, hash::Hash};
|
use std::future::Future;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
macros::support::{Pin, Poll},
|
macros::support::{Pin, Poll},
|
||||||
sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||||
|
@ -8,4 +8,16 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
derive_more = {version = "0.99", features = ["display"]}
|
derive_more = {version = "0.99", features = ["display"]}
|
||||||
flowy-sys = { path = "../flowy-sys" }
|
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")]
|
use crate::domain::{User, UserEmail, UserName};
|
||||||
pub async fn user_check() -> String { "".to_owned() }
|
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 error;
|
||||||
mod event;
|
mod event;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
|
Loading…
Reference in New Issue
Block a user