spikard 0.6.2 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +90 -508
  3. data/ext/spikard_rb/Cargo.lock +3287 -0
  4. data/ext/spikard_rb/Cargo.toml +1 -1
  5. data/ext/spikard_rb/extconf.rb +3 -3
  6. data/lib/spikard/app.rb +72 -49
  7. data/lib/spikard/background.rb +38 -7
  8. data/lib/spikard/testing.rb +42 -4
  9. data/lib/spikard/version.rb +1 -1
  10. data/sig/spikard.rbs +4 -0
  11. data/vendor/crates/spikard-bindings-shared/Cargo.toml +1 -1
  12. data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +191 -0
  13. data/vendor/crates/spikard-core/Cargo.toml +1 -1
  14. data/vendor/crates/spikard-core/src/http.rs +1 -0
  15. data/vendor/crates/spikard-core/src/lifecycle.rs +63 -0
  16. data/vendor/crates/spikard-core/tests/bindings_response_tests.rs +136 -0
  17. data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +37 -0
  18. data/vendor/crates/spikard-core/tests/error_mapper.rs +761 -0
  19. data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +106 -0
  20. data/vendor/crates/spikard-core/tests/parameters_full.rs +701 -0
  21. data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +301 -0
  22. data/vendor/crates/spikard-core/tests/request_data_roundtrip.rs +67 -0
  23. data/vendor/crates/spikard-core/tests/validation_coverage.rs +250 -0
  24. data/vendor/crates/spikard-core/tests/validation_error_paths.rs +45 -0
  25. data/vendor/crates/spikard-http/Cargo.toml +1 -1
  26. data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +502 -0
  27. data/vendor/crates/spikard-http/src/jsonrpc/method_registry.rs +648 -0
  28. data/vendor/crates/spikard-http/src/jsonrpc/mod.rs +58 -0
  29. data/vendor/crates/spikard-http/src/jsonrpc/protocol.rs +1207 -0
  30. data/vendor/crates/spikard-http/src/jsonrpc/router.rs +2262 -0
  31. data/vendor/crates/spikard-http/src/testing/test_client.rs +155 -2
  32. data/vendor/crates/spikard-http/src/testing.rs +171 -0
  33. data/vendor/crates/spikard-http/src/websocket.rs +79 -6
  34. data/vendor/crates/spikard-http/tests/auth_integration.rs +647 -0
  35. data/vendor/crates/spikard-http/tests/common/test_builders.rs +633 -0
  36. data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +162 -0
  37. data/vendor/crates/spikard-http/tests/middleware_stack_integration.rs +389 -0
  38. data/vendor/crates/spikard-http/tests/request_extraction_full.rs +513 -0
  39. data/vendor/crates/spikard-http/tests/server_auth_middleware_behavior.rs +244 -0
  40. data/vendor/crates/spikard-http/tests/server_configured_router_behavior.rs +200 -0
  41. data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +82 -0
  42. data/vendor/crates/spikard-http/tests/server_handler_wrappers.rs +464 -0
  43. data/vendor/crates/spikard-http/tests/server_method_router_additional_behavior.rs +286 -0
  44. data/vendor/crates/spikard-http/tests/server_method_router_coverage.rs +118 -0
  45. data/vendor/crates/spikard-http/tests/server_middleware_behavior.rs +99 -0
  46. data/vendor/crates/spikard-http/tests/server_middleware_branches.rs +206 -0
  47. data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +281 -0
  48. data/vendor/crates/spikard-http/tests/server_router_behavior.rs +121 -0
  49. data/vendor/crates/spikard-http/tests/sse_full_behavior.rs +584 -0
  50. data/vendor/crates/spikard-http/tests/sse_handler_behavior.rs +130 -0
  51. data/vendor/crates/spikard-http/tests/test_client_requests.rs +167 -0
  52. data/vendor/crates/spikard-http/tests/testing_helpers.rs +87 -0
  53. data/vendor/crates/spikard-http/tests/testing_module_coverage.rs +156 -0
  54. data/vendor/crates/spikard-http/tests/urlencoded_content_type.rs +82 -0
  55. data/vendor/crates/spikard-http/tests/websocket_full_behavior.rs +440 -0
  56. data/vendor/crates/spikard-http/tests/websocket_integration.rs +152 -0
  57. data/vendor/crates/spikard-rb/Cargo.toml +1 -1
  58. data/vendor/crates/spikard-rb/src/gvl.rs +80 -0
  59. data/vendor/crates/spikard-rb/src/handler.rs +12 -9
  60. data/vendor/crates/spikard-rb/src/lib.rs +137 -124
  61. data/vendor/crates/spikard-rb/src/request.rs +342 -0
  62. data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +1 -8
  63. data/vendor/crates/spikard-rb/src/server.rs +1 -8
  64. data/vendor/crates/spikard-rb/src/testing/client.rs +168 -9
  65. data/vendor/crates/spikard-rb/src/websocket.rs +119 -30
  66. data/vendor/crates/spikard-rb-macros/Cargo.toml +14 -0
  67. data/vendor/crates/spikard-rb-macros/src/lib.rs +52 -0
  68. metadata +44 -1
