use serde_json::{Value as JsonValue};
use apx_core::{
http_types::HeaderValue,
http_utils::remove_quotes,
url::http_uri::HttpUri,
};
use super::constants::AP_PUBLIC;
#[derive(Debug, Clone, Copy)]
pub enum CoreType {
Object,
Link,
Actor,
Activity,
Collection,
PublicKey,
VerificationMethod,
}
pub fn get_core_type(value: &JsonValue) -> CoreType {
if !value["inbox"].is_null() {
CoreType::Actor
}
else if !value["publicKeyMultibase"].is_null() {
CoreType::VerificationMethod
}
else if !value["publicKeyPem"].is_null() {
CoreType::PublicKey
}
else if !value["href"].is_null() {
CoreType::Link
}
else if !value["actor"].is_null() && value["attributedTo"].is_null() {
CoreType::Activity
}
else if
!value["items"].is_null() ||
!value["orderedItems"].is_null() ||
!value["totalItems"].is_null() ||
!value["partOf"].is_null() ||
!value["first"].is_null() ||
!value["last"].is_null() ||
!value["next"].is_null() ||
!value["prev"].is_null() ||
!value["current"].is_null()
{
CoreType::Collection
}
else {
CoreType::Object
}
}
pub fn is_key_like(value: &JsonValue) -> bool {
!value["publicKeyMultibase"].is_null() || !value["publicKeyPem"].is_null()
}
pub fn is_actor(value: &JsonValue) -> bool {
matches!(get_core_type(value), CoreType::Actor)
}
pub fn is_activity(value: &JsonValue) -> bool {
matches!(get_core_type(value), CoreType::Activity)
}
pub fn is_collection(value: &JsonValue) -> bool {
matches!(get_core_type(value), CoreType::Collection)
}
pub fn is_object(value: &JsonValue) -> bool {
matches!(get_core_type(value), CoreType::Object | CoreType::Link)
}
pub fn key_id_to_actor_id(key_id: &str) -> Result<String, &'static str> {
let key_uri = HttpUri::parse(key_id)?;
let actor_id = key_uri.without_fragment();
let actor_id = actor_id.trim_end_matches("/main-key");
Ok(actor_id.to_string())
}
pub fn is_public(target_id: impl AsRef<str>) -> bool {
const PUBLIC_VARIANTS: [&str; 3] = [
AP_PUBLIC,
"as:Public",
"Public",
];
PUBLIC_VARIANTS.contains(&target_id.as_ref())
}
pub fn extract_media_type(header_value: &HeaderValue) -> Option<String> {
header_value.to_str().ok()
.and_then(|value| value.split(',').next())
.map(|value| {
value
.split(';')
.map(|part| {
let part = part.trim();
if let Some((key, value)) = part.split_once('=') {
let value = remove_quotes(value);
format!(r#"{key}="{value}""#)
} else {
part.to_string()
}
})
.filter(|part| !part.starts_with("q=") && !part.starts_with("charset="))
.collect::<Vec<_>>()
.join("; ")
})
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn test_get_core_type_verification_method() {
let object = json!({
"id": "https://social/example/actors/1#main-key",
"controller": "https://social.example/actors/1",
"publicKeyMultibase": "z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2",
});
let core_type = get_core_type(&object);
assert!(matches!(core_type, CoreType::VerificationMethod));
assert!(is_key_like(&object));
}
#[test]
fn test_get_core_type_public_key() {
let object = json!({
"id": "https://social/example/actors/1#main-key",
"owner": "https://social.example/actors/1",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\ntest\n-----END PUBLIC KEY-----\n\n",
});
let core_type = get_core_type(&object);
assert!(matches!(core_type, CoreType::PublicKey));
assert!(is_key_like(&object));
}
#[test]
fn test_get_core_type_with_inbox_and_public_key_pem() {
let object = json!({
"id": "https://social/example/actors/1#main-key",
"owner": "https://social.example/actors/1",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\ntest\n-----END PUBLIC KEY-----\n\n",
"inbox": "https://social.example/actors/1/inbox",
"outbox": "https://social.example/actors/1/outbox",
});
let core_type = get_core_type(&object);
assert!(matches!(core_type, CoreType::Actor));
assert!(is_key_like(&object));
}
#[test]
fn test_is_actor() {
let actor = json!({
"id": "https://social.example/actors/1",
"type": "Person",
"inbox": "https://social.example/actors/1/inbox",
});
assert_eq!(is_actor(&actor), true);
assert_eq!(is_activity(&actor), false);
assert_eq!(is_collection(&actor), false);
assert_eq!(is_object(&actor), false);
}
#[test]
fn test_is_activity() {
let activity = json!({
"id": "https://social.example/activities/1",
"type": "Follow",
"actor": "https://social.example/actors/1",
"object": "https:/other.example/actors/abc",
});
assert_eq!(is_actor(&activity), false);
assert_eq!(is_activity(&activity), true);
assert_eq!(is_collection(&activity), false);
assert_eq!(is_object(&activity), false);
}
#[test]
fn test_is_collection() {
let collection = json!({
"id": "https://social.example/collection/1",
"type": "Collection",
"items": ["https://social.example/objects/1"],
});
assert_eq!(is_actor(&collection), false);
assert_eq!(is_activity(&collection), false);
assert_eq!(is_collection(&collection), true);
assert_eq!(is_object(&collection), false);
}
#[test]
fn test_is_object() {
let object = json!({
"id": "https://social.example/objects/1",
"type": "Note",
"actor": "https://social.example/actors/1",
"attributedTo": "https://social.example/actors/1",
"content": "test",
});
assert_eq!(is_actor(&object), false);
assert_eq!(is_activity(&object), false);
assert_eq!(is_collection(&object), false);
assert_eq!(is_object(&object), true);
}
#[test]
fn test_is_object_lemmy_group() {
let actor = json!({
"id": "https://group.example/c/test",
"type": "Group",
"attributedTo": ["https://group.example/u/mod"],
"inbox": "https://group.example/c/test/inbox",
"outbox": "https://group.example/c/test/outbox",
});
assert_eq!(is_actor(&actor), true);
assert_eq!(is_object(&actor), false);
}
#[test]
fn test_key_id_to_actor_id() {
let key_id = "https://server.example/actor#main-key";
let actor_id = key_id_to_actor_id(key_id).unwrap();
assert_eq!(actor_id, "https://server.example/actor");
let key_id = "https://forte.example/.well-known/apgateway/did:example/actor#rsakey";
let actor_id = key_id_to_actor_id(key_id).unwrap();
assert_eq!(actor_id, "https://forte.example/.well-known/apgateway/did:example/actor");
let key_id = "https://myserver.org/actor/main-key";
let actor_id = key_id_to_actor_id(key_id).unwrap();
assert_eq!(actor_id, "https://myserver.org/actor");
let key_id = "https://connectedplaces.online/?author=2#main-key";
let actor_id = key_id_to_actor_id(key_id).unwrap();
assert_eq!(actor_id, "https://connectedplaces.online/?author=2");
let key_id = "https://ap.podcastindex.org/podcasts?id=920666#main-key";
let actor_id = key_id_to_actor_id(key_id).unwrap();
assert_eq!(actor_id, "https://ap.podcastindex.org/podcasts?id=920666");
let key_id = "https://social.example#main-key";
let actor_id = key_id_to_actor_id(key_id).unwrap();
assert_eq!(actor_id, "https://social.example");
}
#[test]
fn test_extract_media_type_no_whitespace() {
let header_value = HeaderValue::from_static(r#"application/ld+json;profile="https://www.w3.org/ns/activitystreams""#);
let media_type = extract_media_type(&header_value).unwrap();
assert_eq!(media_type, r#"application/ld+json; profile="https://www.w3.org/ns/activitystreams""#);
}
#[test]
fn test_extract_media_type_with_charset() {
let header_value = HeaderValue::from_static(r#"application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8"#);
let media_type = extract_media_type(&header_value).unwrap();
assert_eq!(media_type, r#"application/ld+json; profile="https://www.w3.org/ns/activitystreams""#);
}
#[test]
fn test_extract_media_type_profile_unquoted() {
let header_value = HeaderValue::from_static(r#"application/ld+json; profile=https://www.w3.org/ns/activitystreams"#);
let media_type = extract_media_type(&header_value).unwrap();
assert_eq!(media_type, r#"application/ld+json; profile="https://www.w3.org/ns/activitystreams""#);
}
}