Skip to main content

ash_rpc/
builders.rs

1//! Builder patterns for JSON-RPC types.
2
3use crate::types::{Error, Notification, Request, RequestId, Response};
4
5/// Builder for JSON-RPC requests
6pub struct RequestBuilder {
7    method: String,
8    params: Option<serde_json::Value>,
9    id: Option<RequestId>,
10    correlation_id: Option<String>,
11}
12
13impl RequestBuilder {
14    /// Create a new request builder
15    pub fn new(method: impl Into<String>) -> Self {
16        Self {
17            method: method.into(),
18            params: None,
19            id: None,
20            correlation_id: Some(uuid::Uuid::new_v4().to_string()),
21        }
22    }
23
24    /// Set request parameters
25    #[must_use]
26    pub fn params(mut self, params: serde_json::Value) -> Self {
27        self.params = Some(params);
28        self
29    }
30
31    /// Set request ID
32    #[must_use]
33    pub fn id(mut self, id: RequestId) -> Self {
34        self.id = Some(id);
35        self
36    }
37
38    /// Set correlation ID
39    #[must_use]
40    pub fn correlation_id(mut self, correlation_id: String) -> Self {
41        self.correlation_id = Some(correlation_id);
42        self
43    }
44
45    /// Build the request
46    #[must_use]
47    pub fn build(self) -> Request {
48        Request {
49            jsonrpc: "2.0".to_owned(),
50            method: self.method,
51            params: self.params,
52            id: self.id,
53            correlation_id: self.correlation_id,
54        }
55    }
56}
57
58/// Builder for JSON-RPC responses
59pub struct ResponseBuilder {
60    result: Option<serde_json::Value>,
61    error: Option<Error>,
62    id: Option<RequestId>,
63    correlation_id: Option<String>,
64}
65
66impl ResponseBuilder {
67    /// Create a new response builder
68    #[must_use]
69    pub fn new() -> Self {
70        Self {
71            result: None,
72            error: None,
73            id: None,
74            correlation_id: None,
75        }
76    }
77
78    /// Set successful result
79    #[must_use]
80    pub fn success(mut self, result: serde_json::Value) -> Self {
81        self.result = Some(result);
82        self
83    }
84
85    /// Set error
86    #[must_use]
87    pub fn error(mut self, error: Error) -> Self {
88        self.error = Some(error);
89        self
90    }
91
92    /// Set response ID
93    #[must_use]
94    pub fn id(mut self, id: Option<RequestId>) -> Self {
95        self.id = id;
96        self
97    }
98    /// Set correlation ID
99    #[must_use]
100    pub fn correlation_id(mut self, correlation_id: Option<String>) -> Self {
101        self.correlation_id = correlation_id;
102        self
103    }
104    /// Build the response
105    #[must_use]
106    pub fn build(self) -> Response {
107        Response {
108            jsonrpc: "2.0".to_owned(),
109            result: self.result,
110            error: self.error,
111            id: self.id,
112            correlation_id: self.correlation_id,
113        }
114    }
115}
116
117/// Builder for JSON-RPC notifications
118pub struct NotificationBuilder {
119    method: String,
120    params: Option<serde_json::Value>,
121}
122
123impl NotificationBuilder {
124    /// Create a new notification builder
125    pub fn new(method: impl Into<String>) -> Self {
126        Self {
127            method: method.into(),
128            params: None,
129        }
130    }
131
132    /// Set notification parameters
133    #[must_use]
134    pub fn params(mut self, params: serde_json::Value) -> Self {
135        self.params = Some(params);
136        self
137    }
138
139    /// Build the notification
140    #[must_use]
141    pub fn build(self) -> Notification {
142        Notification {
143            jsonrpc: "2.0".to_owned(),
144            method: self.method,
145            params: self.params,
146        }
147    }
148}
149
150/// Builder for JSON-RPC errors
151pub struct ErrorBuilder {
152    code: i32,
153    message: String,
154    data: Option<serde_json::Value>,
155}
156
157impl ErrorBuilder {
158    /// Create a new error builder
159    pub fn new(code: i32, message: impl Into<String>) -> Self {
160        Self {
161            code,
162            message: message.into(),
163            data: None,
164        }
165    }
166
167    /// Add additional error data
168    #[must_use]
169    pub fn data(mut self, data: serde_json::Value) -> Self {
170        self.data = Some(data);
171        self
172    }
173
174    /// Build the error
175    #[must_use]
176    pub fn build(self) -> Error {
177        Error {
178            code: self.code,
179            message: self.message,
180            data: self.data,
181        }
182    }
183}
184
185impl Default for ResponseBuilder {
186    fn default() -> Self {
187        Self::new()
188    }
189}
190
191/// Builder for security configuration with validation
192#[cfg(any(feature = "tcp", feature = "tcp-stream", feature = "tcp-stream-tls"))]
193pub struct SecurityConfigBuilder {
194    max_connections: usize,
195    max_request_size: usize,
196    request_timeout: std::time::Duration,
197    idle_timeout: std::time::Duration,
198}
199
200#[cfg(any(feature = "tcp", feature = "tcp-stream", feature = "tcp-stream-tls"))]
201impl SecurityConfigBuilder {
202    /// Create a new security config builder with secure defaults
203    #[must_use]
204    pub fn new() -> Self {
205        Self {
206            max_connections: 1000,
207            max_request_size: 1024 * 1024, // 1 MB
208            request_timeout: std::time::Duration::from_secs(30),
209            idle_timeout: std::time::Duration::from_secs(300), // 5 minutes
210        }
211    }
212
213    /// Set maximum concurrent connections
214    ///
215    /// # Arguments
216    /// * `max` - Maximum number of connections (1-100000)
217    ///
218    /// # Panics
219    /// Panics if max is 0 or greater than 100000
220    #[must_use]
221    pub fn max_connections(mut self, max: usize) -> Self {
222        assert!(
223            max > 0 && max <= 100_000,
224            "max_connections must be between 1 and 100000"
225        );
226        self.max_connections = max;
227        self
228    }
229
230    /// Set maximum request size in bytes
231    ///
232    /// # Arguments
233    /// * `size` - Maximum size in bytes (1024 to 100MB)
234    ///
235    /// # Panics
236    /// Panics if size is less than 1024 bytes or greater than 100MB
237    #[must_use]
238    pub fn max_request_size(mut self, size: usize) -> Self {
239        assert!(
240            (1024..=100 * 1024 * 1024).contains(&size),
241            "max_request_size must be between 1KB and 100MB"
242        );
243        self.max_request_size = size;
244        self
245    }
246
247    /// Set request timeout
248    ///
249    /// # Arguments
250    /// * `timeout` - Timeout duration (1 second to 5 minutes)
251    ///
252    /// # Panics
253    /// Panics if timeout is less than 1 second or greater than 5 minutes
254    #[must_use]
255    pub fn request_timeout(mut self, timeout: std::time::Duration) -> Self {
256        assert!(
257            (1..=300).contains(&timeout.as_secs()),
258            "request_timeout must be between 1 second and 5 minutes"
259        );
260        self.request_timeout = timeout;
261        self
262    }
263
264    /// Set idle connection timeout
265    ///
266    /// # Arguments
267    /// * `timeout` - Timeout duration (10 seconds to 1 hour)
268    ///
269    /// # Panics
270    /// Panics if timeout is less than 10 seconds or greater than 1 hour
271    #[must_use]
272    pub fn idle_timeout(mut self, timeout: std::time::Duration) -> Self {
273        assert!(
274            (10..=3600).contains(&timeout.as_secs()),
275            "idle_timeout must be between 10 seconds and 1 hour"
276        );
277        self.idle_timeout = timeout;
278        self
279    }
280
281    /// Build the security configuration with validation
282    pub fn build(self) -> crate::transports::SecurityConfig {
283        tracing::info!(
284            max_connections = self.max_connections,
285            max_request_size = self.max_request_size,
286            request_timeout_secs = self.request_timeout.as_secs(),
287            idle_timeout_secs = self.idle_timeout.as_secs(),
288            "creating security configuration"
289        );
290
291        crate::transports::SecurityConfig {
292            max_connections: self.max_connections,
293            max_request_size: self.max_request_size,
294            request_timeout: self.request_timeout,
295            idle_timeout: self.idle_timeout,
296        }
297    }
298}
299
300#[cfg(any(feature = "tcp", feature = "tcp-stream", feature = "tcp-stream-tls"))]
301impl Default for SecurityConfigBuilder {
302    fn default() -> Self {
303        Self::new()
304    }
305}
306
307#[cfg(all(
308    test,
309    any(feature = "tcp", feature = "tcp-stream", feature = "tcp-stream-tls")
310))]
311mod tests {
312    use super::*;
313
314    #[test]
315    #[should_panic(expected = "max_connections must be between 1 and 100000")]
316    fn test_max_connections_zero_panics() {
317        SecurityConfigBuilder::new().max_connections(0).build();
318    }
319
320    #[test]
321    fn test_valid_security_config() {
322        let config = SecurityConfigBuilder::new()
323            .max_connections(500)
324            .max_request_size(2 * 1024 * 1024)
325            .request_timeout(std::time::Duration::from_secs(60))
326            .idle_timeout(std::time::Duration::from_secs(600))
327            .build();
328
329        assert_eq!(config.max_connections, 500);
330        assert_eq!(config.max_request_size, 2 * 1024 * 1024);
331    }
332
333    // RequestBuilder tests
334    #[test]
335    fn test_request_builder_basic() {
336        let request = RequestBuilder::new("test_method").build();
337        assert_eq!(request.method, "test_method");
338        assert_eq!(request.jsonrpc, "2.0");
339        assert!(request.correlation_id.is_some());
340    }
341
342    #[test]
343    fn test_request_builder_with_params() {
344        let params = serde_json::json!({"key": "value"});
345        let request = RequestBuilder::new("method").params(params.clone()).build();
346        assert_eq!(request.params, Some(params));
347    }
348
349    #[test]
350    fn test_request_builder_with_id() {
351        let id = serde_json::json!(123);
352        let request = RequestBuilder::new("method").id(id.clone()).build();
353        assert_eq!(request.id, Some(id));
354    }
355
356    #[test]
357    fn test_request_builder_with_correlation_id() {
358        let correlation_id = "custom-correlation-id".to_string();
359        let request = RequestBuilder::new("method")
360            .correlation_id(correlation_id.clone())
361            .build();
362        assert_eq!(request.correlation_id, Some(correlation_id));
363    }
364
365    #[test]
366    fn test_request_builder_complete() {
367        let params = serde_json::json!([1, 2, 3]);
368        let id = serde_json::json!(456);
369        let correlation_id = "test-corr-id".to_string();
370
371        let request = RequestBuilder::new("complete_method")
372            .params(params.clone())
373            .id(id.clone())
374            .correlation_id(correlation_id.clone())
375            .build();
376
377        assert_eq!(request.method, "complete_method");
378        assert_eq!(request.params, Some(params));
379        assert_eq!(request.id, Some(id));
380        assert_eq!(request.correlation_id, Some(correlation_id));
381    }
382
383    // ResponseBuilder tests
384    #[test]
385    fn test_response_builder_success() {
386        let result = serde_json::json!({"status": "ok"});
387        let id = serde_json::json!(1);
388
389        let response = ResponseBuilder::new()
390            .success(result.clone())
391            .id(Some(id.clone()))
392            .build();
393
394        assert_eq!(response.result, Some(result));
395        assert!(response.error.is_none());
396        assert_eq!(response.id, Some(id));
397    }
398
399    #[test]
400    fn test_response_builder_error() {
401        let error =
402            crate::ErrorBuilder::new(crate::error_codes::INVALID_REQUEST, "Invalid request")
403                .build();
404        let id = serde_json::json!(2);
405
406        let response = ResponseBuilder::new()
407            .error(error.clone())
408            .id(Some(id.clone()))
409            .build();
410
411        assert!(response.result.is_none());
412        assert_eq!(
413            response.error.unwrap().code,
414            crate::error_codes::INVALID_REQUEST
415        );
416        assert_eq!(response.id, Some(id));
417    }
418
419    #[test]
420    fn test_response_builder_with_correlation_id_basic() {
421        let correlation_id = "resp-corr-id".to_string();
422        let response = ResponseBuilder::new()
423            .success(serde_json::json!("ok"))
424            .correlation_id(Some(correlation_id.clone()))
425            .build();
426
427        assert_eq!(response.correlation_id, Some(correlation_id));
428    }
429
430    #[test]
431    fn test_response_builder_jsonrpc_version() {
432        let response = ResponseBuilder::new()
433            .success(serde_json::json!("test"))
434            .build();
435        assert_eq!(response.jsonrpc, "2.0");
436    }
437
438    // NotificationBuilder tests
439    #[test]
440    fn test_notification_builder_basic() {
441        let notification = NotificationBuilder::new("event").build();
442        assert_eq!(notification.method, "event");
443        assert_eq!(notification.jsonrpc, "2.0");
444        assert!(notification.params.is_none());
445    }
446
447    #[test]
448    fn test_notification_builder_with_params() {
449        let params = serde_json::json!({"event": "update"});
450        let notification = NotificationBuilder::new("notify")
451            .params(params.clone())
452            .build();
453        assert_eq!(notification.params, Some(params));
454    }
455
456    // ErrorBuilder tests
457    #[test]
458    fn test_error_builder_basic() {
459        let error = ErrorBuilder::new(crate::error_codes::INVALID_REQUEST, "Test error").build();
460        assert_eq!(error.code, crate::error_codes::INVALID_REQUEST);
461        assert_eq!(error.message, "Test error");
462        assert!(error.data.is_none());
463    }
464
465    #[test]
466    fn test_error_builder_with_data() {
467        let data = serde_json::json!({"detail": "more info"});
468        let error = ErrorBuilder::new(crate::error_codes::INTERNAL_ERROR, "Error")
469            .data(data.clone())
470            .build();
471        assert_eq!(error.data, Some(data));
472    }
473
474    #[test]
475    fn test_error_builder_string_conversion() {
476        let error = ErrorBuilder::new(
477            crate::error_codes::INTERNAL_ERROR,
478            String::from("Dynamic error"),
479        )
480        .build();
481        assert_eq!(error.message, "Dynamic error");
482    }
483
484    // SecurityConfigBuilder validation tests
485    #[test]
486    #[should_panic(expected = "max_request_size must be between")]
487    fn test_security_config_request_size_too_small() {
488        SecurityConfigBuilder::new()
489            .max_request_size(512) // Less than 1KB
490            .build();
491    }
492
493    #[test]
494    #[should_panic(expected = "max_request_size must be between")]
495    fn test_security_config_request_size_too_large() {
496        SecurityConfigBuilder::new()
497            .max_request_size(200 * 1024 * 1024) // More than 100MB
498            .build();
499    }
500
501    #[test]
502    #[should_panic(expected = "max_connections must be between")]
503    fn test_security_config_connections_too_large() {
504        SecurityConfigBuilder::new()
505            .max_connections(150_000)
506            .build();
507    }
508
509    #[test]
510    fn test_security_config_boundary_values() {
511        // Test minimum valid values
512        let config_min = SecurityConfigBuilder::new()
513            .max_connections(1)
514            .max_request_size(1024)
515            .build();
516        assert_eq!(config_min.max_connections, 1);
517        assert_eq!(config_min.max_request_size, 1024);
518
519        // Test maximum valid values
520        let config_max = SecurityConfigBuilder::new()
521            .max_connections(100_000)
522            .max_request_size(100 * 1024 * 1024)
523            .build();
524        assert_eq!(config_max.max_connections, 100_000);
525        assert_eq!(config_max.max_request_size, 100 * 1024 * 1024);
526    }
527
528    #[test]
529    fn test_security_config_timeouts() {
530        let request_timeout = std::time::Duration::from_secs(45);
531        let idle_timeout = std::time::Duration::from_secs(900);
532
533        let config = SecurityConfigBuilder::new()
534            .request_timeout(request_timeout)
535            .idle_timeout(idle_timeout)
536            .build();
537
538        assert_eq!(config.request_timeout, request_timeout);
539        assert_eq!(config.idle_timeout, idle_timeout);
540    }
541
542    #[test]
543    fn test_security_config_defaults() {
544        let config = SecurityConfigBuilder::new().build();
545        assert_eq!(config.max_connections, 1000);
546        assert_eq!(config.max_request_size, 1024 * 1024);
547        assert_eq!(config.request_timeout, std::time::Duration::from_secs(30));
548        assert_eq!(config.idle_timeout, std::time::Duration::from_secs(300));
549    }
550
551    #[test]
552    fn test_security_config_builder_default() {
553        let builder = SecurityConfigBuilder::default();
554        let config = builder.build();
555        assert_eq!(config.max_connections, 1000);
556    }
557
558    #[test]
559    #[should_panic(expected = "request_timeout must be between")]
560    fn test_security_config_timeout_too_short() {
561        SecurityConfigBuilder::new()
562            .request_timeout(std::time::Duration::from_millis(500))
563            .build();
564    }
565
566    #[test]
567    #[should_panic(expected = "request_timeout must be between")]
568    fn test_security_config_timeout_too_long() {
569        SecurityConfigBuilder::new()
570            .request_timeout(std::time::Duration::from_secs(400))
571            .build();
572    }
573
574    #[test]
575    #[should_panic(expected = "idle_timeout must be between")]
576    fn test_security_config_idle_timeout_too_short() {
577        SecurityConfigBuilder::new()
578            .idle_timeout(std::time::Duration::from_secs(5))
579            .build();
580    }
581
582    #[test]
583    #[should_panic(expected = "idle_timeout must be between")]
584    fn test_security_config_idle_timeout_too_long() {
585        SecurityConfigBuilder::new()
586            .idle_timeout(std::time::Duration::from_secs(4000))
587            .build();
588    }
589
590    #[test]
591    fn test_request_builder_method_set() {
592        let request = RequestBuilder::new("test_method").build();
593        assert_eq!(request.method, "test_method");
594    }
595
596    #[test]
597    fn test_request_builder_auto_correlation_id() {
598        let request = RequestBuilder::new("test").build();
599        assert!(request.correlation_id.is_some());
600    }
601
602    #[test]
603    fn test_request_builder_custom_correlation_id() {
604        let custom_id = "custom-correlation-123".to_string();
605        let request = RequestBuilder::new("test")
606            .correlation_id(custom_id.clone())
607            .build();
608        assert_eq!(request.correlation_id, Some(custom_id));
609    }
610
611    #[test]
612    fn test_request_builder_full_chain() {
613        let request = RequestBuilder::new("full_test")
614            .params(serde_json::json!({"key": "value"}))
615            .id(serde_json::json!(123))
616            .correlation_id("corr-123".to_string())
617            .build();
618
619        assert_eq!(request.method, "full_test");
620        assert!(request.params.is_some());
621        assert_eq!(request.id, Some(serde_json::json!(123)));
622        assert_eq!(request.correlation_id, Some("corr-123".to_string()));
623    }
624
625    #[test]
626    fn test_response_builder_default_trait() {
627        let builder = ResponseBuilder::default();
628        let response = builder.build();
629        assert_eq!(response.jsonrpc, "2.0");
630    }
631
632    #[test]
633    fn test_response_builder_success_with_null() {
634        let response = ResponseBuilder::new()
635            .success(serde_json::json!(null))
636            .build();
637        assert_eq!(response.result, Some(serde_json::json!(null)));
638    }
639
640    #[test]
641    fn test_response_builder_with_correlation_id() {
642        let corr_id = "test-correlation".to_string();
643        let response = ResponseBuilder::new()
644            .success(serde_json::json!(42))
645            .correlation_id(Some(corr_id.clone()))
646            .build();
647        assert_eq!(response.correlation_id, Some(corr_id));
648    }
649
650    #[test]
651    fn test_response_builder_full_error() {
652        let error = ErrorBuilder::new(crate::error_codes::INTERNAL_ERROR, "Custom error")
653            .data(serde_json::json!({"field": "value"}))
654            .build();
655
656        let response = ResponseBuilder::new()
657            .error(error.clone())
658            .id(Some(serde_json::json!(1)))
659            .correlation_id(Some("err-corr".to_string()))
660            .build();
661
662        assert!(response.result.is_none());
663        assert_eq!(response.error, Some(error));
664        assert_eq!(response.id, Some(serde_json::json!(1)));
665        assert_eq!(response.correlation_id, Some("err-corr".to_string()));
666    }
667
668    #[test]
669    fn test_notification_builder_string_method() {
670        let notification = NotificationBuilder::new(String::from("dynamic_method")).build();
671        assert_eq!(notification.method, "dynamic_method");
672    }
673
674    #[test]
675    fn test_error_builder_multiple_data() {
676        let error = ErrorBuilder::new(crate::error_codes::INTERNAL_ERROR, "Error")
677            .data(serde_json::json!({"key1": "value1"}))
678            .build();
679
680        assert!(error.data.is_some());
681        let data = error.data.unwrap();
682        assert_eq!(data["key1"], "value1");
683    }
684}
685
686/// Builder for streaming response
687#[cfg(feature = "streaming")]
688pub struct StreamResponseBuilder {
689    result: Option<serde_json::Value>,
690    error: Option<Error>,
691    id: RequestId,
692    stream_id: String,
693    stream_status: Option<crate::streaming::StreamStatus>,
694}
695
696#[cfg(feature = "streaming")]
697impl StreamResponseBuilder {
698    /// Create a new stream response builder
699    pub fn new(stream_id: impl Into<String>, id: RequestId) -> Self {
700        Self {
701            result: None,
702            error: None,
703            id,
704            stream_id: stream_id.into(),
705            stream_status: None,
706        }
707    }
708
709    /// Set successful result
710    #[must_use]
711    pub fn success(mut self, result: serde_json::Value) -> Self {
712        self.result = Some(result);
713        self.stream_status = Some(crate::streaming::StreamStatus::Active);
714        self
715    }
716
717    /// Set error
718    #[must_use]
719    pub fn error(mut self, error: Error) -> Self {
720        self.error = Some(error);
721        self.stream_status = Some(crate::streaming::StreamStatus::Error);
722        self
723    }
724
725    /// Set stream status
726    #[must_use]
727    pub fn status(mut self, status: crate::streaming::StreamStatus) -> Self {
728        self.stream_status = Some(status);
729        self
730    }
731
732    /// Build the stream response
733    #[must_use]
734    pub fn build(self) -> crate::streaming::StreamResponse {
735        crate::streaming::StreamResponse {
736            jsonrpc: "2.0".to_owned(),
737            result: self.result,
738            error: self.error,
739            id: self.id,
740            stream_id: self.stream_id,
741            stream_status: self.stream_status,
742        }
743    }
744}