Skip to content

Add the built-in WP REST API validation to CountryCodeTrait to avoid errors when clearing all audience countries in the onboarding flow #2003

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 8 commits into from
Jun 30, 2023

Conversation

eason9487
Copy link
Member

@eason9487 eason9487 commented Jun 28, 2023

Changes proposed in this Pull Request:

This PR solves a task of 📌 Clarify API error messages in #1993

Closes #1855

Originally, clearing the recipient country did not cause API errors. After #1327, the POST /mc/target_audience API was also validated by this check, which causes the auto-saving behavior in the onboarding flow to get errors.

  • Add the built-in validation of WP REST API for the country codes in CountryCodeTrait.
  • Change to use the parameter's schema to validate if the country codes array is not empty for needed Controllers:
    • BudgetRecommendationController
    • CampaignController
  • Add or adjust tests for the related Controllers:
    • Add basic tests for the TargetAudienceController and ShippingTimeBatchController.
    • Add the test case of empty country codes for the BudgetRecommendationController
    • Remove an unused variable from the test_create_campaign_empty_targeted_locations in CampaignControllerTest.php

📌 Please take a look at the Additional details for contexts and other solutions.

Screenshots:

Kapture.2023-06-28.at.15.09.40.mp4

Detailed test instructions:

  1. Go to step 2 of the onboarding flow.
  2. Remove all audience countries.
  3. Check if there is no API error.

Additional details:

About the validation mechanism of WP REST API

When extending the WP REST API, the mechanism of validate_callback and sanitize_callback is somewhat surprising. In summary, the findings are:

  1. If the sanitize_callback is not specified but type is specified, both built-in validate_callback and sanitize_callback will be applied no matter whether the validate_callback is a custom one.
  2. If both validate_callback and sanitize_callback are specified with custom callbacks, the built-in schema validation won't be applied.
  3. The built-in validate_callback may be applied two times if not specify both callbacks.
  4. When the built-in validate_callback is skipped, the required specified in args will still work. This makes the validation options of args work or not, resulting in more unexpected inconsistencies.
  5. Looks like there is no way to specify in args that when a custom validate_callback is specified, still uses the built-in schema validation.

This can be a bit confusing to use because the main goal of sanitize_callback doesn't seem to be data validation, but whether the built-in schema validation works in the end depends on sanitize_callback and not just validate_callback.

Why the current solution: Add the built-in validation to CountryCodeTrait

When dealing with this issue, I have thought about other possible solutions, and the reasons for choosing the current one so far are:

But I still have some doubts about whether doing so within CountryCodeTrait is too subtle and becomes inconsistent at a higher level concept.

Other possible solutions

  1. Add the third parameter to CountryCodeTrait::validate_country_codes and to all related callers.
  2. Similar to the first one but don't add the parameter to callers. Instead, add a new method such as get_country_code_and_allow_empty_validate_callback to CountryCodeTrait.
  3. Wrap the validate_callback in TargetAudienceController with an additional process: When countries is found to be empty, skip calling to the validation function returned by get_country_code_validate_callback.
  4. Remove the custom sanitize_callback from all CountryCodeTrait::validate_country_codes-related callers so that the built-in validate_callback will be applied.
  5. Add a new API dedicated to removing the audience countries and change the original POST /mc/target_audience to create audience countries instead of a PUT like the one defined in the RESTful API.
  6. Any others?

Changelog entry

Fix - Avoid errors when clearing all audience countries in the onboarding flow.

@github-actions github-actions bot added changelog: fix Took care of something that wasn't working. type: bug The issue is a confirmed bug. labels Jun 28, 2023
@eason9487 eason9487 self-assigned this Jun 28, 2023
@eason9487 eason9487 requested a review from a team June 28, 2023 08:42
@codecov
Copy link

codecov bot commented Jun 28, 2023

Codecov Report

Merging #2003 (4eeffe8) into develop (e434627) will increase coverage by 0.4%.
The diff coverage is 100.0%.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             develop   #2003     +/-   ##
===========================================
+ Coverage       57.5%   57.9%   +0.4%     
  Complexity      4111    4111             
===========================================
  Files            456     456             
  Lines          17655   17658      +3     
