service_kit 0.1.2

A foundational toolkit for building high-performance, modular services in Rust.
Documentation
use crate::error::{Error, Result};
use crate::handler::ApiHandlerInventory;
use axum::{
    body::Body,
    extract::{FromRequestParts, Path},
    response::{IntoResponse, Response},
    routing::{on, MethodFilter},
    Router,
};
use axum::http::{Request, HeaderMap};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use utoipa::openapi::{OpenApi, PathItem};

async fn extract_and_merge_params(req: Request<Body>) -> std::result::Result<(Value, HeaderMap, Option<String>, Vec<u8>, Option<String>), Response> {
    let (mut parts, body) = req.into_parts();

    let path_params: HashMap<String, String> =
        match Path::<HashMap<String, String>>::from_request_parts(&mut parts, &()).await {
            Ok(path) => path.0,
            Err(e) => return Err(e.into_response()),
        };
    let mut merged_params = serde_json::to_value(path_params)
        .unwrap_or_else(|_| Value::Object(Default::default()));

    let raw_query: Option<String> = parts.uri.query().map(|s| s.to_string());
    if let Some(query_str) = raw_query.as_deref() {
        if let Ok(pairs) = serde_urlencoded::from_str::<Vec<(String, String)>>(query_str) {
            if let Some(merged) = merged_params.as_object_mut() {
                for (k, v) in pairs {
                    if let Ok(n) = v.parse::<f64>() {
                        merged.insert(k, serde_json::json!(n));
                    } else if v.eq_ignore_ascii_case("true") || v.eq_ignore_ascii_case("false") {
                        merged.insert(k, serde_json::json!(v.eq_ignore_ascii_case("true")));
                    } else {
                        merged.insert(k, Value::String(v));
                    }
                }
            }
        }
    }

    // Always capture raw body bytes and content-type for downstream decoding
    let headers = parts.headers.clone();
    let content_type_opt: Option<String> = headers
        .get(axum::http::header::CONTENT_TYPE)
        .and_then(|v| v.to_str().ok())
        .map(|s| s.to_string());

    let body_bytes = match axum::body::to_bytes(body, usize::MAX).await {
        Ok(bytes) => bytes.to_vec(),
        Err(e) => {
            return Err((
                axum::http::StatusCode::INTERNAL_SERVER_ERROR,
                format!("Failed to read request body: {}", e),
            )
                .into_response())
        }
    };

    // 不再注入 Authorization 到 params,鉴权改由 handlers 通过 HeaderMap 读取

    Ok((merged_params, headers, raw_query, body_bytes, content_type_opt))
}

#[derive(Default, Clone)]
pub struct RestRouterBuilder {
    openapi: Option<OpenApi>,
    router_state: Option<Arc<dyn std::any::Any + Send + Sync>>, 
}

impl RestRouterBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn openapi(mut self, openapi: OpenApi) -> Self {
        self.openapi = Some(openapi);
        self
    }

    /// 为运行时执行提供可选的全局 Router 状态(以 &dyn Any 形式透传到宏生成的执行函数)。
    pub fn with_state<S: Send + Sync + 'static>(mut self, state: S) -> Self {
        self.router_state = Some(Arc::new(state));
        self
    }

    pub fn build<S: Send + Sync + Clone + 'static>(self) -> Result<Router<S>> {
        let openapi = self.openapi.ok_or_else(|| {
            Error::SpecError("OpenAPI document not provided".to_string())
        })?;
        let mut handler_map: std::collections::HashMap<&'static str, fn(&serde_json::Value, HeaderMap, Option<String>, Option<Arc<dyn std::any::Any + Send + Sync>>, Vec<u8>, Option<String>) -> crate::handler::DynHandlerFuture> = std::collections::HashMap::new();
        for inv in inventory::iter::<ApiHandlerInventory> {
            handler_map.insert(inv.operation_id, inv.handler);
        }

        // 调试:确认 inventory 中是否已注册 callback-v2 的处理器
        {
            let has_cb2 = handler_map.contains_key("connections_callback_v2");
            eprintln!("[service_kit][debug] inventory.has(callback_v2)={}", has_cb2);
        }

        let mut router: Router<S> = Router::new();
        let router_state_outer = self.router_state.clone();

        for (path, path_item) in openapi.paths.paths.iter() {
            for (method, operation) in operations_from_path_item(path_item) {
                if path == "/api/v1/connections/callback-v2" {
                    eprintln!(
                        "[service_kit][debug] scanning path='{}' method='{:?}' op_id={:?}",
                        path,
                        method,
                        operation.operation_id
                    );
                }
                if let Some(op_id) = operation.operation_id.as_deref() {
                    if let Some(handler_fn) = handler_map.get(op_id) {
                        if path == "/api/v1/connections/callback-v2" {
                            eprintln!(
                                "[service_kit][debug] handler found for op_id='{}' -> registering route",
                                op_id
                            );
                        }
                        let handler_fn = *handler_fn;
                        let router_state_for_route = router_state_outer.clone();
                        let path_for_handler = path.clone();
                        let route_handler = move |req: Request<Body>| async move {
                            if path_for_handler == "/api/v1/connections/callback-v2" {
                                let m = req.method().clone();
                                eprintln!(
                                    "[service_kit][debug] route_handler invoked path='{}' method='{}'",
                                    path_for_handler,
                                    m
                                );
                            }
                            match extract_and_merge_params(req).await {
                                Ok((params, headers, raw_query, body_bytes, content_type)) => {
                                    let router_state_cloned = router_state_for_route.clone();
                                    match handler_fn(&params, headers, raw_query, router_state_cloned, body_bytes, content_type).await {
                                        Ok(resp) => resp,
                                        Err(e) => e.into_response(),
                                    }
                                },
                                Err(response) => response,
                            }
                        };

                        let axum_path = path.clone();
                        router = router.route(&axum_path, on(method, route_handler));
                    } else {
                        if path == "/api/v1/connections/callback-v2" {
                            eprintln!(
                                "[service_kit][debug] handler NOT found for op_id='{}' at path='{}' method='{:?}'",
                                op_id,
                                path,
                                method
                            );
                        }
                        eprintln!(
                            "[service_kit] warning: handler not found for operation_id '{}', path '{}', method '{:?}'",
                            op_id, path, method
                        );
                    }
                }
            }
        }
        Ok(router)
    }
}

fn operations_from_path_item(path_item: &PathItem) -> Vec<(MethodFilter, &utoipa::openapi::path::Operation)> {
    let mut operations = Vec::new();
    if let Some(op) = &path_item.get { operations.push((MethodFilter::GET, op)); }
    if let Some(op) = &path_item.post { operations.push((MethodFilter::POST, op)); }
    if let Some(op) = &path_item.put { operations.push((MethodFilter::PUT, op)); }
    if let Some(op) = &path_item.delete { operations.push((MethodFilter::DELETE, op)); }
    if let Some(op) = &path_item.patch { operations.push((MethodFilter::PATCH, op)); }
    operations
}