canic-core 0.99.4

Canic — a canister orchestration and management toolkit for the Internet Computer
Documentation
use crate::{
    dto::icrc21::{ConsentMessageRequest, ConsentMessageResponse, ErrorInfo, Icrc21Error},
    log,
    log::Topic,
};
use std::{cell::RefCell, collections::HashMap, sync::Arc};

//
// ICRC 21 REGISTRY
//

thread_local! {
    static ICRC_21_REGISTRY: RefCell<HashMap<String, RegisteredConsentHandler>> = RefCell::new(HashMap::new());
}

#[derive(Clone)]
struct RegisteredConsentHandler {
    handler: Arc<dyn Fn(ConsentMessageRequest) -> ConsentMessageResponse + 'static>,
}

impl RegisteredConsentHandler {
    fn new<F>(handler: F) -> Self
    where
        F: Fn(ConsentMessageRequest) -> ConsentMessageResponse + 'static,
    {
        Self {
            handler: Arc::new(handler),
        }
    }

    fn handle(&self, req: ConsentMessageRequest) -> ConsentMessageResponse {
        self.handler.as_ref()(req)
    }
}

///
/// Icrc21Dispatcher
///
/// Runtime dispatch table for ICRC-21 consent message handlers.
///
/// Invariants:
/// - Handlers must be registered during init/startup before any
///   ICRC-21 consent_message calls occur.
/// - Registry is process-local and cleared on upgrade.
///

pub struct Icrc21Dispatcher;

impl Icrc21Dispatcher {
    ///
    /// Register the consent message handler for one canister method.
    ///
    /// Handlers are usually built from the upstream ICRC-21 consent message
    /// builder and installed during init/startup.
    ///

    pub fn register<F>(method: &str, handler: F)
    where
        F: Fn(ConsentMessageRequest) -> ConsentMessageResponse + 'static,
    {
        ICRC_21_REGISTRY.with_borrow_mut(|reg| {
            let replaced = reg.insert(method.to_string(), RegisteredConsentHandler::new(handler));
            if replaced.is_some() {
                log!(
                    Topic::Icrc,
                    Warn,
                    "icrc21 handler replaced for method={method}"
                );
            }
        });
    }

    #[must_use]
    fn get_handler(method: &str) -> Option<RegisteredConsentHandler> {
        ICRC_21_REGISTRY.with_borrow(|reg| reg.get(method).cloned())
    }

    #[must_use]
    /// Resolve and execute the registered consent handler for the request.
    pub fn consent_message(req: ConsentMessageRequest) -> ConsentMessageResponse {
        match Self::get_handler(&req.method) {
            Some(handler) => handler.handle(req),
            None => ConsentMessageResponse::Err(Icrc21Error::UnsupportedCanisterCall(ErrorInfo {
                description: "No handler registered for this method.".to_string(),
            })),
        }
    }
}