rmcp_openapi/
openapi_spec.rs1use crate::error::OpenApiError;
2use crate::server::ToolMetadata;
3use crate::tool_generator::ToolGenerator;
4use openapiv3::OpenAPI;
5use reqwest::Method;
6use serde_json::Value;
7use url::Url;
8
9#[derive(Debug, Clone)]
12pub struct OpenApiSpec {
13 pub spec: OpenAPI,
14}
15
16impl OpenApiSpec {
17 pub async fn from_url(url: &Url) -> Result<Self, OpenApiError> {
19 let client = reqwest::Client::new();
20 let response = client.get(url.clone()).send().await?;
21 let text = response.text().await?;
22 let spec: OpenAPI = serde_json::from_str(&text)?;
23
24 Ok(OpenApiSpec { spec })
25 }
26
27 pub async fn from_file(path: &str) -> Result<Self, OpenApiError> {
29 let content = tokio::fs::read_to_string(path).await?;
30 let spec: OpenAPI = serde_json::from_str(&content)?;
31
32 Ok(OpenApiSpec { spec })
33 }
34
35 pub fn from_value(json_value: Value) -> Result<Self, OpenApiError> {
37 let spec: OpenAPI = serde_json::from_value(json_value)?;
38 Ok(OpenApiSpec { spec })
39 }
40
41 pub fn to_tool_metadata(&self) -> Result<Vec<ToolMetadata>, OpenApiError> {
43 let mut tools = Vec::new();
44
45 for (path, path_item_ref) in &self.spec.paths.paths {
46 let path_item = match path_item_ref {
48 openapiv3::ReferenceOr::Item(item) => item,
49 openapiv3::ReferenceOr::Reference { .. } => continue, };
51
52 let operations = [
54 (Method::GET, &path_item.get),
55 (Method::POST, &path_item.post),
56 (Method::PUT, &path_item.put),
57 (Method::DELETE, &path_item.delete),
58 (Method::PATCH, &path_item.patch),
59 (Method::HEAD, &path_item.head),
60 (Method::OPTIONS, &path_item.options),
61 (Method::TRACE, &path_item.trace),
62 ];
63
64 for (method, operation_ref) in operations {
65 if let Some(operation) = operation_ref {
66 let tool_metadata = ToolGenerator::generate_tool_metadata(
67 operation,
68 method.to_string(),
69 path.clone(),
70 )?;
71 tools.push(tool_metadata);
72 }
73 }
74 }
75
76 Ok(tools)
77 }
78
79 pub fn get_operation(
81 &self,
82 operation_id: &str,
83 ) -> Option<(&openapiv3::Operation, String, String)> {
84 for (path, path_item_ref) in &self.spec.paths.paths {
85 let path_item = match path_item_ref {
87 openapiv3::ReferenceOr::Item(item) => item,
88 openapiv3::ReferenceOr::Reference { .. } => continue, };
90
91 let operations = [
92 (Method::GET, &path_item.get),
93 (Method::POST, &path_item.post),
94 (Method::PUT, &path_item.put),
95 (Method::DELETE, &path_item.delete),
96 (Method::PATCH, &path_item.patch),
97 (Method::HEAD, &path_item.head),
98 (Method::OPTIONS, &path_item.options),
99 (Method::TRACE, &path_item.trace),
100 ];
101
102 for (method, operation_ref) in operations {
103 if let Some(operation) = operation_ref {
104 let default_id = format!(
105 "{}_{}",
106 method,
107 path.replace('/', "_").replace(['{', '}'], "")
108 );
109 let op_id = operation.operation_id.as_deref().unwrap_or(&default_id);
110
111 if op_id == operation_id {
112 return Some((operation, method.to_string(), path.clone()));
113 }
114 }
115 }
116 }
117 None
118 }
119
120 pub fn get_operation_ids(&self) -> Vec<String> {
122 let mut operation_ids = Vec::new();
123
124 for (path, path_item_ref) in &self.spec.paths.paths {
125 let path_item = match path_item_ref {
127 openapiv3::ReferenceOr::Item(item) => item,
128 openapiv3::ReferenceOr::Reference { .. } => continue, };
130
131 let operations = [
132 (Method::GET, &path_item.get),
133 (Method::POST, &path_item.post),
134 (Method::PUT, &path_item.put),
135 (Method::DELETE, &path_item.delete),
136 (Method::PATCH, &path_item.patch),
137 (Method::HEAD, &path_item.head),
138 (Method::OPTIONS, &path_item.options),
139 (Method::TRACE, &path_item.trace),
140 ];
141
142 for (method, operation_ref) in operations {
143 if let Some(operation) = operation_ref {
144 let default_id = format!(
145 "{}_{}",
146 method,
147 path.replace('/', "_").replace(['{', '}'], "")
148 );
149 let op_id = operation.operation_id.as_deref().unwrap_or(&default_id);
150 operation_ids.push(op_id.to_string());
151 }
152 }
153 }
154
155 operation_ids
156 }
157}