use crate::{
GcpHttpClient, Result,
ops::dlp::DlpOps,
types::dlp::{DiscoveryConfig, ProjectDataProfile},
};
pub struct DlpClient<'a> {
ops: DlpOps<'a>,
}
impl<'a> DlpClient<'a> {
pub(crate) fn new(client: &'a GcpHttpClient) -> Self {
Self {
ops: DlpOps::new(client),
}
}
pub async fn list_discovery_configs(
&self,
project: &str,
location: &str,
) -> Result<Vec<DiscoveryConfig>> {
let parent = format!("projects/{}/locations/{}", project, location);
let mut all = Vec::new();
let mut page_token = String::new();
loop {
let resp = self
.ops
.list_discovery_configs(&parent, "100", &page_token, "")
.await?;
all.extend(resp.discovery_configs);
match resp.next_page_token {
Some(tok) if !tok.is_empty() => page_token = tok,
_ => break,
}
}
Ok(all)
}
pub async fn list_project_data_profiles(
&self,
project: &str,
location: &str,
) -> Result<Vec<ProjectDataProfile>> {
let parent = format!("projects/{}/locations/{}", project, location);
let mut all = Vec::new();
let mut page_token = String::new();
loop {
let resp = self
.ops
.list_project_data_profiles(&parent, "100", &page_token, "", "")
.await?;
all.extend(resp.project_data_profiles);
match resp.next_page_token {
Some(tok) if !tok.is_empty() => page_token = tok,
_ => break,
}
}
Ok(all)
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
#[tokio::test]
async fn test_list_discovery_configs() {
let mut mock = crate::MockClient::new();
mock.expect_get("/v2/projects/my-project/locations/global/discoveryConfigs?pageSize=100")
.returning_json(json!({
"discoveryConfigs": [
{
"name": "projects/my-project/locations/global/discoveryConfigs/config-1",
"displayName": "BigQuery Scanner",
"status": "RUNNING",
"createTime": "2024-01-01T00:00:00Z",
"updateTime": "2025-01-01T00:00:00Z",
"lastRunTime": "2025-02-01T00:00:00Z"
}
]
}))
.times(1);
let client = crate::GcpHttpClient::from_mock(mock);
let dlp = client.dlp();
let result = dlp.list_discovery_configs("my-project", "global").await;
assert!(result.is_ok());
let configs = result.unwrap();
assert_eq!(configs.len(), 1);
assert_eq!(
configs[0].name,
"projects/my-project/locations/global/discoveryConfigs/config-1"
);
assert_eq!(configs[0].status.as_deref(), Some("RUNNING"));
assert_eq!(
configs[0].last_run_time.as_deref(),
Some("2025-02-01T00:00:00Z")
);
}
#[tokio::test]
async fn test_list_discovery_configs_paginated() {
let mut mock = crate::MockClient::new();
mock.expect_get(
"/v2/projects/my-project/locations/global/discoveryConfigs?pageSize=100&pageToken=tok123",
)
.returning_json(json!({
"discoveryConfigs": [{"name": "projects/my-project/locations/global/discoveryConfigs/config-2", "status": "RUNNING"}]
}))
.times(1);
mock.expect_get(
"/v2/projects/my-project/locations/global/discoveryConfigs?pageSize=100",
)
.returning_json(json!({
"discoveryConfigs": [{"name": "projects/my-project/locations/global/discoveryConfigs/config-1", "status": "RUNNING"}],
"nextPageToken": "tok123"
}))
.times(1);
let client = crate::GcpHttpClient::from_mock(mock);
let dlp = client.dlp();
let result = dlp.list_discovery_configs("my-project", "global").await;
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 2);
}
#[tokio::test]
async fn test_list_project_data_profiles() {
let mut mock = crate::MockClient::new();
mock.expect_get(
"/v2/projects/my-project/locations/global/projectDataProfiles?pageSize=100",
)
.returning_json(json!({
"projectDataProfiles": [
{
"name": "projects/my-project/locations/global/projectDataProfiles/my-project",
"projectId": "my-project",
"profileLastGenerated": "2025-02-10T12:00:00Z",
"sensitivityScore": {"score": "SENSITIVITY_HIGH"},
"dataRiskLevel": {"score": "RISK_HIGH"},
"tableDataProfileCount": "42",
"fileStoreDataProfileCount": "5"
}
]
}))
.times(1);
let client = crate::GcpHttpClient::from_mock(mock);
let dlp = client.dlp();
let result = dlp.list_project_data_profiles("my-project", "global").await;
assert!(result.is_ok());
let profiles = result.unwrap();
assert_eq!(profiles.len(), 1);
assert_eq!(
profiles[0].name,
"projects/my-project/locations/global/projectDataProfiles/my-project"
);
assert_eq!(profiles[0].project_id.as_deref(), Some("my-project"));
let sens = profiles[0].sensitivity_score.as_ref().unwrap();
assert_eq!(sens.score.as_deref(), Some("SENSITIVITY_HIGH"));
let risk = profiles[0].data_risk_level.as_ref().unwrap();
assert_eq!(risk.score.as_deref(), Some("RISK_HIGH"));
}
#[tokio::test]
async fn test_list_project_data_profiles_paginated() {
let mut mock = crate::MockClient::new();
mock.expect_get(
"/v2/projects/my-project/locations/global/projectDataProfiles?pageSize=100&pageToken=tok456",
)
.returning_json(json!({
"projectDataProfiles": [{"name": "projects/my-project/locations/global/projectDataProfiles/proj-2", "projectId": "proj-2"}]
}))
.times(1);
mock.expect_get(
"/v2/projects/my-project/locations/global/projectDataProfiles?pageSize=100",
)
.returning_json(json!({
"projectDataProfiles": [{"name": "projects/my-project/locations/global/projectDataProfiles/proj-1", "projectId": "proj-1"}],
"nextPageToken": "tok456"
}))
.times(1);
let client = crate::GcpHttpClient::from_mock(mock);
let dlp = client.dlp();
let result = dlp.list_project_data_profiles("my-project", "global").await;
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 2);
}
}