unobtanium 3.0.0

Opinioated Web search engine library with crawler and viewer companion.
Documentation

use rusqlite::Result as RusqliteResult;
use rusqlite::ToSql;
use rusqlite::types::FromSql;
use rusqlite::types::FromSqlResult;
use rusqlite::types::ToSqlOutput;
use rusqlite::types::ValueRef;
use serde::{Serialize,Deserialize};
use serde::Serializer;
use serde::Deserializer;
use serde::de::Error as SerdeDeError;
use url::ParseError;
pub use url::Url;

use std::error::Error;
use std::fmt::Display;
use std::fmt::Error as FmtError;
use std::fmt::Formatter;
use std::ops::Deref;
use std::str::FromStr;

/// Represents an Url, but the fragment part is guaranteed to be unset.
///
/// When deserializing or converting a string it will
/// raise an error if the fragmentpart is present.
///
/// Converting from an Url silently discards the fragment.
#[derive(Clone,Debug,PartialEq,Eq,Hash,PartialOrd,Ord)]
pub struct UrlWithoutFragment {
	url: Url,
}

impl UrlWithoutFragment {

	/// Constructs a new UrlWithoutFragment from an existing Url,
	/// discarding the fragment part.
	pub fn new(mut url: Url) -> Self {
		url.set_fragment(None);
		Self { url }
	}

	/// Take the URL out of this struct, i.e. for modification purposes
	pub fn take_url(self) -> Url {
		self.url
	}

	/// Convert to a regular Url and add an optional fragment
	pub fn with_fragment(mut self, fragment: Option<&str>) -> Url {
		self.url.set_fragment(fragment);
		self.url
	}
	
	/// Convert to a regular Url and add an optional fragment from a String
	///
	/// Mainly useful for database operations with rusqlite.
	pub fn with_fragment_string(self, fragment: Option<String>) -> Url {
		if let Some(fragment) = fragment {
			self.with_fragment(Some(fragment.as_str()))
		} else {
			self.url
		}
	}
}

#[derive(Debug,Clone)]
pub enum UrlWithoutFragmentParseError {
	UnwantedFragment,
	Url(ParseError),
}

impl Display for UrlWithoutFragmentParseError {
	fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
		match self {
			Self::UnwantedFragment => {
				f.write_str("Fragments on URLs are explicitly disabled here.")
			},
			Self::Url(e) => {
				e.fmt(f)
			},
		}
	}
}

impl Error for UrlWithoutFragmentParseError {
	fn source(&self) -> Option<&(dyn Error + 'static)> {
		match self {
			Self::UnwantedFragment => None,
			Self::Url(e) => Some(e),
		}
	}
}

impl FromStr for UrlWithoutFragment {
	type Err = UrlWithoutFragmentParseError;

	fn from_str(input: &str) -> Result<Self, UrlWithoutFragmentParseError> {
		match Url::from_str(input) {
			Ok(url) => {
				if url.fragment().is_some() {
					return Err(UrlWithoutFragmentParseError::UnwantedFragment);
				}
				Ok(Self{ url })
			},
			Err(e) => {
				Err(UrlWithoutFragmentParseError::Url(e))
			}
		}
	}
}


impl Deref for UrlWithoutFragment {
	type Target = Url;

	fn deref(&self) -> &Self::Target {
		&self.url
	}
}

impl From<Url> for UrlWithoutFragment {
	fn from(url: Url) -> Self {
		Self::new(url)
	}
}

impl From<UrlWithoutFragment> for Url {
	fn from(url: UrlWithoutFragment) -> Self {
		url.url
	}
}

impl Display for UrlWithoutFragment {
	fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
		self.url.fmt(f)
	}
}

impl Serialize for UrlWithoutFragment {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer
	{
		self.url.serialize(serializer)
	}
}

impl<'de> Deserialize<'de> for UrlWithoutFragment {
	fn deserialize<D>(deserializer: D) -> Result<UrlWithoutFragment, D::Error>
	where
		D: Deserializer<'de>
	{
		match Url::deserialize(deserializer) {
			Ok(url) => {
				if url.fragment().is_some() {
					return Err(D::Error::custom("No fragment part allowed for URLs in this place"));
				}
				return Ok(Self::new(url));
			}
			Err(e) => {
				return Err(e);
			}
		}
	}
}

impl ToSql for UrlWithoutFragment {
	fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>> {
		self.url.to_sql()
	}
}

impl FromSql for UrlWithoutFragment {
	fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
		Ok(Self::new(Url::column_result(value)?))
	}
}