use crate::client::KibanaClient;
use crate::etl::Extractor;
use crate::{Error, Result, ResultContext};
use serde_json::Value;
pub struct SpacesExtractor {
client: KibanaClient,
manifest: Option<super::SpacesManifest>,
}
impl SpacesExtractor {
pub fn new(client: KibanaClient, manifest: Option<super::SpacesManifest>) -> Self {
Self { client, manifest }
}
pub fn all(client: KibanaClient) -> Self {
Self::new(client, None)
}
pub async fn search_spaces(&self, _query: Option<&str>) -> Result<Vec<Value>> {
self.fetch_all_spaces().await
}
async fn fetch_all_spaces(&self) -> Result<Vec<Value>> {
let path = "/api/spaces/space";
tracing::debug!("Fetching spaces from {}", path);
let response = self
.client
.get(path)
.await
.with_context(|| "Failed to fetch spaces from Kibana")?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
return Err(Error::api_response(status, body));
}
let spaces: Vec<Value> = response
.json()
.await
.with_context(|| "Failed to parse spaces response")?;
tracing::info!("Fetched {} spaces from Kibana", spaces.len());
Ok(spaces)
}
pub async fn fetch_space(&self, space_id: &str) -> Result<Value> {
let path = format!("/api/spaces/space/{}", space_id);
tracing::debug!("Fetching space '{}' from {}", space_id, path);
let response = self
.client
.get(&path)
.await
.with_context(|| format!("Failed to fetch space '{}'", space_id))?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
return Err(Error::api_response(status, body));
}
let space: Value = response
.json()
.await
.with_context(|| format!("Failed to parse space '{}' response", space_id))?;
tracing::debug!("Fetched space: {}", space_id);
Ok(space)
}
async fn fetch_manifest_spaces(&self, manifest: &super::SpacesManifest) -> Result<Vec<Value>> {
let mut spaces = Vec::new();
for entry in &manifest.spaces {
match self.fetch_space(&entry.id).await {
Ok(space) => spaces.push(space),
Err(e) => {
tracing::warn!("Failed to fetch space '{}': {}", entry.id, e);
}
}
}
tracing::info!("Fetched {} space(s) from manifest", spaces.len());
Ok(spaces)
}
fn filter_spaces(&self, spaces: Vec<Value>) -> Vec<Value> {
if let Some(manifest) = &self.manifest {
spaces
.into_iter()
.filter(|space| {
if let Some(id) = space.get("id").and_then(|v| v.as_str()) {
manifest.contains(id)
} else {
false
}
})
.collect()
} else {
spaces
}
}
}
impl Extractor for SpacesExtractor {
type Item = Value;
async fn extract(&self) -> Result<Vec<Self::Item>> {
let spaces = if let Some(manifest) = &self.manifest {
self.fetch_manifest_spaces(manifest).await?
} else {
let all_spaces = self.fetch_all_spaces().await?;
self.filter_spaces(all_spaces)
};
tracing::info!(
"Extracted {} space(s){}",
spaces.len(),
if self.manifest.is_some() {
" (from manifest)"
} else {
""
}
);
Ok(spaces)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::Auth;
use serde_json::json;
use url::Url;
#[test]
fn test_filter_with_manifest() {
let url = Url::parse("http://localhost:5601").unwrap();
let client = KibanaClient::new(url, Auth::None).unwrap();
let manifest = super::super::SpacesManifest::with_spaces(vec![
super::super::SpaceEntry::new("default".to_string(), "Default".to_string()),
super::super::SpaceEntry::new("marketing".to_string(), "Marketing".to_string()),
]);
let extractor = SpacesExtractor::new(client, Some(manifest));
let spaces = vec![
json!({"id": "default", "name": "Default"}),
json!({"id": "marketing", "name": "Marketing"}),
json!({"id": "engineering", "name": "Engineering"}),
];
let filtered = extractor.filter_spaces(spaces);
assert_eq!(filtered.len(), 2);
let ids: Vec<&str> = filtered
.iter()
.filter_map(|s| s.get("id").and_then(|v| v.as_str()))
.collect();
assert!(ids.contains(&"default"));
assert!(ids.contains(&"marketing"));
assert!(!ids.contains(&"engineering"));
}
#[test]
fn test_filter_without_manifest() {
let url = Url::parse("http://localhost:5601").unwrap();
let client = KibanaClient::new(url, Auth::None).unwrap();
let extractor = SpacesExtractor::all(client);
let spaces = vec![
json!({"id": "default", "name": "Default"}),
json!({"id": "marketing", "name": "Marketing"}),
];
let filtered = extractor.filter_spaces(spaces.clone());
assert_eq!(filtered.len(), 2);
assert_eq!(filtered, spaces);
}
}