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

Addition of new middleware, RoutesBuilderMiddleware #32

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
composer.lock
phpunit.xml
.phpunit.result.cache
.DS_Store
20 changes: 18 additions & 2 deletions docs/middlewares.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,31 @@

Middlewares are an optional bit of logic to transform the given data at various lifecycle points.

### Path
## Route generation
### Routes

To add a route middleware create a class that implements `\Vyuldashev\LaravelOpenApi\Contracts\RoutesBuilderMiddleware` then register it by referencing it in the `openapi.collections.default.middlewares.paths` config array like `MyRouteMiddleware::class`

Available lifecycle points are:
- `after` - after the `RouteInformation` has been built.

### Paths

To add a path middleware create a class that implements `\Vyuldashev\LaravelOpenApi\Contracts\PathMiddleware` then register it by referencing it in the `openapi.collections.default.middlewares.paths` config array like `MyPathMiddleware::class`

Available lifecycle points are:
- `before` - after collecting all `RouteInformation` but before processing them.
- `after` - after the `PathItem` has been built.

### Component
## Component generation
### Pre-component generation

To add a path middleware create a class that implements `\Vyuldashev\LaravelOpenApi\Contracts\ComponentCreateMiddleware` then register it by referencing it in the `openapi.collections.default.middlewares.components` config array like `MyComponentMiddleware::class`

Available lifecycle points are:
- `before` - before the `Components` has been built.

### Post-component generation

To add a path middleware create a class that implements `\Vyuldashev\LaravelOpenApi\Contracts\ComponentMiddleware` then register it by referencing it in the `openapi.collections.default.middlewares.components` config array like `MyComponentMiddleware::class`

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Examples\Petstore\OpenApi\Middlewares;

use Examples\Petstore\OpenApi\RequestBodies\EmptyRequestBody;
use Vyuldashev\LaravelOpenApi\Attributes as OpenApi;
use Vyuldashev\LaravelOpenApi\Contracts\RoutesBuilderMiddleware;
use Vyuldashev\LaravelOpenApi\Generator;
use Vyuldashev\LaravelOpenApi\RouteInformation;

class InsertRequestBodyMiddleware implements RoutesBuilderMiddleware
{
public function after(RouteInformation $routeInformation): RouteInformation
{
$routeInformation->actionAttributes[] = new OpenApi\RequestBody(EmptyRequestBody::class);
$routeInformation->actionAttributes[] = new OpenApi\Collection([Generator::COLLECTION_DEFAULT]);

return $routeInformation;
}
}
23 changes: 23 additions & 0 deletions examples/petstore/OpenApi/RequestBodies/EmptyRequestBody.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Examples\Petstore\OpenApi\RequestBodies;

use GoldSpecDigital\ObjectOrientedOAS\Objects\RequestBody;
use Vyuldashev\LaravelOpenApi\Factories\RequestBodyFactory;
use Vyuldashev\LaravelOpenApi\RouteInformation;

class EmptyRequestBody extends RequestBodyFactory
{
private RouteInformation $routeInformation;

public function __construct(RouteInformation $routeInformation)
{
$this->routeInformation = $routeInformation;
}

public function build(): RequestBody
{
return RequestBody::create('EmptyRequestBody')
->description('Empty '.$this->routeInformation->method.' body');
}
}
25 changes: 11 additions & 14 deletions src/Builders/ComponentsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
use Vyuldashev\LaravelOpenApi\Builders\Components\ResponsesBuilder;
use Vyuldashev\LaravelOpenApi\Builders\Components\SchemasBuilder;
use Vyuldashev\LaravelOpenApi\Builders\Components\SecuritySchemesBuilder;
use Vyuldashev\LaravelOpenApi\Contracts\ComponentCreateMiddleware;
use Vyuldashev\LaravelOpenApi\Contracts\ComponentMiddleware;
use Vyuldashev\LaravelOpenApi\Generator;
use Vyuldashev\LaravelOpenApi\Middleware;

