use std::collections::HashMap;
use std::sync::Arc;
use viewpoint_cdp::CdpConnection;
use viewpoint_cdp::protocol::network::GetResponseBodyParams;
use super::request::Request;
use crate::error::NetworkError;
#[derive(Debug, Clone)]
pub struct Response {
url: String,
status: u16,
status_text: String,
headers: HashMap<String, String>,
mime_type: String,
from_cache: bool,
from_service_worker: bool,
request: Request,
connection: Arc<CdpConnection>,
session_id: String,
request_id: String,
security_details: Option<SecurityDetails>,
remote_address: Option<RemoteAddress>,
}
impl Response {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
cdp_response: viewpoint_cdp::protocol::network::Response,
request: Request,
connection: Arc<CdpConnection>,
session_id: String,
request_id: String,
) -> Self {
let remote_address = cdp_response.remote_ip_address.map(|ip| RemoteAddress {
ip_address: ip,
port: cdp_response.remote_port.unwrap_or(0) as u16,
});
let security_details = cdp_response.security_details.map(SecurityDetails::from);
Self {
url: cdp_response.url,
status: cdp_response.status as u16,
status_text: cdp_response.status_text,
headers: cdp_response.headers,
mime_type: cdp_response.mime_type,
from_cache: cdp_response.from_disk_cache.unwrap_or(false),
from_service_worker: cdp_response.from_service_worker.unwrap_or(false),
request,
connection,
session_id,
request_id,
security_details,
remote_address,
}
}
pub fn url(&self) -> &str {
&self.url
}
pub fn status(&self) -> u16 {
self.status
}
pub fn status_text(&self) -> &str {
&self.status_text
}
pub fn ok(&self) -> bool {
(200..300).contains(&self.status)
}
pub fn headers(&self) -> &HashMap<String, String> {
&self.headers
}
pub fn header_value(&self, name: &str) -> Option<&str> {
self.headers
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(name))
.map(|(_, v)| v.as_str())
}
pub async fn all_headers(&self) -> HashMap<String, String> {
self.headers.clone()
}
pub fn mime_type(&self) -> &str {
&self.mime_type
}
pub fn from_cache(&self) -> bool {
self.from_cache
}
pub fn from_service_worker(&self) -> bool {
self.from_service_worker
}
pub fn request(&self) -> &Request {
&self.request
}
pub async fn body(&self) -> Result<Vec<u8>, NetworkError> {
let result: viewpoint_cdp::protocol::network::GetResponseBodyResult = self
.connection
.send_command(
"Network.getResponseBody",
Some(GetResponseBodyParams {
request_id: self.request_id.clone(),
}),
Some(&self.session_id),
)
.await
.map_err(NetworkError::from)?;
if result.base64_encoded {
use base64::Engine;
base64::engine::general_purpose::STANDARD
.decode(&result.body)
.map_err(|e| NetworkError::InvalidResponse(format!("Failed to decode base64: {e}")))
} else {
Ok(result.body.into_bytes())
}
}
pub async fn text(&self) -> Result<String, NetworkError> {
let body = self.body().await?;
String::from_utf8(body)
.map_err(|e| NetworkError::InvalidResponse(format!("Response is not valid UTF-8: {e}")))
}
pub async fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, NetworkError> {
let text = self.text().await?;
serde_json::from_str(&text)
.map_err(|e| NetworkError::InvalidResponse(format!("Failed to parse JSON: {e}")))
}
pub fn security_details(&self) -> Option<&SecurityDetails> {
self.security_details.as_ref()
}
pub fn server_addr(&self) -> Option<&RemoteAddress> {
self.remote_address.as_ref()
}
pub async fn finished(&self) -> Result<(), NetworkError> {
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct SecurityDetails {
pub protocol: String,
pub subject_name: String,
pub issuer: String,
pub valid_from: f64,
pub valid_to: f64,
pub san_list: Vec<String>,
}
impl From<viewpoint_cdp::protocol::network::SecurityDetails> for SecurityDetails {
fn from(details: viewpoint_cdp::protocol::network::SecurityDetails) -> Self {
Self {
protocol: details.protocol,
subject_name: details.subject_name,
issuer: details.issuer,
valid_from: details.valid_from,
valid_to: details.valid_to,
san_list: details.san_list,
}
}
}
#[derive(Debug, Clone)]
pub struct RemoteAddress {
pub ip_address: String,
pub port: u16,
}
#[cfg(test)]
mod tests;