Skip to content

Commit

Permalink
Rework auto discovery Rust types (#513)
Browse files Browse the repository at this point in the history
* Adds auto discovery attempt type

* Remove state machine from login auto discovery

* Rework auto discovery results

* Implement helpers for AutoDiscoveryAttemptResult & AutoDiscoveryAttemptFailure

* Rename AutoDiscoveryAttemptType::Original as UserInput

* Add attempt specific fields to AutoDiscoveryUniffiResult
  • Loading branch information
oguzkocer authored Jan 27, 2025
1 parent f9dfbed commit a10122f
Show file tree
Hide file tree
Showing 7 changed files with 439 additions and 286 deletions.
4 changes: 2 additions & 2 deletions wp_api/src/api_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ pub enum WpErrorCode {
CustomError(String),
}

#[derive(Debug, PartialEq, Eq, thiserror::Error, uniffi::Error)]
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, uniffi::Error)]
pub enum RequestExecutionError {
#[error(
"Request execution failed!\nStatus Code: '{:?}'.\nResponse: '{}'",
Expand All @@ -429,7 +429,7 @@ pub enum RequestExecutionError {
},
}

#[derive(Debug, PartialEq, Eq, thiserror::Error, uniffi::Error)]
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, uniffi::Error)]
pub enum MediaUploadRequestExecutionError {
#[error(
"Request execution failed!\nStatus Code: '{:?}'.\nResponse: '{}'",
Expand Down
13 changes: 5 additions & 8 deletions wp_api/src/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ use std::str;
use std::sync::Arc;
use wp_serde_helper::deserialize_i64_or_string;

pub use login_client::WpLoginClient;
pub use url_discovery::{UrlDiscoveryError, UrlDiscoveryState, UrlDiscoverySuccess};

use crate::ParsedUrl;
use crate::WpUuid;

const KEY_APPLICATION_PASSWORDS: &str = "application-passwords";

mod login_client;
mod url_discovery;
pub mod login_client;
pub mod url_discovery;

#[derive(Debug, uniffi::Record)]
pub struct WpRestApiUrls {
Expand Down Expand Up @@ -47,7 +44,7 @@ pub fn extract_login_details_from_url(
})
}

#[derive(Debug, Serialize, Deserialize, uniffi::Object)]
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Object)]
pub struct WpApiDetails {
pub name: String,
pub description: String,
Expand All @@ -70,12 +67,12 @@ impl WpApiDetails {
}
}

#[derive(Debug, Serialize, Deserialize, uniffi::Record)]
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct WpRestApiAuthenticationScheme {
pub endpoints: WpRestApiAuthenticationEndpoint,
}

#[derive(Debug, Serialize, Deserialize, uniffi::Record)]
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct WpRestApiAuthenticationEndpoint {
pub authorization: String,
}
Expand Down
176 changes: 97 additions & 79 deletions wp_api/src/login/login_client.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use std::str;
use std::sync::Arc;

use super::url_discovery::{
self, AutoDiscoveryAttempt, AutoDiscoveryAttemptFailure, AutoDiscoveryAttemptResult,
AutoDiscoveryAttemptSuccess, AutoDiscoveryResult, AutoDiscoveryUniffiResult,
ParseApiRootUrlError,
};
use super::WpApiDetails;
use crate::request::endpoint::WpEndpointUrl;
use crate::request::{
RequestExecutor, RequestMethod, WpNetworkHeaderMap, WpNetworkRequest, WpNetworkResponse,
};
use crate::ParsedUrl;

use super::url_discovery::{
self, FetchApiDetailsError, FetchApiRootUrlError, StateInitial, UrlDiscoveryAttemptError,
UrlDiscoveryAttemptSuccess, UrlDiscoveryError, UrlDiscoveryState, UrlDiscoverySuccess,
};
use crate::{ParsedUrl, RequestExecutionError};

const API_ROOT_LINK_HEADER: &str = "https://api.w.org/";

Expand All @@ -28,11 +29,8 @@ impl UniffiWpLoginClient {
}
}

async fn api_discovery(
&self,
site_url: String,
) -> Result<UrlDiscoverySuccess, UrlDiscoveryError> {
self.inner.api_discovery(site_url).await
async fn api_discovery(&self, site_url: String) -> AutoDiscoveryUniffiResult {
self.inner.api_discovery(site_url).await.into()
}
}

Expand All @@ -46,77 +44,101 @@ impl WpLoginClient {
Self { request_executor }
}

