Skip to content

Plugins endpoint #90

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
May 21, 2024
Merged

Plugins endpoint #90

merged 25 commits into from
May 21, 2024

Conversation

oguzkocer
Copy link
Contributor

@oguzkocer oguzkocer commented May 16, 2024

Implements the /plugins endpoint variants - except for filtering. It closely follows the /users implementation, so there isn't too much that's completely new.

  • Adds backup-wp-content-plugins, restore-wp-content-plugins & delete-wp-plugins-backup Make tasks. For most of our integration tests, restoring the DB to a previous state will be enough. However, with /plugins the filesystem is also involved, so we need new helpers just for /plugins integration tests.
  • Adds test_helpers::run_and_restore_wp_content_plugins function. This is similar to wp_db::run_and_restore function, providing a convenient way to handle the restoration of wp-contents/plugins folder from a backup using the Make task.
  • Adds several integration tests, but the create, delete & update plugin tests are a little fragile at the moment. They require the test site to have hello-dolly and classic-editor plugins to be installed. (one being active, the other inactive) I am considering adding specific Make tasks to ensure a specific state before running these tests, but I'd like to split that into its own PR - if I do implement it.

There has been several annoying issues in this implementation:

Plugin Slugs

  • The plugin slugs returned from the /plugins endpoint will be in foo/bar format, for example hello-dolly/hello & classic-editor/classic-editor
  • The plugin slugs in WordPress.org directory is in foo format: hello-dolly & classic-editor

These discrepancies could become a headache for the consumers, so I think we might want to introduce PluginSlug & WpOrgPluginSlug types to clearly differentiate them in our API. I'd like to do this in its own PR.

Furthermore, most of the endpoints expect the plugin slug to be part of the url and not the query pairs. So, for example with classic-editor, the proper retrieve request url would be /plugins/classic-editor/classic-editor as opposed to something like /plugins?slug=classic-editor%2Fclassic-editor where the / character is encoded. To handle this, the plugins_url_with_slug helper function will first split the given slug using / as the separator, and then use it to extend the url.

Incorrect(?) documentation


  • author seems to return a String as opposed to a JSONObject.

Both update & delete endpoints' documentation claims that the context argument will determine which fields will be in the response, however it seems it always returns the fields for edit. (which contains all the fields) I've tried this by sending the context parameter, both as a query pair as well as part of the JSON body of the POST request and in both cases, I got all the fields back. I might have made a mistake in my testing, but I have some confidence that this is correct, because while testing I accidentally sent context: View in the JSON body and got an error back saying context is not one of edit, embed & view values.

To avoid confusion, I've decided not to include the context parameter for these endpoints.


Note that one small discrepancy with plugins implementation, compared to /users is that I've decided to include specific parsers for create & update endpoints - as opposed to using the parser for retrieve response. I'd like to go back and change /users as well to follow this pattern to make it easier to find the correct parser for each response. This will add a few "unnecessary" functions, but I think it's worth it.


To Test

  • make test-server && make dump-mysql && make backup-wp-content-plugins
  • cargo test --test '*' -- --nocapture --test-threads 1

What Next

  • Refactoring some common implementations between users & plugins
  • Reorganization of some of the Rust modules and especially the WPApiHelper
  • Filtering for plugins
  • Error handling for plugins
  • More integration tests for plugins - and maybe make them less fragile

@oguzkocer oguzkocer added this to the 0.1 milestone May 16, 2024
@oguzkocer oguzkocer marked this pull request as ready for review May 16, 2024 19:23
@oguzkocer oguzkocer requested review from crazytonyli and jkmassel May 16, 2024 19:23
@crazytonyli
Copy link
Contributor

Both update & delete endpoints' documentation claims that the context argument will determine which fields will be in the response, however it seems it always returns the fields for edit.
[...]
To avoid confusion, I've decided not to include the context parameter for these endpoints.

My understanding is all endpoints' POST requests only use edit context. I agree it makes sense to not include context parameter for them, which is the case for /users endpoints implementation too.

Copy link
Contributor

@crazytonyli crazytonyli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good to address the potential issue of parsing a unknown PluginStatus.

plugin_slug,
parsed_response
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

author seems to return a String as opposed to a JSONObject.

