diff --git a/native/swift/Sources/wordpress-api/Login/API+Login.swift b/native/swift/Sources/wordpress-api/Login/API+Login.swift index cded264e3..b09a3ce20 100644 --- a/native/swift/Sources/wordpress-api/Login/API+Login.swift +++ b/native/swift/Sources/wordpress-api/Login/API+Login.swift @@ -18,6 +18,6 @@ public extension WordPressAPI { func getRestAPICapabilities(forApiRoot url: URL, using session: URLSession) async throws -> WpApiDetails { let wpResponse = try await self.perform(request: WpNetworkRequest(method: .get, url: url, headerMap: [:])) - return try parseApiDetailsResponse(response: wpResponse) + return try wpResponse.parseApiDetailsResponse() } } diff --git a/wp_api/src/lib.rs b/wp_api/src/lib.rs index 78bdb1dd5..7e2806e81 100644 --- a/wp_api/src/lib.rs +++ b/wp_api/src/lib.rs @@ -178,17 +178,6 @@ impl WpApiParamOrder { } } -#[uniffi::export] -pub fn parse_api_details_response(response: WpNetworkResponse) -> Result { - let api_details = - serde_json::from_slice(&response.body).map_err(|err| WpApiError::ParsingError { - reason: err.to_string(), - response: response.body_as_string(), - })?; - - Ok(api_details) -} - #[uniffi::export] pub fn get_link_header(response: &WpNetworkResponse, name: &str) -> Option { if let Some(url) = response.get_link_header(name) { diff --git a/wp_api/src/request.rs b/wp_api/src/request.rs index f99518c7d..1b20d2f51 100644 --- a/wp_api/src/request.rs +++ b/wp_api/src/request.rs @@ -1,8 +1,10 @@ use std::{collections::HashMap, fmt::Debug}; +use http::HeaderMap; use serde::Deserialize; use url::Url; +use crate::login::WpApiDetails; use crate::WpApiError; use self::endpoint::WpEndpointUrl; @@ -55,23 +57,45 @@ impl Debug for WpNetworkRequest { } // Has custom `Debug` trait implementation -#[derive(uniffi::Record)] +#[derive(uniffi::Object)] pub struct WpNetworkResponse { - pub body: Vec, - pub status_code: u16, - // TODO: We probably want to implement a specific type for these headers instead of using a - // regular HashMap. - // - // It could be something similar to `reqwest`'s [`header`](https://docs.rs/reqwest/latest/reqwest/header/index.html) - // module. - pub header_map: Option>, + body: Vec, + status_code: u16, + header_map: HeaderMap, +} + +#[uniffi::export] +impl WpNetworkResponse { + #[uniffi::constructor] + pub fn new( + body: Vec, + status_code: u16, + header_map: Option>, + ) -> Self { + let header_map: HeaderMap = header_map + .and_then(|m| (&m).try_into().ok()) + .unwrap_or_default(); + + Self { + body, + status_code, + header_map, + } + } + + pub fn parse_api_details_response(&self) -> Result { + serde_json::from_slice(&self.body).map_err(|err| WpApiError::ParsingError { + reason: err.to_string(), + response: self.body_as_string(), + }) + } } impl WpNetworkResponse { pub fn get_link_header(&self, name: &str) -> Option { self.header_map - .as_ref() - .map(|h_map| h_map.get(LINK_HEADER_KEY))? + .get(LINK_HEADER_KEY) + .and_then(|v| v.to_str().ok()) .and_then(|link_header| parse_link_header::parse_with_rel(link_header).ok()) .and_then(|link_map| { link_map @@ -187,11 +211,11 @@ mod tests { #[case] expected_prev_link_header: Option<&str>, #[case] expected_next_link_header: Option<&str>, ) { - let response = WpNetworkResponse { - body: Vec::with_capacity(0), - status_code: 200, - header_map: Some([("Link".to_string(), link.to_string())].into()), - }; + let response = WpNetworkResponse::new( + Vec::with_capacity(0), + 200, + Some([("Link".to_string(), link.to_string())].into()), + ); assert_eq!( expected_prev_link_header.and_then(|s| Url::parse(s).ok()), @@ -202,4 +226,49 @@ mod tests { response.get_link_header("next") ); } + + #[test] + fn test_headers_case_insentive() { + let headers: HashMap = [ + ("server".to_string(), "nginx".to_string()), + ("x-nananana".to_string(), "Batcache-Hit".to_string()), + ( + "date".to_string(), + "Thu, 30 May 2024 23:52:17 GMT".to_string(), + ), + ( + "content-type".to_string(), + "text/html; charset=UTF-8".to_string(), + ), + ( + "strict-transport-security".to_string(), + "max-age=31536000".to_string(), + ), + ("vary".to_string(), "Accept-Encoding".to_string()), + ( + "Link".to_string(), + "; rel=\"next\"".to_string(), + ), + ] + .into(); + let response = WpNetworkResponse::new(Vec::with_capacity(0), 200, Some(headers)); + + assert_eq!(response.header_map.get("Server").unwrap(), "nginx"); + assert_eq!( + response.header_map.get("X-Nananana").unwrap(), + "Batcache-Hit" + ); + assert_eq!( + response.header_map.get("Date").unwrap(), + "Thu, 30 May 2024 23:52:17 GMT" + ); + assert_eq!( + response.header_map.get("Content-Type").unwrap(), + "text/html; charset=UTF-8" + ); + assert_eq!( + response.header_map.get("link").unwrap(), + "; rel=\"next\"" + ); + } } diff --git a/wp_api/tests/integration_test_common.rs b/wp_api/tests/integration_test_common.rs index 12d80b5f8..3773d06b6 100644 --- a/wp_api/tests/integration_test_common.rs +++ b/wp_api/tests/integration_test_common.rs @@ -194,12 +194,12 @@ impl AsyncWpNetworking { request = request.body(body); } let response = request.send().await?; - - Ok(WpNetworkResponse { - status_code: response.status().as_u16(), - body: response.bytes().await.unwrap().to_vec(), - header_map: None, // TODO: Properly read the headers - }) + let status_code = response.status().as_u16(); + Ok(WpNetworkResponse::new( + response.bytes().await.unwrap().to_vec(), + status_code, + None, // TODO: Properly read the headers + )) } fn request_method(method: RequestMethod) -> http::Method {