567 lines
18 KiB
Rust
567 lines
18 KiB
Rust
//! Tables related to web page metadata, XRD and JRD, `host-meta`, `WebFinger`, and so on.
|
|
//!
|
|
//! # Intended usage
|
|
//!
|
|
//! 1. An user agent requests an application for the descriptor for a given resource;
|
|
//! 2. **The application queries the database using these structures to:**
|
|
//! 1. [Determine if a descriptor should be returned, or if it should be handled by another application](MetaSubject);
|
|
//! 2. [Determine which aliases the resource has](MetaAlias);
|
|
//! 3. [Determine which properties the resource has](MetaProperty);
|
|
//! 4. [Determine which links the resource has](MetaLink);
|
|
//! 5. [Determine the titles of each link](MetaLinkTitle);
|
|
//! 6. [Determine the properties of each link](MetaLinkProperty);
|
|
//! 3. The application compiles the data in a single structure that the user agent supports
|
|
//! 4. The application returns the data to the user agent
|
|
//!
|
|
//! # Matching
|
|
//!
|
|
//! Matching is performed for each single record separately, allowing for example a system administrator to define many separate subjects while globally defining a property for all the resources served.
|
|
//!
|
|
//! # Reference
|
|
//!
|
|
//! See [`acrate_rd`] for more information on the specification these structures are based on.
|
|
//!
|
|
//! # Used by
|
|
//!
|
|
//! - [`acrate_rdserver`]
|
|
//!
|
|
|
|
use diesel::deserialize::FromSql;
|
|
use diesel::{AsExpression, Associations, FromSqlRow, Identifiable, Insertable, IntoSql, PgTextExpressionMethods, QueryResult, Queryable, QueryableByName, Selectable, SelectableHelper, ExpressionMethods, BelongingToDsl};
|
|
use diesel::pg::{Pg, PgConnection};
|
|
use diesel::serialize::{Output, ToSql};
|
|
use diesel_async::AsyncPgConnection;
|
|
use mediatype::MediaTypeBuf;
|
|
use uuid::Uuid;
|
|
|
|
use crate::schema;
|
|
use crate::impl_to_insert;
|
|
|
|
/// Wrapper to use [`mime::Mime`] with [`diesel`].
|
|
#[derive(Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression)]
|
|
#[diesel(sql_type = diesel::sql_types::Text)]
|
|
pub struct MediaTypeDatabase(pub MediaTypeBuf);
|
|
|
|
|
|
/// A matchable record denoting the existence of a resource descriptor.
|
|
///
|
|
/// # See also
|
|
///
|
|
/// - [`MetaSubjectInsert`]
|
|
///
|
|
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable)]
|
|
#[diesel(table_name = schema::meta_subjects)]
|
|
#[diesel(check_for_backend(Pg))]
|
|
pub struct MetaSubject {
|
|
|
|
/// The identity column of the record.
|
|
pub id: Uuid,
|
|
|
|
/// The document the record is valid for.
|
|
pub document: String,
|
|
|
|
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
|
|
///
|
|
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
|
|
pub pattern: String,
|
|
|
|
/// The value the `subject` property should take when this record is matched.
|
|
///
|
|
/// If [`None`], its value is up for specification by the querying application.
|
|
pub subject: Option<String>,
|
|
|
|
/// Where the querying application should redirect the user agent to when this record is matched.
|
|
///
|
|
/// If [`Some`], should always override everything else.
|
|
pub redirect: Option<String>,
|
|
}
|
|
|
|
/// An [`Insertable`] version of [`MetaSubject`].
|
|
#[derive(Debug, Insertable)]
|
|
#[diesel(table_name = schema::meta_subjects)]
|
|
#[diesel(check_for_backend(Pg))]
|
|
pub struct MetaSubjectInsert {
|
|
|
|
/// The document the record is valid for.
|
|
pub document: String,
|
|
|
|
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
|
|
///
|
|
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
|
|
pub pattern: String,
|
|
|
|
/// The value the `subject` property should take when this record is matched.
|
|
///
|
|
/// If [`None`], its value is up for specification by the querying application.
|
|
pub subject: Option<String>,
|
|
|
|
/// Where the querying application should redirect the user agent to when this record is matched.
|
|
///
|
|
/// If [`Some`], should always override everything else.
|
|
pub redirect: Option<String>,
|
|
}
|
|
|
|
/// A matchable record denoting an alias belonging to a subject.
|
|
///
|
|
/// # See also
|
|
///
|
|
/// - [`MetaAliasInsert`]
|
|
///
|
|
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable)]
|
|
#[diesel(table_name = schema::meta_aliases)]
|
|
#[diesel(check_for_backend(Pg))]
|
|
pub struct MetaAlias {
|
|
|
|
/// The identity column of the record.
|
|
pub id: Uuid,
|
|
|
|
/// The document the record is valid for.
|
|
pub document: String,
|
|
|
|
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
|
|
///
|
|
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
|
|
pub pattern: String,
|
|
|
|
/// The alias to **add** to the list of aliases of the resource.
|
|
pub alias: String,
|
|
}
|
|
|
|
/// An [`Insertable`] version of [`MetaAlias`].
|
|
#[derive(Debug, Insertable)]
|
|
#[diesel(table_name = schema::meta_aliases)]
|
|
#[diesel(check_for_backend(Pg))]
|
|
pub struct MetaAliasInsert {
|
|
|
|
/// The document the record is valid for.
|
|
pub document: String,
|
|
|
|
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
|
|
///
|
|
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
|
|
pub pattern: String,
|
|
|
|
/// The alias to **add** to the list of aliases of the resource.
|
|
pub alias: String,
|
|
}
|
|
|
|
/// A matchable record denoting a link towards another resource someway related with the subject.
|
|
///
|
|
/// # See also
|
|
///
|
|
/// - [`MetaLinkInsert`]
|
|
///
|
|
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable)]
|
|
#[diesel(table_name = schema::meta_links)]
|
|
#[diesel(check_for_backend(Pg))]
|
|
pub struct MetaLink {
|
|
|
|
/// The identity column of the record.
|
|
pub id: Uuid,
|
|
|
|
/// The document the record is valid for.
|
|
pub document: String,
|
|
|
|
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
|
|
///
|
|
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
|
|
pub pattern: String,
|
|
|
|
/// The relationship the link establishes between itself and the value of the link.
|
|
pub rel: String,
|
|
|
|
/// The media type of the value of the link.
|
|
///
|
|
/// Can be [`None`] if it shouldn't be specified.
|
|
pub type_: Option<MediaTypeDatabase>,
|
|
|
|
/// The URI to the document this property is linking the subject to.
|
|
///
|
|
/// Can be [`None`] if it shouldn't be specified, for example if [`Self::template`] is [`Some`].
|
|
pub href: Option<String>,
|
|
|
|
/// The template to the document this property is linking the subject to.
|
|
///
|
|
/// Can be [`None`] if it shouldn't be specified, for example if [`Self::href`] is [`Some`].
|
|
pub template: Option<String>,
|
|
}
|
|
|
|
/// An [`Insertable`] version of [`MetaLink`].
|
|
#[derive(Debug, Insertable)]
|
|
#[diesel(table_name = schema::meta_links)]
|
|
#[diesel(check_for_backend(Pg))]
|
|
pub struct MetaLinkInsert {
|
|
|
|
/// The document the record is valid for.
|
|
pub document: String,
|
|
|
|
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
|
|
///
|
|
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
|
|
pub pattern: String,
|
|
|
|
/// The relationship the link establishes between itself and the value of the link.
|
|
pub rel: String,
|
|
|
|
/// The media type of the value of the link.
|
|
///
|
|
/// Can be [`None`] if it shouldn't be specified.
|
|
pub type_: Option<MediaTypeDatabase>,
|
|
|
|
/// The URI to the document this property is linking the subject to.
|
|
///
|
|
/// Can be [`None`] if it shouldn't be specified, for example if [`Self::template`] is [`Some`].
|
|
pub href: Option<String>,
|
|
|
|
/// The template to the document this property is linking the subject to.
|
|
///
|
|
/// Can be [`None`] if it shouldn't be specified, for example if [`Self::href`] is [`Some`].
|
|
pub template: Option<String>,
|
|
}
|
|
|
|
/// A property that a [`MetaLink`] has.
|
|
///
|
|
/// # See also
|
|
///
|
|
/// - [`MetaLinkPropertyInsert`]
|
|
///
|
|
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Associations)]
|
|
#[diesel(belongs_to(MetaLink))]
|
|
#[diesel(table_name = schema::meta_link_properties)]
|
|
#[diesel(check_for_backend(Pg))]
|
|
pub struct MetaLinkProperty {
|
|
|
|
/// The identity column of the record.
|
|
pub id: Uuid,
|
|
|
|
/// The [`MetaLink::id`] this record refers to.
|
|
pub meta_link_id: Uuid,
|
|
|
|
/// The relationship the property establishes between the [`MetaLink`] and the [`MetaLinkProperty::value`].
|
|
pub rel: String,
|
|
|
|
/// The value that the property has.
|
|
pub value: Option<String>,
|
|
}
|
|
|
|
/// An [`Insertable`] version of [`MetaLinkProperty`].
|
|
#[derive(Debug, Insertable)]
|
|
#[diesel(belongs_to(MetaLink))]
|
|
#[diesel(table_name = schema::meta_link_properties)]
|
|
#[diesel(check_for_backend(Pg))]
|
|
pub struct MetaLinkPropertyInsert {
|
|
|
|
/// The [`MetaLink::id`] this record refers to.
|
|
pub meta_link_id: Uuid,
|
|
|
|
/// The relationship the property establishes between the [`MetaLink`] and the [`MetaLinkProperty::value`].
|
|
pub rel: String,
|
|
|
|
/// The value that the property has.
|
|
pub value: Option<String>,
|
|
}
|
|
|
|
/// A title that a [`MetaLink`] has in a certain language.
|
|
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable, Associations)]
|
|
#[diesel(belongs_to(MetaLink))]
|
|
#[diesel(table_name = schema::meta_link_titles)]
|
|
#[diesel(check_for_backend(Pg))]
|
|
pub struct MetaLinkTitle {
|
|
|
|
/// The identity column of the record.
|
|
pub id: Uuid,
|
|
|
|
/// The [`MetaLink::id`] this record refers to.
|
|
pub meta_link_id: Uuid,
|
|
|
|
/// The language of the title.
|
|
pub language: String,
|
|
|
|
/// The actual contents of the title.
|
|
pub value: String,
|
|
}
|
|
|
|
/// An [`Insertable`] version of [`MetaLinkTitle`].
|
|
#[derive(Debug, Insertable)]
|
|
#[diesel(belongs_to(MetaLink))]
|
|
#[diesel(table_name = schema::meta_link_titles)]
|
|
#[diesel(check_for_backend(Pg))]
|
|
pub struct MetaLinkTitleInsert {
|
|
|
|
/// The [`MetaLink::id`] this record refers to.
|
|
pub meta_link_id: Uuid,
|
|
|
|
/// The language of the title.
|
|
pub language: String,
|
|
|
|
/// The actual contents of the title.
|
|
pub value: String,
|
|
}
|
|
|
|
/// A matchable record denoting a property that a subject has.
|
|
///
|
|
/// # See also
|
|
///
|
|
/// - [`MetaPropertyInsert`]
|
|
///
|
|
#[derive(Debug, Queryable, QueryableByName, Identifiable, Selectable)]
|
|
#[diesel(table_name = schema::meta_properties)]
|
|
#[diesel(check_for_backend(Pg))]
|
|
pub struct MetaProperty {
|
|
|
|
/// The identity column of the record.
|
|
pub id: Uuid,
|
|
|
|
/// The document the record is valid for.
|
|
pub document: String,
|
|
|
|
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
|
|
///
|
|
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
|
|
pub pattern: String,
|
|
|
|
/// The relationship the property establishes between the [`MetaSubject`] and the [`MetaProperty::value`].
|
|
pub rel: String,
|
|
|
|
/// The value that the property has.
|
|
pub value: Option<String>,
|
|
}
|
|
|
|
/// An [`Insertable`] version of [`MetaLinkTitle`].
|
|
#[derive(Debug, Insertable)]
|
|
#[diesel(table_name = schema::meta_properties)]
|
|
#[diesel(check_for_backend(Pg))]
|
|
pub struct MetaPropertyInsert {
|
|
|
|
/// The document the record is valid for.
|
|
pub document: String,
|
|
|
|
/// The [PostgreSQL ILIKE pattern] to match the search term against for the record to be valid.
|
|
///
|
|
/// [PostgreSQL ILIKE pattern]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE
|
|
pub pattern: String,
|
|
|
|
/// The relationship the property establishes between the [`MetaSubject`] and the [`MetaProperty::value`].
|
|
pub rel: String,
|
|
|
|
/// The value that the property has.
|
|
pub value: Option<String>,
|
|
}
|
|
|
|
/// Allow [`diesel::sql_types::Text`] values to be parsed as [`MediaTypeDatabase`].
|
|
impl<DB> FromSql<diesel::sql_types::Text, DB> for MediaTypeDatabase
|
|
where
|
|
DB: diesel::backend::Backend,
|
|
String: FromSql<diesel::sql_types::Text, DB>,
|
|
{
|
|
fn from_sql(bytes: <DB as diesel::backend::Backend>::RawValue<'_>) -> diesel::deserialize::Result<Self> {
|
|
log::trace!("Reading TEXT from the database...");
|
|
let s = <String as FromSql<diesel::sql_types::Text, DB>>::from_sql(bytes)?;
|
|
|
|
log::trace!("Attempting to parse as a media type: {s:?}");
|
|
let mt: MediaTypeBuf = s.parse()?;
|
|
|
|
log::trace!("Successfully parsed media type: {mt:?}");
|
|
Ok(Self(mt))
|
|
}
|
|
}
|
|
|
|
/// Allow [`diesel::sql_types::Text`] values to be written to with [`MediaTypeDatabase`].
|
|
impl<DB> ToSql<diesel::sql_types::Text, DB> for MediaTypeDatabase
|
|
where
|
|
DB: diesel::backend::Backend,
|
|
str: ToSql<diesel::sql_types::Text, DB>,
|
|
{
|
|
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result {
|
|
log::trace!("Getting the essence of a media type to prepare for serialization...");
|
|
let mt = self.0.as_str();
|
|
|
|
log::trace!("Serializing media type as TEXT: {mt:?}");
|
|
<str as ToSql<diesel::sql_types::Text, DB>>::to_sql(mt, out)
|
|
}
|
|
}
|
|
|
|
impl MetaSubject {
|
|
/// Synchronously query the records matching the given document and resource.
|
|
pub fn query_matching(conn: &mut PgConnection, doc: &str, resource: &str) -> QueryResult<Vec<Self>> {
|
|
use diesel::QueryDsl;
|
|
use diesel::RunQueryDsl;
|
|
use schema::meta_subjects::dsl::*;
|
|
|
|
let document_is_equal = document.eq(doc);
|
|
let resource_matches_pattern = resource.into_sql::<diesel::sql_types::Text>().ilike(pattern);
|
|
|
|
meta_subjects
|
|
.filter(document_is_equal)
|
|
.filter(resource_matches_pattern)
|
|
.select(Self::as_select())
|
|
.load(conn)
|
|
}
|
|
|
|
/// Asynchronously query the records matching the given document and resource.
|
|
pub async fn aquery_matching(conn: &mut AsyncPgConnection, doc: &str, resource: &str) -> QueryResult<Vec<Self>> {
|
|
use diesel::QueryDsl;
|
|
use diesel_async::RunQueryDsl;
|
|
use schema::meta_subjects::dsl::*;
|
|
|
|
let document_is_equal = document.eq(doc);
|
|
let resource_matches_pattern = resource.into_sql::<diesel::sql_types::Text>().ilike(pattern);
|
|
|
|
meta_subjects
|
|
.filter(document_is_equal)
|
|
.filter(resource_matches_pattern)
|
|
.select(Self::as_select())
|
|
.load(conn)
|
|
.await
|
|
}
|
|
}
|
|
|
|
impl MetaAlias {
|
|
/// Synchronously query the records matching the given document and resource.
|
|
pub fn query_matching(conn: &mut PgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
|
|
use diesel::QueryDsl;
|
|
use diesel::RunQueryDsl;
|
|
use schema::meta_aliases::dsl::*;
|
|
|
|
let document_is_equal = document.eq(doc);
|
|
let subject_matches_pattern = subject.into_sql::<diesel::sql_types::Text>().ilike(pattern);
|
|
|
|
meta_aliases
|
|
.filter(document_is_equal)
|
|
.filter(subject_matches_pattern)
|
|
.select(Self::as_select())
|
|
.load(conn)
|
|
}
|
|
|
|
/// Asynchronously query the records matching the given document and resource.
|
|
pub async fn aquery_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
|
|
use diesel::QueryDsl;
|
|
use diesel_async::RunQueryDsl;
|
|
use schema::meta_aliases::dsl::*;
|
|
|
|
let document_is_equal = document.eq(doc);
|
|
let subject_matches_pattern = subject.into_sql::<diesel::sql_types::Text>().ilike(pattern);
|
|
|
|
meta_aliases
|
|
.filter(document_is_equal)
|
|
.filter(subject_matches_pattern)
|
|
.select(Self::as_select())
|
|
.load(conn)
|
|
.await
|
|
}
|
|
}
|
|
|
|
impl MetaLink {
|
|
/// Synchronously query the records matching the given document and resource.
|
|
pub async fn query_matching(conn: &mut PgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
|
|
use diesel::QueryDsl;
|
|
use diesel::RunQueryDsl;
|
|
use schema::meta_links::dsl::*;
|
|
|
|
let document_is_equal = document.eq(doc);
|
|
let subject_matches_pattern = subject.into_sql::<diesel::sql_types::Text>().ilike(pattern);
|
|
|
|
meta_links
|
|
.filter(document_is_equal)
|
|
.filter(subject_matches_pattern)
|
|
.select(Self::as_select())
|
|
.load(conn)
|
|
}
|
|
|
|
/// Asynchronously query the records matching the given document and resource.
|
|
pub async fn aquery_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
|
|
use diesel::QueryDsl;
|
|
use diesel_async::RunQueryDsl;
|
|
use schema::meta_links::dsl::*;
|
|
|
|
let document_is_equal = document.eq(doc);
|
|
let subject_matches_pattern = subject.into_sql::<diesel::sql_types::Text>().ilike(pattern);
|
|
|
|
meta_links
|
|
.filter(document_is_equal)
|
|
.filter(subject_matches_pattern)
|
|
.select(Self::as_select())
|
|
.load(conn)
|
|
.await
|
|
}
|
|
}
|
|
|
|
impl MetaLinkProperty {
|
|
/// Synchronously query the records belonging to the given [`MetaLink`]s.
|
|
pub fn query_by_link(conn: &mut PgConnection, links: &[MetaLink]) -> QueryResult<Vec<Self>> {
|
|
use diesel::RunQueryDsl;
|
|
|
|
Self::belonging_to(links)
|
|
.load(conn)
|
|
}
|
|
|
|
/// Asynchronously query the records belonging to the given [`MetaLink`]s.
|
|
pub async fn aquery_by_link(conn: &mut AsyncPgConnection, links: &[MetaLink]) -> QueryResult<Vec<Self>> {
|
|
use diesel_async::RunQueryDsl;
|
|
|
|
Self::belonging_to(links)
|
|
.load(conn)
|
|
.await
|
|
}
|
|
}
|
|
|
|
impl MetaLinkTitle {
|
|
/// Synchronously query the records belonging to the given [`MetaLink`]s.
|
|
pub fn query_by_link(conn: &mut PgConnection, links: &[MetaLink]) -> QueryResult<Vec<Self>> {
|
|
use diesel::RunQueryDsl;
|
|
|
|
Self::belonging_to(links)
|
|
.load(conn)
|
|
}
|
|
|
|
/// Asynchronously query the records belonging to the given [`MetaLink`]s.
|
|
pub async fn aquery_by_link(conn: &mut AsyncPgConnection, links: &[MetaLink]) -> QueryResult<Vec<Self>> {
|
|
use diesel_async::RunQueryDsl;
|
|
|
|
Self::belonging_to(links)
|
|
.load(conn)
|
|
.await
|
|
}
|
|
}
|
|
|
|
impl MetaProperty {
|
|
/// Synchronously query the records matching the given document and resource.
|
|
pub fn query_matching(conn: &mut PgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
|
|
use diesel::QueryDsl;
|
|
use diesel::RunQueryDsl;
|
|
use schema::meta_properties::dsl::*;
|
|
|
|
let document_is_equal = document.eq(doc);
|
|
let subject_matches_pattern = subject.into_sql::<diesel::sql_types::Text>().ilike(pattern);
|
|
|
|
meta_properties
|
|
.filter(document_is_equal)
|
|
.filter(subject_matches_pattern)
|
|
.select(Self::as_select())
|
|
.load(conn)
|
|
}
|
|
|
|
/// Asynchronously query the records matching the given document and resource.
|
|
pub async fn aquery_matching(conn: &mut AsyncPgConnection, doc: &str, subject: &str) -> QueryResult<Vec<Self>> {
|
|
use diesel::QueryDsl;
|
|
use diesel_async::RunQueryDsl;
|
|
use schema::meta_properties::dsl::*;
|
|
|
|
let document_is_equal = document.eq(doc);
|
|
let subject_matches_pattern = subject.into_sql::<diesel::sql_types::Text>().ilike(pattern);
|
|
|
|
meta_properties
|
|
.filter(document_is_equal)
|
|
.filter(subject_matches_pattern)
|
|
.select(Self::as_select())
|
|
.load(conn)
|
|
.await
|
|
}
|
|
}
|
|
|
|
impl_to_insert!(MetaSubjectInsert => MetaSubject: schema::meta_subjects::table);
|
|
impl_to_insert!(MetaAliasInsert => MetaAlias: schema::meta_aliases::table);
|
|
impl_to_insert!(MetaLinkInsert => MetaLink: schema::meta_links::table);
|
|
impl_to_insert!(MetaLinkPropertyInsert => MetaLinkProperty: schema::meta_link_properties::table);
|
|
impl_to_insert!(MetaLinkTitleInsert => MetaLinkTitle: schema::meta_link_titles::table);
|
|
impl_to_insert!(MetaPropertyInsert => MetaProperty: schema::meta_properties::table);
|