@@ -0,0 +1,1207 @@
1
+ //! JSON-RPC 2.0 Protocol Types
2
+ //!
3
+ //! This module provides type definitions for the JSON-RPC 2.0 specification.
4
+ //! See [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification)
5
+ //!
6
+ //! # Overview
7
+ //!
8
+ //! JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol.
9
+ //! This module implements the complete specification including:
10
+ //!
11
+ //! - Request/Response messages
12
+ //! - Standard error codes
13
+ //! - Helper constructors for building valid messages
14
+ //! - Full serialization support via serde
15
+
16
+ use serde::{Deserialize, Serialize};
17
+ use serde_json::Value;
18
+
19
+ /// Maximum allowed length for a JSON-RPC method name
20
+ const MAX_METHOD_NAME_LENGTH: usize = 255;
21
+
22
+ /// Validates a JSON-RPC method name for security and correctness
23
+ ///
24
+ /// This function prevents DoS attacks through method name validation by enforcing:
25
+ /// - Maximum length of 255 characters to prevent resource exhaustion
26
+ /// - Only allowed characters: alphanumeric (a-z, A-Z, 0-9), dot (.), underscore (_), hyphen (-)
27
+ /// - No control characters (0x00-0x1F, 0x7F) to prevent injection attacks
28
+ /// - No leading or trailing whitespace to ensure proper formatting
29
+ ///
30
+ /// # Arguments
31
+ ///
32
+ /// * `method_name` - The method name to validate
33
+ ///
34
+ /// # Returns
35
+ ///
36
+ /// * `Ok(())` - If the method name is valid
37
+ /// * `Err(String)` - If the method name is invalid with a descriptive error message
38
+ ///
39
+ /// # Example
40
+ ///
41
+ /// ```ignore
42
+ /// use spikard_http::jsonrpc::validate_method_name;
43
+ ///
44
+ /// // Valid method names
45
+ /// assert!(validate_method_name("user.getById").is_ok());
46
+ /// assert!(validate_method_name("calculate_sum").is_ok());
47
+ /// assert!(validate_method_name("api-v1-handler").is_ok());
48
+ /// assert!(validate_method_name("rpc1").is_ok());
49
+ ///
50
+ /// // Invalid method names
51
+ /// assert!(validate_method_name("").is_err()); // Empty
52
+ /// assert!(validate_method_name(" method").is_err()); // Leading whitespace
53
+ /// assert!(validate_method_name("method ").is_err()); // Trailing whitespace
54
+ /// assert!(validate_method_name("method\x00name").is_err()); // Control character
55
+ /// assert!(validate_method_name("a".repeat(256)).is_err()); // Too long
56
+ /// ```
57
+ pub fn validate_method_name(method_name: &str) -> Result<(), String> {
58
+ if method_name.is_empty() {
59
+ return Err("Method name cannot be empty".to_string());
60
+ }
61
+
62
+ if method_name.starts_with(char::is_whitespace) || method_name.ends_with(char::is_whitespace) {
63
+ return Err("Method name cannot have leading or trailing whitespace".to_string());
64
+ }
65
+
66
+ if method_name.len() > MAX_METHOD_NAME_LENGTH {
67
+ return Err(format!(
68
+ "Method name exceeds maximum length of {} characters (got {})",
69
+ MAX_METHOD_NAME_LENGTH,
70
+ method_name.len()
71
+ ));
72
+ }
73
+
74
+ for ch in method_name.chars() {
75
+ match ch {
76
+ 'a'..='z' | 'A'..='Z' | '0'..='9' => {}
77
+ '.' => {}
78
+ '_' => {}
79
+ '-' => {}
80
+ c if (c as u32) < 0x20 || (c as u32) == 0x7F => {
81
+ return Err(format!(
82
+ "Method name contains invalid control character: 0x{:02X}",
83
+ c as u32
84
+ ));
85
+ }
86
+ c => {
87
+ return Err(format!(
88
+ "Method name contains invalid character: '{}' (0x{:02X}). \
89
+ Only alphanumeric, dot (.), underscore (_), and hyphen (-) are allowed",
90
+ c, c as u32
91
+ ));
92
+ }
93
+ }
94
+ }
95
+
96
+ Ok(())
97
+ }
98
+
99
+ /// JSON-RPC 2.0 Request
100
+ ///
101
+ /// Represents a JSON-RPC request method invocation with optional parameters and identifier.
102
+ ///
103
+ /// # Fields
104
+ ///
105
+ /// * `jsonrpc` - A String specifying the JSON-RPC version. MUST be exactly "2.0"
106
+ /// * `method` - A String containing the name of the method to be invoked
107
+ /// * `params` - Optional structured data that serves as arguments to the method.
108
+ /// The order of the objects in the Array is significant to the method.
109
+ /// * `id` - A value which is used to match the response with the request that it is replying to.
110
+ /// Can be a string, number, or NULL. Notifications MUST NOT include an "id".
111
+ ///
112
+ /// # Example
113
+ ///
114
+ /// ```ignore
115
+ /// use serde_json::json;
116
+ /// use spikard_http::jsonrpc::JsonRpcRequest;
117
+ ///
118
+ /// // Request with parameters and ID
119
+ /// let req = JsonRpcRequest::new("add", Some(json!([1, 2])), Some(json!(1)));
120
+ /// assert!(!req.is_notification());
121
+ ///
122
+ /// // Notification (no ID)
123
+ /// let notif = JsonRpcRequest::new("notify", Some(json!({})), None);
124
+ /// assert!(notif.is_notification());
125
+ /// ```
126
+ #[derive(Debug, Clone, Serialize, Deserialize)]
127
+ pub struct JsonRpcRequest {
128
+ /// JSON-RPC version, must be "2.0"
129
+ pub jsonrpc: String,
130
+
131
+ /// The name of the method to invoke
132
+ pub method: String,
133
+
134
+ /// Optional parameters for the method
135
+ #[serde(skip_serializing_if = "Option::is_none")]
136
+ pub params: Option<Value>,
137
+
138
+ /// Optional request identifier. When absent, this is a notification.
139
+ #[serde(skip_serializing_if = "Option::is_none")]
140
+ pub id: Option<Value>,
141
+ }
142
+
143
+ impl JsonRpcRequest {
144
+ /// Creates a new JSON-RPC 2.0 request
145
+ ///
146
+ /// # Arguments
147
+ ///
148
+ /// * `method` - The method name to invoke
149
+ /// * `params` - Optional parameters (can be array, object, or null)
150
+ /// * `id` - Optional request identifier (string, number, or null)
151
+ ///
152
+ /// # Example
153
+ ///
154
+ /// ```ignore
155
+ /// use serde_json::json;
156
+ /// use spikard_http::jsonrpc::JsonRpcRequest;
157
+ ///
158
+ /// let req = JsonRpcRequest::new("subtract", Some(json!({"a": 5, "b": 3})), Some(json!(2)));
159
+ /// assert_eq!(req.method, "subtract");
160
+ /// assert!(!req.is_notification());
161
+ /// ```
162
+ pub fn new(method: impl Into<String>, params: Option<Value>, id: Option<Value>) -> Self {
163
+ Self {
164
+ jsonrpc: "2.0".to_string(),
165
+ method: method.into(),
166
+ params,
167
+ id,
168
+ }
169
+ }
170
+
171
+ /// Checks if this request is a notification
172
+ ///
173
+ /// A notification is a JSON-RPC request without an "id" field.
174
+ /// The server MUST NOT reply to a notification.
175
+ ///
176
+ /// # Example
177
+ ///
178
+ /// ```ignore
179
+ /// use spikard_http::jsonrpc::JsonRpcRequest;
180
+ ///
181
+ /// let req = JsonRpcRequest::new("method", None, Some(serde_json::json!(1)));
182
+ /// assert!(!req.is_notification());
183
+ ///
184
+ /// let notif = JsonRpcRequest::new("notify", None, None);
185
+ /// assert!(notif.is_notification());
186
+ /// ```
187
+ pub fn is_notification(&self) -> bool {
188
+ self.id.is_none()
189
+ }
190
+ }
191
+
192
+ /// JSON-RPC 2.0 Success Response
193
+ ///
194
+ /// Represents a successful JSON-RPC response containing the result of the method invocation.
195
+ ///
196
+ /// # Fields
197
+ ///
198
+ /// * `jsonrpc` - A String specifying the JSON-RPC version. MUST be exactly "2.0"
199
+ /// * `result` - The result of the method invocation. This MUST be null in case of an error.
200
+ /// * `id` - This MUST be the same id as the request it is responding to
201
+ ///
202
+ /// # Example
203
+ ///
204
+ /// ```ignore
205
+ /// use serde_json::json;
206
+ /// use spikard_http::jsonrpc::JsonRpcResponse;
207
+ ///
208
+ /// let response = JsonRpcResponse::success(json!(19), json!(1));
209
+ /// assert_eq!(response.jsonrpc, "2.0");
210
+ /// ```
211
+ #[derive(Debug, Clone, Serialize, Deserialize)]
212
+ pub struct JsonRpcResponse {
213
+ /// JSON-RPC version, must be "2.0"
214
+ pub jsonrpc: String,
215
+
216
+ /// The result of the method invocation
217
+ pub result: Value,
218
+
219
+ /// The request identifier this response corresponds to
220
+ pub id: Value,
221
+ }
222
+
223
+ impl JsonRpcResponse {
224
+ /// Creates a new JSON-RPC 2.0 success response
225
+ ///
226
+ /// # Arguments
227
+ ///
228
+ /// * `result` - The result value from the method invocation
229
+ /// * `id` - The request identifier from the original request
230
+ ///
231
+ /// # Example
232
+ ///
233
+ /// ```ignore
234
+ /// use serde_json::json;
235
+ /// use spikard_http::jsonrpc::JsonRpcResponse;
236
+ ///
237
+ /// let response = JsonRpcResponse::success(json!({"sum": 7}), json!("abc"));
238
+ /// assert_eq!(response.jsonrpc, "2.0");
239
+ /// ```
240
+ pub fn success(result: Value, id: Value) -> Self {
241
+ Self {
242
+ jsonrpc: "2.0".to_string(),
243
+ result,
244
+ id,
245
+ }
246
+ }
247
+ }
248
+
249
+ /// JSON-RPC 2.0 Error Object
250
+ ///
251
+ /// Represents a JSON-RPC error that occurred during method invocation.
252
+ ///
253
+ /// # Fields
254
+ ///
255
+ /// * `code` - A Number that indicates the error type that occurred
256
+ /// * `message` - A String providing a short description of the error
257
+ /// * `data` - Optional additional error information
258
+ ///
259
+ /// # Standard Error Codes
260
+ ///
261
+ /// - `-32700`: Parse error
262
+ /// - `-32600`: Invalid Request
263
+ /// - `-32601`: Method not found
264
+ /// - `-32602`: Invalid params
265
+ /// - `-32603`: Internal error
266
+ /// - `-32000 to -32099`: Server error (reserved)
267
+ ///
268
+ /// # Example
269
+ ///
270
+ /// ```ignore
271
+ /// use spikard_http::jsonrpc::{JsonRpcError, error_codes};
272
+ ///
273
+ /// let err = JsonRpcError {
274
+ /// code: error_codes::INVALID_PARAMS,
275
+ /// message: "Invalid method parameters".to_string(),
276
+ /// data: None,
277
+ /// };
278
+ /// ```
279
+ #[derive(Debug, Clone, Serialize, Deserialize)]
280
+ pub struct JsonRpcError {
281
+ /// Numeric error code
282
+ pub code: i32,
283
+
284
+ /// Human-readable error description
285
+ pub message: String,
286
+
287
+ /// Optional additional error context
288
+ #[serde(skip_serializing_if = "Option::is_none")]
289
+ pub data: Option<Value>,
290
+ }
291
+
292
+ /// JSON-RPC 2.0 Error Response
293
+ ///
294
+ /// Represents a JSON-RPC response containing an error result.
295
+ ///
296
+ /// # Fields
297
+ ///
298
+ /// * `jsonrpc` - A String specifying the JSON-RPC version. MUST be exactly "2.0"
299
+ /// * `error` - An Error Object with error information
300
+ /// * `id` - This MUST be the same id as the request it is responding to
301
+ ///
302
+ /// # Example
303
+ ///
304
+ /// ```ignore
305
+ /// use serde_json::json;
306
+ /// use spikard_http::jsonrpc::{JsonRpcErrorResponse, error_codes};
307
+ ///
308
+ /// let err_response = JsonRpcErrorResponse::error(
309
+ /// error_codes::METHOD_NOT_FOUND,
310
+ /// "Method not found",
311
+ /// json!(1)
312
+ /// );
313
+ /// assert_eq!(err_response.jsonrpc, "2.0");
314
+ /// ```
315
+ #[derive(Debug, Clone, Serialize, Deserialize)]
316
+ pub struct JsonRpcErrorResponse {
317
+ /// JSON-RPC version, must be "2.0"
318
+ pub jsonrpc: String,
319
+
320
+ /// Error object containing error information
321
+ pub error: JsonRpcError,
322
+
323
+ /// The request identifier this response corresponds to
324
+ pub id: Value,
325
+ }
326
+
327
+ impl JsonRpcErrorResponse {
328
+ /// Creates a new JSON-RPC 2.0 error response
329
+ ///
330
+ /// # Arguments
331
+ ///
332
+ /// * `code` - The numeric error code
333
+ /// * `message` - The error message
334
+ /// * `id` - The request identifier from the original request
335
+ ///
336
+ /// # Example
337
+ ///
338
+ /// ```ignore
339
+ /// use serde_json::json;
340
+ /// use spikard_http::jsonrpc::{JsonRpcErrorResponse, error_codes};
341
+ ///
342
+ /// let response = JsonRpcErrorResponse::error(
343
+ /// error_codes::METHOD_NOT_FOUND,
344
+ /// "Unknown method",
345
+ /// json!(null)
346
+ /// );
347
+ /// assert_eq!(response.jsonrpc, "2.0");
348
+ /// ```
349
+ pub fn error(code: i32, message: impl Into<String>, id: Value) -> Self {
350
+ Self {
351
+ jsonrpc: "2.0".to_string(),
352
+ error: JsonRpcError {
353
+ code,
354
+ message: message.into(),
355
+ data: None,
356
+ },
357
+ id,
358
+ }
359
+ }
360
+
361
+ /// Creates a new JSON-RPC 2.0 error response with additional error data
362
+ ///
363
+ /// # Arguments
364
+ ///
365
+ /// * `code` - The numeric error code
366
+ /// * `message` - The error message
367
+ /// * `data` - Additional context about the error
368
+ /// * `id` - The request identifier from the original request
369
+ ///
370
+ /// # Example
371
+ ///
372
+ /// ```ignore
373
+ /// use serde_json::json;
374
+ /// use spikard_http::jsonrpc::{JsonRpcErrorResponse, error_codes};
375
+ ///
376
+ /// let response = JsonRpcErrorResponse::error_with_data(
377
+ /// error_codes::INVALID_PARAMS,
378
+ /// "Invalid method parameters",
379
+ /// json!({"reason": "Missing required field 'name'"}),
380
+ /// json!(1)
381
+ /// );
382
+ /// assert_eq!(response.jsonrpc, "2.0");
383
+ /// assert!(response.error.data.is_some());
384
+ /// ```
385
+ pub fn error_with_data(code: i32, message: impl Into<String>, data: Value, id: Value) -> Self {
386
+ Self {
387
+ jsonrpc: "2.0".to_string(),
388
+ error: JsonRpcError {
389
+ code,
390
+ message: message.into(),
391
+ data: Some(data),
392
+ },
393
+ id,
394
+ }
395
+ }
396
+ }
397
+
398
+ /// JSON-RPC 2.0 Response Type
399
+ ///
400
+ /// An enum that represents either a successful response or an error response.
401
+ /// This is useful for untagged deserialization and handling both response types uniformly.
402
+ ///
403
+ /// # Variants
404
+ ///
405
+ /// * `Success(JsonRpcResponse)` - A successful response with a result
406
+ /// * `Error(JsonRpcErrorResponse)` - An error response with error details
407
+ ///
408
+ /// # Example
409
+ ///
410
+ /// ```ignore
411
+ /// use serde_json::json;
412
+ /// use spikard_http::jsonrpc::{JsonRpcResponseType, JsonRpcResponse, JsonRpcErrorResponse, error_codes};
413
+ ///
414
+ /// let success = JsonRpcResponseType::Success(
415
+ /// JsonRpcResponse::success(json!(42), json!(1))
416
+ /// );
417
+ ///
418
+ /// let error = JsonRpcResponseType::Error(
419
+ /// JsonRpcErrorResponse::error(error_codes::INVALID_PARAMS, "Bad params", json!(1))
420
+ /// );
421
+ /// ```
422
+ #[derive(Debug, Clone, Serialize, Deserialize)]
423
+ #[serde(untagged)]
424
+ pub enum JsonRpcResponseType {
425
+ /// Successful response containing a result
426
+ Success(JsonRpcResponse),
427
+
428
+ /// Error response containing error details
429
+ Error(JsonRpcErrorResponse),
430
+ }
431
+
432
+ /// JSON-RPC 2.0 Standard Error Codes
433
+ ///
434
+ /// This module contains the standard error codes defined by the JSON-RPC 2.0 specification.
435
+ /// The error codes from -32768 to -32000 are reserved for JSON-RPC specification use.
436
+ pub mod error_codes {
437
+ /// Parse error
438
+ ///
439
+ /// Invalid JSON was received by the server.
440
+ /// An error occurred on the server while parsing the JSON text.
441
+ pub const PARSE_ERROR: i32 = -32700;
442
+
443
+ /// Invalid Request
444
+ ///
445
+ /// The JSON sent is not a valid Request object.
446
+ pub const INVALID_REQUEST: i32 = -32600;
447
+
448
+ /// Method not found
449
+ ///
450
+ /// The method does not exist / is not available.
451
+ pub const METHOD_NOT_FOUND: i32 = -32601;
452
+
453
+ /// Invalid params
454
+ ///
455
+ /// Invalid method parameter(s).
456
+ pub const INVALID_PARAMS: i32 = -32602;
457
+
458
+ /// Internal error
459
+ ///
460
+ /// Internal JSON-RPC error.
461
+ pub const INTERNAL_ERROR: i32 = -32603;
462
+
463
+ /// Server error (base)
464
+ ///
465
+ /// Server errors are reserved for implementation-defined server-errors.
466
+ /// The error codes from -32099 to -32000 are reserved for server error codes.
467
+ pub const SERVER_ERROR_BASE: i32 = -32000;
468
+
469
+ /// Server error (end of reserved range)
470
+ pub const SERVER_ERROR_END: i32 = -32099;
471
+
472
+ /// Helper function to check if a code is a reserved server error code
473
+ pub fn is_server_error(code: i32) -> bool {
474
+ (SERVER_ERROR_END..=SERVER_ERROR_BASE).contains(&code)
475
+ }
476
+ }
477
+
478
+ #[cfg(test)]
479
+ mod tests {
480
+ use super::*;
481
+ use serde_json::json;
482
+
483
+ #[test]
484
+ fn test_jsonrpc_request_creation() {
485
+ let req = JsonRpcRequest::new("method", Some(json!({"key": "value"})), Some(json!(1)));
486
+ assert_eq!(req.jsonrpc, "2.0");
487
+ assert_eq!(req.method, "method");
488
+ assert!(!req.is_notification());
489
+ }
490
+
491
+ #[test]
492
+ fn test_jsonrpc_notification() {
493
+ let notif = JsonRpcRequest::new("notify", None, None);
494
+ assert!(notif.is_notification());
495
+ }
496
+
497
+ #[test]
498
+ fn test_jsonrpc_response_success() {
499
+ let response = JsonRpcResponse::success(json!(42), json!(1));
500
+ assert_eq!(response.jsonrpc, "2.0");
501
+ assert_eq!(response.result, json!(42));
502
+ assert_eq!(response.id, json!(1));
503
+ }
504
+
505
+ #[test]
506
+ fn test_jsonrpc_error_response() {
507
+ let err = JsonRpcErrorResponse::error(error_codes::METHOD_NOT_FOUND, "Method not found", json!(1));
508
+ assert_eq!(err.jsonrpc, "2.0");
509
+ assert_eq!(err.error.code, error_codes::METHOD_NOT_FOUND);
510
+ assert_eq!(err.error.message, "Method not found");
511
+ assert!(err.error.data.is_none());
512
+ }
513
+
514
+ #[test]
515
+ fn test_jsonrpc_error_response_with_data() {
516
+ let data = json!({"reason": "Missing parameter"});
517
+ let err = JsonRpcErrorResponse::error_with_data(
518
+ error_codes::INVALID_PARAMS,
519
+ "Invalid parameters",
520
+ data.clone(),
521
+ json!(null),
522
+ );
523
+ assert_eq!(err.error.code, error_codes::INVALID_PARAMS);
524
+ assert_eq!(err.error.data, Some(data));
525
+ }
526
+
527
+ #[test]
528
+ fn test_error_codes_constants() {
529
+ assert_eq!(error_codes::PARSE_ERROR, -32700);
530
+ assert_eq!(error_codes::INVALID_REQUEST, -32600);
531
+ assert_eq!(error_codes::METHOD_NOT_FOUND, -32601);
532
+ assert_eq!(error_codes::INVALID_PARAMS, -32602);
533
+ assert_eq!(error_codes::INTERNAL_ERROR, -32603);
534
+ }
535
+
536
+ #[test]
537
+ fn test_is_server_error() {
538
+ assert!(error_codes::is_server_error(-32000));
539
+ assert!(error_codes::is_server_error(-32050));
540
+ assert!(error_codes::is_server_error(-32099));
541
+ assert!(!error_codes::is_server_error(-32700));
542
+ assert!(!error_codes::is_server_error(0));
543
+ }
544
+
545
+ #[test]
546
+ fn test_request_serialization() {
547
+ let req = JsonRpcRequest::new("test", Some(json!([1, 2])), Some(json!(1)));
548
+ let json = serde_json::to_value(&req).unwrap();
549
+ assert_eq!(json["jsonrpc"], "2.0");
550
+ assert_eq!(json["method"], "test");
551
+ assert!(json["params"].is_array());
552
+ assert_eq!(json["id"], 1);
553
+ }
554
+
555
+ #[test]
556
+ fn test_notification_serialization() {
557
+ let notif = JsonRpcRequest::new("notify", Some(json!({})), None);
558
+ let json = serde_json::to_value(&notif).unwrap();
559
+ assert!(!json.get("id").is_some() || json["id"].is_null());
560
+ }
561
+
562
+ #[test]
563
+ fn test_response_serialization() {
564
+ let resp = JsonRpcResponse::success(json!({"result": 100}), json!("string-id"));
565
+ let json = serde_json::to_value(&resp).unwrap();
566
+ assert_eq!(json["jsonrpc"], "2.0");
567
+ assert_eq!(json["id"], "string-id");
568
+ }
569
+
570
+ #[test]
571
+ fn test_error_response_serialization() {
572
+ let err = JsonRpcErrorResponse::error(error_codes::PARSE_ERROR, "Parse error", json!(null));
573
+ let json = serde_json::to_value(&err).unwrap();
574
+ assert_eq!(json["jsonrpc"], "2.0");
575
+ assert_eq!(json["error"]["code"], -32700);
576
+ assert_eq!(json["error"]["message"], "Parse error");
577
+ }
578
+
579
+ #[test]
580
+ fn test_response_type_enum() {
581
+ let success_resp = JsonRpcResponseType::Success(JsonRpcResponse::success(json!(1), json!(1)));
582
+ let error_resp = JsonRpcResponseType::Error(JsonRpcErrorResponse::error(
583
+ error_codes::INVALID_REQUEST,
584
+ "Invalid",
585
+ json!(1),
586
+ ));
587
+
588
+ let _success_json = serde_json::to_value(&success_resp).unwrap();
589
+ let _error_json = serde_json::to_value(&error_resp).unwrap();
590
+ }
591
+
592
+ #[test]
593
+ fn test_validate_method_name_valid_simple() {
594
+ assert!(validate_method_name("test").is_ok());
595
+ assert!(validate_method_name("method").is_ok());
596
+ assert!(validate_method_name("rpc").is_ok());
597
+ }
598
+
599
+ #[test]
600
+ fn test_validate_method_name_valid_with_dot() {
601
+ assert!(validate_method_name("user.get").is_ok());
602
+ assert!(validate_method_name("api.v1.endpoint").is_ok());
603
+ assert!(validate_method_name("service.method.action").is_ok());
604
+ }
605
+
606
+ #[test]
607
+ fn test_validate_method_name_valid_with_underscore() {
608
+ assert!(validate_method_name("get_user").is_ok());
609
+ assert!(validate_method_name("_private_method").is_ok());
610
+ assert!(validate_method_name("method_v1").is_ok());
611
+ }
612
+
613
+ #[test]
614
+ fn test_validate_method_name_valid_with_hyphen() {
615
+ assert!(validate_method_name("get-user").is_ok());
616
+ assert!(validate_method_name("api-v1").is_ok());
617
+ assert!(validate_method_name("my-method-name").is_ok());
618
+ }
619
+
620
+ #[test]
621
+ fn test_validate_method_name_valid_with_numbers() {
622
+ assert!(validate_method_name("method1").is_ok());
623
+ assert!(validate_method_name("v2.endpoint").is_ok());
624
+ assert!(validate_method_name("rpc123abc").is_ok());
625
+ }
626
+
627
+ #[test]
628
+ fn test_validate_method_name_valid_mixed() {
629
+ assert!(validate_method_name("user.get_by_id").is_ok());
630
+ assert!(validate_method_name("api-v1.service_name").is_ok());
631
+ assert!(validate_method_name("Service_v1_2_3").is_ok());
632
+ }
633
+
634
+ #[test]
635
+ fn test_validate_method_name_valid_max_length() {
636
+ let max_name = "a".repeat(255);
637
+ assert!(validate_method_name(&max_name).is_ok());
638
+ }
639
+
640
+ #[test]
641
+ fn test_validate_method_name_empty() {
642
+ let result = validate_method_name("");
643
+ assert!(result.is_err());
644
+ assert!(result.unwrap_err().contains("cannot be empty"));
645
+ }
646
+
647
+ #[test]
648
+ fn test_validate_method_name_leading_space() {
649
+ let result = validate_method_name(" method");
650
+ assert!(result.is_err());
651
+ assert!(result.unwrap_err().contains("leading or trailing whitespace"));
652
+ }
653
+
654
+ #[test]
655
+ fn test_validate_method_name_trailing_space() {
656
+ let result = validate_method_name("method ");
657
+ assert!(result.is_err());
658
+ assert!(result.unwrap_err().contains("leading or trailing whitespace"));
659
+ }
660
+
661
+ #[test]
662
+ fn test_validate_method_name_leading_and_trailing_space() {
663
+ let result = validate_method_name(" method ");
664
+ assert!(result.is_err());
665
+ assert!(result.unwrap_err().contains("leading or trailing whitespace"));
666
+ }
667
+
668
+ #[test]
669
+ fn test_validate_method_name_internal_space() {
670
+ let result = validate_method_name("method name");
671
+ assert!(result.is_err());
672
+ assert!(result.unwrap_err().contains("invalid character"));
673
+ }
674
+
675
+ #[test]
676
+ fn test_validate_method_name_too_long() {
677
+ let too_long_name = "a".repeat(256);
678
+ let result = validate_method_name(&too_long_name);
679
+ assert!(result.is_err());
680
+ assert!(result.unwrap_err().contains("exceeds maximum length"));
681
+ }
682
+
683
+ #[test]
684
+ fn test_validate_method_name_null_byte() {
685
+ let result = validate_method_name("method\x00name");
686
+ assert!(result.is_err());
687
+ assert!(result.unwrap_err().contains("control character"));
688
+ }
689
+
690
+ #[test]
691
+ fn test_validate_method_name_newline() {
692
+ let result = validate_method_name("method\nname");
693
+ assert!(result.is_err());
694
+ assert!(result.unwrap_err().contains("control character"));
695
+ }
696
+
697
+ #[test]
698
+ fn test_validate_method_name_carriage_return() {
699
+ let result = validate_method_name("method\rname");
700
+ assert!(result.is_err());
701
+ assert!(result.unwrap_err().contains("control character"));
702
+ }
703
+
704
+ #[test]
705
+ fn test_validate_method_name_tab() {
706
+ let result = validate_method_name("method\tname");
707
+ assert!(result.is_err());
708
+ assert!(result.unwrap_err().contains("control character"));
709
+ }
710
+
711
+ #[test]
712
+ fn test_validate_method_name_delete_char() {
713
+ let result = validate_method_name("method\x7fname");
714
+ assert!(result.is_err());
715
+ assert!(result.unwrap_err().contains("control character"));
716
+ }
717
+
718
+ #[test]
719
+ fn test_validate_method_name_special_char_at_sign() {
720
+ let result = validate_method_name("method@name");
721
+ assert!(result.is_err());
722
+ assert!(result.unwrap_err().contains("invalid character"));
723
+ }
724
+
725
+ #[test]
726
+ fn test_validate_method_name_special_char_hash() {
727
+ let result = validate_method_name("method#name");
728
+ assert!(result.is_err());
729
+ assert!(result.unwrap_err().contains("invalid character"));
730
+ }
731
+
732
+ #[test]
733
+ fn test_validate_method_name_special_char_percent() {
734
+ let result = validate_method_name("method%name");
735
+ assert!(result.is_err());
736
+ assert!(result.unwrap_err().contains("invalid character"));
737
+ }
738
+
739
+ #[test]
740
+ fn test_validate_method_name_special_char_slash() {
741
+ let result = validate_method_name("method/name");
742
+ assert!(result.is_err());
743
+ assert!(result.unwrap_err().contains("invalid character"));
744
+ }
745
+
746
+ #[test]
747
+ fn test_validate_method_name_special_char_backslash() {
748
+ let result = validate_method_name("method\\name");
749
+ assert!(result.is_err());
750
+ assert!(result.unwrap_err().contains("invalid character"));
751
+ }
752
+
753
+ #[test]
754
+ fn test_validate_method_name_special_char_quote() {
755
+ let result = validate_method_name("method\"name");
756
+ assert!(result.is_err());
757
+ assert!(result.unwrap_err().contains("invalid character"));
758
+ }
759
+
760
+ #[test]
761
+ fn test_validate_method_name_dos_attack_very_long() {
762
+ let very_long = "a".repeat(10000);
763
+ let result = validate_method_name(&very_long);
764
+ assert!(result.is_err());
765
+ assert!(result.unwrap_err().contains("exceeds maximum length"));
766
+ }
767
+
768
+ #[test]
769
+ fn test_validate_method_name_dos_attack_control_chars() {
770
+ let result = validate_method_name("method\x00\x00\x00\x00");
771
+ assert!(result.is_err());
772
+ assert!(result.unwrap_err().contains("control character"));
773
+ }
774
+
775
+ #[test]
776
+ fn test_validate_method_name_edge_case_single_char() {
777
+ assert!(validate_method_name("a").is_ok());
778
+ assert!(validate_method_name("_").is_ok());
779
+ assert!(validate_method_name("-").is_ok());
780
+ assert!(validate_method_name(".").is_ok());
781
+ }
782
+
783
+ #[test]
784
+ fn test_request_with_null_id_is_notification() {
785
+ let json = json!({
786
+ "jsonrpc": "2.0",
787
+ "method": "notify",
788
+ "params": []
789
+ });
790
+
791
+ let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
792
+
793
+ assert!(request.is_notification());
794
+ assert_eq!(request.method, "notify");
795
+ }
796
+
797
+ #[test]
798
+ fn test_request_with_string_id_preserved() {
799
+ let json = json!({
800
+ "jsonrpc": "2.0",
801
+ "method": "add",
802
+ "params": [1, 2],
803
+ "id": "abc-123"
804
+ });
805
+
806
+ let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
807
+
808
+ assert!(!request.is_notification());
809
+ assert_eq!(request.id, Some(json!("abc-123")));
810
+ }
811
+
812
+ #[test]
813
+ fn test_request_with_zero_id_valid() {
814
+ let json = json!({
815
+ "jsonrpc": "2.0",
816
+ "method": "test",
817
+ "id": 0
818
+ });
819
+
820
+ let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
821
+
822
+ assert!(!request.is_notification());
823
+ assert_eq!(request.id, Some(json!(0)));
824
+ }
825
+
826
+ #[test]
827
+ fn test_request_with_negative_id_valid() {
828
+ let json = json!({
829
+ "jsonrpc": "2.0",
830
+ "method": "test",
831
+ "id": -999
832
+ });
833
+
834
+ let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
835
+
836
+ assert!(!request.is_notification());
837
+ assert_eq!(request.id, Some(json!(-999)));
838
+ }
839
+
840
+ #[test]
841
+ fn test_request_without_jsonrpc_field_invalid() {
842
+ let json = json!({
843
+ "method": "test",
844
+ "id": 1
845
+ });
846
+
847
+ let result: serde_json::Result<JsonRpcRequest> = serde_json::from_value(json);
848
+
849
+ assert!(result.is_err());
850
+ }
851
+
852
+ #[test]
853
+ fn test_response_preserves_id_type_numeric() {
854
+ let response = JsonRpcResponse::success(json!(42), json!(999));
855
+ let serialized = serde_json::to_value(&response).unwrap();
856
+
857
+ assert_eq!(serialized["id"], 999);
858
+ assert!(serialized["id"].is_number());
859
+ }
860
+
861
+ #[test]
862
+ fn test_error_response_never_has_result_field() {
863
+ let err = JsonRpcErrorResponse::error(error_codes::INVALID_PARAMS, "Bad params", json!(1));
864
+ let serialized = serde_json::to_value(&err).unwrap();
865
+
866
+ assert!(serialized.get("result").is_none());
867
+ assert!(serialized.get("error").is_some());
868
+ }
869
+
870
+ #[test]
871
+ fn test_success_response_never_has_error_field() {
872
+ let success = JsonRpcResponse::success(json!({"data": 123}), json!(1));
873
+ let serialized = serde_json::to_value(&success).unwrap();
874
+
875
+ assert!(serialized.get("error").is_none());
876
+ assert!(serialized.get("result").is_some());
877
+ }
878
+
879
+ #[test]
880
+ fn test_params_array_type() {
881
+ let json = json!({
882
+ "jsonrpc": "2.0",
883
+ "method": "sum",
884
+ "params": [1, 2, 3, 4, 5],
885
+ "id": 1
886
+ });
887
+
888
+ let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
889
+
890
+ assert!(request.params.is_some());
891
+ let params = request.params.unwrap();
892
+ assert!(params.is_array());
893
+ let arr = params.as_array().unwrap();
894
+ assert_eq!(arr.len(), 5);
895
+ assert_eq!(arr[0], json!(1));
896
+ }
897
+
898
+ #[test]
899
+ fn test_params_object_type() {
900
+ let json = json!({
901
+ "jsonrpc": "2.0",
902
+ "method": "subtract",
903
+ "params": {"a": 5, "b": 3},
904
+ "id": 2
905
+ });
906
+
907
+ let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
908
+
909
+ assert!(request.params.is_some());
910
+ let params = request.params.unwrap();
911
+ assert!(params.is_object());
912
+ let obj = params.as_object().unwrap();
913
+ assert_eq!(obj.get("a"), Some(&json!(5)));
914
+ assert_eq!(obj.get("b"), Some(&json!(3)));
915
+ }
916
+
917
+ #[test]
918
+ fn test_params_null_type() {
919
+ let json_no_params = json!({
920
+ "jsonrpc": "2.0",
921
+ "method": "test",
922
+ "id": 3
923
+ });
924
+
925
+ let request: JsonRpcRequest = serde_json::from_value(json_no_params).unwrap();
926
+ assert!(request.params.is_none());
927
+ }
928
+
929
+ #[test]
930
+ fn test_params_primitive_string() {
931
+ let json = json!({
932
+ "jsonrpc": "2.0",
933
+ "method": "echo",
934
+ "params": "hello world",
935
+ "id": 4
936
+ });
937
+
938
+ let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
939
+
940
+ assert!(request.params.is_some());
941
+ assert_eq!(request.params.unwrap(), json!("hello world"));
942
+ }
943
+
944
+ #[test]
945
+ fn test_params_primitive_number() {
946
+ let json = json!({
947
+ "jsonrpc": "2.0",
948
+ "method": "increment",
949
+ "params": 42,
950
+ "id": 5
951
+ });
952
+
953
+ let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
954
+
955
+ assert!(request.params.is_some());
956
+ assert_eq!(request.params.unwrap(), json!(42));
957
+ }
958
+
959
+ #[test]
960
+ fn test_params_deeply_nested() {
961
+ let json = json!({
962
+ "jsonrpc": "2.0",
963
+ "method": "process",
964
+ "params": {
965
+ "level1": {
966
+ "level2": {
967
+ "level3": {
968
+ "level4": {
969
+ "level5": {
970
+ "level6": {
971
+ "level7": {
972
+ "level8": {
973
+ "level9": {
974
+ "level10": "deep value"
975
+ }
976
+ }
977
+ }
978
+ }
979
+ }
980
+ }
981
+ }
982
+ }
983
+ }
984
+ },
985
+ "id": 6
986
+ });
987
+
988
+ let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
989
+
990
+ assert!(request.params.is_some());
991
+ let deep = request.params.unwrap();
992
+ assert!(
993
+ deep["level1"]["level2"]["level3"]["level4"]["level5"]["level6"]["level7"]["level8"]["level9"]["level10"]
994
+ .is_string()
995
+ );
996
+ assert_eq!(
997
+ deep["level1"]["level2"]["level3"]["level4"]["level5"]["level6"]["level7"]["level8"]["level9"]["level10"],
998
+ "deep value"
999
+ );
1000
+ }
1001
+
1002
+ #[test]
1003
+ fn test_params_unicode_strings() {
1004
+ let json = json!({
1005
+ "jsonrpc": "2.0",
1006
+ "method": "translate",
1007
+ "params": {
1008
+ "emoji": "Hello 👋 World 🌍",
1009
+ "rtl": "שלום עולם",
1010
+ "cjk": "你好世界",
1011
+ "special": "café ñ ü"
1012
+ },
1013
+ "id": 7
1014
+ });
1015
+
1016
+ let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
1017
+
1018
+ let params = request.params.unwrap();
1019
+ assert_eq!(params["emoji"], "Hello 👋 World 🌍");
1020
+ assert_eq!(params["rtl"], "שלום עולם");
1021
+ assert_eq!(params["cjk"], "你好世界");
1022
+ assert_eq!(params["special"], "café ñ ü");
1023
+ }
1024
+
1025
+ #[test]
1026
+ fn test_response_result_with_null_valid() {
1027
+ let response = JsonRpcResponse::success(json!(null), json!(1));
1028
+ let serialized = serde_json::to_value(&response).unwrap();
1029
+
1030
+ assert_eq!(serialized["jsonrpc"], "2.0");
1031
+ assert_eq!(serialized["id"], 1);
1032
+ assert_eq!(serialized["result"], json!(null));
1033
+ assert!(serialized.get("error").is_none());
1034
+ }
1035
+
1036
+ #[test]
1037
+ fn test_response_result_with_false_valid() {
1038
+ let response = JsonRpcResponse::success(json!(false), json!(2));
1039
+ let serialized = serde_json::to_value(&response).unwrap();
1040
+
1041
+ assert_eq!(serialized["jsonrpc"], "2.0");
1042
+ assert_eq!(serialized["id"], 2);
1043
+ assert_eq!(serialized["result"], json!(false));
1044
+ assert!(serialized.get("error").is_none());
1045
+ }
1046
+
1047
+ #[test]
1048
+ fn test_response_result_with_zero_valid() {
1049
+ let response = JsonRpcResponse::success(json!(0), json!(3));
1050
+ let serialized = serde_json::to_value(&response).unwrap();
1051
+
1052
+ assert_eq!(serialized["jsonrpc"], "2.0");
1053
+ assert_eq!(serialized["id"], 3);
1054
+ assert_eq!(serialized["result"], json!(0));
1055
+ assert!(serialized.get("error").is_none());
1056
+ }
1057
+
1058
+ #[test]
1059
+ fn test_response_result_with_empty_object_valid() {
1060
+ let response = JsonRpcResponse::success(json!({}), json!(4));
1061
+ let serialized = serde_json::to_value(&response).unwrap();
1062
+
1063
+ assert_eq!(serialized["jsonrpc"], "2.0");
1064
+ assert_eq!(serialized["id"], 4);
1065
+ assert_eq!(serialized["result"], json!({}));
1066
+ assert!(serialized.get("error").is_none());
1067
+ }
1068
+
1069
+ #[test]
1070
+ fn test_response_result_with_empty_array_valid() {
1071
+ let response = JsonRpcResponse::success(json!([]), json!(5));
1072
+ let serialized = serde_json::to_value(&response).unwrap();
1073
+
1074
+ assert_eq!(serialized["jsonrpc"], "2.0");
1075
+ assert_eq!(serialized["id"], 5);
1076
+ assert_eq!(serialized["result"], json!([]));
1077
+ assert!(serialized.get("error").is_none());
1078
+ }
1079
+
1080
+ #[test]
1081
+ fn test_error_code_parse_error() {
1082
+ let err = JsonRpcErrorResponse::error(error_codes::PARSE_ERROR, "Parse error", json!(null));
1083
+ let serialized = serde_json::to_value(&err).unwrap();
1084
+
1085
+ assert_eq!(serialized["error"]["code"], -32700);
1086
+ assert!(serialized.get("result").is_none());
1087
+ }
1088
+
1089
+ #[test]
1090
+ fn test_error_code_roundtrip() {
1091
+ let codes = vec![
1092
+ error_codes::PARSE_ERROR,
1093
+ error_codes::INVALID_REQUEST,
1094
+ error_codes::METHOD_NOT_FOUND,
1095
+ error_codes::INVALID_PARAMS,
1096
+ error_codes::INTERNAL_ERROR,
1097
+ ];
1098
+
1099
+ for code in codes {
1100
+ let err = JsonRpcErrorResponse::error(code, "Test error", json!(1));
1101
+ let serialized = serde_json::to_value(&err).unwrap();
1102
+ let deserialized: JsonRpcErrorResponse = serde_json::from_value(serialized).unwrap();
1103
+
1104
+ assert_eq!(deserialized.error.code, code);
1105
+ }
1106
+ }
1107
+
1108
+ #[test]
1109
+ fn test_notification_has_no_id_field() {
1110
+ let notif = JsonRpcRequest::new("notify", None, None);
1111
+ let serialized = serde_json::to_value(&notif).unwrap();
1112
+
1113
+ assert!(serialized.get("id").is_none());
1114
+ }
1115
+
1116
+ #[test]
1117
+ fn test_id_preservation_in_batch() {
1118
+ let json_batch = json!([
1119
+ {
1120
+ "jsonrpc": "2.0",
1121
+ "method": "method1",
1122
+ "id": "string-id"
1123
+ },
1124
+ {
1125
+ "jsonrpc": "2.0",
1126
+ "method": "method2",
1127
+ "id": 42
1128
+ },
1129
+ {
1130
+ "jsonrpc": "2.0",
1131
+ "method": "method3"
1132
+ }
1133
+ ]);
1134
+
1135
+ let batch: Vec<JsonRpcRequest> = serde_json::from_value(json_batch).unwrap();
1136
+
1137
+ assert_eq!(batch.len(), 3);
1138
+ assert_eq!(batch[0].id, Some(json!("string-id")));
1139
+ assert_eq!(batch[1].id, Some(json!(42)));
1140
+ assert_eq!(batch[2].id, None);
1141
+ }
1142
+
1143
+ #[test]
1144
+ fn test_mixed_id_types_in_batch() {
1145
+ let responses = vec![
1146
+ JsonRpcResponse::success(json!(100), json!("id1")),
1147
+ JsonRpcResponse::success(json!(200), json!(2)),
1148
+ JsonRpcResponse::success(json!(300), json!(null)),
1149
+ ];
1150
+
1151
+ for resp in responses {
1152
+ let serialized = serde_json::to_value(&resp).unwrap();
1153
+ let deserialized: JsonRpcResponse = serde_json::from_value(serialized).unwrap();
1154
+ assert_eq!(deserialized.jsonrpc, "2.0");
1155
+ }
1156
+ }
1157
+
1158
+ #[test]
1159
+ fn test_large_numeric_id() {
1160
+ let large_id = i64::MAX;
1161
+ let json = json!({
1162
+ "jsonrpc": "2.0",
1163
+ "method": "test",
1164
+ "id": large_id
1165
+ });
1166
+
1167
+ let request: JsonRpcRequest = serde_json::from_value(json).unwrap();
1168
+
1169
+ assert_eq!(request.id, Some(json!(large_id)));
1170
+ }
1171
+
1172
+ #[test]
1173
+ fn test_error_always_has_code() {
1174
+ let err = JsonRpcErrorResponse::error(error_codes::METHOD_NOT_FOUND, "Not found", json!(1));
1175
+ let serialized = serde_json::to_value(&err).unwrap();
1176
+
1177
+ assert!(serialized["error"].get("code").is_some());
1178
+ assert_eq!(serialized["error"]["code"], -32601);
1179
+ }
1180
+
1181
+ #[test]
1182
+ fn test_error_always_has_message() {
1183
+ let err = JsonRpcErrorResponse::error(error_codes::INVALID_PARAMS, "Invalid parameters", json!(2));
1184
+ let serialized = serde_json::to_value(&err).unwrap();
1185
+
1186
+ assert!(serialized["error"].get("message").is_some());
1187
+ assert_eq!(serialized["error"]["message"], "Invalid parameters");
1188
+ }
1189
+
1190
+ #[test]
1191
+ fn test_error_data_optional() {
1192
+ let err_without_data = JsonRpcErrorResponse::error(error_codes::INTERNAL_ERROR, "Internal error", json!(3));
1193
+ let serialized_without = serde_json::to_value(&err_without_data).unwrap();
1194
+
1195
+ assert!(serialized_without["error"].get("data").is_none());
1196
+
1197
+ let err_with_data = JsonRpcErrorResponse::error_with_data(
1198
+ error_codes::INTERNAL_ERROR,
1199
+ "Internal error",
1200
+ json!({"details": "something went wrong"}),
1201
+ json!(4),
1202
+ );
1203
+ let serialized_with = serde_json::to_value(&err_with_data).unwrap();
1204
+
1205
+ assert!(serialized_with["error"].get("data").is_some());
1206
+ }
1207
+ }