use super::*;
pub struct LogSearch<'a> {
pub text: &'a str,
pub service_environment_id: Option<&'a str>,
pub level: Option<&'a str>,
pub stream: Option<&'a str>,
pub hours: Option<u32>,
pub limit: Option<u32>,
}
impl ApiClient {
pub async fn attention(&self) -> Result<Answer<AttentionPage>> {
self.get_answer("/api/infra/attention").await
}
pub async fn project_health(&self, project_id: &str) -> Result<Answer<HealthPage>> {
self.get_answer(&format!("/api/infra/projects/{project_id}/health"))
.await
}
pub async fn diagnosis(&self, service_environment_id: &str) -> Result<Answer<DiagnosisPage>> {
self.get_answer(&format!(
"/api/infra/services/{service_environment_id}/diagnosis"
))
.await
}
pub async fn variable_audit(&self, project_id: &str) -> Result<Answer<VariableAuditPage>> {
self.get_answer(&format!("/api/infra/projects/{project_id}/variables/audit"))
.await
}
pub async fn search_logs(
&self,
project_id: &str,
search: &LogSearch<'_>,
) -> Result<Answer<LogSearchPage>> {
self.get_answer(&format!(
"/api/infra/projects/{project_id}/logs/search{}",
search_query(search)
))
.await
}
pub async fn dependencies(
&self,
service_environment_id: &str,
) -> Result<Answer<DependenciesPage>> {
self.get_answer(&format!(
"/api/infra/services/{service_environment_id}/dependencies"
))
.await
}
pub async fn manifest(&self, project_id: &str) -> Result<Answer<ManifestPage>> {
self.get_answer(&format!("/api/infra/projects/{project_id}/manifest"))
.await
}
pub async fn compare_deployments(
&self,
from_id: &str,
to_id: &str,
) -> Result<Answer<ComparisonPage>> {
self.get_answer(&format!("/api/infra/deployments/{from_id}/compare/{to_id}"))
.await
}
pub async fn probe(
&self,
service_environment_id: &str,
path: &str,
) -> Result<Answer<ProbePage>> {
self.post_answer(
&format!("/api/infra/services/{service_environment_id}/probe"),
&serde_json::json!({ "path": path }),
)
.await
}
pub async fn wake(&self, service_environment_id: &str) -> Result<Answer<WokenPage>> {
let path = format!("/api/infra/services/{service_environment_id}/wake");
let response = self
.unbounded_request(Method::POST, &path, Some(&serde_json::json!({})))
.send()
.await?;
let text = Self::check(response).await?;
let raw = serde_json::from_str(&text)
.with_context(|| format!("could not read the answer from {path}"))?;
decoded(raw, &path)
}
pub async fn workflow_stats(
&self,
project_id: &str,
limit: u32,
) -> Result<Answer<WorkflowStatsPage>> {
self.get_answer(&format!(
"/api/infra/projects/{project_id}/workflows/stats?limit={limit}"
))
.await
}
}
fn search_query(search: &LogSearch<'_>) -> String {
let mut query = format!("?q={}", utf8_percent_encode(search.text, NON_ALPHANUMERIC));
let pairs = [
("service", search.service_environment_id.map(str::to_owned)),
("level", search.level.map(str::to_owned)),
("stream", search.stream.map(str::to_owned)),
("hours", search.hours.map(|hours| hours.to_string())),
("limit", search.limit.map(|limit| limit.to_string())),
];
for (name, value) in pairs {
if let Some(value) = value {
query.push_str(&format!(
"&{name}={}",
utf8_percent_encode(&value, NON_ALPHANUMERIC)
));
}
}
query
}
#[cfg(test)]
mod tests {
use super::*;
fn only_text(text: &str) -> LogSearch<'_> {
LogSearch {
text,
service_environment_id: None,
level: None,
stream: None,
hours: None,
limit: None,
}
}
#[test]
fn it_should_escape_the_text_it_searches_for() {
assert_eq!(search_query(&only_text("a b&c")), "?q=a%20b%26c".to_owned());
}
#[test]
fn it_should_leave_out_every_filter_not_given() {
assert_eq!(search_query(&only_text("boom")), "?q=boom");
}
#[test]
fn it_should_send_the_filters_that_were_given() {
let search = LogSearch {
level: Some("error"),
hours: Some(48),
..only_text("boom")
};
assert_eq!(search_query(&search), "?q=boom&level=error&hours=48");
}
}