Skip to main content

adguardian/fetch/
fetch_query_log.rs

1use base64::{engine::general_purpose::STANDARD, Engine as _};
2use reqwest::header::{HeaderValue, AUTHORIZATION, CONTENT_LENGTH};
3use serde::Deserialize;
4
5#[derive(Default, Deserialize)]
6#[serde(default)]
7pub struct QueryResponse {
8  pub data: Vec<Query>,
9}
10
11#[derive(Default, Deserialize)]
12#[serde(default)]
13pub struct Query {
14  pub cached: bool,
15  pub client: String,
16  pub upstream: String,
17  #[serde(rename = "elapsedMs")]
18  pub elapsed_ms: String,
19  pub question: Question,
20  pub reason: String,
21  pub time: String,
22}
23
24#[derive(Default, Deserialize)]
25#[serde(default)]
26pub struct Question {
27  pub class: String,
28  pub name: String,
29  #[serde(rename = "type")]
30  pub question_type: String,
31}
32
33pub async fn fetch_adguard_query_log(
34  client: &reqwest::Client,
35  endpoint: &str,
36  username: &str,
37  password: &str,
38  limit: u32,
39) -> Result<QueryResponse, anyhow::Error> {
40  let auth_string = format!("{}:{}", username, password);
41  let auth_header_value = format!("Basic {}", STANDARD.encode(&auth_string));
42  let mut headers = reqwest::header::HeaderMap::new();
43  headers.insert(AUTHORIZATION, auth_header_value.parse()?);
44  headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
45
46  let url = format!("{}/control/querylog?limit={}", endpoint, limit);
47  let response = client.get(&url).headers(headers).send().await?;
48  if !response.status().is_success() {
49    return Err(anyhow::anyhow!(
50      "Request failed with status code {}",
51      response.status()
52    ));
53  }
54
55  let data = response.json().await?;
56  Ok(data)
57}
58
59#[cfg(test)]
60mod tests {
61  use super::*;
62  use crate::fetch::fetch_stats::StatsResponse;
63  use crate::fetch::fetch_status::StatusResponse;
64
65  // Missing or partial fields get decoded to defaults, instead of erroring
66  #[test]
67  fn empty_and_partial_json_decode_to_defaults() {
68    serde_json::from_str::<QueryResponse>("{}").unwrap();
69    serde_json::from_str::<StatsResponse>("{}").unwrap();
70    serde_json::from_str::<StatusResponse>("{}").unwrap();
71    serde_json::from_str::<StatsResponse>(r#"{"num_dns_queries":5}"#).unwrap();
72
73    // A blocked query has no `upstream`, default to empty
74    let q = r#"{"cached":false,"client":"1.2.3.4","elapsedMs":"0.1",
75      "question":{"class":"IN","name":"x.com","type":"A"},"reason":"x","time":"t"}"#;
76    assert_eq!(serde_json::from_str::<Query>(q).unwrap().upstream, "");
77  }
78}