scramble icon indicating copy to clipboard operation
scramble copied to clipboard

[Bug]: Body Param Ordering Wrong

Open CWFranklin opened this issue 5 months ago • 6 comments

What happened?

Body param order to be same as validation ruleset. Rule:: items seem to be shuffled to the bottom.

How to reproduce the bug

Using the following (simplified) ruleset on a custom Request class:

 [
    /**
     * Type of form being submitted.<br>
     * In the past we have used the following: `appointment`, `brochure`, `career`, `customercare`, `event`, `general`, `interest`, `land`, and `siteplan`.
     */
    'type' => 'required|string',

    /**
     * Customer's full name.
     * @example "Steven Berkshire"
     */
    'name' => 'required|string',

    // Development(s) that the contact request is relevant to.
    'development' => 'nullable|array',
    'development.*' => [
        Rule::in(['test', 'test2']),
    ],

    // Denotes how the customer wishes to be contacted.
    'contact_method' => 'nullable|array',
    'contact_method.*' => [
        Rule::enum(ContactMethod::class),
    ],

    /**
     * Job title being applied to for careers form submissions.
     * @example "Senior Architect"
     */
    'job' => 'nullable|string',
];

Produces the following docs: Image

and the same order in the JSON version: Image

Package Version

0.12.23

PHP Version

8.4.6

Laravel Version

12.19.3

Which operating systems does with happen with?

Windows

Notes

Condensed config file.

[
    'api_path' => 'api',
    'api_domain' => null,
    'export_path' => 'api.json',
    'info' => [
        'version' => env('API_VERSION', '0.0.1'),
        'description' => '',
    ],
    'ui' => [
        'title' => null,
        'theme' => 'dark',
        'hide_try_it' => false,
        'hide_schemas' => true,
        'logo' => '',
        'try_it_credentials_policy' => 'include',
        'layout' => 'responsive',
    ],
    'servers' => null,
    'enum_cases_description_strategy' => 'description',
    'middleware' => [
        'web',
        'auth',
        'verified',
        RestrictedDocsAccess::class,
    ],
    'extensions' => [],
];

CWFranklin avatar Jul 10 '25 14:07 CWFranklin

I pulled forked and had a play with a new test in ValidationRulesDocumentingTest.php. Using buildRulesToParameters, it maintains the same order as expected so I expect the issue occurs when it's translated into OpenAPI spec but I've not gotten that far yet.

Edit: Just in case you want to use it.

it('maintains the same rule order with enum included', function () {
    $rules = [
        'name' => 'string|required',
        'status' => Rule::enum(StatusValidationEnum::class),
        'type' => 'string|nullable',
    ];

    $params = ($this->buildRulesToParameters)($rules)->handle();

    expect($params[1]->name)->toBe('status');
});

CWFranklin avatar Jul 10 '25 14:07 CWFranklin

@CWFranklin I think DeepParametersMerger (or the similar-ish name) is messing up with the order (unless $this->buildRulesToParameters IS an instance of DeepParametersMerger)

romalytvynenko avatar Jul 10 '25 14:07 romalytvynenko

@romalytvynenko It doesn't but that class is used in the validationRulesToDocumentationWithDeep function. I wrote a test using that but it still keeps the same order. Anything further down the chain to look at or something I'm missing?

it('maintains ordering on deep merger', function () {
    $rules = [
        'name' => 'string|nullable',
        'status' => Rule::enum(StatusValidationEnum::class),
        'type' => 'string|nullable',
    ];

    $params = validationRulesToDocumentationWithDeep(($this->buildRulesToParameters)($rules));
    dd($params);
    //assertMatchesSnapshot(json_encode(collect($params)->map->toArray()->all()));
});

CWFranklin avatar Jul 10 '25 14:07 CWFranklin

@CWFranklin I don't remember tbh. Apparently yes, there is something going on with these parameters after this initial transformation that breaks the order

romalytvynenko avatar Jul 10 '25 16:07 romalytvynenko

@romalytvynenko Debugged using my actual codebase and managed to get to the route of the issue at least. DeepParametersMerger.php:88 takes the nested params (i.e. Enums / InArray), works with them and then merges them onto the end of the $parameters array.

Edit: I finally managed to deduce that it's probably not even because of the Enum/InArray bits, but instead, where I'm using arrays. I managed to throw this test together which triggers the nested handler code and fails. Hopefully this illustrates the cause and effect perfectly!

it('maintains the same rule order with nested parameter included', function () {
    $rules = [
        'name' => 'string|required',
        'status' => 'array|required',
        'status.*' => 'string',
        'type' => 'string|nullable',
    ];

    $params = validationRulesToDocumentationWithDeep(($this->buildRulesToParameters)($rules));

    expect($params[1]->name)->toBe('status');
})

CWFranklin avatar Jul 10 '25 23:07 CWFranklin

@CWFranklin great! Thank you!

romalytvynenko avatar Jul 11 '25 03:07 romalytvynenko