Skip to main content

systemprompt_agent/services/a2a_server/errors/
jsonrpc.rs

1use crate::models::a2a::jsonrpc::NumberOrString;
2use axum::http::StatusCode;
3use serde_json::{json, Value};
4use systemprompt_logging::LogLevel;
5use systemprompt_traits::RepositoryError;
6
7pub fn classify_database_error(error: &RepositoryError) -> String {
8    let error_str = error.to_string();
9
10    if error_str.contains("FOREIGN KEY constraint failed") {
11        format!(
12            "Database constraint error: Referenced entity does not exist - {}",
13            error
14        )
15    } else if error_str.contains("UNIQUE constraint failed") {
16        format!("Database constraint error: Duplicate entry - {error}")
17    } else if error_str.contains("NOT NULL constraint failed") {
18        format!(
19            "Database constraint error: Required field missing - {}",
20            error
21        )
22    } else {
23        format!("Database error: {error}")
24    }
25}
26
27#[derive(Debug)]
28pub struct JsonRpcErrorBuilder {
29    code: i32,
30    message: String,
31    data: Option<Value>,
32    log_message: Option<String>,
33    log_level: LogLevel,
34}
35
36impl JsonRpcErrorBuilder {
37    pub fn new(code: i32, message: impl Into<String>) -> Self {
38        Self {
39            code,
40            message: message.into(),
41            data: None,
42            log_message: None,
43            log_level: LogLevel::Error,
44        }
45    }
46
47    pub fn with_data(mut self, data: Value) -> Self {
48        self.data = Some(data);
49        self
50    }
51
52    pub fn with_log(mut self, message: impl Into<String>, level: LogLevel) -> Self {
53        self.log_message = Some(message.into());
54        self.log_level = level;
55        self
56    }
57
58    pub fn log_error(mut self, message: impl Into<String>) -> Self {
59        self.log_message = Some(message.into());
60        self.log_level = LogLevel::Error;
61        self
62    }
63
64    pub fn log_warn(mut self, message: impl Into<String>) -> Self {
65        self.log_message = Some(message.into());
66        self.log_level = LogLevel::Warn;
67        self
68    }
69
70    pub async fn build(self, request_id: &NumberOrString) -> Value {
71        if let Some(log_msg) = self.log_message {
72            match self.log_level {
73                LogLevel::Error => {
74                    tracing::error!(topic = "a2a_jsonrpc", "{}", log_msg);
75                },
76                LogLevel::Warn => {
77                    tracing::warn!(topic = "a2a_jsonrpc", "{}", log_msg);
78                },
79                LogLevel::Info => {
80                    tracing::info!(topic = "a2a_jsonrpc", "{}", log_msg);
81                },
82                LogLevel::Debug => {
83                    tracing::debug!(topic = "a2a_jsonrpc", "{}", log_msg);
84                },
85                LogLevel::Trace => {
86                    tracing::trace!(topic = "a2a_jsonrpc", "{}", log_msg);
87                },
88            }
89        }
90
91        let mut error = json!({
92            "code": self.code,
93            "message": self.message
94        });
95
96        if let Some(data) = self.data {
97            error["data"] = data;
98        }
99
100        json!({
101            "jsonrpc": "2.0",
102            "error": error,
103            "id": request_id
104        })
105    }
106
107    pub async fn build_with_status(self, request_id: &NumberOrString) -> (StatusCode, Value) {
108        let status = match self.code {
109            -32600 => StatusCode::BAD_REQUEST,
110            -32601 => StatusCode::NOT_FOUND,
111            -32602 => StatusCode::BAD_REQUEST,
112            -32603 => StatusCode::INTERNAL_SERVER_ERROR,
113            -32700 => StatusCode::BAD_REQUEST,
114            _ => StatusCode::INTERNAL_SERVER_ERROR,
115        };
116
117        (status, self.build(request_id).await)
118    }
119
120    pub fn invalid_request() -> Self {
121        Self::new(-32600, "Invalid Request")
122    }
123
124    pub fn method_not_found() -> Self {
125        Self::new(-32601, "Method not found")
126    }
127
128    pub fn invalid_params() -> Self {
129        Self::new(-32602, "Invalid params")
130    }
131
132    pub fn internal_error() -> Self {
133        Self::new(-32603, "Internal error")
134    }
135
136    pub fn parse_error() -> Self {
137        Self::new(-32700, "Parse error")
138    }
139
140    pub fn unauthorized(reason: impl Into<String>) -> Self {
141        Self::new(-32600, "Unauthorized").with_data(json!({
142            "reason": reason.into()
143        }))
144    }
145
146    pub fn forbidden(reason: impl Into<String>) -> Self {
147        Self::new(-32600, "Forbidden").with_data(json!({
148            "reason": reason.into()
149        }))
150    }
151}
152
153pub async fn unauthorized_response(
154    reason: impl Into<String>,
155    request_id: &NumberOrString,
156) -> (StatusCode, Value) {
157    let reason_str = reason.into();
158    (
159        StatusCode::UNAUTHORIZED,
160        JsonRpcErrorBuilder::unauthorized(&reason_str)
161            .log_warn(&reason_str)
162            .build(request_id)
163            .await,
164    )
165}
166
167pub async fn forbidden_response(
168    reason: impl Into<String>,
169    request_id: &NumberOrString,
170) -> (StatusCode, Value) {
171    let reason_str = reason.into();
172    (
173        StatusCode::FORBIDDEN,
174        JsonRpcErrorBuilder::forbidden(&reason_str)
175            .log_warn(&reason_str)
176            .build(request_id)
177            .await,
178    )
179}