safe-vk 0.3.0-alpha

A simple library to create your own vk bot for conversations
Documentation
use std::{borrow::Cow, collections::HashMap, fmt, sync::Arc};

use super::{
    Handler, ListenerId, ListenerMethod, MethodEndpoint, RequestBuilder, RouteAdapter, RouteFuture,
    Update,
};
use crate::matchit;

pub(super) struct Listener<S> {
    listeners: HashMap<ListenerId, MethodListener<S>>,
    node: Arc<Node>,
    prev_listener_id: ListenerId,
}

#[must_use]
pub struct MethodListener<S = ()> {
    command: MethodEndpoint<S>,
    any: MethodEndpoint<S>,
}

impl<S> Listener<S>
where
    S: Clone + Send + Sync + 'static,
{
    pub(super) fn listen(
        &mut self,
        listener: MethodListener<S>,
        method: ListenerMethod,
    ) -> Result<(), Cow<'static, str>> {
        let endpoint = if let Some((listener_id, method_listener)) = self
            .node
            .method_to_listener_id
            .get(&method)
            .and_then(|listener_id| {
                self.listeners
                    .get(listener_id)
                    .map(|svc| (*listener_id, svc))
            }) {
            let service = method_listener
                .clone()
                .merge_listeners(Some(&method), listener);
            self.listeners.insert(listener_id, service);

            return Ok(());
        } else {
            listener
        };

        let id = self.next_listener_id();
        self.set_node(method, id)?;
        self.listeners.insert(id, endpoint);
        Ok(())
    }

    fn set_node(&mut self, method: ListenerMethod, id: ListenerId) -> Result<(), String> {
        let mut node =
            Arc::try_unwrap(Arc::clone(&self.node)).unwrap_or_else(|node| (*node).clone());
        node.insert(method, id);
        self.node = Arc::new(node);
        Ok(())
    }

    pub(super) fn call_with_state(
        &self,
        update: Update,
        state: S,
        request: Arc<RequestBuilder>,
    ) -> RouteFuture {
        match self.node.at(&update) {
            Ok(id) => {
                let endpoint = self.listeners.get(&id).expect("no listener for id");
                endpoint.call_with_state(update, state, request)
            }
            Err(_) => RouteFuture::dummy(),
        }
    }

    pub(super) fn with_state<S2>(self, state: S) -> Listener<S2> {
        let listeners = self
            .listeners
            .into_iter()
            .map(|(id, endpoint)| {
                let endpoint: MethodListener<S2> = endpoint.with_state(state.clone());
                (id, endpoint)
            })
            .collect();
        Listener {
            listeners,
            node: self.node,
            prev_listener_id: self.prev_listener_id,
        }
    }

    fn next_listener_id(&mut self) -> ListenerId {
        let next_id = self
            .prev_listener_id
            .0
            .checked_add(1)
            .expect("Over `u32::MAX` listeners created");
        self.prev_listener_id = ListenerId(next_id);
        self.prev_listener_id
    }
}

