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

Global unified configuration suggestions #1623

Open
caixingyue opened this issue Jul 8, 2024 · 30 comments
Open

Global unified configuration suggestions #1623

caixingyue opened this issue Jul 8, 2024 · 30 comments

Comments

@caixingyue
Copy link

I have used the Java springdoc-openapi-starter-webmvc-ui package. In the global configuration, I think it is better than the existing PHP swagger package configuration scheme.

This is a solution that I conceived based on the Java swagger configuration. Based on this configuration, multiple YAMLs will be generated. Swagger UI will support switching between multiple configurations.

Java swagger also supports single YAML. It supports both schemes. It seems that it is just the result of different annotations. If necessary, I can also provide relevant references.

tip: The final generated YAML is in line with the swagger specification, but in terms of user and dev experience, it will be more unified and convenient.

#[OA\Configuration]
class OpenApi
{
    #[OA\GroupedOpenApi]
    public function partnerApi()
    {
        return GroupedOpenApi::builder()
            ->group("partner")
            ->pathsToMatch("/api/**")
            ->addOpenApiCustomizer(function ($openApi) {
                $openApi->getInfo()->setTitle("Partner API");
                $openApi->getInfo()->setVersion("1.1");
                $openApi->getInfo()->setDescription("This interface document is only provided to Xingrui's internal partners. Some interfaces require relevant permissions to be opened.");
            })
            ->addOperationCustomizer(function ($operation, $handlerMethod) {
                $operation->addParametersItem((new OA\Parameter())
                    ->in('header')
                    ->name('Authorization')
                    ->description("Authorization token")
                    ->required(true)
                    ->schema(type: 'string')
                );

                return $operation;
            })
            ->addOperationCustomizer($this->globalResponseMessages())
            ->build();
    }

    #[OA\GroupedOpenApi]
    public function merchantApi()
    {
        return GroupedOpenApi::builder()
            ->group("merchant")
            ->pathsToMatch("/merchantApi/**")
            ->addOpenApiCustomizer(function ($openApi) {
                $openApi->getInfo()->setTitle("Merchant API");
                $openApi->getInfo()->setVersion("1.0");
                $openApi->getInfo()->setDescription("This interface document is provided to all merchants. Some interfaces require relevant permissions to be enabled.");
            })
            ->addOperationCustomizer(function ($operation, $handlerMethod) {
                $operation->addParametersItem((new OA\Parameter())
                    ->in('header')
                    ->name('Authorization')
                    ->description("Authorization token")
                    ->required(true)
                    ->schema(type: 'string')
                );

                return $operation;
            })
            ->addOperationCustomizer($this->globalResponseMessages())
            ->build();
    }

    private function globalResponseMessages() {
        return function ($operation, $handlerMethod) {
            $method = $handlerMethod->getMethod();
            $operationAnnotation = $method->getAnnotation(Operation::class);

            $operation->getResponses()->addApiResponse(new OA\Response(response: 404, description: 'Resource not found'));

            return $operation;
        };
    }
}

@caixingyue caixingyue changed the title This interface document is provided to all merchants. Some interfaces require relevant permissions to be enabled. Global unified configuration suggestions Jul 8, 2024
@caixingyue
Copy link
Author

@DerManoMann Big Boss, can this new configuration method be adapted? I know this involves many functions, but this one feels much more convenient to use.

@DerManoMann
Copy link
Collaborator

Could be - I can see how the annotations and the class itself could work. However, there still would need to be some (more) code somethwere to actually use and call it?
I think most could be implemented by just wrapping the use of the Generator - could be a fun project if I had the time :)

@DerManoMann
Copy link
Collaborator

I guess something like this would mostly do the trick (actual implementation missing) - everything additional around it would have to be part of the codebase that actually uses the builder.

The path mappings translate to plugin config, as could the customizers if a corresponding new processor was added...

Not sure what the group is for - just some sort of name, I suppose?

class OpenApiBuilder
{
    public function group(string $group): OpenApiBuilder
    {
        return $this;
    }

    /**
     * @param string|array $paths
     */
    public function pathsToMatch($paths): OpenApiBuilder
    {
        return $this;
    }

