1#![allow(dead_code)]
14
15use jsonrpsee::types::ErrorObjectOwned;
16use serde::{Deserialize, Serialize};
17use serde_json::{json, Value};
18
19pub mod codes {
21 pub const PARSE_ERROR: i32 = -32700;
23 pub const INVALID_REQUEST: i32 = -32600;
25 pub const METHOD_NOT_FOUND: i32 = -32601;
27 pub const INVALID_PARAMS: i32 = -32602;
29 pub const INTERNAL_ERROR: i32 = -32603;
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct TryRequest {
36 pub jsonrpc: String,
37 pub id: u64,
38 pub method: String,
39 #[serde(default, skip_serializing_if = "Vec::is_empty")]
40 pub params: Vec<Value>,
41}
42
43impl TryRequest {
44 pub fn schema() -> Self {
46 Self {
47 jsonrpc: "2.0".to_string(),
48 id: 1,
49 method: "plexus.schema".to_string(),
50 params: vec![],
51 }
52 }
53
54 pub fn method(method: &str, params: Vec<Value>) -> Self {
56 Self {
57 jsonrpc: "2.0".to_string(),
58 id: 1,
59 method: method.to_string(),
60 params,
61 }
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct GuidedErrorData {
68 #[serde(rename = "try")]
70 pub try_request: TryRequest,
71
72 #[serde(flatten)]
74 pub context: Value,
75}
76
77impl GuidedErrorData {
78 pub fn new(try_request: TryRequest) -> Self {
80 Self {
81 try_request,
82 context: json!({}),
83 }
84 }
85
86 pub fn with_context(try_request: TryRequest, context: Value) -> Self {
88 Self {
89 try_request,
90 context,
91 }
92 }
93}
94
95pub struct GuidedError;
97
98impl GuidedError {
99 pub fn parse_error(message: &str) -> ErrorObjectOwned {
101 let data = GuidedErrorData::new(TryRequest::schema());
102 ErrorObjectOwned::owned(
103 codes::PARSE_ERROR,
104 format!("Parse error: {}. This server speaks JSON-RPC 2.0 over WebSocket", message),
105 Some(data),
106 )
107 }
108
109 pub fn invalid_request(message: &str) -> ErrorObjectOwned {
111 let data = GuidedErrorData::new(TryRequest::schema());
112 ErrorObjectOwned::owned(
113 codes::INVALID_REQUEST,
114 format!("Invalid request: {}", message),
115 Some(data),
116 )
117 }
118
119 pub fn activation_not_found(activation: &str, available: Vec<String>) -> ErrorObjectOwned {
121 let data = GuidedErrorData::with_context(
122 TryRequest::schema(),
123 json!({
124 "activation": activation,
125 "available_activations": available,
126 }),
127 );
128 ErrorObjectOwned::owned(
129 codes::METHOD_NOT_FOUND,
130 format!("Activation '{}' not found", activation),
131 Some(data),
132 )
133 }
134
135 pub fn method_not_found(
137 activation: &str,
138 method: &str,
139 available_methods: Vec<String>,
140 example_method: Option<(&str, Vec<Value>)>,
141 ) -> ErrorObjectOwned {
142 let try_request = match example_method {
144 Some((method_name, params)) => TryRequest::method(method_name, params),
145 None => TryRequest::schema(),
146 };
147
148 let data = GuidedErrorData::with_context(
149 try_request,
150 json!({
151 "activation": activation,
152 "method": method,
153 "available_methods": available_methods,
154 }),
155 );
156 ErrorObjectOwned::owned(
157 codes::METHOD_NOT_FOUND,
158 format!("Method '{}' not found in activation '{}'", method, activation),
159 Some(data),
160 )
161 }
162
163 pub fn invalid_params(
165 method: &str,
166 message: &str,
167 usage: Option<&str>,
168 example: Option<TryRequest>,
169 ) -> ErrorObjectOwned {
170 let try_request = example.unwrap_or_else(TryRequest::schema);
171
172 let mut context = json!({
173 "method": method,
174 });
175
176 if let Some(usage_str) = usage {
177 context["usage"] = json!(usage_str);
178 }
179
180 let data = GuidedErrorData::with_context(try_request, context);
181 ErrorObjectOwned::owned(
182 codes::INVALID_PARAMS,
183 format!("Invalid params for {}: {}", method, message),
184 Some(data),
185 )
186 }
187
188 pub fn internal_error(message: &str) -> ErrorObjectOwned {
190 let data = GuidedErrorData::new(TryRequest::schema());
191 ErrorObjectOwned::owned(
192 codes::INTERNAL_ERROR,
193 format!("Internal error: {}", message),
194 Some(data),
195 )
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn test_parse_error_includes_try() {
205 let error = GuidedError::parse_error("invalid JSON");
206 let data: GuidedErrorData = serde_json::from_str(
207 error.data().unwrap().get()
208 ).unwrap();
209
210 assert_eq!(data.try_request.method, "plexus.schema");
211 }
212
213 #[test]
214 fn test_activation_not_found_includes_available() {
215 let error = GuidedError::activation_not_found(
216 "foo",
217 vec!["arbor".into(), "bash".into(), "health".into()],
218 );
219 let data: GuidedErrorData = serde_json::from_str(
220 error.data().unwrap().get()
221 ).unwrap();
222
223 assert_eq!(data.try_request.method, "plexus.schema");
224 assert_eq!(data.context["available_activations"].as_array().unwrap().len(), 3);
225 }
226
227 #[test]
228 fn test_method_not_found_with_example() {
229 let error = GuidedError::method_not_found(
230 "bash",
231 "foo",
232 vec!["execute".into()],
233 Some(("bash_execute", vec![json!("echo hello")])),
234 );
235 let data: GuidedErrorData = serde_json::from_str(
236 error.data().unwrap().get()
237 ).unwrap();
238
239 assert_eq!(data.try_request.method, "bash_execute");
241 assert_eq!(data.try_request.params[0], json!("echo hello"));
242 }
243}