class ComponentsBuilder
{
Expand Down Expand Up @@ -42,44 +45,38 @@ public function build(
$schemas = $this->schemasBuilder->build($collection);
$securitySchemes = $this->securitySchemesBuilder->build($collection);

$components = Components::create();

$hasAnyObjects = false;
$components = Middleware::make($middlewares)
->using(ComponentCreateMiddleware::class)
->send(Components::create())
->through(fn ($middleware, $components) => $middleware->before($components));

if (count($callbacks) > 0) {
$hasAnyObjects = true;

$components = $components->callbacks(...$callbacks);
}

if (count($requestBodies) > 0) {
$hasAnyObjects = true;

$components = $components->requestBodies(...$requestBodies);
}

if (count($responses) > 0) {
$hasAnyObjects = true;
$components = $components->responses(...$responses);
}

if (count($schemas) > 0) {
$hasAnyObjects = true;
$components = $components->schemas(...$schemas);
}

if (count($securitySchemes) > 0) {
$hasAnyObjects = true;
$components = $components->securitySchemes(...$securitySchemes);
}

if (! $hasAnyObjects) {
if (! count($components->toArray())) {
return null;
}

foreach ($middlewares as $middleware) {
app($middleware)->after($components);
}
Middleware::make($middlewares)
->using(ComponentMiddleware::class)
->emit(fn ($middleware) => $middleware->after($components));

return $components;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Builders/Paths/Operation/CallbacksBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public function build(RouteInformation $route): array
{
return $route->actionAttributes
->filter(static fn (object $attribute) => $attribute instanceof CallbackAttribute)
->map(static function (CallbackAttribute $attribute) {
$factory = app($attribute->factory);
->map(static function (CallbackAttribute $attribute) use ($route) {
$factory = app($attribute->factory, ['routeInformation' => $route]);
$pathItem = $factory->build();

if ($factory instanceof Reusable) {
Expand Down
2 changes: 1 addition & 1 deletion src/Builders/Paths/Operation/ParametersBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ protected function buildAttribute(RouteInformation $route): Collection

if ($parameters) {
/** @var ParametersFactory $parametersFactory */
$parametersFactory = app($parameters->factory);
$parametersFactory = app($parameters->factory, ['routeInformation' => $route]);

$parameters = $parametersFactory->build();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Builders/Paths/Operation/RequestBodyBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function build(RouteInformation $route): ?RequestBody

if ($requestBody) {
/** @var RequestBodyFactory $requestBodyFactory */
$requestBodyFactory = app($requestBody->factory);
$requestBodyFactory = app($requestBody->factory, ['routeInformation' => $route]);

$requestBody = $requestBodyFactory->build();

Expand Down
4 changes: 2 additions & 2 deletions src/Builders/Paths/Operation/ResponsesBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public function build(RouteInformation $route): array
{
return $route->actionAttributes
->filter(static fn (object $attribute) => $attribute instanceof ResponseAttribute)
->map(static function (ResponseAttribute $attribute) {
$factory = app($attribute->factory);
->map(static function (ResponseAttribute $attribute) use ($route) {
$factory = app($attribute->factory, ['routeInformation' => $route]);
$response = $factory->build();

if ($factory instanceof Reusable) {
Expand Down
4 changes: 2 additions & 2 deletions src/Builders/Paths/Operation/SecurityBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ public function build(RouteInformation $route): array
return $route->actionAttributes
->filter(static fn (object $attribute) => $attribute instanceof OperationAttribute)
->filter(static fn (OperationAttribute $attribute) => isset($attribute->security))
->map(static function (OperationAttribute $attribute) {
->map(static function (OperationAttribute $attribute) use ($route) {
// return a null scheme if the security is set to ''
if ($attribute->security === '') {
return SecurityRequirement::create()->securityScheme(null);
}
$security = app($attribute->security);
$security = app($attribute->security, ['routeInformation' => $route]);
$scheme = $security->build();

return SecurityRequirement::create()->securityScheme($scheme);
Expand Down
46 changes: 17 additions & 29 deletions src/Builders/PathsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@
namespace Vyuldashev\LaravelOpenApi\Builders;

use GoldSpecDigital\ObjectOrientedOAS\Objects\PathItem;
use Illuminate\Routing\Route;
use Illuminate\Routing\Router;
use Illuminate\Support\Collection;
use Vyuldashev\LaravelOpenApi\Attributes;
use Vyuldashev\LaravelOpenApi\Attributes\Collection as CollectionAttribute;
use Vyuldashev\LaravelOpenApi\Builders\Paths\OperationsBuilder;
use Vyuldashev\LaravelOpenApi\Contracts\PathMiddleware;
use Vyuldashev\LaravelOpenApi\Generator;
use Vyuldashev\LaravelOpenApi\Middleware;
use Vyuldashev\LaravelOpenApi\RouteInformation;

class PathsBuilder
{
protected OperationsBuilder $operationsBuilder;
protected RoutesBuilder $routesBuilder;

public function __construct(
OperationsBuilder $operationsBuilder
OperationsBuilder $operationsBuilder,
RoutesBuilder $routesBuilder
) {
$this->operationsBuilder = $operationsBuilder;
$this->routesBuilder = $routesBuilder;
}

/**
Expand All @@ -32,7 +33,7 @@ public function build(
string $collection,
array $middlewares
): array {
return $this->routes()
return $this->routes($middlewares)
->filter(static function (RouteInformation $routeInformation) use ($collection) {
/** @var CollectionAttribute|null $collectionAttribute */
$collectionAttribute = collect()
Expand All @@ -44,12 +45,12 @@ public function build(
(! $collectionAttribute && $collection === Generator::COLLECTION_DEFAULT) ||
($collectionAttribute && in_array($collection, $collectionAttribute->name, true));
})
->map(static function (RouteInformation $item) use ($middlewares) {
foreach ($middlewares as $middleware) {
app($middleware)->before($item);
}
->map(static function (RouteInformation $routeInformation) use ($middlewares) {
Middleware::make($middlewares)
->using(PathMiddleware::class)
->emit(fn ($middleware) => $middleware->before($routeInformation));

return $item;
return $routeInformation;
})
->groupBy(static fn (RouteInformation $routeInformation) => $routeInformation->uri)
->map(function (Collection $routes, $uri) {
Expand All @@ -60,30 +61,17 @@ public function build(
return $pathItem->operations(...$operations);
})
->map(static function (PathItem $item) use ($middlewares) {
foreach ($middlewares as $middleware) {
$item = app($middleware)->after($item);
}

return $item;
return Middleware::make($middlewares)
->using(PathMiddleware::class)
->send($item)
->through(fn ($middleware, $item) => $middleware->after($item));
})
->values()
->toArray();
}

protected function routes(): Collection
protected function routes(array $middlewares): Collection
{
/** @noinspection CollectFunctionInCollectionInspection */
return collect(app(Router::class)->getRoutes())
->filter(static fn (Route $route) => $route->getActionName() !== 'Closure')
->map(static fn (Route $route) => RouteInformation::createFromRoute($route))
->filter(static function (RouteInformation $route) {
$pathItem = $route->controllerAttributes
->first(static fn (object $attribute) => $attribute instanceof Attributes\PathItem);

$operation = $route->actionAttributes
->first(static fn (object $attribute) => $attribute instanceof Attributes\Operation);

return $pathItem && $operation;
});
return $this->routesBuilder->build($middlewares);
}
}
54 changes: 54 additions & 0 deletions src/Builders/RoutesBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Vyuldashev\LaravelOpenApi\Builders;

use Illuminate\Routing\Route;
use Illuminate\Routing\Router;
use Illuminate\Support\Collection;
use Vyuldashev\LaravelOpenApi\Attributes;
use Vyuldashev\LaravelOpenApi\Contracts\RoutesBuilderMiddleware;
use Vyuldashev\LaravelOpenApi\Middleware;
use Vyuldashev\LaravelOpenApi\RouteInformation;

class RoutesBuilder
{
/**
* @var Router
*/
protected Router $router;

/**
* @param Router $router
*/
public function __construct(Router $router)
{
$this->router = $router;
}

/**
* @param RoutesBuilderMiddleware[] $middlewares
* @return Collection
*/
public function build(array $middlewares): Collection
{
/** @noinspection CollectFunctionInCollectionInspection */
return collect($this->router->getRoutes())
->filter(static fn (Route $route) => $route->getActionName() !== 'Closure')
->map(static fn (Route $route) => RouteInformation::createFromRoute($route))
->map(static function (RouteInformation $route) use ($middlewares): RouteInformation {
return Middleware::make($middlewares)
->using(RoutesBuilderMiddleware::class)
->send($route)
->through(fn ($middleware, $route) => $middleware->after($route));
})
->filter(static function (RouteInformation $route): bool {
$pathItem = $route->controllerAttributes
->first(static fn (object $attribute) => $attribute instanceof Attributes\PathItem);

$operation = $route->actionAttributes
->first(static fn (object $attribute) => $attribute instanceof Attributes\Operation);

return $pathItem && $operation;
});
}
}
10 changes: 10 additions & 0 deletions src/Contracts/ComponentCreateMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Vyuldashev\LaravelOpenApi\Contracts;

use GoldSpecDigital\ObjectOrientedOAS\Objects\Components;

interface ComponentCreateMiddleware
{
public function before(Components $components): Components;
}
10 changes: 10 additions & 0 deletions src/Contracts/RoutesBuilderMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Vyuldashev\LaravelOpenApi\Contracts;

use Vyuldashev\LaravelOpenApi\RouteInformation;

interface RoutesBuilderMiddleware
{
public function after(RouteInformation $routeInformation): RouteInformation;
}
Loading
Loading