%% title = "new in Unreal Engine 5.4: data validation quick fixes"
   thumbnail.id = "01HP1G5WC29GP4KQY1NV1F1RR1"
   thumbnail.alt = "a screenshot with a validation error in it; beneath the error there's a hint that it may be fixed automatically, coupled with a link you can click on to fix the issue"

% id = "01HP1FESY3H9K1QVSM1XMNC8NS"
- a few days ago I got a really cool change into Unreal, which allows you to add quick fixes to any data validation warning/error you emit:

![a screenshot with a validation error in it; beneath the error there's a hint that it may be fixed automatically, coupled with a link you can click on to fix the issue][pic:01HP1G5WC29GP4KQY1NV1F1RR1]

% id = "01HP1FESY3K11EJJ9NSFBDCTWS"
+ you can get this change in...

    % id = "01HP1FESY3CP8AJ00AQ9B6FQB2"
    - [Git commit `0a6de35a5beb8534e19579cf1058460b4eb2bc79`](https://github.com/EpicGames/UnrealEngine/commit/0a6de35a5beb8534e19579cf1058460b4eb2bc79)

    % id = "01HP1FESY3RW98YQZFS04Q7E7C"
    - Perforce changelist 31126352

% id = "01HP1FESY3XJRMHKB173XWF130"
+ the API revolves around the `UE::DataValidation::IFixer` type. _fixers_ are pieces of code that can fix your issues given their application conditions are met

    % id = "01HP1FESY38JWXNSJKPZNHP33Q"
    + the interface you have to implement is:

    ```cpp
    struct IFixer
    {
        virtual EFixApplicability GetApplicability(int32 FixIndex) const = 0;
        virtual FFixResult ApplyFix(int32 FixIndex) = 0;
    }
    ```

        % id = "01HP1FESY3PZH00WDD85CNF1BX"
        - for the curious cats among us: `FixIndex` is an arbitrary number that can be passed to `FFixToken`, to handle multiple different fixes with one fixer

    % id = "01HP1FESY3328D7ECZZX3EZCNE"
    + first, the editor calls `GetApplicability` to know whether the fix can be applied

        % id = "01HP1FESY32EFSRX75ZWJ0K7PW"
        - `EFixApplicability::CanBeApplied` should be returned if the fix can be applied at the moment

        % id = "01HP1FESY3RN3ECQHJ8R3HS4GD"
        - `EFixApplicability::Applied` should be returned if the fix has been applied and can no longer be applied anymore

        % id = "01HP1FESY327FZMRZ64844XC9A"
        - `EFixApplicability::DidNotApply` should be returned if the fix was not applied and can no longer be applied anymore

    % id = "01HP1FESY3N19T00CNY2T182VG"
    + then, if `GetApplicability()` returns `EFixApplicability::CanBeApplied`, `ApplyFix` is called to actually run the fix

        % id = "01HP1FESY3NGQRKA57QTTVC19W"
        - this function is safe to assume the caller has ensured that the fix `CanBeApplied` and therefore need not do any extra validity checks

        % id = "01HP1FESY3AAEHF4HSMAJ3B21H"
        - other than that it's free to do whatever it wants

        % id = "01HP1FESY3CBMBJA3CQPEMCFND"
        - after a fix is applied, the fixer returns an `FFixResult` which is used to display a notification to the user informing them of the changes

    % id = "01HP1FESY3GB4N76JDV8RVG4VD"
    - fortunately you don't have to define a new struct for each fix, and instead can use `UE::DataValidation::TLambdaFixer` together with the `UE::DataValidation::MakeFix` function for more convenience

% id = "01HP1FESY3EJ7TK4SPTD0TBBN9"
+ once you have a fixer, you can display it in your (`FTokenizedMessage`-based) validation messages using `FFixToken`

    % id = "01HP1FESY30HDA6JNR323GXD3D"
    - minimal example:

    ```cpp
    auto Message = FTokenizedMessage::Create(
        EMessageVerbosity::Error,
        LOCTEXT(
            "FoundNoneEntries",
            "Found None entries in ability array."
            "Please remove them, otherwise they may cause crashes during runtime."
        )
    );
    Message->AddToken(
        UE::DataValidation::MakeFix(
            [this]
            {
                Abilities.SetNum(Algo::RemoveIf(
                    Abilities,
                    [](const UGameplayAbility* Ability) { return Ability == nullptr; }
                ));
                return FFixResult::Success(LOCTEXT("NoneEntriesRemoved", "None entries have been removed"));
            }
        )
        ->CreateToken(LOCTEXT("RemoveNoneEntries", "Remove the None entries"))
    );
    Context.AddMessage(MoveTemp(Message));
    ```

% id = "01HP1FESY3V73R3JZEQHQ36V58"
- the user can then apply a fix by clicking on it in the Message Log message that contains it

% id = "01HP1FESY3JM8WPZMRNCZC8GBA"
+ fixers can be freely stacked and composed - there are a few such layers available out of the box in the engine

    % id = "01HP1FESY3VK9X1TB81VR2VA83"
    - the set is quite limited at the moment, but you're free to create your own or contribute them to mainline Unreal :ralsei_love:

    % id = "01HP1FESY3TC6DMBWQ3CEMGRW1"
    - `UE::DataValidation::FSingleUseFixer` makes it so that your fix's applicability becomes `EFixApplicability::Applied` after the user applies the fix

    % id = "01HP1FESY32ZA4K57ATCKPM800"
    - `UE::DataValidation::FObjectSetDependentFixer` makes it so that your fix becomes `DidNotApply` after the specified objects are deleted from memory

    % id = "01HP1FESY3W381HMBCEGCZ3HK7"
    - `UE::DataValidation::FAutoSavingFixer` tells the user to save any assets modified by the fix after it's applied

    % id = "01HP1FESY3SENV59V4462YSZJY"
    - `UE::DataValidation::FValidatingFixer` runs data validation on any assets modified by the fix after it's applied

    % id = "01HP1FESY3AZN3AVF7Z02JHXZA"
    - `UE::DataValidation::FMutuallyExclusiveFixSet` is actually not a fixer, but a fixer _builder_ -
    you give it a set of fixers, and it will make it so that when one is applied, it becomes `EFixApplicability::Applied`, and the rest becomes `EFixApplicability::DidNotApply` -
    thus creating a set of mutually-exclusive fixes

    % id = "01HP1FESY3CC1NZZMQQP56TTD4"
    - you can refer to the `DataValidationFixers.h` header file for more documentation

% id = "01HP1FESY3WH21KDDYAHWZG294"
- all of this is going to be available in Unreal 5.4 :sparkles: