diff --git a/wp_api/src/lib.rs b/wp_api/src/lib.rs index ca464b6d3..d0710f69e 100644 --- a/wp_api/src/lib.rs +++ b/wp_api/src/lib.rs @@ -2,7 +2,8 @@ use request::{ endpoint::ApiBaseUrl, plugins_request_builder::PluginsRequestBuilder, - users_request_builder::UsersRequestBuilder, RequestExecutor, WpNetworkResponse, + users_request_builder::UsersRequestBuilder, RequestExecutor, UsersRequestBuilder2, + WpNetworkResponse, }; use std::sync::Arc; @@ -22,6 +23,43 @@ pub mod users; #[cfg(test)] mod unit_test_common; +// TODO: This is a temporary type that allows building a request type +// Although we'll have a type that does that, it's unlikely that it'll look like this. +// It still does its job for now to prove that `UsersRequestBuilder2` (temporary) type is +// properly generated and utilized in `test_manual_request_builder_immut` integration tests +#[derive(Debug, uniffi::Object)] +pub struct WpApiRequestBuilder { + users: Arc, +} + +#[uniffi::export] +impl WpApiRequestBuilder { + #[uniffi::constructor] + pub fn new( + site_url: String, + authentication: WpAuthentication, + request_executor: Arc, + ) -> Result { + let api_base_url: Arc = ApiBaseUrl::try_from(site_url.as_str()) + .map_err(|err| WpApiError::SiteUrlParsingError { + reason: err.to_string(), + })? + .into(); + let request_builder = Arc::new(request::RequestBuilder::new( + request_executor, + authentication.clone(), + )); + + Ok(Self { + users: UsersRequestBuilder2::new(api_base_url.clone(), request_builder.clone()).into(), + }) + } + + pub fn users(&self) -> Arc { + self.users.clone() + } +} + #[derive(Debug, uniffi::Object)] pub struct WpRequestBuilder { users: Arc, diff --git a/wp_api/src/request.rs b/wp_api/src/request.rs index b2cf1ea5a..a5f8844b0 100644 --- a/wp_api/src/request.rs +++ b/wp_api/src/request.rs @@ -8,6 +8,8 @@ use crate::{api_error::RequestExecutionError, WpApiError, WpAuthentication}; use self::endpoint::WpEndpointUrl; +pub use endpoint::users_endpoint::generated::UsersRequestBuilder2; + pub mod endpoint; pub mod plugins_request_builder; pub mod users_request_builder; diff --git a/wp_api/src/request/endpoint/users_endpoint.rs b/wp_api/src/request/endpoint/users_endpoint.rs index 6f86dfab1..466b1cf47 100644 --- a/wp_api/src/request/endpoint/users_endpoint.rs +++ b/wp_api/src/request/endpoint/users_endpoint.rs @@ -9,7 +9,7 @@ use super::{ApiBaseUrl, ApiEndpointUrl, UrlExtension}; // ``` // cargo expand request::endpoint::users_endpoint::generated -p wp_api // ``` -mod generated { +pub mod generated { use super::*; #[derive(wp_derive_request_builder::WpDerivedRequest)] diff --git a/wp_api/tests/reusable_test_cases.rs b/wp_api/tests/reusable_test_cases.rs new file mode 100644 index 000000000..40386cb0c --- /dev/null +++ b/wp_api/tests/reusable_test_cases.rs @@ -0,0 +1,23 @@ +#![allow(unused_macros)] +use rstest_reuse::{self, template}; + +#[template] +#[rstest] +#[case(UserListParams::default())] +#[case(generate!(UserListParams, (page, Some(1))))] +#[case(generate!(UserListParams, (page, Some(2)), (per_page, Some(5))))] +#[case(generate!(UserListParams, (search, Some("foo".to_string()))))] +#[case(generate!(UserListParams, (exclude, vec![FIRST_USER_ID, SECOND_USER_ID])))] +#[case(generate!(UserListParams, (include, vec![FIRST_USER_ID])))] +#[case(generate!(UserListParams, (per_page, Some(100)), (offset, Some(20))))] +#[case(generate!(UserListParams, (order, Some(WpApiParamOrder::Asc))))] +#[case(generate!(UserListParams, (orderby, Some(WpApiParamUsersOrderBy::Id))))] +#[case(generate!(UserListParams, (order, Some(WpApiParamOrder::Desc)), (orderby, Some(WpApiParamUsersOrderBy::Email))))] +#[case(generate!(UserListParams, (slug, vec!["foo".to_string(), "bar".to_string()])))] +#[case(generate!(UserListParams, (roles, vec!["author".to_string(), "editor".to_string()])))] +#[case(generate!(UserListParams, (slug, vec!["foo".to_string(), "bar".to_string()]), (roles, vec!["author".to_string(), "editor".to_string()])))] +#[case(generate!(UserListParams, (capabilities, vec!["edit_themes".to_string(), "delete_pages".to_string()])))] +#[case::who_all_param_should_be_empty(generate!(UserListParams, (who, Some(WpApiParamUsersWho::All))))] +#[case(generate!(UserListParams, (who, Some(WpApiParamUsersWho::Authors))))] +#[case(generate!(UserListParams, (has_published_posts, Some(WpApiParamUsersHasPublishedPosts::True))))] +pub fn list_users_cases(#[case] params: UserListParams) {} diff --git a/wp_api/tests/test_manual_request_builder_immut.rs b/wp_api/tests/test_manual_request_builder_immut.rs new file mode 100644 index 000000000..f44d28868 --- /dev/null +++ b/wp_api/tests/test_manual_request_builder_immut.rs @@ -0,0 +1,43 @@ +use integration_test_common::{ + read_test_credentials_from_file, AsyncWpNetworking, FIRST_USER_ID, SECOND_USER_ID, +}; +use reusable_test_cases::list_users_cases; +use rstest::*; +use rstest_reuse::{self, apply}; +use std::sync::Arc; +use wp_api::{ + generate, + users::UserWithEditContext, + users::{ + UserListParams, WpApiParamUsersHasPublishedPosts, WpApiParamUsersOrderBy, + WpApiParamUsersWho, + }, + WpApiParamOrder, WpApiRequestBuilder, WpAuthentication, +}; + +pub mod integration_test_common; +pub mod reusable_test_cases; + +#[apply(list_users_cases)] +#[tokio::test] +async fn list_users_with_edit_context(#[case] params: UserListParams) { + let credentials = read_test_credentials_from_file(); + let authentication = WpAuthentication::from_username_and_password( + credentials.admin_username, + credentials.admin_password, + ); + let async_wp_networking = Arc::new(AsyncWpNetworking::default()); + + let request_builder = WpApiRequestBuilder::new( + credentials.site_url, + authentication, + // TODO: A request executor shouldn't be necessary, but we don't have a standalone request + // builder yet + async_wp_networking.clone(), + ) + .expect("Site url is generated by our tooling"); + let wp_request = request_builder.users().list_with_edit_context(¶ms); + let response = async_wp_networking.async_request(wp_request).await; + let result = response.unwrap().parse::>(); + assert!(result.is_ok(), "Response was: '{:?}'", result); +} diff --git a/wp_api/tests/test_users_immut.rs b/wp_api/tests/test_users_immut.rs index 17783181d..35a97760c 100644 --- a/wp_api/tests/test_users_immut.rs +++ b/wp_api/tests/test_users_immut.rs @@ -1,3 +1,4 @@ +use reusable_test_cases::list_users_cases; use rstest::*; use rstest_reuse::{self, apply, template}; use wp_api::{ @@ -14,6 +15,7 @@ use crate::integration_test_common::{ }; pub mod integration_test_common; +pub mod reusable_test_cases; #[apply(filter_fields_cases)] #[tokio::test] @@ -248,27 +250,6 @@ fn validate_sparse_user_fields(user: &SparseUser, fields: &[SparseUserField]) { ); } -#[template] -#[rstest] -#[case(UserListParams::default())] -#[case(generate!(UserListParams, (page, Some(1))))] -#[case(generate!(UserListParams, (page, Some(2)), (per_page, Some(5))))] -#[case(generate!(UserListParams, (search, Some("foo".to_string()))))] -#[case(generate!(UserListParams, (exclude, vec![FIRST_USER_ID, SECOND_USER_ID])))] -#[case(generate!(UserListParams, (include, vec![FIRST_USER_ID])))] -#[case(generate!(UserListParams, (per_page, Some(100)), (offset, Some(20))))] -#[case(generate!(UserListParams, (order, Some(WpApiParamOrder::Asc))))] -#[case(generate!(UserListParams, (orderby, Some(WpApiParamUsersOrderBy::Id))))] -#[case(generate!(UserListParams, (order, Some(WpApiParamOrder::Desc)), (orderby, Some(WpApiParamUsersOrderBy::Email))))] -#[case(generate!(UserListParams, (slug, vec!["foo".to_string(), "bar".to_string()])))] -#[case(generate!(UserListParams, (roles, vec!["author".to_string(), "editor".to_string()])))] -#[case(generate!(UserListParams, (slug, vec!["foo".to_string(), "bar".to_string()]), (roles, vec!["author".to_string(), "editor".to_string()])))] -#[case(generate!(UserListParams, (capabilities, vec!["edit_themes".to_string(), "delete_pages".to_string()])))] -#[case::who_all_param_should_be_empty(generate!(UserListParams, (who, Some(WpApiParamUsersWho::All))))] -#[case(generate!(UserListParams, (who, Some(WpApiParamUsersWho::Authors))))] -#[case(generate!(UserListParams, (has_published_posts, Some(WpApiParamUsersHasPublishedPosts::True))))] -fn list_users_cases(#[case] params: UserListParams) {} - #[template] #[rstest] #[case(None)] diff --git a/wp_derive_request_builder/src/generate.rs b/wp_derive_request_builder/src/generate.rs index b11e9d72c..1cb66b07f 100644 --- a/wp_derive_request_builder/src/generate.rs +++ b/wp_derive_request_builder/src/generate.rs @@ -15,21 +15,89 @@ use crate::{ mod helpers_to_generate_tokens; -pub(crate) fn generate_types(parsed_enum: &ParsedEnum) -> syn::Result { +pub(crate) fn generate_types(parsed_enum: &ParsedEnum) -> TokenStream { let config = Config::new(parsed_enum); + TokenStream::from_iter( + &mut [ + generate_endpoint_type(&config, parsed_enum), + generate_request_builder(&config, parsed_enum), + ] + .into_iter(), + ) +} + +fn generate_request_builder(config: &Config, parsed_enum: &ParsedEnum) -> TokenStream { + let api_base_url_type = &config.api_base_url_type; + let endpoint_ident = &config.endpoint_ident; + let request_builder_ident = &config.request_builder_ident; + let request_builder_type = &config.request_builder_type; + let wp_network_request_type = &config.wp_network_request_type; + + let functions = parsed_enum.variants.iter().map(|variant| { + let url_parts = variant.attr.url_parts.as_slice(); + let params_type = &variant.attr.params; + + ContextAndFilterHandler::from_request_type(variant.attr.request_type) + .into_iter() + .map(|context_and_filter_handler| { + let url_from_endpoint = fn_body_get_url_from_endpoint( + &variant.variant_ident, + url_parts, + params_type, + variant.attr.request_type, + context_and_filter_handler, + ); + let fn_signature = fn_signature( + PartOf::RequestBuilder, + &variant.variant_ident, + url_parts, + params_type, + variant.attr.request_type, + context_and_filter_handler, + &config.sparse_field_type, + ); + let fn_body = + fn_body_build_request_from_url(params_type, variant.attr.request_type); + quote! { + pub #fn_signature -> #wp_network_request_type { + #url_from_endpoint + #fn_body + } + } + }) + .collect::() + }); - Ok(generate_endpoint_type(&config, parsed_enum)) + quote! { + #[derive(Debug, uniffi::Object)] + pub struct #request_builder_ident { + endpoint: #endpoint_ident, + request_builder: #request_builder_type, + } + impl #request_builder_ident { + pub(crate) fn new(api_base_url: #api_base_url_type, request_builder: #request_builder_type) -> Self { + Self { + endpoint: #endpoint_ident::new(api_base_url), + request_builder, + } + } + } + #[uniffi::export] + impl #request_builder_ident { + #(#functions)* + } + } } fn generate_endpoint_type(config: &Config, parsed_enum: &ParsedEnum) -> TokenStream { let api_base_url_type = &config.api_base_url_type; - let endpoint_ident = format_ident!("{}Endpoint", parsed_enum.enum_ident); + let endpoint_ident = &config.endpoint_ident; let functions = parsed_enum.variants.iter().map(|variant| { let url_parts = variant.attr.url_parts.as_slice(); let params_type = &variant.attr.params; let request_type = variant.attr.request_type; - let url_from_endpoint = fn_body_get_url_from_api_base_url(url_parts); + let url_from_api_base_url = fn_body_get_url_from_api_base_url(url_parts); let query_pairs = fn_body_query_pairs(params_type, request_type); ContextAndFilterHandler::from_request_type(request_type) @@ -51,7 +119,7 @@ fn generate_endpoint_type(config: &Config, parsed_enum: &ParsedEnum) -> TokenStr fn_body_fields_query_pairs(&config.crate_ident, context_and_filter_handler); quote! { pub #fn_signature -> #api_endpoint_url_type { - #url_from_endpoint + #url_from_api_base_url #context_query_pair #query_pairs #fields_query_pairs @@ -139,13 +207,15 @@ impl Display for WpContext { #[derive(Debug)] pub struct Config { - pub crate_ident: Ident, pub api_base_url_type: TokenStream, pub api_endpoint_url_type: TokenStream, - pub request_builder_type: TokenStream, + pub crate_ident: Ident, pub endpoint_ident: Ident, pub request_builder_ident: Ident, + pub request_builder_type: TokenStream, pub sparse_field_type: SparseFieldAttr, + pub wp_api_error_type: TokenStream, + pub wp_network_request_type: TokenStream, } impl Config { @@ -162,14 +232,20 @@ impl Config { quote! { std::sync::Arc<#crate_ident::request::endpoint::ApiBaseUrl> }; let api_endpoint_url_type = quote! { #crate_ident::request::endpoint::ApiEndpointUrl }; let request_builder_type = quote! { std::sync::Arc<#crate_ident::request::RequestBuilder> }; + let wp_api_error_type = quote! { #crate_ident::WpApiError }; + let wp_network_request_type = quote! { #crate_ident::request::WpNetworkRequest }; Self { - crate_ident, api_base_url_type, api_endpoint_url_type, - request_builder_type, + crate_ident, endpoint_ident: format_ident!("{}Endpoint", parsed_enum.enum_ident), - request_builder_ident: format_ident!("{}Builder", parsed_enum.enum_ident), + // TODO: We use `2` suffix here to prevent the name clash with the current + // implementation + request_builder_ident: format_ident!("{}Builder2", parsed_enum.enum_ident), + request_builder_type, sparse_field_type: parsed_enum.sparse_field_attr.clone(), + wp_api_error_type, + wp_network_request_type, } } } diff --git a/wp_derive_request_builder/src/generate/helpers_to_generate_tokens.rs b/wp_derive_request_builder/src/generate/helpers_to_generate_tokens.rs index f4263f15b..e0cef6ff8 100644 --- a/wp_derive_request_builder/src/generate/helpers_to_generate_tokens.rs +++ b/wp_derive_request_builder/src/generate/helpers_to_generate_tokens.rs @@ -129,25 +129,6 @@ pub fn fn_name( } } -pub fn fn_body_get_url_from_endpoint( - variant_ident: &Ident, - url_parts: &[UrlPart], - params_type: &ParamsType, - request_type: RequestType, - context_and_filter_handler: ContextAndFilterHandler, -) -> TokenStream { - let fn_name = fn_name(variant_ident, context_and_filter_handler); - let fn_arg_url_parts = fn_arg_url_parts(url_parts); - let fn_arg_context = fn_arg_context(context_and_filter_handler); - let fn_arg_provided_params = - fn_arg_provided_params(PartOf::Endpoint, params_type, request_type); - let fn_arg_fields = fn_arg_fields(context_and_filter_handler); - - quote! { - let url = self.endpoint.#fn_name(#fn_arg_url_parts #fn_arg_context #fn_arg_provided_params #fn_arg_fields); - } -} - fn fn_arg_url_parts(url_parts: &[UrlPart]) -> TokenStream { url_parts .iter() @@ -222,6 +203,25 @@ pub fn fn_body_get_url_from_api_base_url(url_parts: &[UrlPart]) -> TokenStream { } } +pub fn fn_body_get_url_from_endpoint( + variant_ident: &Ident, + url_parts: &[UrlPart], + params_type: &ParamsType, + request_type: RequestType, + context_and_filter_handler: ContextAndFilterHandler, +) -> TokenStream { + let fn_name = fn_name(variant_ident, context_and_filter_handler); + let fn_arg_url_parts = fn_arg_url_parts(url_parts); + let fn_arg_context = fn_arg_context(context_and_filter_handler); + let fn_arg_provided_params = + fn_arg_provided_params(PartOf::Endpoint, params_type, request_type); + let fn_arg_fields = fn_arg_fields(context_and_filter_handler); + + quote! { + let url = self.endpoint.#fn_name(#fn_arg_url_parts #fn_arg_context #fn_arg_provided_params #fn_arg_fields); + } +} + pub fn fn_body_query_pairs(params_type: &ParamsType, request_type: RequestType) -> TokenStream { match request_type { RequestType::ContextualGet | RequestType::Delete | RequestType::Get => { @@ -294,6 +294,31 @@ pub fn fn_body_context_query_pairs( } } +pub fn fn_body_build_request_from_url( + params_type: &ParamsType, + request_type: RequestType, +) -> TokenStream { + match request_type { + RequestType::ContextualGet | RequestType::Get => quote! { + self.request_builder.build_get_request(url) + }, + RequestType::Delete => quote! { + self.request_builder.build_delete_request(url) + }, + RequestType::Post => { + if params_type.tokens().is_some() { + quote! { + self.request_builder.build_post_request(url, params) + } + } else { + quote! { + self.request_builder.build_post_request(url) + } + } + } + } +} + #[cfg(test)] mod tests { #![allow(clippy::too_many_arguments)] @@ -863,6 +888,42 @@ mod tests { ); } + #[rstest] + #[case(&ParamsType::new(None), RequestType::ContextualGet, "self . request_builder . build_get_request (url)")] + #[case( + &referenced_params_type("UserListParams"), + RequestType::ContextualGet, + "self . request_builder . build_get_request (url)" + )] + #[case(&ParamsType::new(None), RequestType::Get, "self . request_builder . build_get_request (url)")] + #[case( + &referenced_params_type("UserListParams"), + RequestType::Get, + "self . request_builder . build_get_request (url)" + )] + #[case(&ParamsType::new(None), RequestType::Delete, "self . request_builder . build_delete_request (url)")] + #[case( + &referenced_params_type("UserListParams"), + RequestType::Delete, + "self . request_builder . build_delete_request (url)" + )] + #[case(&ParamsType::new(None), RequestType::Post, "self . request_builder . build_post_request (url)")] + #[case( + &referenced_params_type("UserListParams"), + RequestType::Post, + "self . request_builder . build_post_request (url , params)" + )] + fn test_fn_body_build_request_from_url( + #[case] params: &ParamsType, + #[case] request_type: RequestType, + #[case] expected_str: &str, + ) { + assert_eq!( + fn_body_build_request_from_url(params, request_type).to_string(), + expected_str + ); + } + #[rstest::fixture] fn sparse_field_type() -> SparseFieldAttr { SparseFieldAttr { diff --git a/wp_derive_request_builder/src/lib.rs b/wp_derive_request_builder/src/lib.rs index 0786209d1..3fddf1c66 100644 --- a/wp_derive_request_builder/src/lib.rs +++ b/wp_derive_request_builder/src/lib.rs @@ -16,9 +16,7 @@ pub fn derive(input: TokenStream) -> TokenStream { if cfg!(feature = "generate_request_builder") { //dbg!("{:#?}", parsed_enum.clone()); - generate::generate_types(&parsed_enum) - .unwrap_or_else(|err| err.into_compile_error()) - .into() + generate::generate_types(&parsed_enum).into() } else { TokenStream::new() }