use activitystreams_vocabulary::{impl_default, impl_display};
use serde::{Deserialize, Serialize};
use sha3::TurboShake256;
use sha3::digest::{ExtendableOutput, Update, XofReader};
use crate::db::{Iri, Name, Uuid};
use crate::{Error, Result};
#[derive(
Clone, Copy, Debug, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize, sqlx::Type,
)]
#[serde(rename_all = "snake_case")]
#[sqlx(type_name = "table_type")]
pub enum TableType {
#[sqlx(rename = "inbox")]
Inbox,
#[sqlx(rename = "outbox")]
Outbox,
#[sqlx(rename = "collaborator")]
Collaborator,
#[sqlx(rename = "follower")]
Follower,
#[sqlx(rename = "key")]
Key,
#[sqlx(rename = "accept_activity")]
Accept,
#[sqlx(rename = "create_activity")]
Create,
#[sqlx(rename = "follow_activity")]
Follow,
#[sqlx(rename = "role_grant")]
Grant,
#[sqlx(rename = "like_activity")]
Like,
#[sqlx(rename = "team")]
Team,
#[sqlx(rename = "factory")]
Factory,
#[sqlx(rename = "patch_tracker")]
PatchTracker,
#[sqlx(rename = "ticket_tracker")]
TicketTracker,
#[sqlx(rename = "activity")]
Activity,
#[sqlx(rename = "object")]
Object,
#[sqlx(rename = "repository")]
Repository,
#[sqlx(rename = "role_filter")]
RoleFilter,
#[sqlx(rename = "application")]
Application,
#[sqlx(rename = "person")]
Person,
#[sqlx(rename = "oauth_grant")]
OAuthGrant,
#[sqlx(rename = "oauth_token")]
OAuthToken,
#[sqlx(rename = "oauth_client")]
OAuthClient,
}
impl TableType {
pub const INBOX: &str = "inbox";
pub const INBOX_PATH: &str = "/api/v1/inboxes";
pub const OUTBOX: &str = "outbox";
pub const OUTBOX_PATH: &str = "/api/v1/outboxes";
pub const COLLABORATOR: &str = "collaborator";
pub const COLLABORATOR_PATH: &str = "/api/v1/collaborators";
pub const FOLLOW: &str = "follow_activity";
pub const FOLLOW_PATH: &str = "/api/v1/follow";
pub const FOLLOWER: &str = "follower";
pub const FOLLOWER_PATH: &str = "/api/v1/followers";
pub const KEY: &str = "key";
pub const KEY_PATH: &str = "/api/v1/keys";
pub const ACCEPT: &str = "accept_activity";
pub const ACCEPT_PATH: &str = "/api/v1/accepts";
pub const CREATE: &str = "create_activity";
pub const CREATE_PATH: &str = "/api/v1/creates";
pub const GRANT: &str = "role_grant";
pub const GRANT_PATH: &str = "/api/v1/grants";
pub const LIKE: &str = "like_activity";
pub const LIKE_PATH: &str = "/api/v1/likes";
pub const TEAM: &str = "team";
pub const TEAM_PATH: &str = "/api/v1/teams";
pub const FACTORY: &str = "factory";
pub const FACTORY_PATH: &str = "/api/v1/factories";
pub const PATCH_TRACKER: &str = "patch_tracker";
pub const PATCH_TRACKER_PATH: &str = "/api/v1/patch_trackers";
pub const TICKET_TRACKER: &str = "ticket_tracker";
pub const TICKET_TRACKER_PATH: &str = "/api/v1/ticket_trackers";
pub const ACTIVITY: &str = "activity";
pub const ACTIVITY_PATH: &str = "/api/v1/activities";
pub const OBJECT: &str = "object";
pub const OBJECT_PATH: &str = "/api/v1/objects";
pub const REPOSITORY: &str = "repository";
pub const REPOSITORY_PATH: &str = "/api/v1/repositories";
pub const ROLE_FILTER: &str = "role_filter";
pub const ROLE_FILTER_PATH: &str = "/api/v1/role_filters";
pub const PERSON: &str = "person";
pub const PERSON_PATH: &str = "/api/v1/persons";
pub const APPLICATION: &str = "application";
pub const APPLICATION_PATH: &str = "/api/v1/applications";
pub const OAUTH_GRANT: &str = "oauth_grant";
pub const OAUTH_GRANT_PATH: &str = "/api/v1/oauth_grants";
pub const OAUTH_TOKEN: &str = "oauth_token";
pub const OAUTH_TOKEN_PATH: &str = "/api/v1/oauth_tokens";
pub const OAUTH_CLIENT: &str = "oauth_client";
pub const OAUTH_CLIENT_PATH: &str = "/api/v1/oauth_clients";
pub const HASH_CONTEXT: u8 = 6;
pub const fn new() -> Self {
Self::Inbox
}
pub const fn as_str(&self) -> &'static str {
match self {
Self::Inbox => Self::INBOX,
Self::Outbox => Self::OUTBOX,
Self::Collaborator => Self::COLLABORATOR,
Self::Follow => Self::FOLLOW,
Self::Follower => Self::FOLLOWER,
Self::Key => Self::KEY,
Self::Accept => Self::ACCEPT,
Self::Create => Self::CREATE,
Self::Like => Self::LIKE,
Self::Grant => Self::GRANT,
Self::Team => Self::TEAM,
Self::Factory => Self::FACTORY,
Self::PatchTracker => Self::PATCH_TRACKER,
Self::TicketTracker => Self::TICKET_TRACKER,
Self::Activity => Self::ACTIVITY,
Self::Object => Self::OBJECT,
Self::Repository => Self::REPOSITORY,
Self::RoleFilter => Self::ROLE_FILTER,
Self::Person => Self::PERSON,
Self::Application => Self::APPLICATION,
Self::OAuthGrant => Self::OAUTH_GRANT,
Self::OAuthToken => Self::OAUTH_TOKEN,
Self::OAuthClient => Self::OAUTH_CLIENT,
}
}
pub const fn path(&self) -> &'static str {
match self {
Self::Inbox => Self::INBOX_PATH,
Self::Outbox => Self::OUTBOX_PATH,
Self::Collaborator => Self::COLLABORATOR_PATH,
Self::Follow => Self::FOLLOW_PATH,
Self::Follower => Self::FOLLOWER_PATH,
Self::Key => Self::KEY_PATH,
Self::Accept => Self::ACCEPT_PATH,
Self::Create => Self::CREATE_PATH,
Self::Like => Self::LIKE_PATH,
Self::Grant => Self::GRANT_PATH,
Self::Team => Self::TEAM_PATH,
Self::Factory => Self::FACTORY_PATH,
Self::PatchTracker => Self::PATCH_TRACKER_PATH,
Self::TicketTracker => Self::TICKET_TRACKER_PATH,
Self::Activity => Self::ACTIVITY_PATH,
Self::Object => Self::OBJECT_PATH,
Self::Repository => Self::REPOSITORY_PATH,
Self::RoleFilter => Self::ROLE_FILTER_PATH,
Self::Person => Self::PERSON_PATH,
Self::Application => Self::APPLICATION_PATH,
Self::OAuthGrant => Self::OAUTH_GRANT_PATH,
Self::OAuthToken => Self::OAUTH_TOKEN_PATH,
Self::OAuthClient => Self::OAUTH_CLIENT_PATH,
}
}
pub fn id_from_uuid(&self, id: &Iri, uuid: Uuid) -> Result<Iri> {
let base_iri = id.base_iri()?;
let path = self.path();
Iri::try_from(format!("{base_iri}{path}/{uuid}"))
}
pub fn id_from_name(&self, iri: &Iri, name: &Name) -> Result<(Uuid, Iri)> {
let base_iri = iri.base_iri()?;
let path = self.path();
let mut hasher = TurboShake256::from_core(sha3::TurboShake256Core::new(Self::HASH_CONTEXT));
hasher.update(base_iri.as_str().as_bytes());
hasher.update(path.as_bytes());
hasher.update(name.as_str().as_bytes());
let mut uuid_bytes = [0u8; 16];
let mut reader = hasher.finalize_xof();
reader.read(uuid_bytes.as_mut());
let uuid = Uuid::from_bytes(uuid_bytes);
Iri::try_from(format!("{base_iri}{path}/{uuid}")).map(|id| (uuid, id))
}
pub fn uuid_from_id<I: Into<Iri>>(&self, id: I) -> Uuid {
let mut hasher = TurboShake256::from_core(sha3::TurboShake256Core::new(Self::HASH_CONTEXT));
hasher.update(self.as_str().as_bytes());
hasher.update(id.into().as_str().as_bytes());
let mut uuid_bytes = [0u8; 16];
let mut reader = hasher.finalize_xof();
reader.read(uuid_bytes.as_mut());
Uuid::from_bytes(uuid_bytes)
}
pub fn uuid_from_ids<'a, I>(&self, ids: I) -> Uuid
where
I: IntoIterator<Item = &'a Iri>,
{
let mut hasher = TurboShake256::from_core(sha3::TurboShake256Core::new(Self::HASH_CONTEXT));
hasher.update(self.as_str().as_bytes());
ids.into_iter()
.for_each(|id| hasher.update(id.as_str().as_bytes()));
let mut uuid_bytes = [0u8; 16];
let mut reader = hasher.finalize_xof();
reader.read(uuid_bytes.as_mut());
Uuid::from_bytes(uuid_bytes)
}
pub const fn is_object(&self) -> bool {
matches!(
self,
Self::Inbox
| Self::Outbox
| Self::Collaborator
| Self::Key
| Self::Object
| Self::RoleFilter
)
}
pub fn check_object(&self) -> Result<()> {
if self.is_object() {
Ok(())
} else {
Err(Error::sql("table: is not for an `Object`"))
}
}
pub const fn is_activity(&self) -> bool {
matches!(
self,
Self::Accept | Self::Create | Self::Follow | Self::Grant | Self::Like | Self::Activity
)
}
pub fn check_activity(&self) -> Result<()> {
if self.is_activity() {
Ok(())
} else {
Err(Error::sql("table: is not for an `Activity`"))
}
}
pub const fn is_actor(&self) -> bool {
matches!(
self,
Self::Application
| Self::Factory
| Self::Follower
| Self::Person
| Self::Repository
| Self::PatchTracker
| Self::TicketTracker
| Self::Team
)
}
pub fn check_actor(&self) -> Result<()> {
if self.is_actor() {
Ok(())
} else {
Err(Error::sql("table: is not for an `Actor`"))
}
}
}
impl_default!(TableType);
impl_display!(TableType, str);