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

Specification for Abstract methods #1365

Closed
armanist opened this issue Dec 16, 2022 · 7 comments · Fixed by #1369
Closed

Specification for Abstract methods #1365

armanist opened this issue Dec 16, 2022 · 7 comments · Fixed by #1369

Comments

@armanist
Copy link

I have several abstract methods in the class:

OA annotation on top
abstract public function signin();

OA annotation on top
abstract public function signup();

OA annotation on top
abstract public function sigout();

When I generate the specification, then only the signin() method specification is getting generated.

Looks like it doesn't generate specification for abstract methods (other then the first one), because if I change the last one to be non abstract like:

OA annotation on top
abstract public function signin();

OA annotation on top
abstract public function signup();

OA annotation on top
public function sigout(){}

Now specification is getting generated for 2 methods signin() and signout().

@DerManoMann
Copy link
Collaborator

Strange. There isn't anything that cares about abstract..

Could you post a simple self-contained example to reproduce this? I am not sure I understand what 'OA annotations on top' means. An operation like get/put I would assume?

I presume there are also controller classes processed that contain the actual implementations? Are those annotated too?

@armanist
Copy link
Author

armanist commented Dec 17, 2022

Ok let me give more info for complete picture.

The folder structure is following:

/Controllers/
    AuthController.php
    /Abstracts/
        OpenApiAuthController.php
        ....
        ....
    

Code piece from OpenApiAuthController.php

/**
 * Class OpenApiAuthController
 * @package Modules\Api\Controllers\Abstracts
 */
abstract class OpenApiAuthController
{

    /**
     * Sign in action
     * @OA\Post(
     *    path="/api/signin",
     *    tags={"Authentication"},
     *    summary="Sign in action",
     *    operationId="userSignIn",
     *    @OA\RequestBody(
     *      @OA\MediaType(
     *      mediaType="application/json",
     *        @OA\Schema(
     *          @OA\Property(
     *            property="email",
     *            type="string"
     *          ),
     *          @OA\Property(
     *            property="password",
     *            type="string"
     *          ),
     *          example={"email": "[email protected]", "password": "password"}
     *        )
     *      )
     *    ),
     *    @OA\Response(
     *      response=200,
     *      description="Success",
     *      @OA\MediaType(
     *        mediaType="application/json",
     *      )
     *    ),
     *    @OA\Response(
     *      response=400,
     *      description="Bad Request"
     *    ),
     *    @OA\Response(
     *      response=401,
     *      description="Unauthenticated"
     *    )
     *  )
     * @param Request $request
     * @param Response $response
     */
    abstract public function signin(Request $request, Response $response);

    /**
     * Gets the logged-in user data
     * @OA\Get(
     *     path="/api/me",
     *     tags={"User"},
     *     summary="Gets the logged-in user data",
     *     operationId="me",
     *     security={
     *       {"bearer_token": {}
     *     }},
     *    @OA\Response(
     *      response=200,
     *      description="Success",
     *      @OA\MediaType(
     *        mediaType="application/json",
     *      )
     *    ),
     *    @OA\Response(
     *      response=401,
     *      description="Unauthenticated"
     *    )
     *  )
     * @param Response $response
     */
    abstract public function me(Response $response);

    /**
     * Sign out action
     * @OA\Get(
     *    path="/api/signout",
     *    tags={"Authentication"},
     *    summary="Sign out action",
     *    operationId="signout",
     *    @OA\Parameter(
     *      name="refresh_token",
     *      description="Refresh token",
     *      required=true,
     *      in="header",
     *      @OA\Schema(
     *        type="string"
     *      )
     *    ),
     *    @OA\Response(
     *      response=200,
     *      description="Success",
     *      @OA\MediaType(
     *        mediaType="application/json",
     *      )
     *    ),
     *    @OA\Response(
     *      response=401,
     *      description="Unauthenticated"
     *    )
     *  )
     * @param Response $response
     */
    abstract public function signout(Response $response);

    /**
     * Sign up action
     * @OA\Post(
     *  path="/api/signup",
     *    tags={"Authentication"},
     *    summary="Sign up action",
     *    operationId="signUpApi",
     *    @OA\RequestBody(
     *      @OA\MediaType(
     *        mediaType="application/json",
     *        @OA\Schema(
     *          @OA\Property(
     *            property="email",
     *            type="string",
     *          ),
     *          @OA\Property(
     *            property="password",
     *            type="string"
     *          ),
     *          @OA\Property(
     *            property="firstname",
     *            type="string",
     *          ),
     *          @OA\Property(
     *            property="lastname",
     *            type="string",
     *          ),
     *          example={"email": "[email protected]", "password": "password",  "firstname": "Jon", "lastname": "Smit"}
     *        )
     *      )
     *    ),
     *    @OA\Response(
     *      response=200,
     *      description="Success",
     *      @OA\MediaType(
     *        mediaType="application/json",
     *      )
     *    ),
     *    @OA\Response(
     *      response=401,
     *      description="Unauthenticated"
     *    )
     *  )
     * @param Request $request
     * @param Response $response
     */
    abstract public function signup(Request $request, Response $response);

Code piece from AuthController.php (the actual implementation)

**
 * Class AuthController
 * @package Modules\Api\Controllers
 */
class AuthController extends OpenApiAuthController
{