What do you think about adding tests for the author property, so that we can be sure that it's indeed a documentation error (rather than a breaking change in the API)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current tests should already cover this as it retrieving plugins would fail with ParsingError if we got a json object instead of the expected String type.

Happy to add more tests if you have anything specific in mind.


Any chance you were thinking that author field was part of a {foo}Params} type, rather than a return type like the SparsePlugin? 😅 That's not the case, but if it was, I agree it would have been good to add a test that utilized that parameter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking adding an explicit check which asserts author is a certain string, not even not nil.

self.api_base_url
// The '/' character has to be preserved and not get encoded
.by_extending(["plugins"].into_iter().chain(plugin.split('/')))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was an issue in iOS app where the app crashed due to unexpected characters in plugin slug. See wordpress-mobile/WordPressKit-iOS#767.

I couldn't reproduce the crash, because I can't find a real-world plugin that has a non-ascii slug. However, I think it's still worthy adding some unit tests to ensure a correct URL is constructed for strange plugin slugs (i.e. has % or non-ascii charaectrs). What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a few more tests for this in b189438. It'd be really good if we can find a real world case for it though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like 4317929?

Active,
#[serde(rename = "inactive")]
Inactive,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a new plugin status is added in a newer WordPress version, will that cause parser errors in this library, because status in contextual models (i.e. PluginWithViewContext) is non-optinal? What do you think adding a "unknown" case here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkmassel could correct me if I am wrong, but I think one of the promises with WordPress.org REST API is that it'll remain backward compatible. So, I don't think we should make things harder for ourselves by taking breaking changes into account.

I don't like the idea of adding an Unknown variant for this - as that'll become a precedent and we'll have to start adding it everywhere else. I'd rather our library break in such an edge case - which I don't think will ever happen for PluginStatus to be honest.

Copy link
Contributor

@crazytonyli crazytonyli May 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one of the promises with WordPress.org REST API is that it'll remain backward compatible.

I believe adding another value (for example, "status": "activating") to an existing property is still "backward compatible". But our library won't be able to parse that response, which introduces issues to the consumer apps.

I'd rather our library break in such an edge case - which I don't think will ever happen for PluginStatus to be honest.

I just checked the source code. I don't know how to reproduce such a case, but I believe the code says status can also be network-active. https://github.com/WordPress/WordPress/blob/6.5.3/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php#L898

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's for multisites, and although we don't plan to directly support them, I see your point.

I still don't like the idea of building separate fallbacks for every enum we add. So, I was thinking maybe we should generalize the Recognized / Unrecognized approach that we used for errors - hopefully in a little bit better way, so we can use it everywhere?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind creating an issue to track this issue of parsing the status property, if it's not going to be addressed in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#95

}
}

pub fn retrieve_plugin_request(&self, context: WPContext, plugin: &str) -> WPNetworkRequest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mentioned introducing PluginSlug & WpOrgPluginSlug types. Will the plugin: &str be replaced by one of those types?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeap, this is one of the cases where it's not clear which type of slug it should take. (It's the PluginSlug in this example, just as a reference)

@oguzkocer
Copy link
Contributor Author

Thanks for the review @crazytonyli. I added some more unit tests as you suggested and replied to your other comments. Ready for another look! 🙇‍♂️

@oguzkocer oguzkocer requested a review from crazytonyli May 17, 2024 04:20
pub fn update_plugin_request(
&self,
plugin: &str,
params: PluginUpdateParams,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be:

Suggested change
params: PluginUpdateParams,
params: &PluginUpdateParams,

It's addressed in a later PR, but I won't include it here to avoid the extra merge conflict. (as I have multiple PRs building on one another)

Copy link
Contributor

@crazytonyli crazytonyli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving to unblock.

Active,
#[serde(rename = "inactive")]
Inactive,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind creating an issue to track this issue of parsing the status property, if it's not going to be addressed in this PR?

@oguzkocer
Copy link
Contributor Author

Thanks @crazytonyli! 🙇‍♂️

@oguzkocer oguzkocer merged commit 0722c3f into trunk May 21, 2024
22 checks passed
@oguzkocer oguzkocer deleted the plugins branch May 21, 2024 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants