sdforge 0.4.4

Multi-protocol SDK framework with unified macro configuration
// Copyright (c) 2026 Kirky.X
// SPDX-License-Identifier: MIT
//! gRPC handler registration — links a `CallRequest.method` to a forge handler.
//!
//! Emitted by the `#[forge]` macro (when `grpc_method` is set) via
//! `inventory::submit!`. At runtime, `SdForgeGrpcService::call` looks up the
//! handler by `method` and invokes it. Only forge functions that explicitly
//! declare `grpc_method` are reachable via gRPC (minimal attack surface).

use crate::core::HandlerFn;

/// Registration linking a gRPC `CallRequest.method` to a forge handler.
///
/// All fields are `Copy` (`&'static str` / `fn` pointer / `Option<&str>`) so the
/// registration lives in read-only memory.
#[derive(Debug, Clone, Copy)]
pub struct GrpcHandlerRegistration {
    /// `CallRequest.method` match key (= the forge macro's `grpc_method` value).
    pub method: &'static str,
    /// Unified handler pointer (shared with CLI via `core::HandlerFn`).
    pub handler: HandlerFn,
    /// Body parameter name, if any. gRPC injects `CallRequest.data` into this
    /// key. `None` means no Body parameter — the `data` field is rejected.
    pub body_param: Option<&'static str>,
}

inventory::collect!(GrpcHandlerRegistration);

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::{HandlerArgs, HandlerFuture, HandlerState};
    use serde_json::Value;

    fn assert_copy_clone<T: Copy + Clone>() {}

    fn dummy_handler(_args: HandlerArgs, _state: HandlerState) -> HandlerFuture {
        Box::pin(async { Ok(Value::Null) })
    }

    #[test]
    fn grpc_handler_registration_is_copy() {
        // 结构体必须 Copy/Clone(inventory 项按值遍历)
        assert_copy_clone::<GrpcHandlerRegistration>();
    }

    inventory::submit! {
        GrpcHandlerRegistration {
            method: "test_probe",
            handler: dummy_handler,
            body_param: None,
        }
    }

    #[test]
    fn grpc_handler_registration_collected() {
        // inventory 收集到本模块 submit 的 test_probe
        let count = inventory::iter::<GrpcHandlerRegistration>().count();
        assert!(count >= 1, "GrpcHandlerRegistration inventory empty");
        let names: Vec<_> = inventory::iter::<GrpcHandlerRegistration>()
            .map(|r| r.method)
            .collect();
        assert!(
            names.contains(&"test_probe"),
            "test_probe missing in {names:?}"
        );
    }
}