1use crate::types::*;
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_string()],
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 pub fn new() -> Self {
131 Self {
132 supports_batch: true,
133 supports_notifications: true,
134 max_batch_size: Some(100),
135 max_request_size: Some(1024 * 1024),
136 request_timeout_secs: Some(30),
137 supported_versions: vec!["2.0".to_string()],
138 }
139 }
140
141 pub fn supports_batch(mut self, enabled: bool) -> Self {
143 self.supports_batch = enabled;
144 self
145 }
146
147 pub fn supports_notifications(mut self, enabled: bool) -> Self {
149 self.supports_notifications = enabled;
150 self
151 }
152
153 pub fn max_batch_size(mut self, size: Option<usize>) -> Self {
161 if let Some(s) = size {
162 assert!(
163 s > 0 && s <= 1000,
164 "max_batch_size must be between 1 and 1000"
165 );
166 }
167 self.max_batch_size = size;
168 self
169 }
170
171 pub fn max_request_size(mut self, size: Option<usize>) -> Self {
179 if let Some(s) = size {
180 assert!(
181 (1024..=100 * 1024 * 1024).contains(&s),
182 "max_request_size must be between 1KB and 100MB"
183 );
184 }
185 self.max_request_size = size;
186 self
187 }
188
189 pub fn request_timeout_secs(mut self, timeout: Option<u64>) -> Self {
197 if let Some(t) = timeout {
198 assert!(
199 t > 0 && t <= 300,
200 "request_timeout_secs must be between 1 and 300"
201 );
202 }
203 self.request_timeout_secs = timeout;
204 self
205 }
206
207 pub fn add_version(mut self, version: impl Into<String>) -> Self {
209 self.supported_versions.push(version.into());
210 self
211 }
212
213 pub fn build(self) -> ProcessorCapabilities {
215 tracing::debug!(
216 supports_batch = self.supports_batch,
217 max_batch_size = ?self.max_batch_size,
218 max_request_size = ?self.max_request_size,
219 request_timeout_secs = ?self.request_timeout_secs,
220 "creating processor capabilities"
221 );
222
223 ProcessorCapabilities {
224 supports_batch: self.supports_batch,
225 supports_notifications: self.supports_notifications,
226 max_batch_size: self.max_batch_size,
227 max_request_size: self.max_request_size,
228 request_timeout_secs: self.request_timeout_secs,
229 supported_versions: self.supported_versions,
230 }
231 }
232}
233
234impl Default for ProcessorCapabilitiesBuilder {
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct OpenApiMethodSpec {
243 pub method_name: String,
244 pub summary: Option<String>,
245 pub description: Option<String>,
246 pub parameters: Option<serde_json::Value>,
247 pub result: Option<serde_json::Value>,
248 pub errors: Vec<OpenApiError>,
249 pub tags: Vec<String>,
250 pub examples: Vec<OpenApiExample>,
251}
252
253impl OpenApiMethodSpec {
254 pub fn new(method_name: impl Into<String>) -> Self {
256 Self {
257 method_name: method_name.into(),
258 summary: None,
259 description: None,
260 parameters: None,
261 result: None,
262 errors: Vec::new(),
263 tags: Vec::new(),
264 examples: Vec::new(),
265 }
266 }
267
268 pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
270 self.summary = Some(summary.into());
271 self
272 }
273
274 pub fn with_description(mut self, description: impl Into<String>) -> Self {
276 self.description = Some(description.into());
277 self
278 }
279
280 pub fn with_parameters(mut self, params: serde_json::Value) -> Self {
282 self.parameters = Some(params);
283 self
284 }
285
286 pub fn with_result(mut self, result: serde_json::Value) -> Self {
288 self.result = Some(result);
289 self
290 }
291
292 pub fn with_error(mut self, error: OpenApiError) -> Self {
294 self.errors.push(error);
295 self
296 }
297
298 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
300 self.tags.push(tag.into());
301 self
302 }
303
304 pub fn with_example(mut self, example: OpenApiExample) -> Self {
306 self.examples.push(example);
307 self
308 }
309}
310
311#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct OpenApiError {
314 pub code: i32,
315 pub message: String,
316 pub description: Option<String>,
317}
318
319impl OpenApiError {
320 pub fn new(code: i32, message: impl Into<String>) -> Self {
322 Self {
323 code,
324 message: message.into(),
325 description: None,
326 }
327 }
328
329 pub fn with_description(mut self, description: impl Into<String>) -> Self {
331 self.description = Some(description.into());
332 self
333 }
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct OpenApiExample {
339 pub name: String,
340 pub summary: Option<String>,
341 pub description: Option<String>,
342 pub params: Option<serde_json::Value>,
343 pub result: Option<serde_json::Value>,
344}
345
346impl OpenApiExample {
347 pub fn new(name: impl Into<String>) -> Self {
349 Self {
350 name: name.into(),
351 summary: None,
352 description: None,
353 params: None,
354 result: None,
355 }
356 }
357
358 pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
360 self.summary = Some(summary.into());
361 self
362 }
363
364 pub fn with_description(mut self, description: impl Into<String>) -> Self {
366 self.description = Some(description.into());
367 self
368 }
369
370 pub fn with_params(mut self, params: serde_json::Value) -> Self {
372 self.params = Some(params);
373 self
374 }
375
376 pub fn with_result(mut self, result: serde_json::Value) -> Self {
378 self.result = Some(result);
379 self
380 }
381}
382
383#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct OpenApiSpec {
386 pub openapi: String,
387 pub info: OpenApiInfo,
388 pub servers: Vec<OpenApiServer>,
389 pub methods: HashMap<String, OpenApiMethodSpec>,
390 pub components: OpenApiComponents,
391}
392
393impl OpenApiSpec {
394 pub fn new(title: impl Into<String>, version: impl Into<String>) -> Self {
396 Self {
397 openapi: "3.0.3".to_string(),
398 info: OpenApiInfo {
399 title: title.into(),
400 version: version.into(),
401 description: None,
402 },
403 servers: Vec::new(),
404 methods: HashMap::new(),
405 components: OpenApiComponents::default(),
406 }
407 }
408
409 pub fn add_method(&mut self, spec: OpenApiMethodSpec) {
411 self.methods.insert(spec.method_name.clone(), spec);
412 }
413
414 pub fn add_methods(&mut self, specs: Vec<OpenApiMethodSpec>) {
416 for spec in specs {
417 self.add_method(spec);
418 }
419 }
420
421 pub fn add_server(&mut self, server: OpenApiServer) {
423 self.servers.push(server);
424 }
425
426 pub fn with_description(mut self, description: impl Into<String>) -> Self {
428 self.info.description = Some(description.into());
429 self
430 }
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
435pub struct OpenApiInfo {
436 pub title: String,
437 pub version: String,
438 pub description: Option<String>,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct OpenApiServer {
444 pub url: String,
445 pub description: Option<String>,
446}
447
448impl OpenApiServer {
449 pub fn new(url: impl Into<String>) -> Self {
451 Self {
452 url: url.into(),
453 description: None,
454 }
455 }
456
457 pub fn with_description(mut self, description: impl Into<String>) -> Self {
459 self.description = Some(description.into());
460 self
461 }
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize, Default)]
466pub struct OpenApiComponents {
467 pub schemas: HashMap<String, serde_json::Value>,
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473 use serde_json::json;
474
475 #[test]
477 fn test_processor_capabilities_default() {
478 let caps = ProcessorCapabilities::default();
479 assert!(caps.supports_batch);
480 assert!(caps.supports_notifications);
481 assert_eq!(caps.max_batch_size, Some(100));
482 assert_eq!(caps.max_request_size, Some(1024 * 1024));
483 assert_eq!(caps.request_timeout_secs, Some(30));
484 assert_eq!(caps.supported_versions, vec!["2.0"]);
485 }
486
487 #[test]
488 fn test_processor_capabilities_builder() {
489 let caps = ProcessorCapabilitiesBuilder::new()
490 .max_batch_size(Some(50))
491 .max_request_size(Some(2 * 1024 * 1024))
492 .request_timeout_secs(Some(60))
493 .build();
494
495 assert_eq!(caps.max_batch_size, Some(50));
496 assert_eq!(caps.max_request_size, Some(2 * 1024 * 1024));
497 assert_eq!(caps.request_timeout_secs, Some(60));
498 }
499
500 #[test]
501 #[should_panic(expected = "max_batch_size must be between 1 and 1000")]
502 fn test_processor_capabilities_invalid_batch_size() {
503 ProcessorCapabilitiesBuilder::new()
504 .max_batch_size(Some(0))
505 .build();
506 }
507
508 #[test]
509 #[should_panic(expected = "max_batch_size must be between 1 and 1000")]
510 fn test_processor_capabilities_batch_size_too_large() {
511 ProcessorCapabilitiesBuilder::new()
512 .max_batch_size(Some(2000))
513 .build();
514 }
515
516 #[test]
517 fn test_processor_capabilities_builder_boundary() {
518 let caps_min = ProcessorCapabilitiesBuilder::new()
520 .max_batch_size(Some(1))
521 .build();
522 assert_eq!(caps_min.max_batch_size, Some(1));
523
524 let caps_max = ProcessorCapabilitiesBuilder::new()
526 .max_batch_size(Some(1000))
527 .build();
528 assert_eq!(caps_max.max_batch_size, Some(1000));
529 }
530
531 #[test]
533 fn test_openapi_method_spec_creation() {
534 let spec = OpenApiMethodSpec::new("test_method");
535 assert_eq!(spec.method_name, "test_method");
536 assert!(spec.description.is_none());
537 assert!(spec.parameters.is_none());
538 assert!(spec.result.is_none());
539 }
540
541 #[test]
542 fn test_openapi_method_spec_with_description() {
543 let spec = OpenApiMethodSpec::new("method").with_description("Test description");
544 assert_eq!(spec.description, Some("Test description".to_string()));
545 }
546
547 #[test]
548 fn test_openapi_method_spec_with_schemas() {
549 let params = json!({"type": "object"});
550 let result = json!({"type": "string"});
551
552 let spec = OpenApiMethodSpec::new("method")
553 .with_parameters(params.clone())
554 .with_result(result.clone());
555
556 assert_eq!(spec.parameters, Some(params));
557 assert_eq!(spec.result, Some(result));
558 }
559
560 #[test]
561 fn test_openapi_method_spec_complete() {
562 let spec = OpenApiMethodSpec::new("complete_method")
563 .with_description("A complete method")
564 .with_parameters(json!({"type": "array"}))
565 .with_result(json!({"type": "number"}));
566
567 assert_eq!(spec.method_name, "complete_method");
568 assert_eq!(spec.description, Some("A complete method".to_string()));
569 assert!(spec.parameters.is_some());
570 assert!(spec.result.is_some());
571 }
572
573 #[test]
575 fn test_openapi_spec_creation() {
576 let spec = OpenApiSpec::new("Test API", "1.0.0");
577 assert_eq!(spec.openapi, "3.0.3");
578 assert_eq!(spec.info.title, "Test API");
579 assert_eq!(spec.info.version, "1.0.0");
580 }
581
582 #[test]
583 fn test_openapi_spec_add_server() {
584 let mut spec = OpenApiSpec::new("API", "1.0.0");
585 spec.add_server(OpenApiServer::new("http://localhost:8080"));
586 assert_eq!(spec.servers.len(), 1);
587 assert_eq!(spec.servers[0].url, "http://localhost:8080");
588 }
589
590 #[test]
591 fn test_openapi_spec_add_method() {
592 let mut spec = OpenApiSpec::new("API", "1.0.0");
593 let method_spec = OpenApiMethodSpec::new("test");
594 spec.add_method(method_spec);
595 assert_eq!(spec.methods.len(), 1);
596 }
597
598 #[test]
599 fn test_openapi_spec_serialization() {
600 let mut spec = OpenApiSpec::new("Test", "1.0");
601 spec.add_server(OpenApiServer::new("http://api.example.com"));
602
603 let json = serde_json::to_string(&spec).unwrap();
604 let deserialized: OpenApiSpec = serde_json::from_str(&json).unwrap();
605
606 assert_eq!(deserialized.info.title, "Test");
607 assert_eq!(deserialized.servers.len(), 1);
608 }
609
610 #[test]
612 fn test_openapi_server_creation() {
613 let server = OpenApiServer::new("http://localhost:3000");
614 assert_eq!(server.url, "http://localhost:3000");
615 assert!(server.description.is_none());
616 }
617
618 #[test]
619 fn test_openapi_server_with_description() {
620 let server = OpenApiServer::new("http://api.com").with_description("Production API");
621 assert_eq!(server.description, Some("Production API".to_string()));
622 }
623
624 #[test]
626 fn test_openapi_info_creation() {
627 let info = OpenApiInfo {
628 title: "My API".to_string(),
629 version: "2.0.0".to_string(),
630 description: Some("API description".to_string()),
631 };
632
633 assert_eq!(info.title, "My API");
634 assert_eq!(info.version, "2.0.0");
635 assert_eq!(info.description, Some("API description".to_string()));
636 }
637
638 #[test]
640 fn test_openapi_components_default() {
641 let components = OpenApiComponents::default();
642 assert_eq!(components.schemas.len(), 0);
643 }
644
645 #[test]
646 fn test_openapi_components_with_schemas() {
647 let mut components = OpenApiComponents::default();
648 components.schemas.insert(
649 "User".to_string(),
650 json!({"type": "object", "properties": {"name": {"type": "string"}}}),
651 );
652
653 assert_eq!(components.schemas.len(), 1);
654 assert!(components.schemas.contains_key("User"));
655 }
656
657 struct TestMethod;
659
660 #[async_trait::async_trait]
661 impl JsonRPCMethod for TestMethod {
662 fn method_name(&self) -> &'static str {
663 "test"
664 }
665
666 async fn call(&self, params: Option<serde_json::Value>, id: Option<RequestId>) -> Response {
667 Response::success(params.unwrap_or(json!(null)), id)
668 }
669 }
670
671 #[tokio::test]
672 async fn test_jsonrpc_method_trait() {
673 let method = TestMethod;
674 assert_eq!(method.method_name(), "test");
675
676 let params = json!({"key": "value"});
677 let response = method.call(Some(params.clone()), Some(json!(1))).await;
678
679 assert!(response.is_success());
680 assert_eq!(response.result, Some(params));
681 }
682
683 #[tokio::test]
684 async fn test_jsonrpc_method_openapi_components() {
685 let method = TestMethod;
686 let spec = method.openapi_components();
687 assert_eq!(spec.method_name, "test");
688 }
689
690 struct TestHandler;
692
693 #[async_trait::async_trait]
694 impl Handler for TestHandler {
695 async fn handle_request(&self, request: Request) -> Response {
696 Response::success(json!({"handled": request.method}), request.id)
697 }
698
699 async fn handle_notification(&self, _notification: Notification) {
700 }
702
703 fn supports_method(&self, method: &str) -> bool {
704 method == "supported"
705 }
706
707 fn get_supported_methods(&self) -> Vec<String> {
708 vec!["supported".to_string(), "another".to_string()]
709 }
710 }
711
712 #[tokio::test]
713 async fn test_handler_handle_request() {
714 let handler = TestHandler;
715 let request = Request {
716 jsonrpc: "2.0".to_string(),
717 method: "test".to_string(),
718 params: None,
719 id: Some(json!(1)),
720 correlation_id: None,
721 };
722
723 let response = handler.handle_request(request).await;
724 assert!(response.is_success());
725 }
726
727 #[tokio::test]
728 async fn test_handler_supports_method() {
729 let handler = TestHandler;
730 assert!(handler.supports_method("supported"));
731 assert!(!handler.supports_method("unsupported"));
732 }
733
734 #[tokio::test]
735 async fn test_handler_get_supported_methods() {
736 let handler = TestHandler;
737 let methods = handler.get_supported_methods();
738 assert_eq!(methods.len(), 2);
739 assert!(methods.contains(&"supported".to_string()));
740 assert!(methods.contains(&"another".to_string()));
741 }
742
743 struct TestProcessor;
745
746 #[async_trait::async_trait]
747 impl MessageProcessor for TestProcessor {
748 async fn process_message(&self, message: Message) -> Option<Response> {
749 match message {
750 Message::Request(req) => Some(Response::success(json!("ok"), req.id)),
751 _ => None,
752 }
753 }
754 }
755
756 #[tokio::test]
757 async fn test_message_processor_single_message() {
758 let processor = TestProcessor;
759 let request = Message::Request(Request {
760 jsonrpc: "2.0".to_string(),
761 method: "test".to_string(),
762 params: None,
763 id: Some(json!(1)),
764 correlation_id: None,
765 });
766
767 let response = processor.process_message(request).await;
768 assert!(response.is_some());
769 }
770
771 #[tokio::test]
772 async fn test_message_processor_batch() {
773 let processor = TestProcessor;
774 let messages = vec![
775 Message::Request(Request {
776 jsonrpc: "2.0".to_string(),
777 method: "test1".to_string(),
778 params: None,
779 id: Some(json!(1)),
780 correlation_id: None,
781 }),
782 Message::Request(Request {
783 jsonrpc: "2.0".to_string(),
784 method: "test2".to_string(),
785 params: None,
786 id: Some(json!(2)),
787 correlation_id: None,
788 }),
789 ];
790
791 let responses = processor.process_batch(messages).await;
792 assert_eq!(responses.len(), 2);
793 }
794
795 #[tokio::test]
796 async fn test_message_processor_supports_batching() {
797 let processor = TestProcessor;
798 assert!(processor.supports_batching());
799 }
800
801 #[tokio::test]
802 async fn test_message_processor_capabilities() {
803 let processor = TestProcessor;
804 let caps = processor.get_capabilities();
805 assert!(caps.supports_batch);
806 assert!(caps.supports_notifications);
807 }
808
809 #[test]
811 fn test_processor_capabilities_builder_disabled_batch() {
812 let caps = ProcessorCapabilitiesBuilder::new()
813 .supports_batch(false)
814 .build();
815 assert!(!caps.supports_batch);
816 }
817
818 #[test]
819 fn test_processor_capabilities_builder_disabled_notifications() {
820 let caps = ProcessorCapabilitiesBuilder::new()
821 .supports_notifications(false)
822 .build();
823 assert!(!caps.supports_notifications);
824 }
825
826 #[test]
827 fn test_processor_capabilities_builder_add_version() {
828 let caps = ProcessorCapabilitiesBuilder::new()
829 .add_version("3.0")
830 .build();
831 assert!(caps.supported_versions.contains(&"2.0".to_string()));
832 assert!(caps.supported_versions.contains(&"3.0".to_string()));
833 }
834
835 #[test]
836 fn test_processor_capabilities_builder_none_limits() {
837 let caps = ProcessorCapabilitiesBuilder::new()
838 .max_batch_size(None)
839 .max_request_size(None)
840 .request_timeout_secs(None)
841 .build();
842
843 assert!(caps.max_batch_size.is_none());
844 assert!(caps.max_request_size.is_none());
845 assert!(caps.request_timeout_secs.is_none());
846 }
847
848 #[test]
850 fn test_openapi_error_creation() {
851 let error = OpenApiError::new(crate::error_codes::INVALID_REQUEST, "Invalid Request");
852 assert_eq!(error.code, crate::error_codes::INVALID_REQUEST);
853 assert_eq!(error.message, "Invalid Request");
854 assert!(error.description.is_none());
855 }
856
857 #[test]
858 fn test_openapi_error_with_description() {
859 let error = OpenApiError::new(crate::error_codes::INVALID_REQUEST, "Invalid Request")
860 .with_description("The JSON sent is not a valid Request object");
861 assert_eq!(
862 error.description,
863 Some("The JSON sent is not a valid Request object".to_string())
864 );
865 }
866
867 #[test]
869 fn test_openapi_example_creation() {
870 let example = OpenApiExample::new("basic_example");
871 assert_eq!(example.name, "basic_example");
872 assert!(example.summary.is_none());
873 assert!(example.description.is_none());
874 }
875
876 #[test]
877 fn test_openapi_example_complete() {
878 let example = OpenApiExample::new("complete")
879 .with_summary("Complete example")
880 .with_description("A complete example with all fields")
881 .with_params(json!({"x": 1, "y": 2}))
882 .with_result(json!(3));
883
884 assert_eq!(example.summary, Some("Complete example".to_string()));
885 assert_eq!(
886 example.description,
887 Some("A complete example with all fields".to_string())
888 );
889 assert!(example.params.is_some());
890 assert!(example.result.is_some());
891 }
892
893 #[test]
895 fn test_openapi_method_spec_with_error() {
896 let error = OpenApiError::new(crate::error_codes::INVALID_PARAMS, "Invalid params");
897 let spec = OpenApiMethodSpec::new("method").with_error(error);
898
899 assert_eq!(spec.errors.len(), 1);
900 assert_eq!(spec.errors[0].code, crate::error_codes::INVALID_PARAMS);
901 }
902
903 #[test]
904 fn test_openapi_method_spec_with_tag() {
905 let spec = OpenApiMethodSpec::new("method")
906 .with_tag("utility")
907 .with_tag("public");
908
909 assert_eq!(spec.tags.len(), 2);
910 assert!(spec.tags.contains(&"utility".to_string()));
911 assert!(spec.tags.contains(&"public".to_string()));
912 }
913
914 #[test]
915 fn test_openapi_method_spec_with_example() {
916 let example = OpenApiExample::new("example1");
917 let spec = OpenApiMethodSpec::new("method").with_example(example);
918
919 assert_eq!(spec.examples.len(), 1);
920 assert_eq!(spec.examples[0].name, "example1");
921 }
922
923 #[test]
924 fn test_openapi_method_spec_with_summary() {
925 let spec = OpenApiMethodSpec::new("method").with_summary("Method summary");
926 assert_eq!(spec.summary, Some("Method summary".to_string()));
927 }
928
929 #[test]
931 fn test_openapi_spec_add_multiple_methods() {
932 let mut spec = OpenApiSpec::new("API", "1.0");
933 let methods = vec![
934 OpenApiMethodSpec::new("method1"),
935 OpenApiMethodSpec::new("method2"),
936 OpenApiMethodSpec::new("method3"),
937 ];
938
939 spec.add_methods(methods);
940 assert_eq!(spec.methods.len(), 3);
941 }
942
943 #[test]
944 fn test_openapi_spec_with_description() {
945 let spec = OpenApiSpec::new("API", "1.0").with_description("Test API description");
946 assert_eq!(
947 spec.info.description,
948 Some("Test API description".to_string())
949 );
950 }
951
952 #[test]
953 fn test_openapi_spec_multiple_servers() {
954 let mut spec = OpenApiSpec::new("API", "1.0");
955 spec.add_server(OpenApiServer::new("http://dev.example.com"));
956 spec.add_server(OpenApiServer::new("https://prod.example.com"));
957
958 assert_eq!(spec.servers.len(), 2);
959 }
960}