    /**
     * @param class-string<AbstractAnnotation> $class
     */
    public function addCustomizer(string $class, callable $customizer): OpenApiBuilder
    {
        return $this;
    }

    public function build(): Generator
    {
        return new Generator();
    }
}

@DerManoMann
Copy link
Collaborator

... I guess you can tell I am curious :) I will have a play around - maybe its time to start working on my openapi-extras package again ;)

@caixingyue
Copy link
Author

Not sure what the group is for - just some sort of name, I suppose?

This group will implement the selection box function in the image, and other configurations will be applied according to the group.
image

This means that each API document will support setting up a separate configuration, such as the following information.

@OA\Server(url=L5_SWAGGER_CONST_HOST, description="Request address")

@OA\Info(title="Partner API", version="1.0", description="This interface document is only provided to Xingrui's internal partners. Some interfaces require relevant permissions to be opened.")

@DerManoMann
Copy link
Collaborator

Yeah, but that is not part of the spec that is generated, right?

@caixingyue
Copy link
Author

... I guess you can tell I am curious :) I will have a play around - maybe its time to start working on my openapi-extras package again ;)

I'm glad you're curious about this feature, and it makes me excited. If there's anything I can do to help, please feel free to ask. I'll continue to study the Java implementation and polish it into our PHP implementation.

@DerManoMann
Copy link
Collaborator

Keep in mind that swagger-php is only concerned with generating the spec, not with managing multiple versions or rendering a UI.

@caixingyue
Copy link
Author

caixingyue commented Oct 9, 2024

Yeah, but that is not part of the spec that is generated, right?

Yes, although this is not the swagger specification, it will generate swagger specification data, and the swagger UI also supports this implementation.

@DerManoMann
Copy link
Collaborator

So how would the group show up in the YAML?

@caixingyue
Copy link
Author

So how would the group show up in the YAML?

You need to generate multiple YAML files. When you switch the selection, different YAML files will be used.

@DerManoMann
Copy link
Collaborator

See, that is my point - generating multiple files is outside of the scope of the library, IMO.

@caixingyue
Copy link
Author

Keep in mind that swagger-php is only concerned with generating the spec, not with managing multiple versions or rendering a UI.

I understand, but this feature will provide a more convenient use solution without affecting the generated specifications. This is something exciting for us developers because we will have more advanced features for quick use and development.

@caixingyue
Copy link
Author

See, that is my point - generating multiple files is outside of the scope of the library, IMO.

I have come up with a solution that may be of interest to you:

php-swagger only needs to implement the above configuration functions, transferring the configuration from comments/annotations to configuration methods, and does not need to implement the group method, which is implemented by other extensions.

@caixingyue
Copy link
Author

Extension is equivalent to wrapping a layer for php-swagger to achieve more functions. php-swagger provides a single underlying service. Currently, the following configuration needs to be implemented through comments/annotations, which is not conducive to expanding its capabilities.

@OA\Server(url=L5_SWAGGER_CONST_HOST, description="Request address")

@OA\Info(title="Partner API", version="1.0", description="This interface document is only provided to Xingrui's internal partners. Some interfaces require relevant permissions to be opened.")

@caixingyue
Copy link
Author

Each GroupedOpenApi::builder() can instantiate an OpenApiBuilder and pass its configuration to the developer for calling, so that GroupedOpenApi can generate multiple YAML files based on multiple OpenApiBuilders.

@caixingyue
Copy link
Author

caixingyue commented Oct 9, 2024

I wrote a code example that illustrates the functionality of php-swagger and the extensions that need to be implemented.