===========================================
+ Hits           10156   10224     +68     
+ Misses          7499    7434     -65     
Flag Coverage Δ
php-unit-tests 57.9% <100.0%> (+0.4%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...Controllers/Ads/BudgetRecommendationController.php 100.0% <100.0%> (ø)
...rc/API/Site/Controllers/Ads/CampaignController.php 100.0% <100.0%> (ø)
src/API/Site/Controllers/CountryCodeTrait.php 100.0% <100.0%> (ø)

... and 2 files with indirect coverage changes

Comment on lines 52 to 56
$valid = rest_validate_request_arg( $countries, $request, $param );

if ( true !== $valid ) {
return $valid;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd suggest renaming this as $validation_result, $validation or something like that. Since it can return a WP_Error object, hence is not "valid".

Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed in bdfb365.

* @param bool $check_supported_country Whether to check the country is supported.
* @param mixed $countries An individual string or an array of strings.
* @param Request $request The request to validate.
* @param string $param The parameter name, used in error messages.
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can add somewhere a short explanation of your findings of why rest_validate_request_arg is not being called automatically.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added in 4eeffe8.

@@ -0,0 +1,109 @@
<?php
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe We can add some tests for:

  • Countries with wrong format return Error
  • Countries not supported can be added without errors.

@@ -0,0 +1,104 @@
<?php
Copy link
Contributor

@puntope puntope Jun 28, 2023

Choose a reason for hiding this comment

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

@puntope
Copy link
Contributor

puntope commented Jun 28, 2023

Can we test if in ShippingRateController the validation for country also works?

Same for ShippingRateSuggestionsController, at least would be nice to include the country_codes test.

@puntope
Copy link
Contributor

puntope commented Jun 28, 2023

If the sanitize_callback is not specified but type is specified, both built-in validate_callback and sanitize_callback will be applied no matter whether the validate_callback is a custom one.

Checking here, seems like it runs rest_parse_request_arg as the default sanitize callback when a type is added.
https://github.com/WordPress/WordPress/blob/master/wp-includes/rest-api/class-wp-rest-request.php#L821

That rest_validate_request_arg runs the validation.

When the built-in validate_callback is skipped, the required specified in args will still work. This makes the validation options of args work or not, resulting in more unexpected inconsistencies.

Seems like the dispatcher is calling has_valid() function which checks the required params.

@puntope
Copy link
Contributor

puntope commented Jun 28, 2023

Hi @eason9487 , the PR looks good to me. I tested it locally and I cannot see anymore the early error when no countries are selected.

I left some checks I did after your findings. And some suggestions in regards test and some variable naming. Since the variable naming and the tests are not blockers, I will approve this in advance.

  1. Any others?

To say something one simple solution might be to just not call the endpoint if country codes are empty in the JS side and keep all of them as required (minItems 1) in the backend.

@eason9487
Copy link
Member Author

eason9487 commented Jun 29, 2023

@puntope, thanks for the review.

Can we test if in ShippingRateController the validation for country also works?
Same for ShippingRateSuggestionsController, at least would be nice to include the country_codes test.

ShippingRateController specifies the country as a string type and it only indicates the required option, so it's not affected by this PR and doesn't have a validation schema that will work. In addition, the POST mc/shipping/rates API and all mc/shipping/rates/{country_code} APIs have not been in use after #411.

About ShippingRateSuggestionsController, I just recalled that its API endpoint mc/shipping/rates/suggestions has not been in use for some time after the use was removed by #1616 Perhaps removing it would be a bit better than writing tests for it.

Maybe We can add some tests for: [...]
Quoted from #2003 (comment) and #2003 (comment)

Considering several controllers use CountryCodeTrait to get the validation callback for country codes, it would be a bit repetitive to add the relevant tests for each of them. So I'm going to add tests for CountryCodeTrait instead, but the code changes were already more than expected and beyond this PR scope. I will open a separate PR for it.

6. Any others?

To say something one simple solution might be to just not call the endpoint if country codes are empty in the JS side and keep all of them as required (minItems 1) in the backend.

The intention of calling this API even if the countries array is empty on the UI side is to always remember the entered data during the onboarding flow, so that the user can continue with the previously entered data if they interrupt or refresh the onboarding flow. So I would think this solution would conflict with the purpose.

@eason9487
Copy link
Member Author

Sorry, @puntope. A thing I mentioned in #2003 (comment) is incorrect. The POST mc/shipping/rates API and most mc/shipping/rates/{country_code} APIs are still being used by ShippingRateBatchController internally. I have also added a strikethrough for the incorrect description.

@eason9487 eason9487 merged commit afebc1e into develop Jun 30, 2023
@eason9487 eason9487 deleted the fix/1855-avoid-error-onboarding-empty-audience branch June 30, 2023 02:22
@eason9487 eason9487 mentioned this pull request Jul 11, 2023
17 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog: fix Took care of something that wasn't working. type: bug The issue is a confirmed bug.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Audience Auto-save shows error when selector is empty.
2 participants