Explorar o código

Add request header strategies to allow extensibility

The default strategy behaves the same way as the original behaviour, which
is to return the headers from the route rules.

Making this into a Strategy means that projects may add their own strategies
to control which headers are shown in various circumstances.

This commit also includes some tidying up of the example request templates
for cases when no headers are required for the request.
Robbie Averill %!s(int64=5) %!d(string=hai) anos
pai
achega
53a99ac6ed

+ 8 - 2
docs/plugins.md

@@ -2,16 +2,17 @@
 You can use plugins to alter how the Generator fetches data about your routes. For instance, suppose all your routes have a body parameter `organizationId`, and you don't want to annotate this with `@queryParam` on each method. You can create a plugin that adds this to all your body parameters. Let's see how to do this.
 
 ## The stages of route processing
-Route processing is performed in four stages:
+Route processing is performed in six stages:
 - metadata (this covers route `title`, route `description`, route `groupName`, route `groupDescription`, and authentication status (`authenticated`))
 - urlParameters
 - bodyParameters
 - queryParameters
 - responses
+- requestHeaeers
 
 For each stage, the Generator attempts the specified strategies to fetch data. The Generator will call of the strategies configured, progressively combining their results together before to produce the final output of that stage.
 
-There are a number of strategies inccluded with the package, so you don't have to set up anything to get it working.
+There are a number of strategies included with the package, so you don't have to set up anything to get it working.
 
 > Note: The included ResponseCalls strategy is designed to stop if a response with a 2xx status code has already been gotten via any other strategy.
 
@@ -73,6 +74,9 @@ The last thing to do is to register the strategy. Strategies are registered in a
             \Mpociot\ApiDoc\Extracting\Strategies\Responses\UseTransformerTags::class,
             \Mpociot\ApiDoc\Extracting\Strategies\Responses\ResponseCalls::class,
         ],
+        'requestHeaders' => [
+            \Mpociot\ApiDoc\Extracting\Strategies\RequestHeaders\GetFromRouteRules::class,
+        ],
     ],
 ...
 ```
@@ -168,3 +172,5 @@ Each strategy class must implement the __invoke method with the parameters as de
 ```
 
 Responses are _additive_. This means all the responses returned from each stage are added to the `responses` array. But note that the `ResponseCalls` strategy will only attempt to fetch a response if there are no responses with a status code of 2xx already.
+
+- In the `requestHeaders` stage, you can return an array of headers. You may also negate existing headers by providing `false` as the header value.

+ 4 - 0
resources/views/partials/example-requests/javascript.blade.php

@@ -13,6 +13,7 @@ Object.keys(params)
     .forEach(key => url.searchParams.append(key, params[key]));
 @endif
 
+@if(!empty($route['headers']))
 let headers = {
 @foreach($route['headers'] as $header => $value)
     "{{$header}}": "{{$value}}",
@@ -24,6 +25,7 @@ let headers = {
     "Content-Type": "application/json",
 @endif
 };
+@endif
 @if(count($route['cleanBodyParameters']))
 
 let body = {!! json_encode($route['cleanBodyParameters'], JSON_PRETTY_PRINT) !!}
@@ -31,7 +33,9 @@ let body = {!! json_encode($route['cleanBodyParameters'], JSON_PRETTY_PRINT) !!}
 
 fetch(url, {
     method: "{{$route['methods'][0]}}",
+@if(count($route['headers']))
     headers: headers,
+@endif
 @if(count($route['bodyParameters']))
     body: body
 @endif

+ 4 - 0
resources/views/partials/example-requests/php.blade.php

@@ -1,6 +1,7 @@
 ```php
 
 $client = new \GuzzleHttp\Client();
+@if($hasRequestOptions)
 $response = $client->{{ strtolower($route['methods'][0]) }}(
   '{{ rtrim($baseUrl, '/') . '/' . ltrim($route['boundUri'], '/') }}',
   [
@@ -19,6 +20,9 @@ $response = $client->{{ strtolower($route['methods'][0]) }}(
 @endif
   ]
 );
+@else
+$response = $client->{{ strtolower($route['methods'][0]) }}('{{ rtrim($baseUrl, '/') . '/' . ltrim($route['boundUri'], '/') }}');
+@endif
 $body = $response->getBody();
 print_r(json_decode((string) $body));
 ```

+ 3 - 1
resources/views/partials/example-requests/python.blade.php

@@ -15,6 +15,7 @@ params = {
 
 }
 @endif
+@if(!empty($route['headers']))
 headers = {
 @foreach($route['headers'] as $header => $value)
   '{{$header}}': '{{$value}}'@if(!($loop->last)),
@@ -22,6 +23,7 @@ headers = {
 @endforeach
 
 }
-response = requests.request('{{$route['methods'][0]}}', url, headers=headers{{ count($route['cleanBodyParameters']) ? ', json=payload' : '' }}{{ count($route['cleanQueryParameters']) ? ', params=params' : ''}})
+@endif
+response = requests.request('{{$route['methods'][0]}}', url{{ count($route['headers']) ?', headers=headers' : '' }}{{ count($route['cleanBodyParameters']) ? ', json=payload' : '' }}{{ count($route['cleanQueryParameters']) ? ', params=params' : ''}})
 response.json()
 ```

+ 12 - 1
src/Extracting/Generator.php

@@ -80,7 +80,8 @@ class Generator
         $parsedRoute['responses'] = $responses;
         $parsedRoute['showresponse'] = ! empty($responses);
 
-        $parsedRoute['headers'] = $routeRules['headers'] ?? [];
+        $requestHeaders = $this->fetchRequestHeaders($controller, $method, $route, $routeRules, $parsedRoute);
+        $parsedRoute['headers'] = $requestHeaders;
 
         return $parsedRoute;
     }
@@ -125,6 +126,13 @@ class Generator
         return null;
     }
 
+    protected function fetchRequestHeaders(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
+    {
+        $headers = $this->iterateThroughStrategies('requestHeaders', $context, [$route, $controller, $method, $rulesToApply]);
+
+        return array_filter($headers);
+    }
+
     protected function iterateThroughStrategies(string $stage, array $context, array $arguments)
     {
         $defaultStrategies = [
@@ -147,6 +155,9 @@ class Generator
                 \Mpociot\ApiDoc\Extracting\Strategies\Responses\UseTransformerTags::class,
                 \Mpociot\ApiDoc\Extracting\Strategies\Responses\ResponseCalls::class,
             ],
+            'requestHeaders' => [
+                \Mpociot\ApiDoc\Extracting\Strategies\RequestHeaders\GetFromRouteRules::class,
+            ],
         ];
 
         // Use the default strategies for the stage, unless they were explicitly set

+ 16 - 0
src/Extracting/Strategies/RequestHeaders/GetFromRouteRules.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace Mpociot\ApiDoc\Extracting\Strategies\RequestHeaders;
+
+use Illuminate\Routing\Route;
+use Mpociot\ApiDoc\Extracting\Strategies\Strategy;
+use ReflectionClass;
+use ReflectionMethod;
+
+class GetFromRouteRules extends Strategy
+{
+    public function __invoke(Route $route, ReflectionClass $controller, ReflectionMethod $method, array $routeRules, array $context = [])
+    {
+        return $routeRules['headers'] ?? [];
+    }
+}

+ 3 - 0
src/Writing/Writer.php

@@ -172,7 +172,10 @@ class Writer
                     // Set content type if the user forgot to set it
                     $route['headers']['Content-Type'] = 'application/json';
                 }
+
+                $hasRequestOptions = !empty($route['headers']) || !empty($route['cleanQueryParameters']) || !empty($route['cleanBodyParameters']);
                 $route['output'] = (string) view('apidoc::partials.route')
+                    ->with('hasRequestOptions', $hasRequestOptions)
                     ->with('route', $route)
                     ->with('settings', $settings)
                     ->with('baseUrl', $this->baseUrl)

+ 3 - 0
tests/Unit/GeneratorTestCase.php

@@ -39,6 +39,9 @@ abstract class GeneratorTestCase extends TestCase
                 \Mpociot\ApiDoc\Extracting\Strategies\Responses\UseTransformerTags::class,
                 \Mpociot\ApiDoc\Extracting\Strategies\Responses\ResponseCalls::class,
             ],
+            'requestHeaders' => [
+                \Mpociot\ApiDoc\Extracting\Strategies\RequestHeaders\GetFromRouteRules::class,
+            ],
         ],
         'default_group' => 'general',
     ];