summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/channel/app.rs8
-rw-r--r--src/channel/mod.rs5
-rw-r--r--src/channel/name.rs30
-rw-r--r--src/channel/repo.rs14
-rw-r--r--src/channel/routes/channel/post.rs4
-rw-r--r--src/channel/routes/post.rs4
-rw-r--r--src/channel/snapshot.rs4
-rw-r--r--src/invite/app.rs8
-rw-r--r--src/invite/routes/invite/post.rs4
-rw-r--r--src/lib.rs1
-rw-r--r--src/login/app.rs4
-rw-r--r--src/login/mod.rs4
-rw-r--r--src/login/name.rs28
-rw-r--r--src/login/password.rs7
-rw-r--r--src/login/repo.rs10
-rw-r--r--src/login/routes/login/post.rs4
-rw-r--r--src/login/snapshot.rs4
-rw-r--r--src/message/app.rs4
-rw-r--r--src/message/body.rs30
-rw-r--r--src/message/mod.rs5
-rw-r--r--src/message/repo.rs28
-rw-r--r--src/message/snapshot.rs4
-rw-r--r--src/nfc.rs103
-rw-r--r--src/setup/app.rs4
-rw-r--r--src/setup/routes/post.rs4
-rw-r--r--src/test/fixtures/channel.rs10
-rw-r--r--src/test/fixtures/login.rs12
-rw-r--r--src/test/fixtures/message.rs6
-rw-r--r--src/token/app.rs4
-rw-r--r--src/token/repo/auth.rs6
-rw-r--r--src/token/repo/token.rs4
31 files changed, 284 insertions, 83 deletions
diff --git a/src/channel/app.rs b/src/channel/app.rs
index 75c662d..ea60943 100644
--- a/src/channel/app.rs
+++ b/src/channel/app.rs
@@ -2,7 +2,7 @@ use chrono::TimeDelta;
use itertools::Itertools;
use sqlx::sqlite::SqlitePool;
-use super::{repo::Provider as _, Channel, History, Id};
+use super::{repo::Provider as _, Channel, History, Id, Name};
use crate::{
clock::DateTime,
db::{Duplicate as _, NotFound as _},
@@ -20,14 +20,14 @@ impl<'a> Channels<'a> {
Self { db, events }
}
- pub async fn create(&self, name: &str, created_at: &DateTime) -> Result<Channel, CreateError> {
+ pub async fn create(&self, name: &Name, created_at: &DateTime) -> Result<Channel, CreateError> {
let mut tx = self.db.begin().await?;
let created = tx.sequence().next(created_at).await?;
let channel = tx
.channels()
.create(name, &created)
.await
- .duplicate(|| CreateError::DuplicateName(name.into()))?;
+ .duplicate(|| CreateError::DuplicateName(name.clone()))?;
tx.commit().await?;
self.events
@@ -134,7 +134,7 @@ impl<'a> Channels<'a> {
#[derive(Debug, thiserror::Error)]
pub enum CreateError {
#[error("channel named {0} already exists")]
- DuplicateName(String),
+ DuplicateName(Name),
#[error(transparent)]
Database(#[from] sqlx::Error),
}
diff --git a/src/channel/mod.rs b/src/channel/mod.rs
index eb8200b..fb13e92 100644
--- a/src/channel/mod.rs
+++ b/src/channel/mod.rs
@@ -2,8 +2,11 @@ pub mod app;
pub mod event;
mod history;
mod id;
+mod name;
pub mod repo;
mod routes;
mod snapshot;
-pub use self::{event::Event, history::History, id::Id, routes::router, snapshot::Channel};
+pub use self::{
+ event::Event, history::History, id::Id, name::Name, routes::router, snapshot::Channel,
+};
diff --git a/src/channel/name.rs b/src/channel/name.rs
new file mode 100644
index 0000000..fc82dec
--- /dev/null
+++ b/src/channel/name.rs
@@ -0,0 +1,30 @@
+use std::fmt;
+
+use crate::nfc;
+
+#[derive(
+ Clone, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, sqlx::Type,
+)]
+#[serde(transparent)]
+#[sqlx(transparent)]
+pub struct Name(nfc::String);
+
+impl fmt::Display for Name {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let Self(name) = self;
+ name.fmt(f)
+ }
+}
+
+impl From<String> for Name {
+ fn from(name: String) -> Self {
+ Self(name.into())
+ }
+}
+
+impl From<Name> for String {
+ fn from(name: Name) -> Self {
+ let Name(name) = name;
+ name.into()
+ }
+}
diff --git a/src/channel/repo.rs b/src/channel/repo.rs
index 27d35f0..3353bfd 100644
--- a/src/channel/repo.rs
+++ b/src/channel/repo.rs
@@ -1,7 +1,7 @@
use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction};
use crate::{
- channel::{Channel, History, Id},
+ channel::{Channel, History, Id, Name},
clock::DateTime,
event::{Instant, ResumePoint, Sequence},
};
@@ -19,7 +19,7 @@ impl<'c> Provider for Transaction<'c, Sqlite> {
pub struct Channels<'t>(&'t mut SqliteConnection);
impl<'c> Channels<'c> {
- pub async fn create(&mut self, name: &str, created: &Instant) -> Result<History, sqlx::Error> {
+ pub async fn create(&mut self, name: &Name, created: &Instant) -> Result<History, sqlx::Error> {
let id = Id::generate();
let channel = sqlx::query!(
r#"
@@ -28,7 +28,7 @@ impl<'c> Channels<'c> {
values ($1, $2, $3, $4)
returning
id as "id: Id",
- name as "name!", -- known non-null as we just set it
+ name as "name!: Name", -- known non-null as we just set it
created_at as "created_at: DateTime",
created_sequence as "created_sequence: Sequence"
"#,
@@ -57,7 +57,7 @@ impl<'c> Channels<'c> {
r#"
select
id as "id: Id",
- channel.name,
+ channel.name as "name: Name",
channel.created_at as "created_at: DateTime",
channel.created_sequence as "created_sequence: Sequence",
deleted.deleted_at as "deleted_at?: DateTime",
@@ -89,7 +89,7 @@ impl<'c> Channels<'c> {
r#"
select
id as "id: Id",
- channel.name,
+ channel.name as "name: Name",
channel.created_at as "created_at: DateTime",
channel.created_sequence as "created_sequence: Sequence",
deleted.deleted_at as "deleted_at: DateTime",
@@ -125,7 +125,7 @@ impl<'c> Channels<'c> {
r#"
select
id as "id: Id",
- channel.name,
+ channel.name as "name: Name",
channel.created_at as "created_at: DateTime",
channel.created_sequence as "created_sequence: Sequence",
deleted.deleted_at as "deleted_at: DateTime",
@@ -235,7 +235,7 @@ impl<'c> Channels<'c> {
r#"
select
channel.id as "id: Id",
- channel.name,
+ channel.name as "name: Name",
channel.created_at as "created_at: DateTime",
channel.created_sequence as "created_sequence: Sequence",
deleted.deleted_at as "deleted_at?: DateTime",
diff --git a/src/channel/routes/channel/post.rs b/src/channel/routes/channel/post.rs
index b489a77..d0cae05 100644
--- a/src/channel/routes/channel/post.rs
+++ b/src/channel/routes/channel/post.rs
@@ -9,7 +9,7 @@ use crate::{
clock::RequestedAt,
error::{Internal, NotFound},
login::Login,
- message::{app::SendError, Message},
+ message::{app::SendError, Body, Message},
};
pub async fn handler(
@@ -29,7 +29,7 @@ pub async fn handler(
#[derive(serde::Deserialize)]
pub struct Request {
- pub body: String,
+ pub body: Body,
}
#[derive(Debug)]
diff --git a/src/channel/routes/post.rs b/src/channel/routes/post.rs
index a05c312..d354f79 100644
--- a/src/channel/routes/post.rs
+++ b/src/channel/routes/post.rs
@@ -6,7 +6,7 @@ use axum::{
use crate::{
app::App,
- channel::{app, Channel},
+ channel::{app, Channel, Name},
clock::RequestedAt,
error::Internal,
login::Login,
@@ -29,7 +29,7 @@ pub async fn handler(
#[derive(serde::Deserialize)]
pub struct Request {
- pub name: String,
+ pub name: Name,
}
#[derive(Debug)]
diff --git a/src/channel/snapshot.rs b/src/channel/snapshot.rs
index 2b7d89a..dc2894d 100644
--- a/src/channel/snapshot.rs
+++ b/src/channel/snapshot.rs
@@ -1,13 +1,13 @@
use super::{
event::{Created, Event},
- Id,
+ Id, Name,
};
use crate::clock::DateTime;
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct Channel {
pub id: Id,
- pub name: String,
+ pub name: Name,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTime>,
}
diff --git a/src/invite/app.rs b/src/invite/app.rs
index ee7f74f..285a819 100644
--- a/src/invite/app.rs
+++ b/src/invite/app.rs
@@ -6,7 +6,7 @@ use crate::{
clock::DateTime,
db::{Duplicate as _, NotFound as _},
event::repo::Provider as _,
- login::{repo::Provider as _, Login, Password},
+ login::{repo::Provider as _, Login, Name, Password},
token::{repo::Provider as _, Secret},
};
@@ -42,7 +42,7 @@ impl<'a> Invites<'a> {
pub async fn accept(
&self,
invite: &Id,
- name: &str,
+ name: &Name,
password: &Password,
accepted_at: &DateTime,
) -> Result<(Login, Secret), AcceptError> {
@@ -68,7 +68,7 @@ impl<'a> Invites<'a> {
.logins()
.create(name, &password_hash, &created)
.await
- .duplicate(|| AcceptError::DuplicateLogin(name.into()))?;
+ .duplicate(|| AcceptError::DuplicateLogin(name.clone()))?;
let secret = tx.tokens().issue(&login, accepted_at).await?;
tx.commit().await?;
@@ -92,7 +92,7 @@ pub enum AcceptError {
#[error("invite not found: {0}")]
NotFound(Id),
#[error("name in use: {0}")]
- DuplicateLogin(String),
+ DuplicateLogin(Name),
#[error(transparent)]
Database(#[from] sqlx::Error),
#[error(transparent)]
diff --git a/src/invite/routes/invite/post.rs b/src/invite/routes/invite/post.rs
index c072929..8160465 100644
--- a/src/invite/routes/invite/post.rs
+++ b/src/invite/routes/invite/post.rs
@@ -9,7 +9,7 @@ use crate::{
clock::RequestedAt,
error::{Internal, NotFound},
invite::app,
- login::{Login, Password},
+ login::{Login, Name, Password},
token::extract::IdentityToken,
};
@@ -31,7 +31,7 @@ pub async fn handler(
#[derive(serde::Deserialize)]
pub struct Request {
- pub name: String,
+ pub name: Name,
pub password: Password,
}
diff --git a/src/lib.rs b/src/lib.rs
index 73a2cb0..4d0d9b9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,6 +16,7 @@ mod id;
mod invite;
mod login;
mod message;
+mod nfc;
mod setup;
#[cfg(test)]
mod test;
diff --git a/src/login/app.rs b/src/login/app.rs
index b6f7e1c..ebc1c00 100644
--- a/src/login/app.rs
+++ b/src/login/app.rs
@@ -1,6 +1,6 @@
use sqlx::sqlite::SqlitePool;
-use super::{repo::Provider as _, Login, Password};
+use super::{repo::Provider as _, Login, Name, Password};
use crate::{
clock::DateTime,
event::{repo::Provider as _, Broadcaster, Event},
@@ -18,7 +18,7 @@ impl<'a> Logins<'a> {
pub async fn create(
&self,
- name: &str,
+ name: &Name,
password: &Password,
created_at: &DateTime,
) -> Result<Login, CreateError> {
diff --git a/src/login/mod.rs b/src/login/mod.rs
index 98cc3d7..71d5bfc 100644
--- a/src/login/mod.rs
+++ b/src/login/mod.rs
@@ -4,11 +4,13 @@ pub mod event;
pub mod extract;
mod history;
mod id;
+mod name;
pub mod password;
pub mod repo;
mod routes;
mod snapshot;
pub use self::{
- event::Event, history::History, id::Id, password::Password, routes::router, snapshot::Login,
+ event::Event, history::History, id::Id, name::Name, password::Password, routes::router,
+ snapshot::Login,
};
diff --git a/src/login/name.rs b/src/login/name.rs
new file mode 100644
index 0000000..d882ff9
--- /dev/null
+++ b/src/login/name.rs
@@ -0,0 +1,28 @@
+use std::fmt;
+
+use crate::nfc;
+
+#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, sqlx::Type)]
+#[serde(transparent)]
+#[sqlx(transparent)]
+pub struct Name(nfc::String);
+
+impl fmt::Display for Name {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let Self(name) = self;
+ name.fmt(f)
+ }
+}
+
+impl From<String> for Name {
+ fn from(name: String) -> Self {
+ Self(name.into())
+ }
+}
+
+impl From<Name> for String {
+ fn from(name: Name) -> Self {
+ let Name(name) = name;
+ name.into()
+ }
+}
diff --git a/src/login/password.rs b/src/login/password.rs
index 14fd981..f9ecf37 100644
--- a/src/login/password.rs
+++ b/src/login/password.rs
@@ -4,6 +4,8 @@ use argon2::Argon2;
use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use rand_core::OsRng;
+use crate::nfc;
+
#[derive(sqlx::Type)]
#[sqlx(transparent)]
pub struct StoredHash(String);
@@ -31,7 +33,7 @@ impl fmt::Debug for StoredHash {
#[derive(serde::Deserialize)]
#[serde(transparent)]
-pub struct Password(String);
+pub struct Password(nfc::String);
impl Password {
pub fn hash(&self) -> Result<StoredHash, password_hash::Error> {
@@ -56,9 +58,8 @@ impl fmt::Debug for Password {
}
}
-#[cfg(test)]
impl From<String> for Password {
fn from(password: String) -> Self {
- Self(password)
+ Password(password.into())
}
}
diff --git a/src/login/repo.rs b/src/login/repo.rs
index 7d0fcb1..204329f 100644
--- a/src/login/repo.rs
+++ b/src/login/repo.rs
@@ -3,7 +3,7 @@ use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction};
use crate::{
clock::DateTime,
event::{Instant, ResumePoint, Sequence},
- login::{password::StoredHash, History, Id, Login},
+ login::{password::StoredHash, History, Id, Login, Name},
};
pub trait Provider {
@@ -21,7 +21,7 @@ pub struct Logins<'t>(&'t mut SqliteConnection);
impl<'c> Logins<'c> {
pub async fn create(
&mut self,
- name: &str,
+ name: &Name,
password_hash: &StoredHash,
created: &Instant,
) -> Result<History, sqlx::Error> {
@@ -34,7 +34,7 @@ impl<'c> Logins<'c> {
values ($1, $2, $3, $4, $5)
returning
id as "id: Id",
- name,
+ name as "name: Name",
created_sequence as "created_sequence: Sequence",
created_at as "created_at: DateTime"
"#,
@@ -62,7 +62,7 @@ impl<'c> Logins<'c> {
r#"
select
id as "id: Id",
- name,
+ name as "name: Name",
created_sequence as "created_sequence: Sequence",
created_at as "created_at: DateTime"
from login
@@ -88,7 +88,7 @@ impl<'c> Logins<'c> {
r#"
select
id as "id: Id",
- name,
+ name as "name: Name",
created_sequence as "created_sequence: Sequence",
created_at as "created_at: DateTime"
from login
diff --git a/src/login/routes/login/post.rs b/src/login/routes/login/post.rs
index 67eaa6d..7a685e2 100644
--- a/src/login/routes/login/post.rs
+++ b/src/login/routes/login/post.rs
@@ -8,7 +8,7 @@ use crate::{
app::App,
clock::RequestedAt,
error::Internal,
- login::{Login, Password},
+ login::{Login, Name, Password},
token::{app, extract::IdentityToken},
};
@@ -29,7 +29,7 @@ pub async fn handler(
#[derive(serde::Deserialize)]
pub struct Request {
- pub name: String,
+ pub name: Name,
pub password: Password,
}
diff --git a/src/login/snapshot.rs b/src/login/snapshot.rs
index 1a92f5c..85800e4 100644
--- a/src/login/snapshot.rs
+++ b/src/login/snapshot.rs
@@ -1,6 +1,6 @@
use super::{
event::{Created, Event},
- Id,
+ Id, Name,
};
// This also implements FromRequestParts (see `./extract.rs`). As a result, it
@@ -10,7 +10,7 @@ use super::{
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct Login {
pub id: Id,
- pub name: String,
+ pub name: Name,
// The omission of the hashed password is deliberate, to minimize the
// chance that it ends up tangled up in debug output or in some other chunk
// of logic elsewhere.
diff --git a/src/message/app.rs b/src/message/app.rs
index 4e50513..af87553 100644
--- a/src/message/app.rs
+++ b/src/message/app.rs
@@ -2,7 +2,7 @@ use chrono::TimeDelta;
use itertools::Itertools;
use sqlx::sqlite::SqlitePool;
-use super::{repo::Provider as _, Id, Message};
+use super::{repo::Provider as _, Body, Id, Message};
use crate::{
channel::{self, repo::Provider as _},
clock::DateTime,
@@ -26,7 +26,7 @@ impl<'a> Messages<'a> {
channel: &channel::Id,
sender: &Login,
sent_at: &DateTime,
- body: &str,
+ body: &Body,
) -> Result<Message, SendError> {
let mut tx = self.db.begin().await?;
let channel = tx
diff --git a/src/message/body.rs b/src/message/body.rs
new file mode 100644
index 0000000..a415f85
--- /dev/null
+++ b/src/message/body.rs
@@ -0,0 +1,30 @@
+use std::fmt;
+
+use crate::nfc;
+
+#[derive(
+ Clone, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, sqlx::Type,
+)]
+#[serde(transparent)]
+#[sqlx(transparent)]
+pub struct Body(nfc::String);
+
+impl fmt::Display for Body {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let Self(body) = self;
+ body.fmt(f)
+ }
+}
+
+impl From<String> for Body {
+ fn from(body: String) -> Self {
+ Self(body.into())
+ }
+}
+
+impl From<Body> for String {
+ fn from(body: Body) -> Self {
+ let Body(body) = body;
+ body.into()
+ }
+}
diff --git a/src/message/mod.rs b/src/message/mod.rs
index a8f51ab..c2687bc 100644
--- a/src/message/mod.rs
+++ b/src/message/mod.rs
@@ -1,4 +1,5 @@
pub mod app;
+mod body;
pub mod event;
mod history;
mod id;
@@ -6,4 +7,6 @@ pub mod repo;
mod routes;
mod snapshot;
-pub use self::{event::Event, history::History, id::Id, routes::router, snapshot::Message};
+pub use self::{
+ body::Body, event::Event, history::History, id::Id, routes::router, snapshot::Message,
+};
diff --git a/src/message/repo.rs b/src/message/repo.rs
index 85a69fc..4cfefec 100644
--- a/src/message/repo.rs
+++ b/src/message/repo.rs
@@ -1,6 +1,6 @@
use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction};
-use super::{snapshot::Message, History, Id};
+use super::{snapshot::Message, Body, History, Id};
use crate::{
channel,
clock::DateTime,
@@ -26,24 +26,24 @@ impl<'c> Messages<'c> {
channel: &channel::History,
sender: &Login,
sent: &Instant,
- body: &str,
+ body: &Body,
) -> Result<History, sqlx::Error> {
let id = Id::generate();
let channel_id = channel.id();
let message = sqlx::query!(
r#"
- insert into message
- (id, channel, sender, sent_at, sent_sequence, body)
- values ($1, $2, $3, $4, $5, $6)
- returning
- id as "id: Id",
+ insert into message
+ (id, channel, sender, sent_at, sent_sequence, body)
+ values ($1, $2, $3, $4, $5, $6)
+ returning
+ id as "id: Id",
channel as "channel: channel::Id",
sender as "sender: login::Id",
sent_at as "sent_at: DateTime",
sent_sequence as "sent_sequence: Sequence",
- body
- "#,
+ body as "body: Body"
+ "#,
id,
channel_id,
sender.id,
@@ -76,7 +76,7 @@ impl<'c> Messages<'c> {
message.channel as "channel: channel::Id",
message.sender as "sender: login::Id",
id as "id: Id",
- message.body,
+ message.body as "body: Body",
message.sent_at as "sent_at: DateTime",
message.sent_sequence as "sent_sequence: Sequence",
deleted.deleted_at as "deleted_at: DateTime",
@@ -113,7 +113,7 @@ impl<'c> Messages<'c> {
message.channel as "channel: channel::Id",
message.sender as "sender: login::Id",
id as "id: Id",
- message.body,
+ message.body as "body: Body",
message.sent_at as "sent_at: DateTime",
message.sent_sequence as "sent_sequence: Sequence",
deleted.deleted_at as "deleted_at: DateTime",
@@ -150,7 +150,7 @@ impl<'c> Messages<'c> {
message.channel as "channel: channel::Id",
message.sender as "sender: login::Id",
id as "id: Id",
- message.body,
+ message.body as "body: Body",
message.sent_at as "sent_at: DateTime",
message.sent_sequence as "sent_sequence: Sequence",
deleted.deleted_at as "deleted_at?: DateTime",
@@ -256,7 +256,7 @@ impl<'c> Messages<'c> {
message.sender as "sender: login::Id",
message.sent_at as "sent_at: DateTime",
message.sent_sequence as "sent_sequence: Sequence",
- message.body,
+ message.body as "body: Body",
deleted.deleted_at as "deleted_at?: DateTime",
deleted.deleted_sequence as "deleted_sequence?: Sequence"
from message
@@ -293,7 +293,7 @@ impl<'c> Messages<'c> {
message.sender as "sender: login::Id",
message.sent_at as "sent_at: DateTime",
message.sent_sequence as "sent_sequence: Sequence",
- message.body,
+ message.body as "body: Body",
deleted.deleted_at as "deleted_at: DateTime",
deleted.deleted_sequence as "deleted_sequence: Sequence"
from message
diff --git a/src/message/snapshot.rs b/src/message/snapshot.rs
index 7300918..53b7176 100644
--- a/src/message/snapshot.rs
+++ b/src/message/snapshot.rs
@@ -1,6 +1,6 @@
use super::{
event::{Event, Sent},
- Id,
+ Body, Id,
};
use crate::{channel, clock::DateTime, event::Instant, login};
@@ -11,7 +11,7 @@ pub struct Message {
pub channel: channel::Id,
pub sender: login::Id,
pub id: Id,
- pub body: String,
+ pub body: Body,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTime>,
}
diff --git a/src/nfc.rs b/src/nfc.rs
new file mode 100644
index 0000000..70e936c
--- /dev/null
+++ b/src/nfc.rs
@@ -0,0 +1,103 @@
+use std::{fmt, string::String as StdString};
+
+use sqlx::{
+ encode::{Encode, IsNull},
+ Database, Decode, Type,
+};
+use unicode_normalization::UnicodeNormalization as _;
+
+#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
+#[serde(from = "StdString", into = "StdString")]
+pub struct String(StdString);
+
+impl fmt::Display for String {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let Self(value) = self;
+ value.fmt(f)
+ }
+}
+
+impl From<StdString> for String {
+ fn from(value: StdString) -> Self {
+ let value = value.nfc().collect();
+
+ Self(value)
+ }
+}
+
+impl From<String> for StdString {
+ fn from(value: String) -> Self {
+ let String(value) = value;
+ value
+ }
+}
+
+impl std::ops::Deref for String {
+ type Target = StdString;
+
+ fn deref(&self) -> &Self::Target {
+ let Self(value) = self;
+ value
+ }
+}
+
+// Type is manually implemented so that we can implement Decode to do
+// normalization on read. Implementation is otherwise based on
+// `#[derive(sqlx::Type)]` with the `#[sqlx(transparent)]` attribute.
+impl<DB> Type<DB> for String
+where
+ DB: Database,
+ StdString: Type<DB>,
+{
+ fn type_info() -> <DB as Database>::TypeInfo {
+ <StdString as Type<DB>>::type_info()
+ }
+
+ fn compatible(ty: &<DB as Database>::TypeInfo) -> bool {
+ <StdString as Type<DB>>::compatible(ty)
+ }
+}
+
+impl<'r, DB> Decode<'r, DB> for String
+where
+ DB: Database,
+ StdString: Decode<'r, DB>,
+{
+ fn decode(value: <DB as Database>::ValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
+ let value = StdString::decode(value)?;
+ let value = value.nfc().collect();
+ Ok(Self(value))
+ }
+}
+
+impl<'q, DB> Encode<'q, DB> for String
+where
+ DB: Database,
+ StdString: Encode<'q, DB>,
+{
+ fn encode_by_ref(
+ &self,
+ buf: &mut <DB as Database>::ArgumentBuffer<'q>,
+ ) -> Result<IsNull, sqlx::error::BoxDynError> {
+ let Self(value) = self;
+ value.encode_by_ref(buf)
+ }
+
+ fn encode(
+ self,
+ buf: &mut <DB as Database>::ArgumentBuffer<'q>,
+ ) -> Result<IsNull, sqlx::error::BoxDynError> {
+ let Self(value) = self;
+ value.encode(buf)
+ }
+
+ fn produces(&self) -> Option<<DB as Database>::TypeInfo> {
+ let Self(value) = self;
+ value.produces()
+ }
+
+ fn size_hint(&self) -> usize {
+ let Self(value) = self;
+ value.size_hint()
+ }
+}
diff --git a/src/setup/app.rs b/src/setup/app.rs
index d015813..9fbcf6d 100644
--- a/src/setup/app.rs
+++ b/src/setup/app.rs
@@ -4,7 +4,7 @@ use super::repo::Provider as _;
use crate::{
clock::DateTime,
event::{repo::Provider as _, Broadcaster, Event},
- login::{repo::Provider as _, Login, Password},
+ login::{repo::Provider as _, Login, Name, Password},
token::{repo::Provider as _, Secret},
};
@@ -20,7 +20,7 @@ impl<'a> Setup<'a> {
pub async fn initial(
&self,
- name: &str,
+ name: &Name,
password: &Password,
created_at: &DateTime,
) -> Result<(Login, Secret), Error> {
diff --git a/src/setup/routes/post.rs b/src/setup/routes/post.rs
index 34f4ed2..6a3fa11 100644
--- a/src/setup/routes/post.rs
+++ b/src/setup/routes/post.rs
@@ -8,7 +8,7 @@ use crate::{
app::App,
clock::RequestedAt,
error::Internal,
- login::{Login, Password},
+ login::{Login, Name, Password},
setup::app,
token::extract::IdentityToken,
};
@@ -30,7 +30,7 @@ pub async fn handler(
#[derive(serde::Deserialize)]
pub struct Request {
- pub name: String,
+ pub name: Name,
pub password: Password,
}
diff --git a/src/test/fixtures/channel.rs b/src/test/fixtures/channel.rs
index a1dda61..024ac1b 100644
--- a/src/test/fixtures/channel.rs
+++ b/src/test/fixtures/channel.rs
@@ -8,7 +8,7 @@ use rand;
use crate::{
app::App,
- channel::{self, Channel},
+ channel::{self, Channel, Name},
clock::RequestedAt,
event::Event,
};
@@ -21,13 +21,13 @@ pub async fn create(app: &App, created_at: &RequestedAt) -> Channel {
.expect("should always succeed if the channel is actually new")
}
-pub fn propose() -> String {
- rand::random::<Name>().to_string()
+pub fn propose() -> Name {
+ rand::random::<NameTemplate>().to_string().into()
}
-struct Name(String);
+struct NameTemplate(String);
faker_impl_from_templates! {
- Name; "{} {}", CityName, FullName;
+ NameTemplate; "{} {}", CityName, FullName;
}
pub fn events(event: Event) -> future::Ready<Option<channel::Event>> {
diff --git a/src/test/fixtures/login.rs b/src/test/fixtures/login.rs
index b6766fe..0a42320 100644
--- a/src/test/fixtures/login.rs
+++ b/src/test/fixtures/login.rs
@@ -4,7 +4,7 @@ use uuid::Uuid;
use crate::{
app::App,
clock::RequestedAt,
- login::{self, Login, Password},
+ login::{self, Login, Name, Password},
};
pub async fn create_with_password(app: &App, created_at: &RequestedAt) -> (Login, Password) {
@@ -29,16 +29,16 @@ pub async fn create(app: &App, created_at: &RequestedAt) -> Login {
pub fn fictitious() -> Login {
Login {
id: login::Id::generate(),
- name: name(),
+ name: propose_name(),
}
}
-pub fn propose() -> (String, Password) {
- (name(), propose_password())
+pub fn propose() -> (Name, Password) {
+ (propose_name(), propose_password())
}
-fn name() -> String {
- rand::random::<internet::Username>().to_string()
+fn propose_name() -> Name {
+ rand::random::<internet::Username>().to_string().into()
}
pub fn propose_password() -> Password {
diff --git a/src/test/fixtures/message.rs b/src/test/fixtures/message.rs
index eb00e7c..c450bce 100644
--- a/src/test/fixtures/message.rs
+++ b/src/test/fixtures/message.rs
@@ -8,7 +8,7 @@ use crate::{
clock::RequestedAt,
event::Event,
login::Login,
- message::{self, Message},
+ message::{self, Body, Message},
};
pub async fn send(app: &App, channel: &Channel, login: &Login, sent_at: &RequestedAt) -> Message {
@@ -20,8 +20,8 @@ pub async fn send(app: &App, channel: &Channel, login: &Login, sent_at: &Request
.expect("should succeed if the channel exists")
}
-pub fn propose() -> String {
- rand::random::<Paragraphs>().to_string()
+pub fn propose() -> Body {
+ rand::random::<Paragraphs>().to_string().into()
}
pub fn events(event: Event) -> future::Ready<Option<message::Event>> {
diff --git a/src/token/app.rs b/src/token/app.rs
index 0dc1a46..d4dd1a0 100644
--- a/src/token/app.rs
+++ b/src/token/app.rs
@@ -12,7 +12,7 @@ use super::{
use crate::{
clock::DateTime,
db::NotFound as _,
- login::{Login, Password},
+ login::{Login, Name, Password},
};
pub struct Tokens<'a> {
@@ -27,7 +27,7 @@ impl<'a> Tokens<'a> {
pub async fn login(
&self,
- name: &str,
+ name: &Name,
password: &Password,
login_at: &DateTime,
) -> Result<(Login, Secret), LoginError> {
diff --git a/src/token/repo/auth.rs b/src/token/repo/auth.rs
index 88d0878..c621b65 100644
--- a/src/token/repo/auth.rs
+++ b/src/token/repo/auth.rs
@@ -3,7 +3,7 @@ use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction};
use crate::{
clock::DateTime,
event::{Instant, Sequence},
- login::{self, password::StoredHash, History, Login},
+ login::{self, password::StoredHash, History, Login, Name},
};
pub trait Provider {
@@ -19,12 +19,12 @@ impl<'c> Provider for Transaction<'c, Sqlite> {
pub struct Auth<'t>(&'t mut SqliteConnection);
impl<'t> Auth<'t> {
- pub async fn for_name(&mut self, name: &str) -> Result<(History, StoredHash), sqlx::Error> {
+ pub async fn for_name(&mut self, name: &Name) -> Result<(History, StoredHash), sqlx::Error> {
let found = sqlx::query!(
r#"
select
id as "id: login::Id",
- name,
+ name as "name: Name",
password_hash as "password_hash: StoredHash",
created_sequence as "created_sequence: Sequence",
created_at as "created_at: DateTime"
diff --git a/src/token/repo/token.rs b/src/token/repo/token.rs
index c592dcd..960bb72 100644
--- a/src/token/repo/token.rs
+++ b/src/token/repo/token.rs
@@ -3,7 +3,7 @@ use uuid::Uuid;
use crate::{
clock::DateTime,
- login::{self, History, Login},
+ login::{self, History, Login, Name},
token::{Id, Secret},
};
@@ -128,7 +128,7 @@ impl<'c> Tokens<'c> {
select
token.id as "token_id: Id",
login.id as "login_id: login::Id",
- login.name as "login_name"
+ login.name as "login_name: Name"
from login
join token on login.id = token.login
where token.secret = $1