mod matcher;
use std::fmt;
use educe::Educe;
pub use matcher::*;
use url::Url;
#[async_trait::async_trait]
pub trait RegistryEntry<T> {
fn priority(&self) -> u32;
fn rules(&self) -> &[Rule];
async fn handler(&mut self, input: Input) -> T;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Source {
Url(Url),
String(String),
}
impl From<String> for Source {
fn from(value: String) -> Self {
match Url::parse(&value) {
Ok(url) => Self::Url(url),
Err(_) => Self::String(value),
}
}
}
impl fmt::Display for Source {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Url(url) => url.fmt(f),
Self::String(s) => s.fmt(f),
}
}
}
impl From<Source> for String {
fn from(value: Source) -> Self {
match value {
Source::Url(url) => url.to_string(),
Source::String(s) => s.to_string(),
}
}
}
impl Source {
pub fn try_into_url(self) -> Option<Url> {
match self {
Self::Url(url) => Some(url),
Self::String(_) => None,
}
}
pub fn into_url(self) -> Url {
self.try_into_url().expect("incorrect input type")
}
pub fn try_into_string(self) -> Option<String> {
match self {
Self::String(s) => Some(s),
Self::Url(_) => None,
}
}
pub fn into_string(self) -> String {
self.try_into_string().expect("incorrect input type")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Input {
pub prefix: Option<String>,
pub source: Source,
}
impl Input {
pub fn into_raw(self) -> String {
format!("{self}")
}
}
impl fmt::Display for Input {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(prefix) = &self.prefix {
prefix.fmt(f)?;
}
self.source.fmt(f)
}
}
#[derive(Clone, Debug)]
pub enum InputType {
Raw(String),
Parsed(Input),
}
impl From<Input> for InputType {
fn from(value: Input) -> Self {
Self::Parsed(value)
}
}
impl<T> From<T> for InputType
where
T: Into<String>,
{
fn from(value: T) -> Self {
Self::Raw(value.into())
}
}
#[derive(Educe)]
#[educe(Debug)]
pub struct Registry<T> {
#[educe(Debug = false)]
generators: Vec<Box<dyn RegistryEntry<T> + Send + Sync>>,
}
impl<T> Default for Registry<T> {
fn default() -> Self {
Self::new()
}
}
macro_rules! filter_matches {
($iter:expr, $v:ident, $enum_match:pat) => {
$iter
.iter()
.filter_map(|m| if let $enum_match = m { Some($v) } else { None })
};
}
impl<T> Registry<T> {
pub fn new() -> Self {
Self {
generators: Vec::new(),
}
}
pub fn entry<E>(mut self, entry: E) -> Self
where
E: RegistryEntry<T> + Send + Sync + 'static,
{
self.generators.push(Box::new(entry));
self.generators.sort_by_key(|k| k.priority());
self
}
pub async fn find_match<I>(&mut self, input: I) -> Option<T>
where
I: Into<InputType>,
{
let input = input.into();
let parsed_input = match &input {
InputType::Raw(raw_input) => Input {
prefix: None,
source: raw_input.clone().into(),
},
InputType::Parsed(parsed_input) => parsed_input.clone(),
};
for generator in &mut self.generators {
let prefix_rules = filter_matches!(generator.rules(), m, Rule::Prefix(m));
match &input {
InputType::Raw(raw_input) => {
for rule in prefix_rules {
if let Some(input) = rule.match_input(raw_input) {
let input = Input {
prefix: Some(rule.prefix.clone()),
source: input.into(),
};
return Some(generator.handler(input).await);
}
}
}
InputType::Parsed(parsed_input) => {
for rule in prefix_rules {
if rule.matches_parsed(parsed_input) {
return Some(generator.handler(parsed_input.clone()).await);
}
}
}
};
if let res @ Some(_) = handle(generator, &parsed_input).await {
return res;
}
}
None
}
}
async fn handle<T>(
generator: &mut Box<dyn RegistryEntry<T> + Send + Sync>,
parsed_input: &Input,
) -> Option<T> {
let matchers = generator.rules();
match &parsed_input.source {
Source::Url(url) => {
let url_rules = filter_matches!(matchers, m, Rule::Url(m));
for rule in url_rules {
if rule.matches_input(url) {
return Some(generator.handler(parsed_input.clone()).await);
}
}
}
Source::String(id) => {
let string_rules = filter_matches!(matchers, m, Rule::String(m));
for rule in string_rules {
if rule.matches_input(id) {
return Some(generator.handler(parsed_input.clone()).await);
}
}
}
}
None
}