1use dbmcp_server::pagination::Cursor;
9use indexmap::IndexMap;
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13
14pub use dbmcp_server::types::{
15 ListEntries, ListFunctionsResponse, ListProceduresResponse, ListTablesResponse, ListTriggersRequest,
16 ListTriggersResponse, ListViewsResponse,
17};
18
19#[derive(Debug, Default, Deserialize, JsonSchema)]
21#[serde(rename_all = "camelCase")]
22pub struct DropTableRequest {
23 #[serde(default)]
25 pub database: Option<String>,
26 pub table: String,
28 #[serde(default)]
30 pub cascade: bool,
31}
32
33#[derive(Debug, Default, Deserialize, JsonSchema)]
35#[serde(rename_all = "camelCase")]
36pub struct ListTablesRequest {
37 #[serde(default)]
39 pub database: Option<String>,
40 #[serde(default)]
42 pub cursor: Option<Cursor>,
43 #[serde(default)]
46 pub search: Option<String>,
47 #[serde(default)]
51 pub detailed: bool,
52}
53
54#[derive(Debug, Default, Deserialize, JsonSchema)]
56#[serde(rename_all = "camelCase")]
57pub struct ListFunctionsRequest {
58 #[serde(default)]
60 pub database: Option<String>,
61 #[serde(default)]
63 pub cursor: Option<Cursor>,
64 #[serde(default)]
67 pub search: Option<String>,
68 #[serde(default)]
73 pub detailed: bool,
74}
75
76#[derive(Debug, Default, Deserialize, JsonSchema)]
78#[serde(rename_all = "camelCase")]
79pub struct ListViewsRequest {
80 #[serde(default)]
82 pub database: Option<String>,
83 #[serde(default)]
85 pub cursor: Option<Cursor>,
86 #[serde(default)]
89 pub search: Option<String>,
90 #[serde(default)]
94 pub detailed: bool,
95}
96
97#[derive(Debug, Default, Deserialize, JsonSchema)]
99#[serde(rename_all = "camelCase")]
100pub struct ListMaterializedViewsRequest {
101 #[serde(default)]
103 pub database: Option<String>,
104 #[serde(default)]
106 pub cursor: Option<Cursor>,
107 #[serde(default)]
110 pub search: Option<String>,
111 #[serde(default)]
115 pub detailed: bool,
116}
117
118#[derive(Debug, Serialize, JsonSchema)]
120#[serde(rename_all = "camelCase")]
121pub struct ListMaterializedViewsResponse {
122 pub materialized_views: ListEntries,
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub next_cursor: Option<Cursor>,
127}
128
129impl ListMaterializedViewsResponse {
130 #[must_use]
132 pub fn brief(materialized_views: Vec<String>, next_cursor: Option<Cursor>) -> Self {
133 Self {
134 materialized_views: ListEntries::Brief(materialized_views),
135 next_cursor,
136 }
137 }
138
139 #[must_use]
141 pub fn detailed(materialized_views: IndexMap<String, Value>, next_cursor: Option<Cursor>) -> Self {
142 Self {
143 materialized_views: ListEntries::Detailed(materialized_views),
144 next_cursor,
145 }
146 }
147}
148
149#[derive(Debug, Default, Deserialize, JsonSchema)]
151#[serde(rename_all = "camelCase")]
152pub struct ListProceduresRequest {
153 #[serde(default)]
155 pub database: Option<String>,
156 #[serde(default)]
158 pub cursor: Option<Cursor>,
159 #[serde(default)]
162 pub search: Option<String>,
163 #[serde(default)]
167 pub detailed: bool,
168}
169
170#[cfg(test)]
171mod tests {
172 use indexmap::IndexMap;
173 use serde_json::json;
174
175 use super::{
176 ListEntries, ListFunctionsRequest, ListMaterializedViewsRequest, ListMaterializedViewsResponse,
177 ListProceduresRequest, ListTablesRequest, ListViewsRequest,
178 };
179
180 #[test]
181 fn list_tables_request_defaults_to_brief_mode_without_search() {
182 let req: ListTablesRequest = serde_json::from_str("{}").expect("empty object should parse");
183 assert!(req.search.is_none());
184 assert!(!req.detailed, "detailed must default to false");
185 }
186
187 #[test]
188 fn list_tables_request_accepts_search_and_detailed() {
189 let req: ListTablesRequest = serde_json::from_str(r#"{"search": "order", "detailed": true}"#).expect("parse");
190 assert_eq!(req.search.as_deref(), Some("order"));
191 assert!(req.detailed);
192 }
193
194 #[test]
195 fn list_functions_request_defaults_to_brief_mode_without_search() {
196 let req: ListFunctionsRequest = serde_json::from_str("{}").expect("empty object should parse");
197 assert!(req.search.is_none());
198 assert!(!req.detailed, "detailed must default to false");
199 }
200
201 #[test]
202 fn list_functions_request_accepts_search_and_detailed() {
203 let req: ListFunctionsRequest =
204 serde_json::from_str(r#"{"search": "order", "detailed": true}"#).expect("parse");
205 assert_eq!(req.search.as_deref(), Some("order"));
206 assert!(req.detailed);
207 }
208
209 #[test]
210 fn list_procedures_request_defaults_to_brief_mode_without_search() {
211 let req: ListProceduresRequest = serde_json::from_str("{}").expect("empty object should parse");
212 assert!(req.search.is_none());
213 assert!(!req.detailed, "detailed must default to false");
214 }
215
216 #[test]
217 fn list_procedures_request_accepts_search_and_detailed() {
218 let req: ListProceduresRequest =
219 serde_json::from_str(r#"{"search": "archive", "detailed": true}"#).expect("parse");
220 assert_eq!(req.search.as_deref(), Some("archive"));
221 assert!(req.detailed);
222 }
223
224 #[test]
225 fn list_views_request_defaults_to_brief_mode_without_search() {
226 let req: ListViewsRequest = serde_json::from_str("{}").expect("empty object should parse");
227 assert!(req.search.is_none());
228 assert!(!req.detailed, "detailed must default to false");
229 }
230
231 #[test]
232 fn list_views_request_accepts_search_and_detailed() {
233 let req: ListViewsRequest = serde_json::from_str(r#"{"search": "active", "detailed": true}"#).expect("parse");
234 assert_eq!(req.search.as_deref(), Some("active"));
235 assert!(req.detailed);
236 }
237
238 #[test]
239 fn list_materialized_views_request_defaults_to_brief_mode_without_search() {
240 let req: ListMaterializedViewsRequest = serde_json::from_str("{}").expect("empty object should parse");
241 assert!(req.search.is_none());
242 assert!(!req.detailed, "detailed must default to false");
243 }
244
245 #[test]
246 fn list_materialized_views_request_accepts_search_and_detailed() {
247 let req: ListMaterializedViewsRequest =
248 serde_json::from_str(r#"{"search": "orders", "detailed": true}"#).expect("parse");
249 assert_eq!(req.search.as_deref(), Some("orders"));
250 assert!(req.detailed);
251 }
252
253 #[test]
254 fn list_materialized_views_response_brief_constructor_wraps_vec() {
255 let response = ListMaterializedViewsResponse::brief(vec!["mv_recent_orders".into()], None);
256 assert!(matches!(response.materialized_views, ListEntries::Brief(ref v) if v == &["mv_recent_orders"]));
257 assert!(response.next_cursor.is_none());
258 }
259
260 #[test]
261 fn list_materialized_views_response_detailed_constructor_wraps_indexmap() {
262 let map = IndexMap::from([("mv_recent_orders".into(), json!({"populated": true}))]);
263 let response = ListMaterializedViewsResponse::detailed(map, None);
264 assert!(matches!(response.materialized_views, ListEntries::Detailed(_)));
265 }
266
267 #[test]
268 fn list_materialized_views_response_brief_matches_legacy_wire_shape() {
269 let response = ListMaterializedViewsResponse::brief(vec!["mv_recent_orders".into()], None);
270 assert_eq!(
271 serde_json::to_value(&response).unwrap(),
272 json!({"materializedViews": ["mv_recent_orders"]})
273 );
274 }
275}