Skip to content
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

Array typing doesn't allow multiple array item types #601

Open
dnkmdg opened this issue Oct 24, 2024 · 9 comments
Open

Array typing doesn't allow multiple array item types #601

dnkmdg opened this issue Oct 24, 2024 · 9 comments

Comments

@dnkmdg
Copy link

dnkmdg commented Oct 24, 2024

When annotating a return type as array<TypeOne|TypeTwo> only the first type is selected, and the rest ignored.
This also causes the following types to be ignored from the Schema generation.

My assumption is that the array type currently isn't set to allow "anyOf" type of definitions.

Example

Definition:
image

Response example
image

Schemas
image

@dnkmdg
Copy link
Author

dnkmdg commented Oct 25, 2024

This would be the expected implementation!
https://www.baeldung.com/openapi-array-of-varying-types

@romalytvynenko
Copy link
Member

@dnkmdg I started implementing this, but noticed Laravel Data itself does not support this. Under the hood, Laravel Data just keeps the first type of the provided union type.

Here is my test setup:

class WithUnionInArrayData extends \Spatie\LaravelData\Data
{
    /**
     * @param array<FooData|BarData> $something
     */
    public function __construct(
        public array $something,
    ) {}
}
class FooData extends \Spatie\LaravelData\Data
{
    public function __construct(
        public string $foo,
    ) {}
}
class BarData extends \Spatie\LaravelData\Data
{
    public function __construct(
        public string $bar,
    ) {}
}

And now when I'm trying to create the object just with the "FooData":

$data = WithUnionInArrayData::from([
    'something' => [
        ['foo' => 'a'],
    ],
]);

dump($data); 
WithUnionInArrayData�{#1621
  #_additional: []
  #_dataContext: null
  +something: array:1 [
    0 => FooData�{#1638
      #_additional: []
      #_dataContext: null
      +foo: "a"
    }
  ]
} 

But if I try to create the WithUnionInArrayData with both FooData and BarData:

$data = WithUnionInArrayData::from([
    'something' => [
        ['foo' => 'a'],
        ['bar' => 'b'],
    ],
]);

I get the error that happens because LaravelData is trying to create FooData from BarData payload:

Spatie\LaravelData\Exceptions\CannotCreateData: Could not create `FooData`: the constructor requires 1 parameters, 0 given. Parameters missing: foo.

And this is expected, due to internally Laravel Data got only FooData type for something:

image

So am I missing something and you have been able to make it work, or indeed this is not possible on Laravel Data level?

@romalytvynenko
Copy link
Member

Fixed in 0.11.24 (Laravel Data however indeed works as described)

@romalytvynenko
Copy link
Member

Re-opened due to missing property promotion inference

@cosmastech
Copy link

I had a similar issue with a return type like this:

* @return DataCollection<BudgetSchema>|ForbiddenResponse

@romalytvynenko
Copy link
Member

romalytvynenko commented Nov 21, 2024

@cosmastech can you please share more context? Is it a comment in your controller's method?

@cosmastech
Copy link

Yes, it's in the controller method.

image

@J5Dev
Copy link

J5Dev commented Dec 5, 2024

Were having the same issue described, but even without the array aspect... essentially anything with a multiple type response adds the first response, and ignores the rest.

This...

/**
 * Verify a user using their email address.
 * @unauthenticated
 *
 * @param Request $request
 * @param string $verificationId
 *
 * @return UserVerificationResource|JsonResponse
 */
public function email(Request $request, string $verificationId): UserVerificationResource|JsonResponse
{
    $userVerification = UserVerification::with(['user'])
                                        ->where('type', 'email')
                                        ->where('id', $verificationId)
                                        ->first();

    if (
        !$userVerification ||
        $userVerification->expires->isPast() ||
        $userVerification->attempts >= $this->authSettings->max_verify_attempts
    ) {
        return response()->json(
            [
                'meta' => [
                    'status' => '410',
                    'message' => 'Invalid User Verification code',
                ],
            ],
            410
        );
    }

    return new UserVerificationResource($userVerification);
}

Sadly results in only the UserVerificationResource showing in the docs or export, however if you swap the declarations for the return or typecast, then you get the JsonResponse listed, and no UserVerificationResource.

What is strange, is that if I remove the DocBlock AND the return Typehint, then the output is exactly as expected.

@romalytvynenko
Copy link
Member

@J5Dev created a separate issue for this: #653

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants