Skip to main content

dbmcp_postgres/
types.rs

1//! PostgreSQL-specific MCP tool request types.
2//!
3//! Shared `ListTriggersRequest` and the shared brief/detailed payload
4//! (`ListEntries`, `ListEntriesResponse`) live in the `dbmcp-server` crate;
5//! they are re-exported here so call sites can keep importing them from
6//! `crate::types`.
7
8use dbmcp_server::pagination::Cursor;
9use schemars::JsonSchema;
10use serde::Deserialize;
11
12pub use dbmcp_server::types::{ListEntries, ListEntriesResponse, ListTriggersRequest};
13
14/// Request for the `dropTable` tool.
15#[derive(Debug, Default, Deserialize, JsonSchema)]
16pub struct DropTableRequest {
17    /// Name of the table to drop. Must be non-empty.
18    pub table: String,
19    /// If true, use CASCADE to also drop dependent foreign key constraints. Defaults to false.
20    #[serde(default)]
21    pub cascade: bool,
22    /// Database containing the table. Defaults to the active database.
23    #[serde(default)]
24    pub database: Option<String>,
25}
26
27/// Request for the Postgres `listTables` tool — extends the shared shape with `search` and `detailed`.
28#[derive(Debug, Default, Deserialize, JsonSchema)]
29pub struct ListTablesRequest {
30    /// Opaque cursor from a prior response's `nextCursor`; omit for the first page.
31    #[serde(default)]
32    pub cursor: Option<Cursor>,
33    /// Optional case-insensitive filter on table names. The input is used within an `ILIKE`
34    /// clause: `%` matches any sequence of characters and `_` matches any single character.
35    #[serde(default)]
36    pub search: Option<String>,
37    /// When `true`, each returned entry is a full metadata object (columns,
38    /// constraints, indexes, triggers, owner, comment, kind); when `false` or
39    /// omitted, each entry is the bare table-name string.
40    #[serde(default)]
41    pub detailed: bool,
42    /// Database to list tables from. Defaults to the active database.
43    #[serde(default)]
44    pub database: Option<String>,
45}
46
47/// Request for the Postgres `listFunctions` tool — extends the shared shape with `search` and `detailed`.
48#[derive(Debug, Default, Deserialize, JsonSchema)]
49pub struct ListFunctionsRequest {
50    /// Opaque cursor from a prior response's `nextCursor`; omit for the first page.
51    #[serde(default)]
52    pub cursor: Option<Cursor>,
53    /// Optional case-insensitive filter on function names. The input is used within an `ILIKE`
54    /// clause: `%` matches any sequence of characters and `_` matches any single character.
55    #[serde(default)]
56    pub search: Option<String>,
57    /// When `true`, each returned entry is a full metadata object (schema, name,
58    /// language, arguments, returnType, volatility, strict, security,
59    /// parallelSafety, owner, description, definition); when `false` or omitted,
60    /// each entry is the bare function-name string.
61    #[serde(default)]
62    pub detailed: bool,
63    /// Database to list functions from. Defaults to the active database.
64    #[serde(default)]
65    pub database: Option<String>,
66}
67
68/// Request for the Postgres `listViews` tool — extends the shared shape with `search` and `detailed`.
69#[derive(Debug, Default, Deserialize, JsonSchema)]
70pub struct ListViewsRequest {
71    /// Opaque cursor from a prior response's `nextCursor`; omit for the first page.
72    #[serde(default)]
73    pub cursor: Option<Cursor>,
74    /// Optional case-insensitive filter on view names. The input is used within an `ILIKE`
75    /// clause: `%` matches any sequence of characters and `_` matches any single character.
76    #[serde(default)]
77    pub search: Option<String>,
78    /// When `true`, each returned entry is a full metadata object (schema, owner,
79    /// description, definition); when `false` or omitted, each entry is the bare
80    /// view-name string.
81    #[serde(default)]
82    pub detailed: bool,
83    /// Database to list views from. Defaults to the active database.
84    #[serde(default)]
85    pub database: Option<String>,
86}
87
88/// Request for the Postgres `listMaterializedViews` tool — extends the shared shape with `search` and `detailed`.
89#[derive(Debug, Default, Deserialize, JsonSchema)]
90pub struct ListMaterializedViewsRequest {
91    /// Opaque cursor from a prior response's `nextCursor`; omit for the first page.
92    #[serde(default)]
93    pub cursor: Option<Cursor>,
94    /// Optional case-insensitive filter on matview names. The input is used within an `ILIKE`
95    /// clause: `%` matches any sequence of characters and `_` matches any single character.
96    #[serde(default)]
97    pub search: Option<String>,
98    /// When `true`, each returned entry is a full metadata object (schema, owner,
99    /// description, definition, populated, indexed); when `false` or omitted, each
100    /// entry is the bare matview-name string.
101    #[serde(default)]
102    pub detailed: bool,
103    /// Database to list materialized views from. Defaults to the active database.
104    #[serde(default)]
105    pub database: Option<String>,
106}
107
108/// Request for the Postgres `listProcedures` tool — extends the shared shape with `search` and `detailed`.
109#[derive(Debug, Default, Deserialize, JsonSchema)]
110pub struct ListProceduresRequest {
111    /// Opaque cursor from a prior response's `nextCursor`; omit for the first page.
112    #[serde(default)]
113    pub cursor: Option<Cursor>,
114    /// Optional case-insensitive filter on procedure names. The input is used within an `ILIKE`
115    /// clause: `%` matches any sequence of characters and `_` matches any single character.
116    #[serde(default)]
117    pub search: Option<String>,
118    /// When `true`, each returned entry is a full metadata object (schema, name,
119    /// language, arguments, security, owner, description, definition); when `false`
120    /// or omitted, each entry is the bare procedure-name string.
121    #[serde(default)]
122    pub detailed: bool,
123    /// Database to list procedures from. Defaults to the active database.
124    #[serde(default)]
125    pub database: Option<String>,
126}
127
128#[cfg(test)]
129mod tests {
130    use super::{
131        ListFunctionsRequest, ListMaterializedViewsRequest, ListProceduresRequest, ListTablesRequest, ListViewsRequest,
132    };
133
134    #[test]
135    fn list_tables_request_defaults_to_brief_mode_without_search() {
136        let req: ListTablesRequest = serde_json::from_str("{}").expect("empty object should parse");
137        assert!(req.search.is_none());
138        assert!(!req.detailed, "detailed must default to false");
139        assert!(req.database.is_none());
140    }
141
142    #[test]
143    fn list_tables_request_accepts_search_and_detailed() {
144        let req: ListTablesRequest = serde_json::from_str(r#"{"search": "order", "detailed": true}"#).expect("parse");
145        assert_eq!(req.search.as_deref(), Some("order"));
146        assert!(req.detailed);
147    }
148
149    #[test]
150    fn list_tables_request_accepts_database_and_other_fields() {
151        let req: ListTablesRequest = serde_json::from_str(r#"{"database": "mydb", "search": "order"}"#).expect("parse");
152        assert_eq!(req.database.as_deref(), Some("mydb"));
153        assert_eq!(req.search.as_deref(), Some("order"));
154    }
155
156    #[test]
157    fn list_functions_request_defaults_to_brief_mode_without_search() {
158        let req: ListFunctionsRequest = serde_json::from_str("{}").expect("empty object should parse");
159        assert!(req.search.is_none());
160        assert!(!req.detailed, "detailed must default to false");
161        assert!(req.database.is_none());
162    }
163
164    #[test]
165    fn list_functions_request_accepts_search_and_detailed() {
166        let req: ListFunctionsRequest =
167            serde_json::from_str(r#"{"search": "order", "detailed": true}"#).expect("parse");
168        assert_eq!(req.search.as_deref(), Some("order"));
169        assert!(req.detailed);
170    }
171
172    #[test]
173    fn list_functions_request_accepts_database() {
174        let req: ListFunctionsRequest =
175            serde_json::from_str(r#"{"database": "mydb", "search": "calc"}"#).expect("parse");
176        assert_eq!(req.database.as_deref(), Some("mydb"));
177        assert_eq!(req.search.as_deref(), Some("calc"));
178    }
179
180    #[test]
181    fn list_procedures_request_defaults_to_brief_mode_without_search() {
182        let req: ListProceduresRequest = 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        assert!(req.database.is_none());
186    }
187
188    #[test]
189    fn list_procedures_request_accepts_search_and_detailed() {
190        let req: ListProceduresRequest =
191            serde_json::from_str(r#"{"search": "archive", "detailed": true}"#).expect("parse");
192        assert_eq!(req.search.as_deref(), Some("archive"));
193        assert!(req.detailed);
194    }
195
196    #[test]
197    fn list_procedures_request_accepts_database() {
198        let req: ListProceduresRequest = serde_json::from_str(r#"{"database": "mydb"}"#).expect("parse");
199        assert_eq!(req.database.as_deref(), Some("mydb"));
200    }
201
202    #[test]
203    fn list_views_request_defaults_to_brief_mode_without_search() {
204        let req: ListViewsRequest = serde_json::from_str("{}").expect("empty object should parse");
205        assert!(req.search.is_none());
206        assert!(!req.detailed, "detailed must default to false");
207        assert!(req.database.is_none());
208    }
209
210    #[test]
211    fn list_views_request_accepts_search_and_detailed() {
212        let req: ListViewsRequest = serde_json::from_str(r#"{"search": "active", "detailed": true}"#).expect("parse");
213        assert_eq!(req.search.as_deref(), Some("active"));
214        assert!(req.detailed);
215    }
216
217    #[test]
218    fn list_views_request_accepts_database() {
219        let req: ListViewsRequest = serde_json::from_str(r#"{"database": "mydb"}"#).expect("parse");
220        assert_eq!(req.database.as_deref(), Some("mydb"));
221    }
222
223    #[test]
224    fn list_materialized_views_request_defaults_to_brief_mode_without_search() {
225        let req: ListMaterializedViewsRequest = serde_json::from_str("{}").expect("empty object should parse");
226        assert!(req.search.is_none());
227        assert!(!req.detailed, "detailed must default to false");
228        assert!(req.database.is_none());
229    }
230
231    #[test]
232    fn list_materialized_views_request_accepts_search_and_detailed() {
233        let req: ListMaterializedViewsRequest =
234            serde_json::from_str(r#"{"search": "orders", "detailed": true}"#).expect("parse");
235        assert_eq!(req.search.as_deref(), Some("orders"));
236        assert!(req.detailed);
237    }
238
239    #[test]
240    fn list_materialized_views_request_accepts_database() {
241        let req: ListMaterializedViewsRequest = serde_json::from_str(r#"{"database": "mydb"}"#).expect("parse");
242        assert_eq!(req.database.as_deref(), Some("mydb"));
243    }
244}