Skip to content

Commit cb846ba

Browse files
committed
Adjust: review suggestions
1 parent 6d39ea0 commit cb846ba

File tree

7 files changed

+262
-287
lines changed

7 files changed

+262
-287
lines changed

application/controllers/ApiController.php

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,23 @@
1919
class ApiController extends CompatController
2020
{
2121
/**
22-
* Index action for the API controller.
22+
* Handle API requests and route them to the appropriate endpoint class.
2323
*
24-
* This method handles API requests, validates that the request has a valid format,
25-
* and dispatches the appropriate endpoint based on the request method and parameters.
24+
* This method checks for the required permissions, validates the request,
25+
* and routes the request to the appropriate API endpoint class based on the
26+
* version and endpoint parameters. It handles exceptions and emits the response.
2627
*
2728
* @return never
2829
*/
2930
public function indexAction(): never
3031
{
31-
// TODO: temporary workaround until we have proper middleware support!!!
3232
try {
3333
$this->assertPermission('notifications/api');
3434

3535
$request = $this->getRequest();
3636
if (
3737
! $request->isApiRequest()
38-
&& strtolower($request->getParam('endpoint')) !== OpenApi::OPENAPI_ENDPOINT
38+
&& strtolower($request->getParam('endpoint')) !== (new OpenApi())->getEndpoint()
3939
) {
4040
$this->httpBadRequest('No API request');
4141
}
@@ -57,11 +57,11 @@ public function indexAction(): never
5757
$serverRequest = (new ServerRequest(
5858
method: $request->getMethod(),
5959
uri: $request->getRequestUri(),
60-
serverParams: $request->getServer()
60+
headers: ['Content-Type' => $request->getHeader('Content-Type')],
61+
serverParams: $request->getServer(),
6162
))
6263
->withParsedBody($this->getRequestBody($request))
63-
->withAttribute('identifier', $identifier)
64-
->withHeader('Content-Type', $request->getHeader('Content-Type'));
64+
->withAttribute('identifier', $identifier);
6565

6666
$response = (new $className())->handle($serverRequest);
6767
} catch (HttpExceptionInterface $e) {
@@ -91,8 +91,10 @@ public function indexAction(): never
9191
* Validate that the request has a JSON content type and return the parsed JSON content.
9292
*
9393
* @param Request $request The request object to validate.
94-
* @return ?array The validated JSON content as an associative array, or null if not applicable.
95-
* @throws HttpBadRequestException If the request content is not valid JSON.
94+
*
95+
* @return ?array The validated JSON content as an associative array.
96+
*
97+
* @throws HttpBadRequestException
9698
* @throws Zend_Controller_Request_Exception
9799
*/
98100
private function getRequestBody(Request $request): ?array
@@ -118,6 +120,7 @@ private function getRequestBody(Request $request): ?array
118120
* Sends the status code, headers, and body of the response to the client.
119121
*
120122
* @param ResponseInterface $response The response object to emit.
123+
*
121124
* @return void
122125
*/
123126
protected function emitResponse(ResponseInterface $response): void

library/Notifications/Api/ApiCore.php

Lines changed: 18 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,59 +3,43 @@
33
namespace Icinga\Module\Notifications\Api;
44

55
use GuzzleHttp\Psr7\Response;
6-
use Icinga\Exception\Http\HttpBadRequestException;
76
use Icinga\Exception\Http\HttpException;
87
use Icinga\Module\Notifications\Api\Elements\HttpMethod;
9-
use LogicException;
108
use Psr\Http\Message\ResponseInterface;
119
use Psr\Http\Message\ServerRequestInterface;
10+
use Psr\Http\Message\StreamInterface;
1211
use Psr\Http\Server\RequestHandlerInterface;
13-
use Ramsey\Uuid\Uuid;
1412
use ValueError;
1513

1614
abstract class ApiCore implements RequestHandlerInterface
1715
{
1816
/**
19-
* Suffix for plural method names.
17+
* Handle the API request and return a ResponseInterface object.
2018
*
21-
* This constant is used to differentiate between singular and plural method names
19+
* @param ServerRequestInterface $request
2220
*
23-
* @var string
24-
*/
25-
protected const PLURAL_SUFFIX = 'Plural';
26-
27-
/**
28-
* Handle the API request and return a Response object.
29-
*
30-
* This method should be implemented by subclasses to handle the specific API request
31-
* by calling the appropriate methods of the endpoint based on the HTTP method
32-
* and return a Response object.
33-
*
34-
* @return Response
21+
* @return ResponseInterface
3522
*/
3623
abstract protected function handleRequest(ServerRequestInterface $request): ResponseInterface;
3724

3825
/**
39-
* Get the endpoint path for the API.
40-
*
41-
* This method returns the endpoint path for the API, which is defined by the ENDPOINT constant.
42-
* If the ENDPOINT constant is not defined in the subclass, a LogicException is thrown.
26+
* Get the name of the API endpoint.
4327
*
4428
* @return string
45-
* @throws LogicException
4629
*/
4730
abstract public function getEndpoint(): string;
4831

4932
/**
5033
* Handle the incoming server request and return a response.
5134
*
52-
* This method processes the incoming request, determines the appropriate method to call
53-
* based on the HTTP method and presence of an identifier, and invokes that method.
54-
* It also handles validation of the identifier and request body for POST and PUT requests.
35+
* This method determines the HTTP method of the request and checks if the corresponding
36+
* method exists in the subclass. If the method does not exist,
37+
* it throws an HttpException with a 405 status code.
5538
*
5639
* @param ServerRequestInterface $request The incoming server request.
40+
*
5741
* @return ResponseInterface The response generated by the invoked method.
58-
* @throws HttpBadRequestException If the request is not valid.
42+
*
5943
* @throws HttpException If the requested method does not exist.
6044
*/
6145
public function handle(ServerRequestInterface $request): ResponseInterface
@@ -85,19 +69,16 @@ public function handle(ServerRequestInterface $request): ResponseInterface
8569
* Validate the incoming request.
8670
*
8771
* This method can be overridden in subclasses to implement API- or module-specific
88-
* request validation logic. The default implementation checks if the 'identifier'
89-
* attribute in the request is a valid UUID, if it is present.
72+
* request validation logic. By default, it does nothing.
9073
*
9174
* @param ServerRequestInterface $request The incoming server request to validate.
75+
*
9276
* @return void
93-
* @throws HttpBadRequestException If the identifier is not a valid UUID.
9477
*/
9578
protected function assertValidRequest(ServerRequestInterface $request): void
9679
{
97-
// API- / Module-specific request validation can be implemented in this method in child-classes
9880
}
9981

100-
10182
/**
10283
* Get allowed HTTP methods for the API.
10384
*
@@ -116,18 +97,14 @@ protected function getAllowedMethods(): string
11697
}
11798

11899
/**
119-
* Create a Response object from the given data array.
100+
* Create a Response object.
120101
*
121-
* The data array can contain the following keys:
122-
* - 'status': The HTTP status code (default: 200)
123-
* - 'headers': An associative array of HTTP headers (default: empty array)
124-
* - 'body': The response body as null|StreamInterface|resource|string (default: null)
102+
* @param int $status The HTTP status code.
103+
* @param array $headers An associative array of HTTP headers.
104+
* @param ?(StreamInterface|resource|string) $body The response body.
105+
* @param string $version The HTTP version.
106+
* @param ?string $reason The reason phrase (optional).
125107
*
126-
* @param int $status
127-
* @param array $headers
128-
* @param null $body
129-
* @param string $version
130-
* @param string|null $reason
131108
* @return Response
132109
*/
133110
protected function createResponse(

library/Notifications/Api/V1/ApiV1.php

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@
44

55
use Exception;
66
use Generator;
7-
use GuzzleHttp\Psr7\Response;
87
use Icinga\Exception\Http\HttpBadRequestException;
98
use Icinga\Exception\Http\HttpException;
109
use Icinga\Exception\Json\JsonDecodeException;
1110
use Icinga\Exception\Json\JsonEncodeException;
1211
use Icinga\Module\Notifications\Api\ApiCore;
1312
use Icinga\Module\Notifications\Api\Elements\HttpMethod;
14-
use Icinga\Module\Notifications\Common\Database;
1513
use Icinga\Util\Json;
1614
use ipl\Sql\Compat\FilterProcessor;
1715
use ipl\Sql\Connection;
@@ -22,9 +20,7 @@
2220
use Psr\Http\Message\ServerRequestInterface;
2321
use OpenApi\Attributes as OA;
2422
use Ramsey\Uuid\Uuid;
25-
use Psr\Http\Server\RequestHandlerInterface;
2623
use stdClass;
27-
use ValueError;
2824

2925
/**
3026
* Base class for API version 1.
@@ -71,8 +67,6 @@
7167
abstract class ApiV1 extends ApiCore
7268
{
7369
/**
74-
* API version.
75-
*
7670
* This constant defines the version of the API.
7771
*
7872
* @var string
@@ -84,10 +78,12 @@ abstract class ApiV1 extends ApiCore
8478
*
8579
* This method processes the incoming request, determines the appropriate method to call
8680
* based on the HTTP method and presence of an identifier, and invokes that method.
87-
* It also handles validation of the identifier and request body for POST and PUT requests.
81+
* It also handles validation of the request body for POST and PUT requests.
8882
*
8983
* @param ServerRequestInterface $request The incoming server request.
84+
*
9085
* @return ResponseInterface The response generated by the invoked method.
86+
*
9187
* @throws HttpBadRequestException If the request is not valid.
9288
* @throws HttpException If the requested method does not exist.
9389
*/
@@ -107,6 +103,17 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac
107103
return $this->createResponse(...$responseData);
108104
}
109105

106+
/**
107+
* Validate the incoming request.
108+
*
109+
* This method checks the validity of the request based on the HTTP method,
110+
* presence of an identifier, and query parameters. It throws an HttpBadRequestException
111+
* if any validation fails.
112+
*
113+
* @param ServerRequestInterface $request The request object to validate.
114+
*
115+
* @throws HttpBadRequestException If the request is not valid.
116+
*/
110117
protected function assertValidRequest(ServerRequestInterface $request): void
111118
{
112119
$httpMethod = $request->getAttribute('httpMethod');
@@ -139,61 +146,61 @@ protected function assertValidRequest(ServerRequestInterface $request): void
139146
}
140147
}
141148

142-
143-
//TODO: decide if these following functions should be versioned or moved to ApiCore
144-
145149
/**
146150
* Create a filter from the filter string.
147151
*
148-
* This method parses the filter string and returns an array of filter rules.
149-
* If the filter string is empty, it returns false.
152+
* @param string $filterStr
153+
* @param array $allowedColumns
154+
* @param string $idColumnName
150155
*
151-
* @param callable $listener A listener function to handle conditions in the query string.
152156
* @return array|bool Returns an array of filter rules or false if no filter string is provided.
157+
*
153158
* @throws HttpBadRequestException If the filter string cannot be parsed.
154159
*/
155160
protected function assembleFilter(string $filterStr, array $allowedColumns, string $idColumnName): array|bool
156161
{
157-
if (! empty($filterStr)) {
158-
try {
159-
$filterRule = QueryString::fromString($filterStr)
160-
->on(
161-
QueryString::ON_CONDITION,
162-
function (Condition $condition) use ($allowedColumns, $idColumnName) {
163-
$column = $condition->getColumn();
164-
if (! in_array($column, $allowedColumns)) {
165-
throw new HttpBadRequestException(
166-
sprintf(
167-
'Invalid request parameter: Filter column %s given, only %s are allowed',
168-
$column,
169-
preg_replace('/,([^,]*)$/', ' and$1', implode(', ', $allowedColumns))
170-
)
171-
);
172-
}
173-
174-
if ($column === 'id') {
175-
if (! Uuid::isValid($condition->getValue())) {
176-
throw new HttpBadRequestException('The given filter id is not a valid UUID');
177-
}
162+
if (empty($filterStr)) {
163+
return false;
164+
}
165+
try {
166+
$filterRule = QueryString::fromString($filterStr)
167+
->on(
168+
QueryString::ON_CONDITION,
169+
function (Condition $condition) use ($allowedColumns, $idColumnName) {
170+
$column = $condition->getColumn();
171+
if (! in_array($column, $allowedColumns)) {
172+
throw new HttpBadRequestException(
173+
sprintf(
174+
'Invalid request parameter: Filter column %s given, only %s are allowed',
175+
$column,
176+
preg_replace('/,([^,]*)$/', ' and$1', implode(', ', $allowedColumns))
177+
)
178+
);
179+
}
178180

179-
$condition->setColumn($idColumnName);
181+
if ($column === 'id') {
182+
if (! Uuid::isValid($condition->getValue())) {
183+
throw new HttpBadRequestException('The given filter id is not a valid UUID');
180184
}
185+
186+
$condition->setColumn($idColumnName);
181187
}
182-
)->parse();
188+
}
189+
)->parse();
183190

184-
return FilterProcessor::assembleFilter($filterRule);
185-
} catch (Exception $e) {
186-
throw new HttpBadRequestException($e->getMessage());
187-
}
191+
return FilterProcessor::assembleFilter($filterRule);
192+
} catch (Exception $e) {
193+
throw new HttpBadRequestException($e->getMessage());
188194
}
189-
return false;
190195
}
191196

192197
/**
193198
* Validate that the request has a JSON content type and return the parsed JSON content.
194199
*
195200
* @param ServerRequestInterface $request The request object to validate.
201+
*
196202
* @return array The validated JSON content as an associative array.
203+
*
197204
* @throws HttpBadRequestException If the content type is not application/json.
198205
*/
199206
private function getValidRequestBody(ServerRequestInterface $request): array
@@ -233,6 +240,7 @@ private function getValidRequestBody(ServerRequestInterface $request): array
233240
* @param int $batchSize The number of rows to fetch in each batch (default is 500).
234241
*
235242
* @return Generator Yields JSON-encoded strings representing the content.
243+
*
236244
* @throws JsonEncodeException
237245
*/
238246
protected function createContentGenerator(

0 commit comments

Comments
 (0)