    /**
     * Status error
     */
    const STATUS_ERROR = 'error';

    /**
     * Status success
     */
    const STATUS_SUCCESS = 'success';

    public function signin(Request $request, Response $response)
    {
        try {
            $code = auth()->signin($request->get('email'), $request->get('password'));

            if (filter_var(config()->get('2FA'), FILTER_VALIDATE_BOOLEAN)) {
                $response->set('code', $code);
            }

            $response->json([
                'status' => self::STATUS_SUCCESS
            ]);
        } catch (AuthException $e) {
            $response->json([
                'status' => self::STATUS_ERROR,
                'message' => $e->getMessage()
            ]);
        }
    }

    public function me(Response $response)
    {
        $response->json([
            'status' => self::STATUS_SUCCESS,
            'data' => [
                'firstname' => auth()->user()->firstname,
                'lastname' => auth()->user()->lastname,
                'email' => auth()->user()->email
            ]
        ]);
    }

    public function signout(Response $response)
    {
        if (auth()->signout()) {
            $response->json([
                'status' => self::STATUS_SUCCESS
            ]);
        } else {
            $response->json([
                'status' => self::STATUS_ERROR,
                'message' => t('validation.unauthorizedRequest')
            ]);
        }
    }

    public function signup(Request $request, Response $response)
    {
        auth()->signup($request->all());

        $response->json([
            'status' => self::STATUS_SUCCESS,
            'message' => t('common.successfully_signed_up')
        ]);
    }

But the scanner is pointed to the Abstracts folder so I assume it should lookup only classes from there:

        $openapi = Generator::scan([modules_dir() . DS . 'Controllers' . DS . 'Abstracts']);

        header('Content-Type: application/json');
        echo $openapi->toJson();

This will echo out only the first method specification and not for others:

{
    "openapi": "3.0.0",
    "info": {
        "title": "Quantum API documentation",
        "description": " *Quantum Documentation: https://quantum.softberg.or
g/en/docs/v1/overview",
        "version": "2.9.0"
    },
    "paths": {
        "/api/signin": {
            "post": {
                "tags": [
                    "Authentication"
                ],
                "summary": "Sign in action",
                "description": "Sign in action",
                "operationId": "userSignIn",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "properties": {
                                    "email": {
                                        "type": "string"
                                    },
                                    "password": {
                                        "type": "string"
                                    }
                                },
                                "type": "object",
                                "example": {
                                    "email": "[email protected]",
                                    "password": "password"
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {}
                        }
                    },
                    "400": {
                        "description": "Bad Request"
                    },
                    "401": {
                        "description": "Unauthenticated"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "bearer_token": {
                "type": "apiKey",
                "name": "Authorization",
                "in": "header"
            }
        }
    }
}

@armanist
Copy link
Author

I got more insights that can help to figure out the issue.

If there is a class with several abstract and non abstract methods like the example below:

    /**
     * Sign in action
     * @OA\Post(
     * .....
     * )
     */
    abstract public function signin(Request $request, Response $response);

    /**
     * Gets the logged-in user data
     * @OA\Get(
     * .....
     * )
     */
    public function me(Response $response){}

    /**
     * Sign out action
     * @OA\Get(
     * .....
     * )
     */
    abstract public function signout(Response $response);

   /**
     * Sign up action
     * @OA\Post(
     * .....
     * )
     */
   public function signup(Response $response){}

 /**
     * Activate action
     * @OA\Get(
     * .....
     * )
     */
   public function activate(Response $response){}

The specification will be generated for signin(), signout() and activate() methods. Methods me() and signup() will be ignored. why? I think the annotation reader is looking for curly braces of the method so my guess is that the closing brace } of the me() method counts as the end of the signin() method that's why the method me() is getting ignored. same happens for next abstract method. I'm not quite sure if it's the root cause of the problem or not, but the issue is real.

@DerManoMann
Copy link
Collaborator

That sounds like an awesome clue! It is possible that the abstract keyword confuses the parser and it takes that token as the method name. Everything after that would be offset, I guess.

@DerManoMann
Copy link
Collaborator

@armanist Could you try #1369 and see if that fixes things? The new test at least passes :)

@armanist
Copy link
Author

@armanist Could you try #1369 and see if that fixes things? The new test at least passes :)

Thanks @DerManoMann, I'll check and will post here

@armanist
Copy link
Author

The fix on #1369 works perfectly

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

Successfully merging a pull request may close this issue.

2 participants