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, ListTriggersResponse,
16 ListViewsResponse, PinnedListTriggersRequest, UnpinnedListTriggersRequest,
17};
18
19#[derive(Debug, Default, Deserialize, JsonSchema)]
21#[schemars(rename = "DropTableRequest")]
22pub struct PinnedDropTableRequest {
23 pub table: String,
25 #[serde(default)]
27 pub cascade: bool,
28}
29
30#[derive(Debug, Default, Deserialize, JsonSchema)]
32#[schemars(rename = "DropTableRequest")]
33pub struct UnpinnedDropTableRequest {
34 #[serde(flatten)]
35 pub inner: PinnedDropTableRequest,
36 #[serde(default)]
38 pub database: Option<String>,
39}
40
41#[derive(Debug, Default, Deserialize, JsonSchema)]
43#[schemars(rename = "ListTablesRequest")]
44pub struct PinnedListTablesRequest {
45 #[serde(default)]
47 pub cursor: Option<Cursor>,
48 #[serde(default)]
51 pub search: Option<String>,
52 #[serde(default)]
56 pub detailed: bool,
57}
58
59#[derive(Debug, Default, Deserialize, JsonSchema)]
61#[schemars(rename = "ListTablesRequest")]
62pub struct UnpinnedListTablesRequest {
63 #[serde(flatten)]
64 pub inner: PinnedListTablesRequest,
65 #[serde(default)]
67 pub database: Option<String>,
68}
69
70#[derive(Debug, Default, Deserialize, JsonSchema)]
72#[schemars(rename = "ListFunctionsRequest")]
73pub struct PinnedListFunctionsRequest {
74 #[serde(default)]
76 pub cursor: Option<Cursor>,
77 #[serde(default)]
80 pub search: Option<String>,
81 #[serde(default)]
86 pub detailed: bool,
87}
88
89#[derive(Debug, Default, Deserialize, JsonSchema)]
91#[schemars(rename = "ListFunctionsRequest")]
92pub struct UnpinnedListFunctionsRequest {
93 #[serde(flatten)]
94 pub inner: PinnedListFunctionsRequest,
95 #[serde(default)]
97 pub database: Option<String>,
98}
99
100#[derive(Debug, Default, Deserialize, JsonSchema)]
102#[schemars(rename = "ListViewsRequest")]
103pub struct PinnedListViewsRequest {
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, Default, Deserialize, JsonSchema)]
120#[schemars(rename = "ListViewsRequest")]
121pub struct UnpinnedListViewsRequest {
122 #[serde(flatten)]
123 pub inner: PinnedListViewsRequest,
124 #[serde(default)]
126 pub database: Option<String>,
127}
128
129#[derive(Debug, Default, Deserialize, JsonSchema)]
131#[schemars(rename = "ListMaterializedViewsRequest")]
132pub struct PinnedListMaterializedViewsRequest {
133 #[serde(default)]
135 pub cursor: Option<Cursor>,
136 #[serde(default)]
139 pub search: Option<String>,
140 #[serde(default)]
144 pub detailed: bool,
145}
146
147#[derive(Debug, Default, Deserialize, JsonSchema)]
149#[schemars(rename = "ListMaterializedViewsRequest")]
150pub struct UnpinnedListMaterializedViewsRequest {
151 #[serde(flatten)]
152 pub inner: PinnedListMaterializedViewsRequest,
153 #[serde(default)]
155 pub database: Option<String>,
156}
157
158#[derive(Debug, Serialize, JsonSchema)]
160pub struct ListMaterializedViewsResponse {
161 #[serde(rename = "materializedViews")]
163 pub materialized_views: ListEntries,
164 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
166 pub next_cursor: Option<Cursor>,
167}
168
169impl ListMaterializedViewsResponse {
170 #[must_use]
172 pub fn brief(materialized_views: Vec<String>, next_cursor: Option<Cursor>) -> Self {
173 Self {
174 materialized_views: ListEntries::Brief(materialized_views),
175 next_cursor,
176 }
177 }
178
179 #[must_use]
181 pub fn detailed(materialized_views: IndexMap<String, Value>, next_cursor: Option<Cursor>) -> Self {
182 Self {
183 materialized_views: ListEntries::Detailed(materialized_views),
184 next_cursor,
185 }
186 }
187}
188
189#[derive(Debug, Default, Deserialize, JsonSchema)]
191#[schemars(rename = "ListProceduresRequest")]
192pub struct PinnedListProceduresRequest {
193 #[serde(default)]
195 pub cursor: Option<Cursor>,
196 #[serde(default)]
199 pub search: Option<String>,
200 #[serde(default)]
204 pub detailed: bool,
205}
206
207#[derive(Debug, Default, Deserialize, JsonSchema)]
209#[schemars(rename = "ListProceduresRequest")]
210pub struct UnpinnedListProceduresRequest {
211 #[serde(flatten)]
212 pub inner: PinnedListProceduresRequest,
213 #[serde(default)]
215 pub database: Option<String>,
216}
217
218#[cfg(test)]
219mod tests {
220 use indexmap::IndexMap;
221 use serde_json::json;
222
223 use super::{
224 ListEntries, ListMaterializedViewsResponse, PinnedListFunctionsRequest, PinnedListMaterializedViewsRequest,
225 PinnedListProceduresRequest, PinnedListTablesRequest, PinnedListViewsRequest, UnpinnedListFunctionsRequest,
226 UnpinnedListMaterializedViewsRequest, UnpinnedListProceduresRequest, UnpinnedListTablesRequest,
227 UnpinnedListViewsRequest,
228 };
229
230 #[test]
231 fn unpinned_list_tables_request_defaults_to_brief_mode_without_search() {
232 let req: PinnedListTablesRequest = serde_json::from_str("{}").expect("empty object should parse");
233 assert!(req.search.is_none());
234 assert!(!req.detailed, "detailed must default to false");
235 }
236
237 #[test]
238 fn unpinned_list_tables_request_accepts_search_and_detailed() {
239 let req: PinnedListTablesRequest =
240 serde_json::from_str(r#"{"search": "order", "detailed": true}"#).expect("parse");
241 assert_eq!(req.search.as_deref(), Some("order"));
242 assert!(req.detailed);
243 }
244
245 #[test]
246 fn pinned_list_tables_request_accepts_database_and_inner_fields() {
247 let req: UnpinnedListTablesRequest =
248 serde_json::from_str(r#"{"database": "mydb", "search": "order"}"#).expect("parse");
249 assert_eq!(req.database.as_deref(), Some("mydb"));
250 assert_eq!(req.inner.search.as_deref(), Some("order"));
251 }
252
253 #[test]
254 fn unpinned_list_functions_request_defaults_to_brief_mode_without_search() {
255 let req: PinnedListFunctionsRequest = serde_json::from_str("{}").expect("empty object should parse");
256 assert!(req.search.is_none());
257 assert!(!req.detailed, "detailed must default to false");
258 }
259
260 #[test]
261 fn unpinned_list_functions_request_accepts_search_and_detailed() {
262 let req: PinnedListFunctionsRequest =
263 serde_json::from_str(r#"{"search": "order", "detailed": true}"#).expect("parse");
264 assert_eq!(req.search.as_deref(), Some("order"));
265 assert!(req.detailed);
266 }
267
268 #[test]
269 fn pinned_list_functions_request_accepts_database() {
270 let req: UnpinnedListFunctionsRequest =
271 serde_json::from_str(r#"{"database": "mydb", "search": "calc"}"#).expect("parse");
272 assert_eq!(req.database.as_deref(), Some("mydb"));
273 assert_eq!(req.inner.search.as_deref(), Some("calc"));
274 }
275
276 #[test]
277 fn unpinned_list_procedures_request_defaults_to_brief_mode_without_search() {
278 let req: PinnedListProceduresRequest = serde_json::from_str("{}").expect("empty object should parse");
279 assert!(req.search.is_none());
280 assert!(!req.detailed, "detailed must default to false");
281 }
282
283 #[test]
284 fn unpinned_list_procedures_request_accepts_search_and_detailed() {
285 let req: PinnedListProceduresRequest =
286 serde_json::from_str(r#"{"search": "archive", "detailed": true}"#).expect("parse");
287 assert_eq!(req.search.as_deref(), Some("archive"));
288 assert!(req.detailed);
289 }
290
291 #[test]
292 fn pinned_list_procedures_request_accepts_database() {
293 let req: UnpinnedListProceduresRequest = serde_json::from_str(r#"{"database": "mydb"}"#).expect("parse");
294 assert_eq!(req.database.as_deref(), Some("mydb"));
295 }
296
297 #[test]
298 fn unpinned_list_views_request_defaults_to_brief_mode_without_search() {
299 let req: PinnedListViewsRequest = serde_json::from_str("{}").expect("empty object should parse");
300 assert!(req.search.is_none());
301 assert!(!req.detailed, "detailed must default to false");
302 }
303
304 #[test]
305 fn unpinned_list_views_request_accepts_search_and_detailed() {
306 let req: PinnedListViewsRequest =
307 serde_json::from_str(r#"{"search": "active", "detailed": true}"#).expect("parse");
308 assert_eq!(req.search.as_deref(), Some("active"));
309 assert!(req.detailed);
310 }
311
312 #[test]
313 fn pinned_list_views_request_accepts_database() {
314 let req: UnpinnedListViewsRequest = serde_json::from_str(r#"{"database": "mydb"}"#).expect("parse");
315 assert_eq!(req.database.as_deref(), Some("mydb"));
316 }
317
318 #[test]
319 fn unpinned_list_materialized_views_request_defaults_to_brief_mode_without_search() {
320 let req: PinnedListMaterializedViewsRequest = serde_json::from_str("{}").expect("empty object should parse");
321 assert!(req.search.is_none());
322 assert!(!req.detailed, "detailed must default to false");
323 }
324
325 #[test]
326 fn unpinned_list_materialized_views_request_accepts_search_and_detailed() {
327 let req: PinnedListMaterializedViewsRequest =
328 serde_json::from_str(r#"{"search": "orders", "detailed": true}"#).expect("parse");
329 assert_eq!(req.search.as_deref(), Some("orders"));
330 assert!(req.detailed);
331 }
332
333 #[test]
334 fn pinned_list_materialized_views_request_accepts_database() {
335 let req: UnpinnedListMaterializedViewsRequest = serde_json::from_str(r#"{"database": "mydb"}"#).expect("parse");
336 assert_eq!(req.database.as_deref(), Some("mydb"));
337 }
338
339 #[test]
340 fn list_materialized_views_response_brief_constructor_wraps_vec() {
341 let response = ListMaterializedViewsResponse::brief(vec!["mv_recent_orders".into()], None);
342 assert!(matches!(response.materialized_views, ListEntries::Brief(ref v) if v == &["mv_recent_orders"]));
343 assert!(response.next_cursor.is_none());
344 }
345
346 #[test]
347 fn list_materialized_views_response_detailed_constructor_wraps_indexmap() {
348 let map = IndexMap::from([("mv_recent_orders".into(), json!({"populated": true}))]);
349 let response = ListMaterializedViewsResponse::detailed(map, None);
350 assert!(matches!(response.materialized_views, ListEntries::Detailed(_)));
351 }
352
353 #[test]
354 fn list_materialized_views_response_brief_matches_legacy_wire_shape() {
355 let response = ListMaterializedViewsResponse::brief(vec!["mv_recent_orders".into()], None);
356 assert_eq!(
357 serde_json::to_value(&response).unwrap(),
358 json!({"materializedViews": ["mv_recent_orders"]})
359 );
360 }
361}