Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ internal class WpRequestExecutor(private val dispatcher: CoroutineDispatcher = D

override suspend fun execute(request: WpNetworkRequest): WpNetworkResponse =
withContext(dispatcher) {
val requestBuilder = Request.Builder().url(request.url)
request.headerMap.forEach { (key, value) ->
requestBuilder.header(key, value)
val requestBuilder = Request.Builder().url(request.url())
request.headerMap().toMap().forEach { (key, values) ->
values.forEach { value ->
requestBuilder.addHeader(key, value)
}
}

client.newCall(requestBuilder.build()).execute().use { response ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ val authModule = module {
// Until this works with the included test credentials, you can grab it from the
// `test_credentials` file `make test-server` will generate in the root of the repo
// It's the 3rd line in that file
localTestSitePassword = "l4dvgr1UjuCKXiqkbdbXn1b5"
localTestSitePassword = "s3N7vlbdrFPDDI3MbyFUvS3P"
)
}
}
Expand Down
30 changes: 16 additions & 14 deletions native/swift/Sources/wordpress-api/WordPressAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,25 +92,33 @@ public struct WordPressAPI {
}
}

public extension WpNetworkHeaderMap {
func toFlatMap() -> [String: String] {
self.toMap().mapValues { $0.joined(separator: ",") }
}
}

public extension WpNetworkRequest {
func asURLRequest() -> URLRequest {
let url = URL(string: self.url)!
let url = URL(string: self.url())!
var request = URLRequest(url: url)
request.httpMethod = self.method.rawValue
request.allHTTPHeaderFields = self.headerMap
request.httpBody = self.body
request.httpMethod = self.method().rawValue
request.allHTTPHeaderFields = self.headerMap().toFlatMap()
request.httpBody = self.body()?.contents()
return request
}

#if DEBUG
func debugPrint() {
print("\(method.rawValue) \(url)")
for (name, value) in headerMap {
print("\(method().rawValue) \(self.url())")
for (name, value) in self.headerMap().toMap() {
print("\(name): \(value)")
}

print("")
if let body, let text = String(data: body, encoding: .utf8) {
print(text)

if let bodyString = self.bodyAsString() {
print(bodyString)
}
}
#endif
Expand Down Expand Up @@ -183,12 +191,6 @@ extension RequestMethod {
}
}

extension WpNetworkRequest {
init(method: RequestMethod, url: URL, headerMap: [String: String]) {
self.init(method: method, url: url.absoluteString, headerMap: headerMap, body: nil)
}
}

extension WpRestApiUrl {
func asUrl() -> URL {
guard let url = URL(string: stringValue) else {
Expand Down
2 changes: 1 addition & 1 deletion native/swift/Tests/wordpress-api/Support/HTTPStubs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class HTTPStubs: SafeRequestExecutor {

func stub(path: String, with response: WpNetworkResponse) {
stubs.append((
condition: { URL(string: $0.url)?.path == path },
condition: { URL(string: $0.url())?.path == path },
response: response
))
}
Expand Down
2 changes: 2 additions & 0 deletions native/swift/Tests/wordpress-api/WordPressAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ final class WordPressAPITests: XCTestCase {
} catch let error as URLError {
XCTAssertEqual(error.code, .timedOut)
} catch {
#if canImport(WordPressAPIInternal)
XCTAssertTrue(error is WordPressAPIInternal.WpApiError)
#endif
}
}
#endif
Expand Down
24 changes: 14 additions & 10 deletions wp_api/src/login/login_client.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::collections::HashMap;
use std::str;
use std::sync::Arc;

use crate::request::endpoint::WpEndpointUrl;
use crate::request::{RequestExecutor, RequestMethod, WpNetworkRequest, WpNetworkResponse};
use crate::request::{
RequestExecutor, RequestMethod, WpNetworkHeaderMap, WpNetworkRequest, WpNetworkResponse,
};

use super::url_discovery::{
self, FetchApiDetailsError, FetchApiRootUrlError, ParsedUrl, StateInitial,
Expand Down Expand Up @@ -129,11 +130,11 @@ impl WpLoginClient {
let api_root_request = WpNetworkRequest {
method: RequestMethod::HEAD,
url: WpEndpointUrl(parsed_site_url.url()),
header_map: HashMap::new(),
header_map: WpNetworkHeaderMap::default().into(),
body: None,
};
self.request_executor
.execute(api_root_request)
.execute(api_root_request.into())
.await
.map_err(FetchApiRootUrlError::from)
}
Expand All @@ -143,12 +144,15 @@ impl WpLoginClient {
api_root_url: &ParsedUrl,
) -> Result<WpNetworkResponse, FetchApiDetailsError> {
self.request_executor
.execute(WpNetworkRequest {
method: RequestMethod::GET,
url: WpEndpointUrl(api_root_url.url()),
header_map: HashMap::new(),
body: None,
})
.execute(
WpNetworkRequest {
method: RequestMethod::GET,
url: WpEndpointUrl(api_root_url.url()),
header_map: WpNetworkHeaderMap::default().into(),
body: None,
}
.into(),
)
.await
.map_err(FetchApiDetailsError::from)
}
Expand Down
106 changes: 78 additions & 28 deletions wp_api/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl InnerRequestBuilder {
WpNetworkRequest {
method: RequestMethod::GET,
url: url.into(),
header_map: self.header_map(),
header_map: self.header_map().into(),
body: None,
}
}
Expand All @@ -40,40 +40,44 @@ impl InnerRequestBuilder {
WpNetworkRequest {
method: RequestMethod::POST,
url: url.into(),
header_map: self.header_map_for_post_request(),
body: serde_json::to_vec(json_body).ok(),
header_map: self.header_map_for_post_request().into(),
body: serde_json::to_vec(json_body)
.ok()
.map(|b| Arc::new(WpNetworkRequestBody::new(b))),
}
}

fn delete(&self, url: ApiEndpointUrl) -> WpNetworkRequest {
WpNetworkRequest {
method: RequestMethod::DELETE,
url: url.into(),
header_map: self.header_map(),
header_map: self.header_map().into(),
body: None,
}
}

fn header_map(&self) -> HashMap<String, String> {
let mut header_map = HashMap::new();
fn header_map(&self) -> WpNetworkHeaderMap {
let mut header_map = HeaderMap::new();
header_map.insert(
http::header::ACCEPT.to_string(),
CONTENT_TYPE_JSON.to_string(),
http::header::ACCEPT,
HeaderValue::from_static(CONTENT_TYPE_JSON),
);
match self.authentication {
WpAuthentication::None => None,
WpAuthentication::None => (),
WpAuthentication::AuthorizationHeader { ref token } => {
header_map.insert("Authorization".to_string(), format!("Basic {}", token))
let hv = HeaderValue::from_str(&format!("Basic {}", token));
let hv = hv.expect("It shouldn't be possible to build WpAuthentication::AuthorizationHeader with an invalid token");
header_map.insert(http::header::AUTHORIZATION, hv);
}
};
header_map
header_map.into()
}

fn header_map_for_post_request(&self) -> HashMap<String, String> {
fn header_map_for_post_request(&self) -> WpNetworkHeaderMap {
let mut header_map = self.header_map();
header_map.insert(
http::header::CONTENT_TYPE.to_string(),
CONTENT_TYPE_JSON.to_string(),
header_map.inner.insert(
http::header::CONTENT_TYPE,
HeaderValue::from_static(CONTENT_TYPE_JSON),
);
header_map
}
Expand All @@ -84,27 +88,57 @@ impl InnerRequestBuilder {
pub trait RequestExecutor: Send + Sync + Debug {
async fn execute(
&self,
request: WpNetworkRequest,
request: Arc<WpNetworkRequest>,
) -> Result<WpNetworkResponse, RequestExecutionError>;
}

#[derive(uniffi::Object)]
pub struct WpNetworkRequestBody {
inner: Vec<u8>,
}

impl WpNetworkRequestBody {
fn new(body: Vec<u8>) -> Self {
Self { inner: body }
}
}

#[uniffi::export]
impl WpNetworkRequestBody {
pub fn contents(&self) -> Vec<u8> {
self.inner.clone()
}
}

// Has custom `Debug` trait implementation
#[derive(uniffi::Record)]
#[derive(uniffi::Object)]
pub struct WpNetworkRequest {
pub method: RequestMethod,
pub url: WpEndpointUrl,
// 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: HashMap<String, String>,
pub body: Option<Vec<u8>>,
pub(crate) method: RequestMethod,
pub(crate) url: WpEndpointUrl,
pub(crate) header_map: Arc<WpNetworkHeaderMap>,
pub(crate) body: Option<Arc<WpNetworkRequestBody>>,
}

#[uniffi::export]
impl WpNetworkRequest {
pub fn method(&self) -> RequestMethod {
self.method.clone()
}

pub fn url(&self) -> WpEndpointUrl {
self.url.clone()
}

pub fn header_map(&self) -> Arc<WpNetworkHeaderMap> {
self.header_map.clone()
}

pub fn body(&self) -> Option<Arc<WpNetworkRequestBody>> {
self.body.clone()
}

pub fn body_as_string(&self) -> Option<String> {
self.body.as_ref().map(|b| body_as_string(b))
self.body.as_ref().map(|b| body_as_string(&b.inner))
}
}

Expand Down Expand Up @@ -137,7 +171,7 @@ pub struct WpNetworkResponse {
pub header_map: Arc<WpNetworkHeaderMap>,
}

#[derive(Debug, uniffi::Object)]
#[derive(Debug, Default, Clone, uniffi::Object)]
pub struct WpNetworkHeaderMap {
inner: HeaderMap,
}
Expand Down Expand Up @@ -177,6 +211,10 @@ impl WpNetworkHeaderMap {
})
.collect()
}

pub fn as_header_map(&self) -> HeaderMap {
self.inner.clone()
}
}

#[uniffi::export]
Expand Down Expand Up @@ -206,6 +244,18 @@ impl WpNetworkHeaderMap {
.collect::<Result<HeaderMap, WpNetworkHeaderMapError>>()?;
Ok(Self { inner })
}

fn to_map(&self) -> HashMap<String, Vec<String>> {
let mut header_hashmap = HashMap::new();
self.inner.iter().for_each(|(k, v)| {
let v = String::from_utf8_lossy(v.as_bytes()).into_owned();
header_hashmap
.entry(k.as_str().to_owned())
.or_insert_with(Vec::new)
.push(v)
});
header_hashmap
}
}

impl From<HeaderMap> for WpNetworkHeaderMap {
Expand Down
2 changes: 1 addition & 1 deletion wp_api/src/request/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub(crate) mod users_endpoint;
const WP_JSON_PATH_SEGMENTS: [&str; 3] = ["wp-json", "wp", "v2"];

uniffi::custom_newtype!(WpEndpointUrl, String);
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct WpEndpointUrl(pub String);

impl From<Url> for WpEndpointUrl {
Expand Down
18 changes: 9 additions & 9 deletions wp_api/tests/integration_test_common.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use async_trait::async_trait;
use futures::Future;
use http::HeaderMap;
use std::{process::Command, sync::Arc};
use wp_api::{
request::{
Expand Down Expand Up @@ -160,16 +159,17 @@ impl Default for AsyncWpNetworking {
impl AsyncWpNetworking {
pub async fn async_request(
&self,
wp_request: WpNetworkRequest,
wp_request: Arc<WpNetworkRequest>,
) -> Result<WpNetworkResponse, reqwest::Error> {
let request_headers: HeaderMap = (&wp_request.header_map).try_into().unwrap();

let mut request = self
.client
.request(Self::request_method(wp_request.method), wp_request.url.0)
.headers(request_headers);
if let Some(body) = wp_request.body {
request = request.body(body);
.request(
Self::request_method(wp_request.method()),
wp_request.url().0.as_str(),
)
.headers(wp_request.header_map().as_header_map());
if let Some(body) = wp_request.body() {
request = request.body(body.contents());
}
let mut response = request.send().await?;

Expand All @@ -195,7 +195,7 @@ impl AsyncWpNetworking {
impl RequestExecutor for AsyncWpNetworking {
async fn execute(
&self,
request: WpNetworkRequest,
request: Arc<WpNetworkRequest>,
) -> Result<WpNetworkResponse, RequestExecutionError> {
self.async_request(request).await.map_err(|err| {
RequestExecutionError::RequestExecutionFailed {
Expand Down
2 changes: 1 addition & 1 deletion wp_api/tests/test_manual_request_builder_immut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async fn list_users_with_edit_context(#[case] params: UserListParams) {
WpApiRequestBuilder::new(TEST_CREDENTIALS_SITE_URL.to_string(), authentication)
.expect("Site url is generated by our tooling");
let wp_request = request_builder.users().list_with_edit_context(&params);
let response = async_wp_networking.async_request(wp_request).await;
let response = async_wp_networking.async_request(wp_request.into()).await;
let result = response.unwrap().parse::<Vec<UserWithEditContext>>();
assert!(result.is_ok(), "Response was: '{:?}'", result);
}
2 changes: 1 addition & 1 deletion wp_derive_request_builder/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ fn generate_async_request_executor(config: &Config, parsed_enum: &ParsedEnum) ->
quote! {
pub async #fn_signature -> Result<#output_type, #static_wp_api_error_type> {
#request_from_request_builder
self.request_executor.execute(request).await?.parse()
self.request_executor.execute(std::sync::Arc::new(request)).await?.parse()
}
}
})
Expand Down