impl<S> MethodListener<S>
where
    S: Clone,
{
    pub fn new() -> Self {
        Self {
            command: MethodEndpoint::None,
            any: MethodEndpoint::None,
        }
    }

    pub fn on<H, T>(self, handler: H) -> Self
    where
        H: Handler<T, S> + Sync,
        T: 'static,
        S: Send + Sync + 'static,
    {
        self.on_endpoint(MethodEndpoint::Listener(RouteAdapter::from_handler(
            handler,
        )))
    }

    fn on_endpoint(mut self, endpoint: MethodEndpoint<S>) -> Self {
        fn set_endpoint<S>(out: &mut MethodEndpoint<S>, endpoint: &MethodEndpoint<S>)
        where
            MethodEndpoint<S>: Clone,
            S: Clone,
        {
            *out = endpoint.clone();
        }
        set_endpoint(&mut self.command, &endpoint);
        set_endpoint(&mut self.any, &endpoint);
        self
    }

    pub fn with_state<S2>(self, state: S) -> MethodListener<S2> {
        MethodListener {
            command: self.command.with_state(&state),
            any: self.any.with_state(&state),
        }
    }

    pub(crate) fn merge_listeners(
        mut self,
        method: Option<&ListenerMethod>,
        other: MethodListener<S>,
    ) -> Self {
        fn merge_inner<S>(
            method: Option<&ListenerMethod>,
            name: &str,
            first: MethodEndpoint<S>,
            second: MethodEndpoint<S>,
        ) -> MethodEndpoint<S> {
            match (first, second) {
                (MethodEndpoint::None, MethodEndpoint::None) => MethodEndpoint::None,
                (pick, MethodEndpoint::None) | (MethodEndpoint::None, pick) => pick,
                _ => {
                    if let Some(method) = method {
                        panic!(
                            "Handler for name `{name} with method: {method:?}` is already exists"
                        );
                    } else {
                        panic!("Cannot merge two same methods that both define {name}");
                    }
                }
            }
        }

        self.command = merge_inner(method, "COMMAND", self.command, other.command);
        self.any = merge_inner(method, "ANY", self.any, other.any);

        self
    }

    pub(crate) fn call_with_state(
        &self,
        update: Update,
        state: S,
        request: Arc<RequestBuilder>,
    ) -> RouteFuture {
        macro_rules! call {
            (
                $upd:expr,
                $svc:expr,
                $req:expr
            ) => {
                match $svc {
                    MethodEndpoint::None => {}
                    MethodEndpoint::Route(route) => {
                        return RouteFuture::new(route.clone().oneshot_inner($upd, $req));
                    }
                    MethodEndpoint::Listener(listener) => {
                        let listener = listener.clone().into_route(state);
                        return RouteFuture::new(listener.clone().oneshot_inner($upd, $req));
                    }
                }
            };
        }

        let Self { command, any } = self;

        call!(update, command, request);
        call!(update, any, request);

        RouteFuture::dummy()
    }
}

impl<S> Default for Listener<S> {
    fn default() -> Self {
        Self {
            listeners: Default::default(),
            node: Default::default(),
            prev_listener_id: ListenerId(0),
        }
    }
}

impl<S: 'static> Clone for Listener<S> {
    fn clone(&self) -> Self {
        Self {
            listeners: self.listeners.clone(),
            node: self.node.clone(),
            prev_listener_id: self.prev_listener_id,
        }
    }
}

impl<S> fmt::Debug for MethodListener<S> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("MethodListener")
            .field("command", &self.command)
            .field("any", &self.any)
            .finish()
    }
}

impl<S> fmt::Debug for Listener<S> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Listener")
            .field("listeners", &self.listeners)
            .finish()
    }
}

impl<S: 'static> Clone for MethodListener<S> {
    fn clone(&self) -> Self {
        Self {
            command: self.command.clone(),
            any: self.any.clone(),
        }
    }
}

impl<S> Default for MethodListener<S>
where
    S: Clone,
{
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Default)]
struct Node {
    inner: HashMap<ListenerId, ListenerMethod>,
    listener_id_to_method: HashMap<ListenerId, Arc<ListenerMethod>>,
    method_to_listener_id: HashMap<Arc<ListenerMethod>, ListenerId>,
}

impl Node {
    pub(crate) fn insert(&mut self, method: ListenerMethod, val: ListenerId) {
        let method_arc = Arc::new(method.clone());
        self.inner.insert(val, method.clone());
        self.listener_id_to_method.insert(val, method_arc.clone());
        self.method_to_listener_id.insert(method_arc, val);
    }

    fn at(&self, event: &Update) -> Result<ListenerId, ()> {
        let command_listener = self
            .inner
            .values()
            .filter_map(|method| match method {
                ListenerMethod::Command {
                    update_type,
                    trigger,
                    filter,
                } => event.object.get("message").and_then(|msg| {
                    msg.get("text").and_then(|text| {
                        text.as_str().and_then(|message| {
                            if *update_type == event.update_type
                                && matchit(message, trigger, &filter)
                            {
                                self.method_to_listener_id.get(method).copied()
                            } else {
                                None
                            }
                        })
                    })
                }),
                _ => None,
            })
            .next();

        if let Some(listener_id) = command_listener {
            Ok(listener_id)
        } else {
            self.inner
                .values()
                .find_map(|method| match method {
                    ListenerMethod::Watch => self.method_to_listener_id.get(method).copied(),
                    _ => None,
                })
                .ok_or(())
        }
    }
}