1use indexmap::IndexMap;
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13use crate::pagination::Cursor;
14
15#[derive(Debug, Serialize, JsonSchema)]
21#[serde(untagged)]
22pub enum ListEntries {
23 Brief(Vec<String>),
25 Detailed(IndexMap<String, Value>),
27}
28
29impl ListEntries {
30 #[must_use]
32 pub fn len(&self) -> usize {
33 match self {
34 Self::Brief(v) => v.len(),
35 Self::Detailed(m) => m.len(),
36 }
37 }
38
39 #[must_use]
41 pub fn is_empty(&self) -> bool {
42 self.len() == 0
43 }
44
45 #[must_use]
47 pub fn as_brief(&self) -> Option<&[String]> {
48 if let Self::Brief(v) = self { Some(v) } else { None }
49 }
50
51 #[must_use]
53 pub fn as_detailed(&self) -> Option<&IndexMap<String, Value>> {
54 if let Self::Detailed(m) = self { Some(m) } else { None }
55 }
56
57 #[must_use]
59 pub fn into_brief(self) -> Option<Vec<String>> {
60 if let Self::Brief(v) = self { Some(v) } else { None }
61 }
62}
63
64#[derive(Debug, Serialize, JsonSchema)]
67pub struct ListEntriesResponse {
68 pub entries: ListEntries,
70 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
72 pub next_cursor: Option<Cursor>,
73}
74
75impl ListEntriesResponse {
76 #[must_use]
78 pub fn brief(entries: Vec<String>, next_cursor: Option<Cursor>) -> Self {
79 Self {
80 entries: ListEntries::Brief(entries),
81 next_cursor,
82 }
83 }
84
85 #[must_use]
87 pub fn detailed(entries: IndexMap<String, Value>, next_cursor: Option<Cursor>) -> Self {
88 Self {
89 entries: ListEntries::Detailed(entries),
90 next_cursor,
91 }
92 }
93}
94
95#[derive(Debug, Serialize, JsonSchema)]
97pub struct MessageResponse {
98 pub message: String,
100}
101
102#[derive(Debug, Default, Deserialize, JsonSchema)]
104pub struct ListDatabasesRequest {
105 #[serde(default)]
107 pub cursor: Option<Cursor>,
108}
109
110#[derive(Debug, Serialize, JsonSchema)]
112pub struct ListDatabasesResponse {
113 pub databases: Vec<String>,
115 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
117 pub next_cursor: Option<Cursor>,
118}
119
120#[derive(Debug, Default, Deserialize, JsonSchema)]
122pub struct CreateDatabaseRequest {
123 pub database: String,
125}
126
127#[derive(Debug, Default, Deserialize, JsonSchema)]
129pub struct DropDatabaseRequest {
130 pub database: String,
132}
133
134#[derive(Debug, Default, Deserialize, JsonSchema)]
136pub struct ListViewsRequest {
137 #[serde(default)]
139 pub cursor: Option<Cursor>,
140 #[serde(default)]
142 pub database: Option<String>,
143}
144
145#[derive(Debug, Default, Deserialize, JsonSchema)]
147pub struct ListTriggersRequest {
148 #[serde(default)]
150 pub cursor: Option<Cursor>,
151 #[serde(default)]
154 pub search: Option<String>,
155 #[serde(default)]
159 pub detailed: bool,
160 #[serde(default)]
162 pub database: Option<String>,
163}
164
165#[derive(Debug, Default, Deserialize, JsonSchema)]
167pub struct ListFunctionsRequest {
168 #[serde(default)]
170 pub cursor: Option<Cursor>,
171 #[serde(default)]
173 pub database: Option<String>,
174}
175
176#[derive(Debug, Default, Deserialize, JsonSchema)]
178pub struct QueryRequest {
179 pub query: String,
181 #[serde(default)]
183 pub database: Option<String>,
184}
185
186#[derive(Debug, Default, Deserialize, JsonSchema)]
188pub struct ReadQueryRequest {
189 pub query: String,
191 #[serde(default)]
193 pub cursor: Option<Cursor>,
194 #[serde(default)]
196 pub database: Option<String>,
197}
198
199#[derive(Debug, Serialize, JsonSchema)]
201pub struct QueryResponse {
202 pub rows: Vec<Value>,
204}
205
206#[derive(Debug, Serialize, JsonSchema)]
208pub struct ReadQueryResponse {
209 pub rows: Vec<Value>,
211 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
215 pub next_cursor: Option<Cursor>,
216}
217
218#[derive(Debug, Default, Deserialize, JsonSchema)]
220pub struct ExplainQueryRequest {
221 pub query: String,
223 #[serde(default)]
225 pub analyze: bool,
226 #[serde(default)]
228 pub database: Option<String>,
229}
230
231#[cfg(test)]
232mod tests {
233 use super::{IndexMap, ListEntries, ListEntriesResponse, ListTriggersRequest};
234 use serde_json::{Value, json};
235
236 #[test]
237 fn list_triggers_request_defaults_to_brief_mode_without_search() {
238 let req: ListTriggersRequest = serde_json::from_str("{}").expect("empty object should parse");
239 assert!(req.search.is_none());
240 assert!(!req.detailed, "detailed must default to false");
241 assert!(req.database.is_none());
242 }
243
244 #[test]
245 fn list_triggers_request_accepts_search_and_detailed() {
246 let req: ListTriggersRequest = serde_json::from_str(r#"{"search": "audit", "detailed": true}"#).expect("parse");
247 assert_eq!(req.search.as_deref(), Some("audit"));
248 assert!(req.detailed);
249 }
250
251 #[test]
252 fn list_triggers_request_accepts_database_and_inner_fields() {
253 let req: ListTriggersRequest =
254 serde_json::from_str(r#"{"database": "mydb", "search": "audit", "detailed": true}"#).expect("parse");
255 assert_eq!(req.database.as_deref(), Some("mydb"));
256 assert_eq!(req.search.as_deref(), Some("audit"));
257 assert!(req.detailed);
258 }
259
260 #[test]
261 fn brief_serializes_as_bare_string_array() {
262 let entries = ListEntries::Brief(vec!["customers".into(), "orders".into()]);
263 assert_eq!(serde_json::to_value(&entries).unwrap(), json!(["customers", "orders"]));
264 }
265
266 #[test]
267 fn detailed_serializes_as_keyed_object() {
268 let entries = ListEntries::Detailed(IndexMap::from([("orders".into(), json!({"kind": "TABLE"}))]));
269 assert_eq!(
270 serde_json::to_value(&entries).unwrap(),
271 json!({"orders": {"kind": "TABLE"}})
272 );
273 }
274
275 #[test]
276 fn brief_empty_serializes_as_empty_array() {
277 assert_eq!(serde_json::to_value(ListEntries::Brief(Vec::new())).unwrap(), json!([]));
278 }
279
280 #[test]
281 fn detailed_empty_serializes_as_empty_object() {
282 assert_eq!(
283 serde_json::to_value(ListEntries::Detailed(IndexMap::new())).unwrap(),
284 json!({})
285 );
286 }
287
288 #[test]
289 fn detailed_preserves_insertion_order() {
290 let map = IndexMap::from([
291 ("c".into(), json!({})),
292 ("a".into(), json!({})),
293 ("b".into(), json!({})),
294 ]);
295 let s = serde_json::to_string(&ListEntries::Detailed(map)).unwrap();
296 let positions = ["\"c\"", "\"a\"", "\"b\""].map(|k| s.find(k).expect(k));
297 assert!(positions.is_sorted(), "insertion order not preserved: {s}");
298 }
299
300 #[test]
301 fn list_entries_response_brief_serializes_with_entries_key() {
302 let response = ListEntriesResponse::brief(vec!["a".into()], None);
303 assert_eq!(serde_json::to_value(&response).unwrap(), json!({"entries": ["a"]}));
304 }
305
306 #[test]
307 fn list_entries_response_detailed_serializes_with_entries_key() {
308 let map = IndexMap::from([("a".into(), json!({"kind": "TABLE"}))]);
309 let response = ListEntriesResponse::detailed(map, None);
310 assert_eq!(
311 serde_json::to_value(&response).unwrap(),
312 json!({"entries": {"a": {"kind": "TABLE"}}})
313 );
314 }
315
316 #[test]
317 fn list_entries_response_omits_next_cursor_when_none() {
318 let response = ListEntriesResponse::brief(vec!["a".into()], None);
319 let value = serde_json::to_value(&response).unwrap();
320 assert!(
321 value.get("nextCursor").is_none(),
322 "nextCursor must be omitted when None"
323 );
324 }
325
326 #[test]
327 fn as_brief_and_as_detailed_unwrap_correct_variant() {
328 let brief = ListEntries::Brief(vec!["a".into()]);
329 assert_eq!(brief.as_brief(), Some(&["a".into()][..]));
330 assert!(brief.as_detailed().is_none());
331
332 let det = ListEntries::Detailed(IndexMap::from([("x".into(), json!(1))]));
333 assert!(det.as_brief().is_none());
334 assert_eq!(det.as_detailed().map(IndexMap::len), Some(1));
335 }
336
337 #[test]
341 fn detailed_payload_strictly_smaller_than_array_form() {
342 let metadata = json!({
343 "schema": "public", "kind": "TABLE", "owner": "app", "comment": null,
344 "columns": [
345 {"name": "id", "dataType": "bigint", "ordinalPosition": 1, "nullable": false, "default": null, "comment": null},
346 {"name": "created_at", "dataType": "timestamptz", "ordinalPosition": 2, "nullable": false, "default": "now()", "comment": null},
347 ],
348 "constraints": [{"name": "pk", "type": "PRIMARY KEY", "columns": ["id"], "definition": "PRIMARY KEY (id)"}],
349 "indexes": [], "triggers": [],
350 });
351 let tables = [
352 "customers",
353 "orders",
354 "items",
355 "products",
356 "inventory",
357 "suppliers",
358 "shipments",
359 "invoices",
360 "payments",
361 "audits",
362 ];
363 let new_map: IndexMap<String, Value> = tables.iter().map(|n| ((*n).into(), metadata.clone())).collect();
364 let old: Vec<Value> = tables
365 .iter()
366 .map(|n| {
367 let mut v = metadata.clone();
368 v["name"] = json!(n);
369 v
370 })
371 .collect();
372 let new_len = serde_json::to_vec(&ListEntries::Detailed(new_map)).unwrap().len();
373 let old_len = serde_json::to_vec(&old).unwrap().len();
374 assert!(new_len < old_len, "payload not smaller: new={new_len} old={old_len}");
375 }
376}