diff --git a/wp_api/src/endpoint/plugins_endpoint.rs b/wp_api/src/endpoint/plugins_endpoint.rs index 91d9e00e..3b727ae6 100644 --- a/wp_api/src/endpoint/plugins_endpoint.rs +++ b/wp_api/src/endpoint/plugins_endpoint.rs @@ -1,6 +1,6 @@ use url::Url; -use crate::{plugins::PluginListParams, ApiBaseUrl, PluginSlug, WPContext}; +use crate::{plugins::PluginListParams, ApiBaseUrl, PluginSlug, SparsePluginField, WPContext}; pub struct PluginsEndpoint { api_base_url: ApiBaseUrl, @@ -29,6 +29,15 @@ impl PluginsEndpoint { url } + pub fn filter_list( + &self, + context: WPContext, + params: Option<&PluginListParams>, + fields: &[SparsePluginField], + ) -> Url { + self.append_filter_fields(self.list(context, params), fields) + } + pub fn retrieve(&self, context: WPContext, plugin: &PluginSlug) -> Url { let mut url = self.plugins_url_with_slug(plugin); url.query_pairs_mut() @@ -36,6 +45,15 @@ impl PluginsEndpoint { url } + pub fn filter_retrieve( + &self, + context: WPContext, + plugin: &PluginSlug, + fields: &[SparsePluginField], + ) -> Url { + self.append_filter_fields(self.retrieve(context, plugin), fields) + } + pub fn update(&self, plugin: &PluginSlug) -> Url { self.plugins_url_with_slug(plugin) } @@ -49,6 +67,19 @@ impl PluginsEndpoint { // The '/' character has to be preserved and not get encoded .by_extending(["plugins"].into_iter().chain(plugin.slug.split('/'))) } + + fn append_filter_fields(&self, mut url: Url, fields: &[SparsePluginField]) -> Url { + url.query_pairs_mut().append_pair( + "_fields", + fields + .iter() + .map(|f| f.as_str()) + .collect::>() + .join(",") + .as_str(), + ); + url + } } #[cfg(test)] @@ -94,6 +125,38 @@ mod tests { validate_endpoint(plugins_endpoint.list(context, Some(¶ms)), expected_path); } + #[rstest] + #[case( + WPContext::Edit, + generate!(PluginListParams, (search, Some("foo".to_string()))), + &[SparsePluginField::Author], + "/plugins?context=edit&search=foo&_fields=author" + )] + #[case( + WPContext::Embed, + generate!(PluginListParams, (status, Some(PluginStatus::Active))), + &[SparsePluginField::Name, SparsePluginField::PluginUri], + "/plugins?context=embed&status=active&_fields=name%2Cplugin_uri" + )] + #[case( + WPContext::View, + generate!(PluginListParams, (search, Some("foo".to_string())), (status, Some(PluginStatus::Inactive))), + &[SparsePluginField::NetworkOnly, SparsePluginField::RequiresPhp, SparsePluginField::Textdomain], + "/plugins?context=view&search=foo&status=inactive&_fields=network_only%2Crequires_php%2Ctextdomain" + )] + fn filter_list_plugins_with_params( + plugins_endpoint: PluginsEndpoint, + #[case] context: WPContext, + #[case] params: PluginListParams, + #[case] fields: &[SparsePluginField], + #[case] expected_path: &str, + ) { + validate_endpoint( + plugins_endpoint.filter_list(context, Some(¶ms), fields), + expected_path, + ); + } + #[rstest] #[case( "hello-dolly/hello".into(), @@ -123,6 +186,44 @@ mod tests { ); } + #[rstest] + #[case( + "hello-dolly/hello".into(), + WPContext::View, + &[SparsePluginField::Name], + "/plugins/hello-dolly/hello?context=view&_fields=name" + )] + #[case( + "classic-editor/classic-editor".into(), + WPContext::Embed, + &[SparsePluginField::Description, SparsePluginField::Plugin], + "/plugins/classic-editor/classic-editor?context=embed&_fields=description%2Cplugin" + )] + #[case( + "foo/bar%baz".into(), + WPContext::Edit, + &[SparsePluginField::Status, SparsePluginField::Version], + "/plugins/foo/bar%25baz?context=edit&_fields=status%2Cversion" + )] + #[case( + "foo/です".into(), + WPContext::View, + &[SparsePluginField::NetworkOnly, SparsePluginField::RequiresPhp, SparsePluginField::Textdomain], + "/plugins/foo/%E3%81%A7%E3%81%99?context=view&_fields=network_only%2Crequires_php%2Ctextdomain" + )] + fn filter_retrieve_plugin( + plugins_endpoint: PluginsEndpoint, + #[case] plugin_slug: PluginSlug, + #[case] context: WPContext, + #[case] fields: &[SparsePluginField], + #[case] expected_path: &str, + ) { + validate_endpoint( + plugins_endpoint.filter_retrieve(context, &plugin_slug, fields), + expected_path, + ); + } + #[rstest] #[case("hello-dolly/hello".into(), "/plugins/hello-dolly/hello")] #[case( diff --git a/wp_api/src/lib.rs b/wp_api/src/lib.rs index b95b4da3..c7e736d9 100644 --- a/wp_api/src/lib.rs +++ b/wp_api/src/lib.rs @@ -242,6 +242,24 @@ impl WPApiHelper { } } + pub fn filter_list_plugins_request( + &self, + context: WPContext, + params: &Option, // UniFFI doesn't support Option<&T> + fields: &[SparsePluginField], + ) -> WPNetworkRequest { + WPNetworkRequest { + method: RequestMethod::GET, + url: self + .api_endpoint + .plugins + .filter_list(context, params.as_ref(), fields) + .into(), + header_map: self.header_map(), + body: None, + } + } + pub fn create_plugin_request(&self, params: &PluginCreateParams) -> WPNetworkRequest { WPNetworkRequest { method: RequestMethod::POST, @@ -264,6 +282,24 @@ impl WPApiHelper { } } + pub fn filter_retrieve_plugin_request( + &self, + context: WPContext, + plugin: &PluginSlug, + fields: &[SparsePluginField], + ) -> WPNetworkRequest { + WPNetworkRequest { + method: RequestMethod::GET, + url: self + .api_endpoint + .plugins + .filter_retrieve(context, plugin, fields) + .into(), + header_map: self.header_map(), + body: None, + } + } + pub fn update_plugin_request( &self, plugin: &PluginSlug, diff --git a/wp_api/src/plugins.rs b/wp_api/src/plugins.rs index 9333cd0d..e636bb5f 100644 --- a/wp_api/src/plugins.rs +++ b/wp_api/src/plugins.rs @@ -3,6 +3,8 @@ use wp_derive::WPContextual; use crate::{add_uniffi_exported_parser, parse_wp_response, WPApiError, WPNetworkResponse}; +add_uniffi_exported_parser!(parse_filter_plugins_response, Vec); +add_uniffi_exported_parser!(parse_filter_retrieve_plugin_response, SparsePlugin); add_uniffi_exported_parser!( parse_list_plugins_response_with_edit_context, Vec @@ -93,6 +95,37 @@ pub struct SparsePlugin { pub textdomain: Option, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)] +pub enum SparsePluginField { + Author, + Description, + Name, + NetworkOnly, + Plugin, + PluginUri, + RequiresPhp, + Status, + Textdomain, + Version, +} + +impl SparsePluginField { + pub fn as_str(&self) -> &str { + match self { + Self::Author => "author", + Self::Description => "description", + Self::Name => "name", + Self::NetworkOnly => "network_only", + Self::Plugin => "plugin", + Self::PluginUri => "plugin_uri", + Self::RequiresPhp => "requires_php", + Self::Status => "status", + Self::Textdomain => "textdomain", + Self::Version => "version", + } + } +} + #[derive(Debug, Serialize, Deserialize, uniffi::Record)] pub struct PluginDeleteResponse { pub deleted: bool, diff --git a/wp_networking/tests/test_plugins_immut.rs b/wp_networking/tests/test_plugins_immut.rs index ac314636..e2ae6c09 100644 --- a/wp_networking/tests/test_plugins_immut.rs +++ b/wp_networking/tests/test_plugins_immut.rs @@ -1,5 +1,10 @@ use rstest::*; -use wp_api::{generate, plugins::PluginListParams, plugins::PluginStatus, PluginSlug, WPContext}; +use rstest_reuse::{self, apply, template}; +use wp_api::{ + generate, + plugins::{PluginListParams, PluginStatus}, + PluginSlug, SparsePlugin, SparsePluginField, WPContext, +}; use crate::test_helpers::{ api, WPNetworkRequestExecutor, WPNetworkResponseParser, CLASSIC_EDITOR_PLUGIN_SLUG, @@ -8,6 +13,46 @@ use crate::test_helpers::{ pub mod test_helpers; +#[apply(filter_fields_cases)] +#[tokio::test] +async fn filter_plugins( + #[case] fields: &[SparsePluginField], + #[values( + PluginListParams::default(), + generate!(PluginListParams, (status, Some(PluginStatus::Active))), + generate!(PluginListParams, (search, Some("foo".to_string()))) + )] + params: PluginListParams, +) { + let parsed_response = api() + .filter_list_plugins_request(WPContext::Edit, &Some(params), fields) + .execute() + .await + .unwrap() + .parse(wp_api::parse_filter_plugins_response); + assert!(parsed_response.is_ok()); + parsed_response + .unwrap() + .iter() + .for_each(|plugin| validate_sparse_plugin_fields(&plugin, fields)); +} + +#[apply(filter_fields_cases)] +#[tokio::test] +async fn filter_retrieve_plugin( + #[case] fields: &[SparsePluginField], + #[values(CLASSIC_EDITOR_PLUGIN_SLUG, HELLO_DOLLY_PLUGIN_SLUG)] slug: &str, +) { + let plugin_result = api() + .filter_retrieve_plugin_request(WPContext::Edit, &slug.into(), fields) + .execute() + .await + .unwrap() + .parse(wp_api::parse_filter_retrieve_plugin_response); + assert!(plugin_result.is_ok()); + validate_sparse_plugin_fields(&plugin_result.unwrap(), fields); +} + #[rstest] #[case(PluginListParams::default())] #[case(generate!(PluginListParams, (search, Some("foo".to_string()))))] @@ -15,7 +60,7 @@ pub mod test_helpers; #[case(generate!(PluginListParams, (search, Some("foo".to_string())), (status, Some(PluginStatus::Inactive))))] #[trace] #[tokio::test] -async fn test_plugin_list_params_parametrized( +async fn plugin_list_params_parametrized( #[case] params: PluginListParams, #[values(WPContext::Edit, WPContext::Embed, WPContext::View)] context: WPContext, ) { @@ -77,3 +122,67 @@ async fn retrieve_plugin_with_edit_context( ); assert_eq!(expected_author, parsed_response.unwrap().author); } + +fn validate_sparse_plugin_fields(plugin: &SparsePlugin, fields: &[SparsePluginField]) { + assert_eq!( + plugin.author.is_some(), + fields.contains(&SparsePluginField::Author) + ); + + assert_eq!( + plugin.author.is_some(), + fields.contains(&SparsePluginField::Author) + ); + assert_eq!( + plugin.description.is_some(), + fields.contains(&SparsePluginField::Description) + ); + assert_eq!( + plugin.name.is_some(), + fields.contains(&SparsePluginField::Name) + ); + assert_eq!( + plugin.network_only.is_some(), + fields.contains(&SparsePluginField::NetworkOnly) + ); + assert_eq!( + plugin.plugin.is_some(), + fields.contains(&SparsePluginField::Plugin) + ); + assert_eq!( + plugin.plugin_uri.is_some(), + fields.contains(&SparsePluginField::PluginUri) + ); + assert_eq!( + plugin.requires_php.is_some(), + fields.contains(&SparsePluginField::RequiresPhp) + ); + assert_eq!( + plugin.status.is_some(), + fields.contains(&SparsePluginField::Status) + ); + assert_eq!( + plugin.textdomain.is_some(), + fields.contains(&SparsePluginField::Textdomain) + ); + assert_eq!( + plugin.version.is_some(), + fields.contains(&SparsePluginField::Version) + ); +} + +#[template] +#[rstest] +#[case(&[SparsePluginField::Author])] +#[case(&[SparsePluginField::Description])] +#[case(&[SparsePluginField::Name])] +#[case(&[SparsePluginField::NetworkOnly])] +#[case(&[SparsePluginField::Plugin])] +#[case(&[SparsePluginField::PluginUri])] +#[case(&[SparsePluginField::RequiresPhp])] +#[case(&[SparsePluginField::Status])] +#[case(&[SparsePluginField::Textdomain])] +#[case(&[SparsePluginField::Version])] +#[case(&[SparsePluginField::Author, SparsePluginField::Plugin])] +#[case(&[SparsePluginField::Status, SparsePluginField::Version])] +fn filter_fields_cases(#[case] fields: &[SparsePluginField]) {}