unobtanium 3.0.0

Opinioated Web search engine library with crawler and viewer companion.
Documentation
//! Makes URLs criterium-queryable

use serde::{Serialize,Deserialize};
use url::Url;

use criterium::DirectMatch;
use criterium::NumberCriterium;
use criterium::rusqlite::assembler::*;
use criterium::StringCriterium;
use criterium::sql::Field;


use crate::criterium::OriginCriterium;
use crate::database::fields::UrlField;
use crate::database::fields::OriginField;
use crate::Origin;
use crate::url::UrlWithoutFragment;

/// Matcher for an Url, either in the database or ouside of it.
///
/// This matcher isn't very smart, in the case there is a need for a more
/// advanced matcher it should be implemneted as its own type.
#[derive(Clone,Debug,Serialize,Deserialize)]
#[serde(rename_all="snake_case")]
pub enum UrlCriterium {

	/// The url username, defaults to an empty string, never null
	User(StringCriterium),

	/// The urls password, defaults to null
	Password(StringCriterium),

	/// The URLs path, defaults to an empty string, never null
	Path(StringCriterium),

	/// The URLs query, unparsed and unmodified, defaults to null
	Query(StringCriterium),

	/// The URLs fragment identifier, defaults to null
	Fragment(StringCriterium),

	/// Same as [OriginCriterium::Scheme].
	Scheme(StringCriterium),

	/// Same as [OriginCriterium::Host].
	Host(StringCriterium),

	/// Same as [OriginCriterium::Port].
	Port(NumberCriterium<u16>),

	/// Same as [OriginCriterium::Equals].
	OriginEquals(Origin),

	/// Compare if the URL exactly equals the given URL,
	/// this equivalent to a string comparison between the URLs.
	Equals(Url),

	/// Tests if the URL points to the same Document as the given URL.
	/// This is like the Equals operation, but ignoring the fragment parts
	/// on both sides.
	SameDocumentAs(UrlWithoutFragment),
}

impl UrlCriterium {

	/// Like the [assemble_rusqlite_query()][RusqliteQueryAssembler::assemble_rusqlite_query] method,
	/// but adds an external fragment field.
	pub fn assemble_rusqlite_query_with_fragment<F: Field + From<UrlField> + From<OriginField>>(
		&self,
		assembly_context: &AssemblyContext,
		fragment_assembly_context: &AssemblyContext,
		fragment_field: &F,
	) -> InvertableRusqliteQuery<F> {
		match self {
			Self::Fragment(c) => c.assemble_query(fragment_assembly_context, fragment_field),
			Self::Equals(url) => {
				let fq = Into::<StringCriterium>::into(url.fragment())
					.assemble_query(&fragment_assembly_context.in_and_block(), fragment_field)
					.get_corrected_query();
				Self::SameDocumentAs(url.clone().into())
					.assemble_finished_rusqlite_query(&assembly_context.in_and_block(), &())
					.and(fq)
					.parenthesise_where_clause()
					.as_invertable()
			},
			_ => self.assemble_rusqlite_query(assembly_context, &()),
		}
	}
}

impl<F: Field + From<UrlField> + From<OriginField>> AssembleRusqliteQuery<F, ()> for UrlCriterium {
	fn assemble_rusqlite_query(
		&self,
		assembly_context: &AssemblyContext,
		_context: &(),
	) -> InvertableRusqliteQuery<F> {
		let mut join_origin = false;
		let mut query =	match self {
			Self::User(c) =>     c.assemble_query(assembly_context, &UrlField::Username.into()),
			Self::Password(c) => c.assemble_query(assembly_context, &UrlField::Password.into()),
			Self::Path(c) =>     c.assemble_query(assembly_context, &UrlField::Path.into()),
			Self::Query(c) =>    c.assemble_query(assembly_context, &UrlField::Query.into()),
			Self::Fragment(c) => match c {
				// The unobtanium database stores fragments with
				// the data, not with the urls.
				StringCriterium::IsNone =>
					RusqliteQuery::static_bool(true),
				_ =>
					RusqliteQuery::static_bool(false),
			}.as_invertable(),
			Self::Scheme(c) => {
				join_origin = true;
				c.assemble_query(&assembly_context.prefix_with("url_"), &OriginField::Scheme.into())
			},
			Self::Host(c) => {
				join_origin = true;
				c.assemble_query(&assembly_context.prefix_with("url_"), &OriginField::Host.into())
			},
			Self::Port(c) => {
				join_origin = true;
				c.assemble_query(&assembly_context.prefix_with("url_"), &OriginField::Port.into())
			},
			Self::OriginEquals(origin) => {
				join_origin = true;
				OriginCriterium::Equals(origin.clone()).assemble_rusqlite_query(&assembly_context.prefix_with("url_"), &())
			},
			Self::Equals(url) => {
				if url.fragment().is_none() {
					StringCriterium::Equals(url.to_string())
						.assemble_query(assembly_context, &UrlField::StrUrl.into())
				} else {
					RusqliteQuery::static_bool(false).as_invertable()
				}
			},
			Self::SameDocumentAs(url) => {
				StringCriterium::Equals(url.to_string())
					.assemble_query(assembly_context, &UrlField::StrUrl.into())
			}
		};
		if join_origin {
			query = query.inner_join(
				None,
				OriginField::OriginId.into(),
				Some(assembly_context.prefix()),
				UrlField::OriginId.into(),
			);
		}
		return query;
	}
}

impl DirectMatch<Url> for UrlCriterium {

	type Output =  bool;
	
	fn criterium_match(&self, data: &Url) -> bool {
		match self {
			Self::User(c)     => c.criterium_match(data.username()),
			Self::Password(c) => c.criterium_match(&data.password()),
			Self::Path(c)     => c.criterium_match(data.path()),
			Self::Query(c)    => c.criterium_match(&data.query()),
			Self::Fragment(c) => c.criterium_match(&data.fragment()),
			Self::Scheme(c)   => c.criterium_match(&Some(data.scheme())),
			Self::Host(c)     => c.criterium_match(&data.host_str()),
			Self::Port(c)     => c.criterium_match(&data.port_or_known_default()),
			Self::OriginEquals(origin) =>
				origin.host.as_deref() == data.host_str() &&
				origin.port == data.port_or_known_default() &&
				origin.scheme == data.scheme(),
			Self::Equals(url) => url == data,
			Self::SameDocumentAs(url) =>
				*url == UrlWithoutFragment::new(data.clone()),
		}
	}
}

impl From<OriginCriterium> for UrlCriterium {
	fn from(origin_criterium: OriginCriterium) -> Self {
		match origin_criterium {
			OriginCriterium::Scheme(c) => Self::Scheme(c),
			OriginCriterium::Host(c) => Self::Host(c),
			OriginCriterium::Port(c) => Self::Port(c),
			OriginCriterium::Equals(origin) => Self::OriginEquals(origin),
		}
	}
}