adguardian/fetch/
fetch_query_log.rs1use 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 #[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 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}