#[OA\Configuration] // This annotation is implemented by php-swagger
class OpenApi
{
    #[OA\GroupedOpenApi] // This annotation should be implemented by an extension, but I haven't figured out how php-swagger should achieve a balance. I need to find an answer that satisfies you. Maybe you already have an answer yourself?
    public function partnerApi()
    {
        // Here we actually get OpenApiBuilders, because GroupedOpenApi is an extension, it will do some extended functions (such as groups) and write the configuration required by OpenApiBuilders into OpenApiBuilders (equivalent to transferring the parameters required by OpenApiBuilders), and then php-swagger implements OpenApiBuilders processing.
        return GroupedOpenApi::builder()
            ->group("partner")
            ->pathsToMatch("/api/**")
            ->addOpenApiCustomizer(function ($openApi) {
                $openApi->getInfo()->setTitle("Partner API");
                $openApi->getInfo()->setVersion("1.1");
                $openApi->getInfo()->setDescription("This interface document is only provided to Xingrui's internal partners. Some interfaces require relevant permissions to be opened.");
            })
            ->addOperationCustomizer(function ($operation, $handlerMethod) {
                $operation->addParametersItem((new OA\Parameter())
                    ->in('header')
                    ->name('Authorization')
                    ->description("Authorization token")
                    ->required(true)
                    ->schema(type: 'string')
                );

                return $operation;
            })
            ->addOperationCustomizer($this->globalResponseMessages())
            ->build();
    }

    #[OA\GroupedOpenApi] // In addition to using this annotation, developers can also use the annotations provided by php-swagger. When using the annotations provided by php-swagger, directly instantiate OpenApiBuilders instead of instantiating GroupedOpenApi.
    public function merchantApi()
    {
       return (new OpenApiBuilders())->pathsToMatch("/api/**")
    }

    // ...
}

@DerManoMann
Copy link
Collaborator

Not sure I agree with where the responsibilities are, but the core of the OpennApiBuilder is simple enough.

GroupedOpenApi and Configuration seem like things that would have to be supported by L5 or Nelmio, so without some official interface this doesn't make much sense to implement right now, I think.

We can start somewhere and that is the OpenApiBuilder.

I suggest moving the discussion to DerManoMann/openapi-extras#10, which is where the draft of the builder can be seen.

It is worth noting that the extra library allows sharing of a prefix, headers and responses within a single controller class already via annotations/attributes, so the builder add another way of doing it (on global level)

@caixingyue
Copy link
Author

caixingyue commented Oct 9, 2024

Yes, it is possible to implement the core functions by OpennApiBuilder and single YAML and multi-YAML configuration by L5.

I checked the relevant documents and found that SwaggerUIBundle supports passing in url and urls, which can realize the switching between single documents and multiple documents. Then L5 can consider passing its configuration through x-.

However, I have a question. At present, L5 does not seem to have separate configurations for @OA\Server and @OA\Info. Can it realize different document versions and descriptions for different documents?

@caixingyue
Copy link
Author

The current configuration of L5 is as follows:

@OA\Server(url=L5_SWAGGER_CONST_HOST, description="Request address")

@OA\Info(title="Partner API", version="1.0", description="This interface document is only provided to Xingrui's internal partners. Some interfaces require relevant permissions to be opened.")

But I want to achieve this, which means that sharing Server, Info is not desirable because different documents have different definitions:

            GroupedOpenApi::builder()
            ->group("partner")
            ->pathsToMatch("/api/**")
            ->addOpenApiCustomizer(function ($openApi) {
                $openApi->getInfo()->setTitle("Partner API");
                $openApi->getInfo()->setVersion("1.1");
                $openApi->getInfo()->setDescription("This interface document is only provided to Xingrui's internal partners. Some interfaces require relevant permissions to be opened.");
            })
            ->addOperationCustomizer(function ($operation, $handlerMethod) {
                $operation->addParametersItem((new OA\Parameter())
                    ->in('header')
                    ->name('Authorization')
                    ->description("Authorization token")
                    ->required(true)
                    ->schema(type: 'string')
                );

                return $operation;
            })

            GroupedOpenApi::builder()
            ->group("merchant")
            ->pathsToMatch("/merchantApi/**")
            ->addOpenApiCustomizer(function ($openApi) {
                $openApi->getInfo()->setTitle("Merchant API");
                $openApi->getInfo()->setVersion("1.0");
                $openApi->getInfo()->setDescription("This interface document is provided to all merchants. Some interfaces require relevant permissions to be enabled.");
            })
            ->addOperationCustomizer(function ($operation, $handlerMethod) {
                $operation->addParametersItem((new OA\Parameter())
                    ->in('header')
                    ->name('Authorization')
                    ->description("Authorization token")
                    ->required(true)
                    ->schema(type: 'string')
                );

                return $operation;
            })

