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