Define PreferredUsername struct
This commit is contained in:
parent
fc848a8d53
commit
71824aa524
@ -1,3 +1,4 @@
|
|||||||
|
use ::anyhow::{self, anyhow};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
actor::{ApActor, Group, Person},
|
actor::{ApActor, Group, Person},
|
||||||
base::{AnyBase, Base, Extends},
|
base::{AnyBase, Base, Extends},
|
||||||
@ -18,6 +19,10 @@ use rocket::{
|
|||||||
response::{Responder, Response},
|
response::{Responder, Response},
|
||||||
Outcome,
|
Outcome,
|
||||||
};
|
};
|
||||||
|
use std::{
|
||||||
|
convert::{TryFrom, TryInto},
|
||||||
|
str::FromStr, fmt,
|
||||||
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
runtime,
|
runtime,
|
||||||
time::{sleep, Duration},
|
time::{sleep, Duration},
|
||||||
@ -241,6 +246,86 @@ pub trait IntoId {
|
|||||||
fn into_id(self) -> Id;
|
fn into_id(self) -> Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Shrinkwrap, PartialEq, Eq, Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct PreferredUsername(String);
|
||||||
|
|
||||||
|
// Mastodon allows only /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i for `preferredUsername`
|
||||||
|
impl PreferredUsername {
|
||||||
|
fn validate(name: &str) -> anyhow::Result<()> {
|
||||||
|
let len = name.len();
|
||||||
|
if len < 3 {
|
||||||
|
return Err(anyhow!("FQN must be longer than 2 characters"));
|
||||||
|
}
|
||||||
|
match name.chars().enumerate().find(|(pos, c)| {
|
||||||
|
if pos == &0 || pos == &(len - 1) {
|
||||||
|
c != &'_' && !c.is_ascii_alphanumeric()
|
||||||
|
} else {
|
||||||
|
match c {
|
||||||
|
'_' | '\\' | '.' | '-' => false,
|
||||||
|
_ => !c.is_ascii_alphanumeric(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Some((pos, c)) => Err(anyhow!("Invaliad character at {}: {}", pos, c)),
|
||||||
|
None => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The given string must be match against /\A[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?\z/i in Ruby's RegExp which is required by Mastodon.
|
||||||
|
pub unsafe fn new_unchecked(name: String) -> Self {
|
||||||
|
Self(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(name: String) -> anyhow::Result<Self> {
|
||||||
|
Self::validate(&name).map(|_| unsafe { Self::new_unchecked(name) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PreferredUsername {
|
||||||
|
fn fmt(&self, f:&mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for PreferredUsername {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(name: String) -> std::result::Result<Self, Self::Error> {
|
||||||
|
Self::new(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for PreferredUsername {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(name: &str) -> std::result::Result<Self, Self::Error> {
|
||||||
|
Self::new(name.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PreferredUsername> for String {
|
||||||
|
fn from(preferred_username: PreferredUsername) -> Self {
|
||||||
|
preferred_username.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for PreferredUsername {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for PreferredUsername {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(name: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
name.try_into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ApSignature {
|
pub struct ApSignature {
|
||||||
@ -524,6 +609,35 @@ mod tests {
|
|||||||
use assert_json_diff::assert_json_eq;
|
use assert_json_diff::assert_json_eq;
|
||||||
use serde_json::{from_str, json, to_value};
|
use serde_json::{from_str, json, to_value};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preferred_username() {
|
||||||
|
assert!(PreferredUsername::new("".into()).is_err());
|
||||||
|
assert!(PreferredUsername::new("a".into()).is_err());
|
||||||
|
assert!(PreferredUsername::new("ab".into()).is_err());
|
||||||
|
assert_eq!(
|
||||||
|
"abc",
|
||||||
|
PreferredUsername::new("abc".into()).unwrap().as_str()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"abcd",
|
||||||
|
PreferredUsername::new("abcd".into()).unwrap().as_str()
|
||||||
|
);
|
||||||
|
assert!(PreferredUsername::new("abc-".into()).is_err());
|
||||||
|
assert!(PreferredUsername::new("日本語".into()).is_err());
|
||||||
|
assert_eq!("abc", "abc".parse::<PreferredUsername>().unwrap().as_str());
|
||||||
|
assert!("abc-".parse::<PreferredUsername>().is_err());
|
||||||
|
assert_eq!(
|
||||||
|
PreferredUsername::new("admin".into()).unwrap(),
|
||||||
|
PreferredUsername("admin".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefferred_username_to_string() {
|
||||||
|
let pu = PreferredUsername::new("admin".into()).unwrap();
|
||||||
|
assert_eq!("admin".to_string(), pu.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn se_ap_signature() {
|
fn se_ap_signature() {
|
||||||
let ap_signature = ApSignature {
|
let ap_signature = ApSignature {
|
||||||
|
Loading…
Reference in New Issue
Block a user