@DerManoMann
Copy link
Collaborator

S you still missing a customised for the server details then?

@caixingyue
Copy link
Author

S you still missing a customised for the server details then?

Yes, The need for grouping is generally to provide documents for different systems, so the descriptions and interface suffixes will also be different.

@caixingyue
Copy link
Author

caixingyue commented Oct 10, 2024

For example, I currently have such a scenario. I will provide interfaces and corresponding interface documents for partners and merchants respectively.

->pathsToMatch("/PartnerApi/**")
->addOpenApiCustomizer(function ($openApi) {
    $openApi->getInfo()->setTitle("Partner API");
    $openApi->getInfo()->setVersion("1.1");
    $openApi->getInfo()->setDescription("This interface document is only provided to Xingrui's internal partners. Some interfaces require relevant permissions to be opened.");
})
->pathsToMatch("/merchantApi/**")
->addOpenApiCustomizer(function ($openApi) {
    $openApi->getInfo()->setTitle("Merchant API");
    $openApi->getInfo()->setVersion("1.0");
    $openApi->getInfo()->setDescription("This interface document is provided to all merchants. Some interfaces require relevant permissions to be enabled.");
})

@DerManoMann
Copy link
Collaborator

This should be a good starting point and provide everything to implement this for a specific framework bundle/plugin.

DerManoMann/openapi-extras#10

Something like:

<?php declare(strict_types=1);

use OpenApi\Annotations as OA;

class GroupedOpenApiBuilder extends OpenApiBuilder
{
    protected $group;

    public function group($group): GroupedOpenApiBuilder
    {
        $this->group = $group;

        return $this;
    }

    public function addOpenApiCustomizer(callable $customizer): GroupedOpenApiBuilder
    {
        return $this->addCustomizer(OA\OpenApi::class, $customizer);
    }

    public function addOperationCustomizer(callable $customizer): GroupedOpenApiBuilder
    {
        return $this->addCustomizer(OA\Operation::class, $customizer);
    }

    public static function builder(): GroupedOpenApiBuilder
    {
        return new self();
    }
}

@caixingyue
Copy link
Author

good, when is this extension expected to be merged and released?

@caixingyue
Copy link
Author

caixingyue commented Oct 20, 2024

In addition, if I want to use l5-swagger, can it be used? I use the laravel framework. Is there a recommended combination?

I need to build different documents and then need a UI to display the documents.

If it is a combination of swagger-php+openapi-extras+l5-swagger, then l5-swagger needs to be changed to convert config.php to class configuration.

Of course, config.php can also be used as an auxiliary configuration, that is, the configuration in config.php can be used by default, and then it can be overwritten by class configuration.

@caixingyue
Copy link
Author

I'd like to know your opinion, and then I'll file an issue in l5-swagger if necessary.

@DerManoMann
Copy link
Collaborator

good, when is this extension expected to be merged and released?

https://github.com/DerManoMann/openapi-extras/releases/tag/2.0.1

@DerManoMann
Copy link
Collaborator

If it is a combination of swagger-php+openapi-extras+l5-swagger, then l5-swagger needs to be changed to convert config.php to class configuration.

I suppose you'd have to use all three at the same time, yes.

And, yes, this would require changes in l5. Not sure if anyone there is interested in those, but if it would allow to manage multiple versions then perhaps there is interest.
From the swagger-php POV this is probably as far as it goes in terms of supporting this approach. It is nice, though, as it hides a lot of the complexity and makes the processor options a lot more discoverable.

@caixingyue
Copy link
Author

caixingyue commented Oct 28, 2024

Maybe I should develop a new package based on swagger-php, openapi-extras, and swagger-ui. I don't think l5-swagger can support openapi-extras, which doesn't seem to fit his definition.

If I have time in the near future, I will develop a new package based on this.

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

No branches or pull requests

2 participants