use dbmcp_server::pagination::Cursor;
use indexmap::IndexMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub use dbmcp_server::types::{
ListEntries, ListFunctionsResponse, ListProceduresResponse, ListTablesResponse, ListTriggersRequest,
ListTriggersResponse, ListViewsResponse,
};
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct DropTableRequest {
#[serde(default)]
pub database: Option<String>,
pub table: String,
#[serde(default)]
pub cascade: bool,
}
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ListTablesRequest {
#[serde(default)]
pub database: Option<String>,
#[serde(default)]
pub cursor: Option<Cursor>,
#[serde(default)]
pub search: Option<String>,
#[serde(default)]
pub detailed: bool,
}
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ListFunctionsRequest {
#[serde(default)]
pub database: Option<String>,
#[serde(default)]
pub cursor: Option<Cursor>,
#[serde(default)]
pub search: Option<String>,
#[serde(default)]
pub detailed: bool,
}
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ListViewsRequest {
#[serde(default)]
pub database: Option<String>,
#[serde(default)]
pub cursor: Option<Cursor>,
#[serde(default)]
pub search: Option<String>,
#[serde(default)]
pub detailed: bool,
}
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ListMaterializedViewsRequest {
#[serde(default)]
pub database: Option<String>,
#[serde(default)]
pub cursor: Option<Cursor>,
#[serde(default)]
pub search: Option<String>,
#[serde(default)]
pub detailed: bool,
}
#[derive(Debug, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ListMaterializedViewsResponse {
pub materialized_views: ListEntries,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<Cursor>,
}
impl ListMaterializedViewsResponse {
#[must_use]
pub fn brief(materialized_views: Vec<String>, next_cursor: Option<Cursor>) -> Self {
Self {
materialized_views: ListEntries::Brief(materialized_views),
next_cursor,
}
}
#[must_use]
pub fn detailed(materialized_views: IndexMap<String, Value>, next_cursor: Option<Cursor>) -> Self {
Self {
materialized_views: ListEntries::Detailed(materialized_views),
next_cursor,
}
}
}
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ListProceduresRequest {
#[serde(default)]
pub database: Option<String>,
#[serde(default)]
pub cursor: Option<Cursor>,
#[serde(default)]
pub search: Option<String>,
#[serde(default)]
pub detailed: bool,
}
#[cfg(test)]
mod tests {
use indexmap::IndexMap;
use serde_json::json;
use super::{
ListEntries, ListFunctionsRequest, ListMaterializedViewsRequest, ListMaterializedViewsResponse,
ListProceduresRequest, ListTablesRequest, ListViewsRequest,
};
#[test]
fn list_tables_request_defaults_to_brief_mode_without_search() {
let req: ListTablesRequest = serde_json::from_str("{}").expect("empty object should parse");
assert!(req.search.is_none());
assert!(!req.detailed, "detailed must default to false");
}
#[test]
fn list_tables_request_accepts_search_and_detailed() {
let req: ListTablesRequest = serde_json::from_str(r#"{"search": "order", "detailed": true}"#).expect("parse");
assert_eq!(req.search.as_deref(), Some("order"));
assert!(req.detailed);
}
#[test]
fn list_functions_request_defaults_to_brief_mode_without_search() {
let req: ListFunctionsRequest = serde_json::from_str("{}").expect("empty object should parse");
assert!(req.search.is_none());
assert!(!req.detailed, "detailed must default to false");
}
#[test]
fn list_functions_request_accepts_search_and_detailed() {
let req: ListFunctionsRequest =
serde_json::from_str(r#"{"search": "order", "detailed": true}"#).expect("parse");
assert_eq!(req.search.as_deref(), Some("order"));
assert!(req.detailed);
}
#[test]
fn list_procedures_request_defaults_to_brief_mode_without_search() {
let req: ListProceduresRequest = serde_json::from_str("{}").expect("empty object should parse");
assert!(req.search.is_none());
assert!(!req.detailed, "detailed must default to false");
}
#[test]
fn list_procedures_request_accepts_search_and_detailed() {
let req: ListProceduresRequest =
serde_json::from_str(r#"{"search": "archive", "detailed": true}"#).expect("parse");
assert_eq!(req.search.as_deref(), Some("archive"));
assert!(req.detailed);
}
#[test]
fn list_views_request_defaults_to_brief_mode_without_search() {
let req: ListViewsRequest = serde_json::from_str("{}").expect("empty object should parse");
assert!(req.search.is_none());
assert!(!req.detailed, "detailed must default to false");
}
#[test]
fn list_views_request_accepts_search_and_detailed() {
let req: ListViewsRequest = serde_json::from_str(r#"{"search": "active", "detailed": true}"#).expect("parse");
assert_eq!(req.search.as_deref(), Some("active"));
assert!(req.detailed);
}
#[test]
fn list_materialized_views_request_defaults_to_brief_mode_without_search() {
let req: ListMaterializedViewsRequest = serde_json::from_str("{}").expect("empty object should parse");
assert!(req.search.is_none());
assert!(!req.detailed, "detailed must default to false");
}
#[test]
fn list_materialized_views_request_accepts_search_and_detailed() {
let req: ListMaterializedViewsRequest =
serde_json::from_str(r#"{"search": "orders", "detailed": true}"#).expect("parse");
assert_eq!(req.search.as_deref(), Some("orders"));
assert!(req.detailed);
}
#[test]
fn list_materialized_views_response_brief_constructor_wraps_vec() {
let response = ListMaterializedViewsResponse::brief(vec!["mv_recent_orders".into()], None);
assert!(matches!(response.materialized_views, ListEntries::Brief(ref v) if v == &["mv_recent_orders"]));
assert!(response.next_cursor.is_none());
}
#[test]
fn list_materialized_views_response_detailed_constructor_wraps_indexmap() {
let map = IndexMap::from([("mv_recent_orders".into(), json!({"populated": true}))]);
let response = ListMaterializedViewsResponse::detailed(map, None);
assert!(matches!(response.materialized_views, ListEntries::Detailed(_)));
}
#[test]
fn list_materialized_views_response_brief_matches_legacy_wire_shape() {
let response = ListMaterializedViewsResponse::brief(vec!["mv_recent_orders".into()], None);
assert_eq!(
serde_json::to_value(&response).unwrap(),
json!({"materializedViews": ["mv_recent_orders"]})
);
}
}