pub async fn api_discovery(
&self,
site_url: String,
) -> Result<UrlDiscoverySuccess, UrlDiscoveryError> {
pub async fn api_discovery(&self, site_url: String) -> AutoDiscoveryResult {
let attempts = futures::future::join_all(
url_discovery::construct_attempts(site_url)
.iter()
.map(|s| async { self.attempt_api_discovery(s).await }),
.into_iter()
.map(|attempt| async { self.attempt_api_discovery(attempt).await }),
)
.await;
let successful_attempt = attempts.iter().find_map(|a| {
if let Ok(s) = a {
Some((
Arc::clone(&s.site_url),
Arc::clone(&s.api_details),
Arc::clone(&s.api_root_url),
))
} else {
None
}
});

let attempts = attempts
.into_iter()
.map(|a| match a {
Ok(s) => (s.site_url.url(), UrlDiscoveryState::Success(s)),
Err(e) => (e.site_url(), UrlDiscoveryState::Failure(e)),
})
.collect();
if let Some(s) = successful_attempt {
Ok(UrlDiscoverySuccess {
site_url: s.0,
api_details: s.1,
api_root_url: s.2,
attempts,
})
} else {
Err(UrlDiscoveryError::UrlDiscoveryFailed { attempts })
AutoDiscoveryResult {
attempts: attempts.into_iter().map(|r| (r.attempt_type, r)).collect(),
}
}

async fn attempt_api_discovery(
&self,
site_url: &str,
) -> Result<UrlDiscoveryAttemptSuccess, UrlDiscoveryAttemptError> {
let initial_state = StateInitial::new(site_url);
let parsed_url_state =
initial_state
.parse()
.map_err(|e| UrlDiscoveryAttemptError::FailedToParseSiteUrl {
site_url: site_url.to_string(),
error: e,
})?;
let parsed_site_url = parsed_url_state.site_url.clone();
let state_fetched_api_root_url = self
.fetch_api_root_url(&parsed_url_state.site_url)
.await
.and_then(|r| parsed_url_state.parse_api_root_response(r))
.map_err(|e| UrlDiscoveryAttemptError::FetchApiRootUrlFailed {
site_url: Arc::new(parsed_site_url),
error: e,
})?;
match self
.fetch_wp_api_details(&state_fetched_api_root_url.api_root_url)
.await
attempt: AutoDiscoveryAttempt,
) -> AutoDiscoveryAttemptResult {
let result = self
.inner_attempt_api_discovery(attempt.attempt_site_url.as_str())
.await;
AutoDiscoveryAttemptResult {
attempt_type: attempt.attempt_type,
attempt_site_url: attempt.attempt_site_url,
result,
}
}

async fn inner_attempt_api_discovery(
&self,
attempt_site_url: &str,
) -> Result<AutoDiscoveryAttemptSuccess, AutoDiscoveryAttemptFailure> {
let parsed_site_url = ParsedUrl::parse(attempt_site_url)
.map_err(|error| AutoDiscoveryAttemptFailure::ParseSiteUrl { error })?;
let fetch_api_root_url_response = match self.fetch_api_root_url(&parsed_site_url).await {
Ok(r) => r,
Err(error) => {
return Err(AutoDiscoveryAttemptFailure::FetchApiRootUrl {
parsed_site_url,
error,
})
}
};
let api_root_url =
match self.parse_api_root_response(&parsed_site_url, fetch_api_root_url_response) {
Ok(api_root_url) => api_root_url,
Err(error) => {
return Err(AutoDiscoveryAttemptFailure::ParseApiRootUrl {
parsed_site_url,
error,
})
}
};

let fetch_api_details_response = match self.fetch_wp_api_details(&api_root_url).await {
Ok(r) => r,
Err(error) => {
return Err(AutoDiscoveryAttemptFailure::FetchApiDetails {
parsed_site_url,
api_root_url,
error,
})
}
};
let api_details: WpApiDetails =
match serde_json::from_slice::<WpApiDetails>(&fetch_api_details_response.body) {
Ok(api_details) => api_details,
Err(error) => {
return Err(AutoDiscoveryAttemptFailure::ParseApiDetails {
parsed_site_url,
api_root_url,
parsing_error_message: error.to_string(),
})
}
};

Ok(AutoDiscoveryAttemptSuccess {
parsed_site_url,
api_root_url,
api_details,
})
}

fn parse_api_root_response(
&self,
site_url: &ParsedUrl,
response: WpNetworkResponse,
) -> Result<ParsedUrl, ParseApiRootUrlError> {
match response
.get_link_header(API_ROOT_LINK_HEADER)
.into_iter()
.nth(0)
{
Ok(r) => state_fetched_api_root_url.parse_api_details_response(r),
Err(e) => Err(UrlDiscoveryAttemptError::FetchApiDetailsFailed {
site_url: Arc::new(state_fetched_api_root_url.site_url),
api_root_url: Arc::new(state_fetched_api_root_url.api_root_url),
error: e,
Some(url) => Ok(ParsedUrl::new(url)),
None => Err(ParseApiRootUrlError::ApiRootLinkHeaderNotFound {
header_map: response.header_map,
status_code: response.status_code,
}),
}
}
Expand All @@ -126,23 +148,20 @@ impl WpLoginClient {
async fn fetch_api_root_url(
&self,
parsed_site_url: &ParsedUrl,
) -> Result<WpNetworkResponse, FetchApiRootUrlError> {
) -> Result<WpNetworkResponse, RequestExecutionError> {
let api_root_request = WpNetworkRequest {
method: RequestMethod::HEAD,
url: WpEndpointUrl(parsed_site_url.url()),
header_map: WpNetworkHeaderMap::default().into(),
body: None,
};
self.request_executor
.execute(api_root_request.into())
.await
.map_err(FetchApiRootUrlError::from)
self.request_executor.execute(api_root_request.into()).await
}

async fn fetch_wp_api_details(
&self,
api_root_url: &ParsedUrl,
) -> Result<WpNetworkResponse, FetchApiDetailsError> {
) -> Result<WpNetworkResponse, RequestExecutionError> {
self.request_executor
.execute(
WpNetworkRequest {
Expand All @@ -154,6 +173,5 @@ impl WpLoginClient {
.into(),
)
.await
.map_err(FetchApiDetailsError::from)
}
}
Loading

0 comments on commit a10122f

Please sign in to comment.