use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SelectTenantDTO {
#[serde(default, alias = "tenant_id")]
pub tenant_id: Option<Uuid>,
}
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
#[serde(transparent)]
pub struct GrantDatasetPermissionBody(pub Vec<Uuid>);
#[derive(Debug, Clone, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct GrantDatasetPermissionQuery {
pub permission_name: String,
}
#[derive(Debug, Clone, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct CreateRoleQuery {
pub role_name: String,
}
#[derive(Debug, Clone, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct CreateTenantQuery {
pub tenant_name: String,
}
#[derive(Debug, Clone, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct AssignRoleQuery {
pub role_id: Uuid,
}
#[derive(Debug, Clone, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct AddUserToTenantQuery {
pub tenant_id: Uuid,
}
#[derive(Debug, Clone, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct RevokeDatasetPermissionQuery {
pub permission_name: String,
}
#[derive(Debug, Clone, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct RemoveUserFromRoleQuery {
pub role_id: Uuid,
}
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct MessageResponse {
pub message: String,
}
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct CreateRoleResponse {
pub message: String,
pub role_id: Uuid,
pub tenant_id: Uuid,
}
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct CreateTenantResponse {
pub message: String,
pub tenant_id: Uuid,
}
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct SelectTenantResponse {
pub message: String,
#[serde(serialize_with = "serialize_tenant_id_with_none_literal")]
pub tenant_id: Option<Uuid>,
}
fn serialize_tenant_id_with_none_literal<S>(
value: &Option<Uuid>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match value {
Some(uuid) => serializer.serialize_str(&uuid.hyphenated().to_string()),
None => serializer.serialize_str("None"),
}
}
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct TenantSummary {
pub id: Uuid,
pub name: String,
}
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct RoleSummary {
pub id: Uuid,
pub name: String,
pub description: Option<String>,
pub user_count: Option<usize>,
}
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct UserInRole {
pub id: Uuid,
pub name: String,
}
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct UserInTenant {
pub id: Uuid,
pub email: String,
pub roles: Vec<RoleSummary>,
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test code — panics are acceptable failures"
)]
mod tests {
use super::*;
#[test]
fn select_tenant_dto_accepts_null_tenant_id() {
let parsed: SelectTenantDTO =
serde_json::from_str(r#"{"tenant_id": null}"#).expect("parse");
assert_eq!(parsed.tenant_id, None);
}
#[test]
fn select_tenant_dto_accepts_missing_field() {
let parsed: SelectTenantDTO = serde_json::from_str("{}").expect("parse");
assert_eq!(parsed.tenant_id, None);
}
#[test]
fn select_tenant_dto_accepts_camelcase_input() {
let parsed: SelectTenantDTO =
serde_json::from_str(r#"{"tenantId": "00000000-0000-0000-0000-000000000001"}"#)
.expect("parse camelCase");
assert_eq!(
parsed.tenant_id,
Some(Uuid::parse_str("00000000-0000-0000-0000-000000000001").expect("uuid"))
);
}
#[test]
fn select_tenant_dto_accepts_snake_case_input_via_alias() {
let parsed: SelectTenantDTO =
serde_json::from_str(r#"{"tenant_id": "00000000-0000-0000-0000-000000000001"}"#)
.expect("parse snake_case");
assert_eq!(
parsed.tenant_id,
Some(Uuid::parse_str("00000000-0000-0000-0000-000000000001").expect("uuid"))
);
}
#[test]
fn select_tenant_dto_serializes_camelcase_only() {
let dto = SelectTenantDTO {
tenant_id: Some(Uuid::nil()),
};
let s = serde_json::to_string(&dto).expect("serialize");
assert!(s.contains("\"tenantId\""), "missing tenantId: {s}");
assert!(
!s.contains("\"tenant_id\""),
"snake_case tenant_id leaked: {s}"
);
}
#[test]
fn select_tenant_response_serializes_none_as_string() {
let r = SelectTenantResponse {
message: "Tenant selected.".into(),
tenant_id: None,
};
let s = serde_json::to_string(&r).expect("serialize");
assert!(s.contains(r#""tenant_id":"None""#));
}
#[test]
fn select_tenant_response_serializes_uuid_as_string() {
let id = Uuid::nil();
let r = SelectTenantResponse {
message: "Tenant selected.".into(),
tenant_id: Some(id),
};
let s = serde_json::to_string(&r).expect("serialize");
assert!(s.contains("00000000-0000-0000-0000-000000000000"));
}
}