206 lines
6.6 KiB
Rust
206 lines
6.6 KiB
Rust
use regex::Regex;
|
|
use uuid::Uuid;
|
|
|
|
use fedimovies_models::{posts::types::Post, profiles::types::DbActorProfile};
|
|
use fedimovies_utils::urls::get_hostname;
|
|
|
|
use crate::errors::ValidationError;
|
|
|
|
const ACTOR_KEY_SUFFIX: &str = "#main-key";
|
|
|
|
pub enum LocalActorCollection {
|
|
Inbox,
|
|
Outbox,
|
|
Followers,
|
|
Following,
|
|
Subscribers,
|
|
}
|
|
|
|
impl LocalActorCollection {
|
|
pub fn of(&self, actor_id: &str) -> String {
|
|
let name = match self {
|
|
Self::Inbox => "inbox",
|
|
Self::Outbox => "outbox",
|
|
Self::Followers => "followers",
|
|
Self::Following => "following",
|
|
Self::Subscribers => "subscribers",
|
|
};
|
|
format!("{}/{}", actor_id, name)
|
|
}
|
|
}
|
|
|
|
// Mastodon and Pleroma use the same actor ID format
|
|
pub fn local_actor_id(instance_url: &str, username: &str) -> String {
|
|
format!("{}/users/{}", instance_url, username)
|
|
}
|
|
|
|
pub fn local_actor_inbox(instance_url: &str, username: &str) -> String {
|
|
let actor_id = local_actor_id(instance_url, username);
|
|
LocalActorCollection::Inbox.of(&actor_id)
|
|
}
|
|
|
|
pub fn local_actor_outbox(instance_url: &str, username: &str) -> String {
|
|
let actor_id = local_actor_id(instance_url, username);
|
|
LocalActorCollection::Outbox.of(&actor_id)
|
|
}
|
|
|
|
pub fn local_actor_followers(instance_url: &str, username: &str) -> String {
|
|
let actor_id = local_actor_id(instance_url, username);
|
|
LocalActorCollection::Followers.of(&actor_id)
|
|
}
|
|
|
|
pub fn local_actor_following(instance_url: &str, username: &str) -> String {
|
|
let actor_id = local_actor_id(instance_url, username);
|
|
LocalActorCollection::Following.of(&actor_id)
|
|
}
|
|
|
|
pub fn local_actor_subscribers(instance_url: &str, username: &str) -> String {
|
|
let actor_id = local_actor_id(instance_url, username);
|
|
LocalActorCollection::Subscribers.of(&actor_id)
|
|
}
|
|
|
|
pub fn local_instance_actor_id(instance_url: &str) -> String {
|
|
format!("{}/actor", instance_url)
|
|
}
|
|
|
|
pub fn local_actor_key_id(actor_id: &str) -> String {
|
|
format!("{}{}", actor_id, ACTOR_KEY_SUFFIX)
|
|
}
|
|
|
|
pub fn local_object_id(instance_url: &str, internal_object_id: &Uuid) -> String {
|
|
format!("{}/objects/{}", instance_url, internal_object_id)
|
|
}
|
|
|
|
pub fn local_emoji_id(instance_url: &str, emoji_name: &str) -> String {
|
|
format!("{}/objects/emojis/{}", instance_url, emoji_name)
|
|
}
|
|
|
|
pub fn local_tag_collection(instance_url: &str, tag_name: &str) -> String {
|
|
format!("{}/collections/tags/{}", instance_url, tag_name)
|
|
}
|
|
|
|
pub fn validate_object_id(object_id: &str) -> Result<(), ValidationError> {
|
|
get_hostname(object_id).map_err(|_| ValidationError("invalid object ID".to_string()))?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn parse_local_actor_id(instance_url: &str, actor_id: &str) -> Result<String, ValidationError> {
|
|
let url_regexp_str = format!(
|
|
"^{}/users/(?P<username>[0-9a-zA-Z_]+)$",
|
|
instance_url.replace('.', r"\."),
|
|
);
|
|
let url_regexp =
|
|
Regex::new(&url_regexp_str).map_err(|_| ValidationError("error".to_string()))?;
|
|
let url_caps = url_regexp
|
|
.captures(actor_id)
|
|
.ok_or(ValidationError("invalid actor ID".to_string()))?;
|
|
let username = url_caps
|
|
.name("username")
|
|
.ok_or(ValidationError("invalid actor ID".to_string()))?
|
|
.as_str()
|
|
.to_owned();
|
|
Ok(username)
|
|
}
|
|
|
|
pub fn parse_local_object_id(instance_url: &str, object_id: &str) -> Result<Uuid, ValidationError> {
|
|
let url_regexp_str = format!(
|
|
"^{}/objects/(?P<uuid>[0-9a-f-]+)$",
|
|
instance_url.replace('.', r"\."),
|
|
);
|
|
let url_regexp =
|
|
Regex::new(&url_regexp_str).map_err(|_| ValidationError("error".to_string()))?;
|
|
let url_caps = url_regexp
|
|
.captures(object_id)
|
|
.ok_or(ValidationError("invalid object ID".to_string()))?;
|
|
let internal_object_id: Uuid = url_caps
|
|
.name("uuid")
|
|
.ok_or(ValidationError("invalid object ID".to_string()))?
|
|
.as_str()
|
|
.parse()
|
|
.map_err(|_| ValidationError("invalid object ID".to_string()))?;
|
|
Ok(internal_object_id)
|
|
}
|
|
|
|
pub fn post_object_id(instance_url: &str, post: &Post) -> String {
|
|
match post.object_id {
|
|
Some(ref object_id) => object_id.to_string(),
|
|
None => local_object_id(instance_url, &post.id),
|
|
}
|
|
}
|
|
|
|
pub fn profile_actor_id(instance_url: &str, profile: &DbActorProfile) -> String {
|
|
match profile.actor_json {
|
|
Some(ref actor) => actor.id.clone(),
|
|
None => local_actor_id(instance_url, &profile.username),
|
|
}
|
|
}
|
|
|
|
pub fn profile_actor_url(instance_url: &str, profile: &DbActorProfile) -> String {
|
|
if let Some(ref actor) = profile.actor_json {
|
|
if let Some(ref actor_url) = actor.url {
|
|
return actor_url.to_string();
|
|
};
|
|
};
|
|
profile_actor_id(instance_url, profile)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use fedimovies_utils::id::generate_ulid;
|
|
|
|
const INSTANCE_URL: &str = "https://example.org";
|
|
|
|
#[test]
|
|
fn test_parse_local_actor_id() {
|
|
let username =
|
|
parse_local_actor_id(INSTANCE_URL, "https://example.org/users/test").unwrap();
|
|
assert_eq!(username, "test".to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_local_actor_id_wrong_path() {
|
|
let error =
|
|
parse_local_actor_id(INSTANCE_URL, "https://example.org/user/test").unwrap_err();
|
|
assert_eq!(error.to_string(), "invalid actor ID");
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_local_actor_id_invalid_username() {
|
|
let error =
|
|
parse_local_actor_id(INSTANCE_URL, "https://example.org/users/tes-t").unwrap_err();
|
|
assert_eq!(error.to_string(), "invalid actor ID");
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_local_actor_id_invalid_instance_url() {
|
|
let error =
|
|
parse_local_actor_id(INSTANCE_URL, "https://example.gov/users/test").unwrap_err();
|
|
assert_eq!(error.to_string(), "invalid actor ID");
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_local_object_id() {
|
|
let expected_uuid = generate_ulid();
|
|
let object_id = format!("https://example.org/objects/{}", expected_uuid,);
|
|
let internal_object_id = parse_local_object_id(INSTANCE_URL, &object_id).unwrap();
|
|
assert_eq!(internal_object_id, expected_uuid);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_local_object_id_invalid_uuid() {
|
|
let object_id = "https://example.org/objects/1234";
|
|
let error = parse_local_object_id(INSTANCE_URL, object_id).unwrap_err();
|
|
assert_eq!(error.to_string(), "invalid object ID");
|
|
}
|
|
|
|
#[test]
|
|
fn test_profile_actor_url() {
|
|
let profile = DbActorProfile {
|
|
username: "test".to_string(),
|
|
..Default::default()
|
|
};
|
|
let profile_url = profile_actor_url(INSTANCE_URL, &profile);
|
|
assert_eq!(profile_url, "https://example.org/users/test",);
|
|
}
|
|
}
|