1use crate::types::{Message, Notification, Request, RequestId, Response};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[async_trait::async_trait]
9pub trait JsonRPCMethod: Send + Sync {
10 fn method_name(&self) -> &'static str;
12
13 async fn call(&self, params: Option<serde_json::Value>, id: Option<RequestId>) -> Response;
15
16 fn openapi_components(&self) -> OpenApiMethodSpec {
18 OpenApiMethodSpec::new(self.method_name())
19 }
20}
21
22#[async_trait::async_trait]
24pub trait Handler: Send + Sync {
25 async fn handle_request(&self, request: Request) -> Response;
27
28 async fn handle_notification(&self, notification: Notification);
30
31 fn supports_method(&self, method: &str) -> bool {
33 let _ = method;
34 true
35 }
36
37 fn get_supported_methods(&self) -> Vec<String> {
39 vec![]
40 }
41}
42
43#[async_trait::async_trait]
45pub trait MessageProcessor: Send + Sync {
46 async fn process_message(&self, message: Message) -> Option<Response>;
48
49 async fn process_batch(&self, messages: Vec<Message>) -> Vec<Response> {
51 let mut results = Vec::new();
52 for msg in messages {
53 if let Some(response) = self.process_message(msg).await {
54 results.push(response);
55 }
56 }
57 results
58 }
59
60 fn supports_batching(&self) -> bool {
62 true
63 }
64
65 fn get_capabilities(&self) -> ProcessorCapabilities {
67 ProcessorCapabilities::default()
68 }
69}
70
71#[cfg(feature = "streaming")]
73#[async_trait::async_trait]
74pub trait StreamingMessageProcessor: MessageProcessor {
75 async fn process_stream_request(
77 &self,
78 request: crate::streaming::StreamRequest,
79 ) -> crate::streaming::StreamResponse;
80
81 async fn process_unsubscribe(&self, stream_id: &str) -> Result<(), crate::Error>;
83
84 fn supports_streaming(&self) -> bool {
86 true
87 }
88
89 async fn active_stream_count(&self) -> usize {
91 0
92 }
93}
94
95#[derive(Debug, Clone)]
96pub struct ProcessorCapabilities {
97 pub supports_batch: bool,
98 pub supports_notifications: bool,
99 pub max_batch_size: Option<usize>,
100 pub max_request_size: Option<usize>,
101 pub request_timeout_secs: Option<u64>,
102 pub supported_versions: Vec<String>,
103}
104
105impl Default for ProcessorCapabilities {
106 fn default() -> Self {
107 Self {
108 supports_batch: true,
109 supports_notifications: true,
110 max_batch_size: Some(100), max_request_size: Some(1024 * 1024), request_timeout_secs: Some(30),
113 supported_versions: vec!["2.0".to_owned()],
114 }
115 }
116}
117
118pub struct ProcessorCapabilitiesBuilder {
120 supports_batch: bool,
121 supports_notifications: bool,
122 max_batch_size: Option<usize>,
123 max_request_size: Option<usize>,
124 request_timeout_secs: Option<u64>,
125 supported_versions: Vec<String>,
126}
127
128impl ProcessorCapabilitiesBuilder {
129 #[must_use]
131 pub fn new() -> Self {
132 Self {
133 supports_batch: true,
134 supports_notifications: true,
135 max_batch_size: Some(100),
136 max_request_size: Some(1024 * 1024),
137 request_timeout_secs: Some(30),
138 supported_versions: vec!["2.0".to_owned()],
139 }
140 }
141
142 #[must_use]
144 pub fn supports_batch(mut self, enabled: bool) -> Self {
145 self.supports_batch = enabled;
146 self
147 }
148
149 #[must_use]
151 pub fn supports_notifications(mut self, enabled: bool) -> Self {
152 self.supports_notifications = enabled;
153 self
154 }
155
156 #[must_use]
164 pub fn max_batch_size(mut self, size: Option<usize>) -> Self {
165 if let Some(s) = size {
166 assert!(
167 s > 0 && s <= 1000,
168 "max_batch_size must be between 1 and 1000"
169 );
170 }
171 self.max_batch_size = size;
172 self
173 }
174
175 #[must_use]
183 pub fn max_request_size(mut self, size: Option<usize>) -> Self {
184 if let Some(s) = size {
185 assert!(
186 (1024..=100 * 1024 * 1024).contains(&s),
187 "max_request_size must be between 1KB and 100MB"
188 );
189 }
190 self.max_request_size = size;
191 self
192 }
193
194 #[must_use]
202 pub fn request_timeout_secs(mut self, timeout: Option<u64>) -> Self {
203 if let Some(t) = timeout {
204 assert!(
205 t > 0 && t <= 300,
206 "request_timeout_secs must be between 1 and 300"
207 );
208 }
209 self.request_timeout_secs = timeout;
210 self
211 }
212
213 #[must_use]
215 pub fn add_version(mut self, version: impl Into<String>) -> Self {
216 self.supported_versions.push(version.into());
217 self
218 }
219
220 pub fn build(self) -> ProcessorCapabilities {
222 tracing::debug!(
223 supports_batch = self.supports_batch,
224 max_batch_size = ?self.max_batch_size,
225 max_request_size = ?self.max_request_size,
226 request_timeout_secs = ?self.request_timeout_secs,
227 "creating processor capabilities"
228 );
229
230 ProcessorCapabilities {
231 supports_batch: self.supports_batch,
232 supports_notifications: self.supports_notifications,
233 max_batch_size: self.max_batch_size,
234 max_request_size: self.max_request_size,
235 request_timeout_secs: self.request_timeout_secs,
236 supported_versions: self.supported_versions,
237 }
238 }
239}
240
241impl Default for ProcessorCapabilitiesBuilder {
242 fn default() -> Self {
243 Self::new()
244 }
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct OpenApiMethodSpec {
250 pub method_name: String,
251 pub summary: Option<String>,
252 pub description: Option<String>,
253 pub parameters: Option<serde_json::Value>,
254 pub result: Option<serde_json::Value>,
255 pub errors: Vec<OpenApiError>,
256 pub tags: Vec<String>,
257 pub examples: Vec<OpenApiExample>,
258}
259
260impl OpenApiMethodSpec {
261 pub fn new(method_name: impl Into<String>) -> Self {
263 Self {
264 method_name: method_name.into(),
265 summary: None,
266 description: None,
267 parameters: None,
268 result: None,
269 errors: Vec::new(),
270 tags: Vec::new(),
271 examples: Vec::new(),
272 }
273 }
274
275 #[must_use]
277 pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
278 self.summary = Some(summary.into());
279 self
280 }
281
282 #[must_use]
284 pub fn with_description(mut self, description: impl Into<String>) -> Self {
285 self.description = Some(description.into());
286 self
287 }
288
289 #[must_use]
291 pub fn with_parameters(mut self, params: serde_json::Value) -> Self {
292 self.parameters = Some(params);
293 self
294 }
295
296 #[must_use]
298 pub fn with_result(mut self, result: serde_json::Value) -> Self {
299 self.result = Some(result);
300 self
301 }
302
303 #[must_use]
305 pub fn with_error(mut self, error: OpenApiError) -> Self {
306 self.errors.push(error);
307 self
308 }
309
310 #[must_use]
312 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
313 self.tags.push(tag.into());
314 self
315 }
316
317 #[must_use]
319 pub fn with_example(mut self, example: OpenApiExample) -> Self {
320 self.examples.push(example);
321 self
322 }
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct OpenApiError {
328 pub code: i32,
329 pub message: String,
330 pub description: Option<String>,
331}
332
333impl OpenApiError {
334 pub fn new(code: i32, message: impl Into<String>) -> Self {
336 Self {
337 code,
338 message: message.into(),
339 description: None,
340 }
341 }
342
343 #[must_use]
345 pub fn with_description(mut self, description: impl Into<String>) -> Self {
346 self.description = Some(description.into());
347 self
348 }
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct OpenApiExample {
354 pub name: String,
355 pub summary: Option<String>,
356 pub description: Option<String>,
357 pub params: Option<serde_json::Value>,
358 pub result: Option<serde_json::Value>,
359}
360
361impl OpenApiExample {
362 pub fn new(name: impl Into<String>) -> Self {
364 Self {
365 name: name.into(),
366 summary: None,
367 description: None,
368 params: None,
369 result: None,
370 }
371 }
372
373 #[must_use]
375 pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
376 self.summary = Some(summary.into());
377 self
378 }
379
380 #[must_use]
382 pub fn with_description(mut self, description: impl Into<String>) -> Self {
383 self.description = Some(description.into());
384 self
385 }
386
387 #[must_use]
389 pub fn with_params(mut self, params: serde_json::Value) -> Self {
390 self.params = Some(params);
391 self
392 }
393
394 #[must_use]
396 pub fn with_result(mut self, result: serde_json::Value) -> Self {
397 self.result = Some(result);
398 self
399 }
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct OpenApiSpec {
405 pub openapi: String,
406 pub info: OpenApiInfo,
407 pub servers: Vec<OpenApiServer>,
408 pub methods: HashMap<String, OpenApiMethodSpec>,
409 pub components: OpenApiComponents,
410}
411
412impl OpenApiSpec {
413 pub fn new(title: impl Into<String>, version: impl Into<String>) -> Self {
415 Self {
416 openapi: "3.0.3".to_owned(),
417 info: OpenApiInfo {
418 title: title.into(),
419 version: version.into(),
420 description: None,
421 },
422 servers: Vec::new(),
423 methods: HashMap::new(),
424 components: OpenApiComponents::default(),
425 }
426 }
427
428 pub fn add_method(&mut self, spec: OpenApiMethodSpec) {
430 self.methods.insert(spec.method_name.clone(), spec);
431 }
432
433 pub fn add_methods(&mut self, specs: Vec<OpenApiMethodSpec>) {
435 for spec in specs {
436 self.add_method(spec);
437 }
438 }
439
440 pub fn add_server(&mut self, server: OpenApiServer) {
442 self.servers.push(server);
443 }
444
445 #[must_use]
447 pub fn with_description(mut self, description: impl Into<String>) -> Self {
448 self.info.description = Some(description.into());
449 self
450 }
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
455pub struct OpenApiInfo {
456 pub title: String,
457 pub version: String,
458 pub description: Option<String>,
459}
460
461#[derive(Debug, Clone, Serialize, Deserialize)]
463pub struct OpenApiServer {
464 pub url: String,
465 pub description: Option<String>,
466}
467
468impl OpenApiServer {
469 pub fn new(url: impl Into<String>) -> Self {
471 Self {
472 url: url.into(),
473 description: None,
474 }
475 }
476
477 #[must_use]
479 pub fn with_description(mut self, description: impl Into<String>) -> Self {
480 self.description = Some(description.into());
481 self
482 }
483}
484
485#[derive(Debug, Clone, Serialize, Deserialize, Default)]
487pub struct OpenApiComponents {
488 pub schemas: HashMap<String, serde_json::Value>,
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494 use serde_json::json;
495
496 #[test]
498 fn test_processor_capabilities_default() {
499 let caps = ProcessorCapabilities::default();
500 assert!(caps.supports_batch);
501 assert!(caps.supports_notifications);
502 assert_eq!(caps.max_batch_size, Some(100));
503 assert_eq!(caps.max_request_size, Some(1024 * 1024));
504 assert_eq!(caps.request_timeout_secs, Some(30));
505 assert_eq!(caps.supported_versions, vec!["2.0"]);
506 }
507
508 #[test]
509 fn test_processor_capabilities_builder() {
510 let caps = ProcessorCapabilitiesBuilder::new()
511 .max_batch_size(Some(50))
512 .max_request_size(Some(2 * 1024 * 1024))
513 .request_timeout_secs(Some(60))
514 .build();
515
516 assert_eq!(caps.max_batch_size, Some(50));
517 assert_eq!(caps.max_request_size, Some(2 * 1024 * 1024));
518 assert_eq!(caps.request_timeout_secs, Some(60));
519 }
520
521 #[test]
522 #[should_panic(expected = "max_batch_size must be between 1 and 1000")]
523 fn test_processor_capabilities_invalid_batch_size() {
524 ProcessorCapabilitiesBuilder::new()
525 .max_batch_size(Some(0))
526 .build();
527 }
528
529 #[test]
530 #[should_panic(expected = "max_batch_size must be between 1 and 1000")]
531 fn test_processor_capabilities_batch_size_too_large() {
532 ProcessorCapabilitiesBuilder::new()
533 .max_batch_size(Some(2000))
534 .build();
535 }
536
537 #[test]
538 fn test_processor_capabilities_builder_boundary() {
539 let caps_min = ProcessorCapabilitiesBuilder::new()
541 .max_batch_size(Some(1))
542 .build();
543 assert_eq!(caps_min.max_batch_size, Some(1));
544
545 let caps_max = ProcessorCapabilitiesBuilder::new()
547 .max_batch_size(Some(1000))
548 .build();
549 assert_eq!(caps_max.max_batch_size, Some(1000));
550 }
551
552 #[test]
554 fn test_openapi_method_spec_creation() {
555 let spec = OpenApiMethodSpec::new("test_method");
556 assert_eq!(spec.method_name, "test_method");
557 assert!(spec.description.is_none());
558 assert!(spec.parameters.is_none());
559 assert!(spec.result.is_none());
560 }
561
562 #[test]
563 fn test_openapi_method_spec_with_description() {
564 let spec = OpenApiMethodSpec::new("method").with_description("Test description");
565 assert_eq!(spec.description, Some("Test description".to_string()));
566 }
567
568 #[test]
569 fn test_openapi_method_spec_with_schemas() {
570 let params = json!({"type": "object"});
571 let result = json!({"type": "string"});
572
573 let spec = OpenApiMethodSpec::new("method")
574 .with_parameters(params.clone())
575 .with_result(result.clone());
576
577 assert_eq!(spec.parameters, Some(params));
578 assert_eq!(spec.result, Some(result));
579 }
580
581 #[test]
582 fn test_openapi_method_spec_complete() {
583 let spec = OpenApiMethodSpec::new("complete_method")
584 .with_description("A complete method")
585 .with_parameters(json!({"type": "array"}))
586 .with_result(json!({"type": "number"}));
587
588 assert_eq!(spec.method_name, "complete_method");
589 assert_eq!(spec.description, Some("A complete method".to_string()));
590 assert!(spec.parameters.is_some());
591 assert!(spec.result.is_some());
592 }
593
594 #[test]
596 fn test_openapi_spec_creation() {
597 let spec = OpenApiSpec::new("Test API", "1.0.0");
598 assert_eq!(spec.openapi, "3.0.3");
599 assert_eq!(spec.info.title, "Test API");
600 assert_eq!(spec.info.version, "1.0.0");
601 }
602
603 #[test]
604 fn test_openapi_spec_add_server() {
605 let mut spec = OpenApiSpec::new("API", "1.0.0");
606 spec.add_server(OpenApiServer::new("http://localhost:8080"));
607 assert_eq!(spec.servers.len(), 1);
608 assert_eq!(spec.servers[0].url, "http://localhost:8080");
609 }
610
611 #[test]
612 fn test_openapi_spec_add_method() {
613 let mut spec = OpenApiSpec::new("API", "1.0.0");
614 let method_spec = OpenApiMethodSpec::new("test");
615 spec.add_method(method_spec);
616 assert_eq!(spec.methods.len(), 1);
617 }
618
619 #[test]
620 fn test_openapi_spec_serialization() {
621 let mut spec = OpenApiSpec::new("Test", "1.0");
622 spec.add_server(OpenApiServer::new("http://api.example.com"));
623
624 let json = serde_json::to_string(&spec).unwrap();
625 let deserialized: OpenApiSpec = serde_json::from_str(&json).unwrap();
626
627 assert_eq!(deserialized.info.title, "Test");
628 assert_eq!(deserialized.servers.len(), 1);
629 }
630
631 #[test]
633 fn test_openapi_server_creation() {
634 let server = OpenApiServer::new("http://localhost:3000");
635 assert_eq!(server.url, "http://localhost:3000");
636 assert!(server.description.is_none());
637 }
638
639 #[test]
640 fn test_openapi_server_with_description() {
641 let server = OpenApiServer::new("http://api.com").with_description("Production API");
642 assert_eq!(server.description, Some("Production API".to_string()));
643 }
644
645 #[test]
647 fn test_openapi_info_creation() {
648 let info = OpenApiInfo {
649 title: "My API".to_string(),
650 version: "2.0.0".to_string(),
651 description: Some("API description".to_string()),
652 };
653
654 assert_eq!(info.title, "My API");
655 assert_eq!(info.version, "2.0.0");
656 assert_eq!(info.description, Some("API description".to_string()));
657 }
658
659 #[test]
661 fn test_openapi_components_default() {
662 let components = OpenApiComponents::default();
663 assert_eq!(components.schemas.len(), 0);
664 }
665
666 #[test]
667 fn test_openapi_components_with_schemas() {
668 let mut components = OpenApiComponents::default();
669 components.schemas.insert(
670 "User".to_string(),
671 json!({"type": "object", "properties": {"name": {"type": "string"}}}),
672 );
673
674 assert_eq!(components.schemas.len(), 1);
675 assert!(components.schemas.contains_key("User"));
676 }
677
678 struct TestMethod;
680
681 #[async_trait::async_trait]
682 impl JsonRPCMethod for TestMethod {
683 fn method_name(&self) -> &'static str {
684 "test"
685 }
686
687 async fn call(&self, params: Option<serde_json::Value>, id: Option<RequestId>) -> Response {
688 Response::success(params.unwrap_or(json!(null)), id)
689 }
690 }
691
692 #[tokio::test]
693 async fn test_jsonrpc_method_trait() {
694 let method = TestMethod;
695 assert_eq!(method.method_name(), "test");
696
697 let params = json!({"key": "value"});
698 let response = method.call(Some(params.clone()), Some(json!(1))).await;
699
700 assert!(response.is_success());
701 assert_eq!(response.result, Some(params));
702 }
703
704 #[tokio::test]
705 async fn test_jsonrpc_method_openapi_components() {
706 let method = TestMethod;
707 let spec = method.openapi_components();
708 assert_eq!(spec.method_name, "test");
709 }
710
711 struct TestHandler;
713
714 #[async_trait::async_trait]
715 impl Handler for TestHandler {
716 async fn handle_request(&self, request: Request) -> Response {
717 Response::success(json!({"handled": request.method}), request.id)
718 }
719
720 async fn handle_notification(&self, _notification: Notification) {
721 }
723
724 fn supports_method(&self, method: &str) -> bool {
725 method == "supported"
726 }
727
728 fn get_supported_methods(&self) -> Vec<String> {
729 vec!["supported".to_string(), "another".to_string()]
730 }
731 }
732
733 #[tokio::test]
734 async fn test_handler_handle_request() {
735 let handler = TestHandler;
736 let request = Request {
737 jsonrpc: "2.0".to_string(),
738 method: "test".to_string(),
739 params: None,
740 id: Some(json!(1)),
741 correlation_id: None,
742 };
743
744 let response = handler.handle_request(request).await;
745 assert!(response.is_success());
746 }
747
748 #[tokio::test]
749 async fn test_handler_supports_method() {
750 let handler = TestHandler;
751 assert!(handler.supports_method("supported"));
752 assert!(!handler.supports_method("unsupported"));
753 }
754
755 #[tokio::test]
756 async fn test_handler_get_supported_methods() {
757 let handler = TestHandler;
758 let methods = handler.get_supported_methods();
759 assert_eq!(methods.len(), 2);
760 assert!(methods.contains(&"supported".to_string()));
761 assert!(methods.contains(&"another".to_string()));
762 }
763
764 struct TestProcessor;
766
767 #[async_trait::async_trait]
768 impl MessageProcessor for TestProcessor {
769 async fn process_message(&self, message: Message) -> Option<Response> {
770 match message {
771 Message::Request(req) => Some(Response::success(json!("ok"), req.id)),
772 _ => None,
773 }
774 }
775 }
776
777 #[tokio::test]
778 async fn test_message_processor_single_message() {
779 let processor = TestProcessor;
780 let request = Message::Request(Request {
781 jsonrpc: "2.0".to_string(),
782 method: "test".to_string(),
783 params: None,
784 id: Some(json!(1)),
785 correlation_id: None,
786 });
787
788 let response = processor.process_message(request).await;
789 assert!(response.is_some());
790 }
791
792 #[tokio::test]
793 async fn test_message_processor_batch() {
794 let processor = TestProcessor;
795 let messages = vec![
796 Message::Request(Request {
797 jsonrpc: "2.0".to_string(),
798 method: "test1".to_string(),
799 params: None,
800 id: Some(json!(1)),
801 correlation_id: None,
802 }),
803 Message::Request(Request {
804 jsonrpc: "2.0".to_string(),
805 method: "test2".to_string(),
806 params: None,
807 id: Some(json!(2)),
808 correlation_id: None,
809 }),
810 ];
811
812 let responses = processor.process_batch(messages).await;
813 assert_eq!(responses.len(), 2);
814 }
815
816 #[tokio::test]
817 async fn test_message_processor_supports_batching() {
818 let processor = TestProcessor;
819 assert!(processor.supports_batching());
820 }
821
822 #[tokio::test]
823 async fn test_message_processor_capabilities() {
824 let processor = TestProcessor;
825 let caps = processor.get_capabilities();
826 assert!(caps.supports_batch);
827 assert!(caps.supports_notifications);
828 }
829
830 #[test]
832 fn test_processor_capabilities_builder_disabled_batch() {
833 let caps = ProcessorCapabilitiesBuilder::new()
834 .supports_batch(false)
835 .build();
836 assert!(!caps.supports_batch);
837 }
838
839 #[test]
840 fn test_processor_capabilities_builder_disabled_notifications() {
841 let caps = ProcessorCapabilitiesBuilder::new()
842 .supports_notifications(false)
843 .build();
844 assert!(!caps.supports_notifications);
845 }
846
847 #[test]
848 fn test_processor_capabilities_builder_add_version() {
849 let caps = ProcessorCapabilitiesBuilder::new()
850 .add_version("3.0")
851 .build();
852 assert!(caps.supported_versions.contains(&"2.0".to_string()));
853 assert!(caps.supported_versions.contains(&"3.0".to_string()));
854 }
855
856 #[test]
857 fn test_processor_capabilities_builder_none_limits() {
858 let caps = ProcessorCapabilitiesBuilder::new()
859 .max_batch_size(None)
860 .max_request_size(None)
861 .request_timeout_secs(None)
862 .build();
863
864 assert!(caps.max_batch_size.is_none());
865 assert!(caps.max_request_size.is_none());
866 assert!(caps.request_timeout_secs.is_none());
867 }
868
869 #[test]
871 fn test_openapi_error_creation() {
872 let error = OpenApiError::new(crate::error_codes::INVALID_REQUEST, "Invalid Request");
873 assert_eq!(error.code, crate::error_codes::INVALID_REQUEST);
874 assert_eq!(error.message, "Invalid Request");
875 assert!(error.description.is_none());
876 }
877
878 #[test]
879 fn test_openapi_error_with_description() {
880 let error = OpenApiError::new(crate::error_codes::INVALID_REQUEST, "Invalid Request")
881 .with_description("The JSON sent is not a valid Request object");
882 assert_eq!(
883 error.description,
884 Some("The JSON sent is not a valid Request object".to_string())
885 );
886 }
887
888 #[test]
890 fn test_openapi_example_creation() {
891 let example = OpenApiExample::new("basic_example");
892 assert_eq!(example.name, "basic_example");
893 assert!(example.summary.is_none());
894 assert!(example.description.is_none());
895 }
896
897 #[test]
898 fn test_openapi_example_complete() {
899 let example = OpenApiExample::new("complete")
900 .with_summary("Complete example")
901 .with_description("A complete example with all fields")
902 .with_params(json!({"x": 1, "y": 2}))
903 .with_result(json!(3));
904
905 assert_eq!(example.summary, Some("Complete example".to_string()));
906 assert_eq!(
907 example.description,
908 Some("A complete example with all fields".to_string())
909 );
910 assert!(example.params.is_some());
911 assert!(example.result.is_some());
912 }
913
914 #[test]
916 fn test_openapi_method_spec_with_error() {
917 let error = OpenApiError::new(crate::error_codes::INVALID_PARAMS, "Invalid params");
918 let spec = OpenApiMethodSpec::new("method").with_error(error);
919
920 assert_eq!(spec.errors.len(), 1);
921 assert_eq!(spec.errors[0].code, crate::error_codes::INVALID_PARAMS);
922 }
923
924 #[test]
925 fn test_openapi_method_spec_with_tag() {
926 let spec = OpenApiMethodSpec::new("method")
927 .with_tag("utility")
928 .with_tag("public");
929
930 assert_eq!(spec.tags.len(), 2);
931 assert!(spec.tags.contains(&"utility".to_string()));
932 assert!(spec.tags.contains(&"public".to_string()));
933 }
934
935 #[test]
936 fn test_openapi_method_spec_with_example() {
937 let example = OpenApiExample::new("example1");
938 let spec = OpenApiMethodSpec::new("method").with_example(example);
939
940 assert_eq!(spec.examples.len(), 1);
941 assert_eq!(spec.examples[0].name, "example1");
942 }
943
944 #[test]
945 fn test_openapi_method_spec_with_summary() {
946 let spec = OpenApiMethodSpec::new("method").with_summary("Method summary");
947 assert_eq!(spec.summary, Some("Method summary".to_string()));
948 }
949
950 #[test]
952 fn test_openapi_spec_add_multiple_methods() {
953 let mut spec = OpenApiSpec::new("API", "1.0");
954 let methods = vec![
955 OpenApiMethodSpec::new("method1"),
956 OpenApiMethodSpec::new("method2"),
957 OpenApiMethodSpec::new("method3"),
958 ];
959
960 spec.add_methods(methods);
961 assert_eq!(spec.methods.len(), 3);
962 }
963
964 #[test]
965 fn test_openapi_spec_with_description() {
966 let spec = OpenApiSpec::new("API", "1.0").with_description("Test API description");
967 assert_eq!(
968 spec.info.description,
969 Some("Test API description".to_string())
970 );
971 }
972
973 #[test]
974 fn test_openapi_spec_multiple_servers() {
975 let mut spec = OpenApiSpec::new("API", "1.0");
976 spec.add_server(OpenApiServer::new("http://dev.example.com"));
977 spec.add_server(OpenApiServer::new("https://prod.example.com"));
978
979 assert_eq!(spec.servers.len(), 2);
980 }
981}