Skip to main content

eln_plugin_sdk/
error.rs

1//! SDK 공통 에러.
2
3use serde_json::{Value, json};
4use thiserror::Error;
5
6use crate::Permissions;
7
8#[derive(Debug, Clone, PartialEq, Eq, Error)]
9#[error("permission denied: requires {required:?}, granted {granted:?}")]
10#[non_exhaustive]
11pub struct PermissionDenied {
12    pub required: Permissions,
13    pub granted: Permissions,
14}
15
16impl PermissionDenied {
17    pub fn new(required: Permissions, granted: Permissions) -> Self {
18        Self { required, granted }
19    }
20}
21
22#[derive(Debug, Error)]
23#[non_exhaustive]
24pub enum ToolError {
25    #[error(transparent)]
26    PermissionDenied(#[from] PermissionDenied),
27
28    #[error("invalid argument: {0}")]
29    InvalidArgument(String),
30
31    #[error("internal: {0}")]
32    Internal(String),
33}
34
35/// PermissionDenied용 JSON-RPC error code.
36///
37/// JSON-RPC `server-defined` 영역 (-32000 ~ -32099). 현 rmcp 1.5.0 확장
38/// (`RESOURCE_NOT_FOUND=-32002`, `URL_ELICITATION_REQUIRED=-32042`)과 겹치지 않음.
39/// 미래에 MCP/rmcp가 `-32001`을 예약할 수 있으므로 transport adapter는 코드만
40/// 보지 말고 `data.kind` 식별자도 함께 확인할 것 (codex review 1 권고).
41pub const ERROR_CODE_PERMISSION_DENIED: i32 = -32001;
42
43impl ToolError {
44    /// JSON-RPC error code 매핑 (transport adapter가 호출).
45    pub fn json_rpc_code(&self) -> i32 {
46        match self {
47            ToolError::PermissionDenied(_) => ERROR_CODE_PERMISSION_DENIED,
48            ToolError::InvalidArgument(_) => -32602,
49            ToolError::Internal(_) => -32603,
50        }
51    }
52
53    /// JSON-RPC `error.data` payload — kind 식별자 + 세부 필드.
54    ///
55    /// transport adapter가 error response 직렬화 시 첨부. `data.kind`로 코드 충돌
56    /// 가능성과 무관하게 SDK-level error를 식별 가능 (codex review 1 권고).
57    pub fn json_rpc_data(&self) -> Value {
58        match self {
59            ToolError::PermissionDenied(pd) => json!({
60                "kind": "permission_denied",
61                "required": pd.required.bits(),
62                "granted": pd.granted.bits(),
63            }),
64            ToolError::InvalidArgument(msg) => json!({
65                "kind": "invalid_argument",
66                "message": msg,
67            }),
68            ToolError::Internal(msg) => json!({
69                "kind": "internal",
70                "message": msg,
71            }),
72        }
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn permission_denied_json_rpc_data_carries_kind_and_bits() {
82        let err = ToolError::PermissionDenied(PermissionDenied {
83            required: Permissions::WRITE,
84            granted: Permissions::READ,
85        });
86        assert_eq!(err.json_rpc_code(), ERROR_CODE_PERMISSION_DENIED);
87        let data = err.json_rpc_data();
88        assert_eq!(data["kind"], "permission_denied");
89        assert_eq!(data["required"], Permissions::WRITE.bits());
90        assert_eq!(data["granted"], Permissions::READ.bits());
91    }
92}