From 6a8c797ac7a38397ec50669c669d23f905904051 Mon Sep 17 00:00:00 2001 From: Michal Lulco Date: Thu, 7 May 2020 15:01:01 +0200 Subject: [PATCH 1/4] Open API handler --- composer.json | 1 + src/Handlers/DefaultHandler.php | 3 +- src/Handlers/OpenApiHandler.php | 472 ++++++++++++++++++++++++++++++++ src/Output/AbstractOutput.php | 26 ++ src/Output/JsonOutput.php | 13 +- src/Output/RedirectOutput.php | 22 ++ src/Params/JsonInputParam.php | 5 + src/Presenters/ApiPresenter.php | 10 +- 8 files changed, 541 insertions(+), 11 deletions(-) create mode 100644 src/Handlers/OpenApiHandler.php create mode 100644 src/Output/AbstractOutput.php create mode 100644 src/Output/RedirectOutput.php diff --git a/composer.json b/composer.json index 7fbbc10..436c458 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "nette/di": "^3.0", "latte/latte": "^2.4", "phpunit/phpunit": ">7.0", + "symfony/yaml": "^4.4|5.0", "squizlabs/php_codesniffer": "^3.2" }, "autoload": { diff --git a/src/Handlers/DefaultHandler.php b/src/Handlers/DefaultHandler.php index 6eee995..11ef4d6 100644 --- a/src/Handlers/DefaultHandler.php +++ b/src/Handlers/DefaultHandler.php @@ -4,6 +4,7 @@ namespace Tomaj\NetteApi\Handlers; +use Nette\Http\IResponse; use Tomaj\NetteApi\Response\JsonApiResponse; use Tomaj\NetteApi\Response\ResponseInterface; @@ -14,6 +15,6 @@ class DefaultHandler extends BaseHandler */ public function handle(array $params): ResponseInterface { - return new JsonApiResponse(500, ['status' => 'error', 'message' => 'Unknown api endpoint']); + return new JsonApiResponse(IResponse::S400_BAD_REQUEST, ['status' => 'error', 'message' => 'Unknown api endpoint']); } } diff --git a/src/Handlers/OpenApiHandler.php b/src/Handlers/OpenApiHandler.php new file mode 100644 index 0000000..cf395ab --- /dev/null +++ b/src/Handlers/OpenApiHandler.php @@ -0,0 +1,472 @@ +apiDecider = $apiDecider; + $this->apiLink = $apiLink; + $this->request = $request; + $this->initData = $initData; + } + + public function params(): array + { + return [ + (new GetInputParam('format'))->setAvailableValues(['json', 'yaml'])->setDescription('Response format'), + ]; + } + + /** + * {@inheritdoc} + */ + public function description(): string + { + return 'Open API'; + } + + /** + * {@inheritdoc} + */ + public function tags(): array + { + return ['openapi']; + } + + /** + * {@inheritdoc} + */ + public function handle(array $params): ResponseInterface + { + $version = $this->getEndpoint()->getVersion(); + $apis = $this->getApis($version); + $scheme = $this->request->getUrl()->getScheme(); + $host = $this->request->getUrl()->getHost(); + $baseUrl = $scheme . '://' . $host; + $basePath = $this->getBasePath($apis, $baseUrl); + + $data = [ + 'openapi' => '3.0.0', + 'info' => [ + 'version' => $version, + ], + 'servers' => [ + [ + 'url' => $scheme . '://' . $host . $basePath, + ], + ], + 'components' => [ + 'securitySchemes' => [ + 'Bearer' => [ + 'type' => 'http', + 'scheme' => 'bearer', + ], + ], + 'schemas' => [ + 'ErrorWrongInput' => [ + 'type' => 'object', + 'properties' => [ + 'status' => [ + 'type' => 'string', + 'enum' => ['error'], + ], + 'message' => [ + 'type' => 'string', + 'enum' => ['Wrong input'], + ], + ], + 'required' => ['status', 'message'], + ], + 'ErrorForbidden' => [ + 'type' => 'object', + 'properties' => [ + 'status' => [ + 'type' => 'string', + 'enum' => ['error'], + ], + 'message' => [ + 'type' => 'string', + 'enum' => ['Authorization header HTTP_Authorization is not set', 'Authorization header contains invalid structure'], + ], + ], + 'required' => ['status', 'message'], + ], + 'InternalServerError' => [ + 'type' => 'object', + 'properties' => [ + 'status' => [ + 'type' => 'string', + 'enum' => ['error'], + ], + 'message' => [ + 'type' => 'string', + 'enum' => ['Internal server error'], + ], + ], + 'required' => ['status', 'message'], + ], + ], + ], + + 'paths' => $this->getPaths($apis, $baseUrl, $basePath), + ]; + + if (!empty($this->definitions)) { + $data['components']['schemas'] = array_merge($this->definitions, $data['components']['schemas']); + } + + $data = array_merge_recursive($this->initData, $data); + + if ($params['format'] === 'yaml') { + return new TextApiResponse(IResponse::S200_OK, Yaml::dump($data, PHP_INT_MAX, 2, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE)); + } + return new JsonApiResponse(IResponse::S200_OK, $data); + } + + private function getApis(int $version): array + { + return array_filter($this->apiDecider->getApis(), function (Api $api) use ($version) { + return $version === $api->getEndpoint()->getVersion(); + }); + } + + /** + * @param Api[] $versionApis + * @param string $baseUrl + * @param string $basePath + * @return array + * @throws InvalidLinkException + */ + private function getPaths(array $versionApis, string $baseUrl, string $basePath): array + { + $list = []; + foreach ($versionApis as $api) { + $handler = $api->getHandler(); + $path = str_replace([$baseUrl, $basePath], '', $this->apiLink->link($api->getEndpoint())); + $responses = []; + foreach ($handler->outputs() as $output) { + if ($output instanceof JsonOutput) { + $schema = $this->transformSchema(json_decode($output->getSchema(), true)); + $responses[$output->getCode()] = [ + 'description' => $output->getDescription(), + 'content' => [ + 'application/json' => [ + 'schema' => $schema, + ], + ] + ]; + } + + if ($output instanceof RedirectOutput) { + $responses[$output->getCode()] = [ + 'description' => 'Redirect', + 'headers' => [ + 'Location' => [ + 'description' => $output->getDescription(), + 'schema' => [ + 'type' => 'string', + ] + ], + ] + ]; + } + } + + $responses[IResponse::S400_BAD_REQUEST] = [ + 'description' => 'Bad request', + 'content' => [ + 'application/json' => [ + 'schema' => [ + '$ref' => '#/components/schemas/ErrorWrongInput', + ], + ] + ], + ]; + + $responses[IResponse::S403_FORBIDDEN] = [ + 'description' => 'Operation forbidden', + 'content' => [ + 'application/json' => [ + 'schema' => [ + '$ref' => '#/components/schemas/ErrorForbidden', + ], + ], + ], + ]; + + $responses[IResponse::S500_INTERNAL_SERVER_ERROR] = [ + 'description' => 'Internal server error', + 'content' => [ + 'application/json' => [ + 'schema' => [ + '$ref' => '#/components/schemas/InternalServerError', + ], + ], + ], + ]; + + $settings = [ + 'summary' => $handler->summary(), + 'description' => $handler->description(), + 'tags' => $handler->tags(), + ]; + + if ($handler->deprecated()) { + $settings['deprecated'] = true; + } + + $parameters = $this->createParamsList($handler); + if (!empty($parameters)) { + $settings['parameters'] = $parameters; + } + + $requestBody = $this->createRequestBody($handler); + if (!empty($requestBody)) { + $settings['requestBody'] = $requestBody; + } + + if ($api->getAuthorization() instanceof BearerTokenAuthorization) { + $settings['security'] = [ + [ + 'Bearer' => [], + ], + ]; + } + $settings['responses'] = $responses; + $list[$path][strtolower($api->getEndpoint()->getMethod())] = $settings; + } + return $list; + } + + private function getBasePath($handlers, $baseUrl) + { + $basePath = null; + foreach ($handlers as $handler) { + $basePath = $this->getLongestCommonSubstring($basePath, $this->apiLink->link($handler['endpoint'])); + } + return rtrim(str_replace($baseUrl, '', $basePath), '/'); + } + + private function getLongestCommonSubstring($path1, $path2) + { + if ($path1 === null) { + return $path2; + } + $commonSubstring = ''; + $shortest = min(strlen($path1), strlen($path2)); + for ($i = 0; $i <= $shortest; ++$i) { + if (substr($path1, 0, $i) !== substr($path2, 0, $i)) { + break; + } + $commonSubstring = substr($path1, 0, $i); + } + return $commonSubstring; + } + + /** + * Create array with params for specified handler + * + * @param ApiHandlerInterface $handler + * + * @return array + */ + private function createParamsList(ApiHandlerInterface $handler) + { + $parameters = []; + foreach ($handler->params() as $param) { + if ($param->getType() !== InputParam::TYPE_GET) { + continue; + } + + $schema = [ + 'type' => $param->isMulti() ? 'array' : 'string', + ]; + + $parameter = [ + 'name' => $param->getKey() . ($param->isMulti() ? '[]' : ''), + 'in' => $this->createIn($param->getType()), + 'required' => $param->isRequired(), + 'description' => $param->getDescription(), + ]; + + if ($param->isMulti()) { + $schema['items'] = ['type' => 'string']; + } + if ($param->getAvailableValues()) { + $schema['enum'] = $param->getAvailableValues(); + } + if ($param->getExample() || $param->getDefault()) { + $schema['example'] = $param->getExample() ?: $param->getDefault(); + } + + $parameter['schema'] = $schema; + + $parameters[] = $parameter; + } + return $parameters; + } + + private function createRequestBody(ApiHandlerInterface $handler) + { + $postParams = [ + 'properties' => [], + 'required' => [], + ]; + $postParamsExample = []; + foreach ($handler->params() as $param) { + if ($param instanceof JsonInputParam) { + $schema = json_decode($param->getSchema(), true); + if ($param->getExample()) { + $schema['example'] = $param->getExample(); + } + return [ + 'description' => $param->getDescription(), + 'required' => $param->isRequired(), + 'content' => [ + 'application/json' => [ + 'schema' => $this->transformSchema($schema), + ], + ], + ]; + } + if ($param->getType() === InputParam::TYPE_POST) { + $property = [ + 'type' => $param->isMulti() ? 'array' : 'string', + 'description' => $param->getDescription(), + ]; + if ($param->isMulti()) { + $property['items'] = ['type' => 'string']; + } + if ($param->getAvailableValues()) { + $property['enum'] = $param->getAvailableValues(); + } + + $postParams['properties'][$param->getKey() . ($param->isMulti() ? '[]' : '')] = $property; + if ($param->isRequired()) { + $postParams['required'][] = $param->getKey() . ($param->isMulti() ? '[]' : ''); + } + + if ($param->getExample() || $param->getDefault()) { + $postParamsExample[$param->getKey()] = $param->getExample() ?: $param->getDefault(); + } + } + } + + if (!empty($postParams['properties'])) { + $postParamsSchema = [ + 'type' => 'object', + 'properties' => $postParams['properties'], + 'required' => $postParams['required'], + ]; + + if ($postParamsExample) { + $postParamsSchema['example'] = $postParamsExample; + } + + return [ + 'required' => true, + 'content' => [ + 'application/x-www-form-urlencoded' => [ + 'schema' => $postParamsSchema, + ], + ], + ]; + } + + return null; + } + + private function createIn($type) + { + if ($type == InputParam::TYPE_GET) { + return 'query'; + } + if ($type == InputParam::TYPE_COOKIE) { + return 'cookie'; + } + return 'body'; + } + + private function transformSchema(array $schema) + { + $this->transformTypes($schema); + + if (isset($schema['definitions'])) { + foreach ($schema['definitions'] as $name => $definition) { + $this->addDefinition($name, $this->transformSchema($definition)); + } + unset($schema['definitions']); + } + return json_decode(str_replace('#/definitions/', '#/components/schemas/', json_encode($schema, JSON_UNESCAPED_SLASHES)), true); + } + + private function transformTypes(array &$schema) + { + foreach ($schema as $key => &$value) { + if ($key === 'type' && is_array($value)) { + if (count($value) === 2 && in_array('null', $value)) { + unset($value[array_search('null', $value)]); + $value = implode(',', $value); + $schema['nullable'] = true; + } else { + throw new InvalidArgumentException('Type cannot be array and if so, one element have to be "null"'); + } + } elseif (is_array($value)) { + $this->transformTypes($value); + } + } + } + + private function addDefinition($name, $definition) + { + if (isset($this->definitions[$name])) { + throw new InvalidArgumentException('Definition with name ' . $name . ' already exists. Rename it or use existing one.'); + } + $this->definitions[$name] = $definition; + } +} diff --git a/src/Output/AbstractOutput.php b/src/Output/AbstractOutput.php new file mode 100644 index 0000000..6193568 --- /dev/null +++ b/src/Output/AbstractOutput.php @@ -0,0 +1,26 @@ +code = $code; + $this->description = $description; + } + + public function getCode(): int + { + return $this->code; + } + + public function getDescription(): string + { + return $this->description; + } +} diff --git a/src/Output/JsonOutput.php b/src/Output/JsonOutput.php index 5d85ac5..38bcf68 100644 --- a/src/Output/JsonOutput.php +++ b/src/Output/JsonOutput.php @@ -10,15 +10,13 @@ use Tomaj\NetteApi\ValidationResult\ValidationResult; use Tomaj\NetteApi\ValidationResult\ValidationResultInterface; -class JsonOutput implements OutputInterface +class JsonOutput extends AbstractOutput { - private $code; - private $schema; - public function __construct(int $code, $schema) + public function __construct(int $code, string $schema, string $description = '') { - $this->code = $code; + parent::__construct($code, $description); $this->schema = $schema; } @@ -36,4 +34,9 @@ public function validate(ResponseInterface $response): ValidationResultInterface $schemaValidator = new JsonSchemaValidator(); return $schemaValidator->validate($value, $this->schema); } + + public function getSchema(): string + { + return $this->schema; + } } diff --git a/src/Output/RedirectOutput.php b/src/Output/RedirectOutput.php new file mode 100644 index 0000000..d656deb --- /dev/null +++ b/src/Output/RedirectOutput.php @@ -0,0 +1,22 @@ +code !== $response->getCode()) { + return new ValidationResult(ValidationResult::STATUS_ERROR, ['Response code doesn\'t match']); + } + return new ValidationResult(ValidationResult::STATUS_OK); + } +} diff --git a/src/Params/JsonInputParam.php b/src/Params/JsonInputParam.php index 87e2c9f..5b27aa7 100644 --- a/src/Params/JsonInputParam.php +++ b/src/Params/JsonInputParam.php @@ -53,6 +53,11 @@ public function validate(): ValidationResultInterface return $schemaValidator->validate($value, $this->schema); } + public function getSchema(): string + { + return $this->schema; + } + protected function addFormInput(Form $form, string $key): BaseControl { $this->description .= ' diff --git a/src/Presenters/ApiPresenter.php b/src/Presenters/ApiPresenter.php index 8bdbdec..8c7af0a 100644 --- a/src/Presenters/ApiPresenter.php +++ b/src/Presenters/ApiPresenter.php @@ -4,13 +4,13 @@ namespace Tomaj\NetteApi\Presenters; -use Exception; use Nette\Application\IPresenter; use Nette\Application\IResponse; use Nette\Application\Request; use Nette\Application\Responses\JsonResponse; use Nette\DI\Container; use Nette\Http\Response; +use Throwable; use Tomaj\NetteApi\Api; use Tomaj\NetteApi\ApiDecider; use Tomaj\NetteApi\Authorization\ApiAuthorizationInterface; @@ -104,14 +104,14 @@ public function run(Request $request): IResponse $outputValidatorErrors[] = $validationResult->getErrors(); } if (!$outputValid) { - $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error', 'details' => $outputValidatorErrors]); + $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error', 'details' => $outputValidatorErrors]); } $code = $response->getCode(); - } catch (Exception $exception) { + } catch (Throwable $exception) { if (Debugger::isEnabled()) { - $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error', 'detail' => $exception->getMessage()]); + $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error', 'detail' => $exception->getMessage()]); } else { - $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error']); + $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error']); } $code = $response->getCode(); Debugger::log($exception, Debugger::EXCEPTION); From 87630edd2ce1e010a565a72c2d313bed21e1323c Mon Sep 17 00:00:00 2001 From: Michal Lulco Date: Thu, 7 May 2020 15:41:51 +0200 Subject: [PATCH 2/4] Fixy, doplnene required veci a pridany paramter typu raw input --- src/Handlers/OpenApiHandler.php | 27 +++++++++++++++++++++------ tests/Handler/DefaultHandlerTest.php | 2 +- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Handlers/OpenApiHandler.php b/src/Handlers/OpenApiHandler.php index cf395ab..a6397dd 100644 --- a/src/Handlers/OpenApiHandler.php +++ b/src/Handlers/OpenApiHandler.php @@ -16,6 +16,7 @@ use Tomaj\NetteApi\Params\GetInputParam; use Tomaj\NetteApi\Params\InputParam; use Tomaj\NetteApi\Params\JsonInputParam; +use Tomaj\NetteApi\Params\RawInputParam; use Tomaj\NetteApi\Response\JsonApiResponse; use Tomaj\NetteApi\Response\ResponseInterface; use Tomaj\NetteApi\Response\TextApiResponse; @@ -93,7 +94,8 @@ public function handle(array $params): ResponseInterface $data = [ 'openapi' => '3.0.0', 'info' => [ - 'version' => $version, + 'version' => (string)$version, + 'title' => 'Nette API', ], 'servers' => [ [ @@ -160,7 +162,7 @@ public function handle(array $params): ResponseInterface $data['components']['schemas'] = array_merge($this->definitions, $data['components']['schemas']); } - $data = array_merge_recursive($this->initData, $data); + $data = array_replace_recursive($data, $this->initData); if ($params['format'] === 'yaml') { return new TextApiResponse(IResponse::S200_OK, Yaml::dump($data, PHP_INT_MAX, 2, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE)); @@ -283,11 +285,11 @@ private function getPaths(array $versionApis, string $baseUrl, string $basePath) return $list; } - private function getBasePath($handlers, $baseUrl) + private function getBasePath(array $apis, string $baseUrl): string { - $basePath = null; - foreach ($handlers as $handler) { - $basePath = $this->getLongestCommonSubstring($basePath, $this->apiLink->link($handler['endpoint'])); + $basePath = ''; + foreach ($apis as $handler) { + $basePath = $this->getLongestCommonSubstring($basePath, $this->apiLink->link($handler->getEndpoint())); } return rtrim(str_replace($baseUrl, '', $basePath), '/'); } @@ -374,6 +376,19 @@ private function createRequestBody(ApiHandlerInterface $handler) ], ]; } + if ($param instanceof RawInputParam) { + return [ + 'description' => $param->getDescription(), + 'required' => $param->isRequired(), + 'content' => [ + 'text/plain' => [ + 'schema' => [ + 'type' => 'string', + ], + ], + ], + ]; + } if ($param->getType() === InputParam::TYPE_POST) { $property = [ 'type' => $param->isMulti() ? 'array' : 'string', diff --git a/tests/Handler/DefaultHandlerTest.php b/tests/Handler/DefaultHandlerTest.php index 61bc155..13abd66 100644 --- a/tests/Handler/DefaultHandlerTest.php +++ b/tests/Handler/DefaultHandlerTest.php @@ -18,7 +18,7 @@ public function testResponse() { $defaultHandler = new DefaultHandler(); $result = $defaultHandler->handle([]); - $this->assertEquals(500, $result->getCode()); + $this->assertEquals(400, $result->getCode()); $this->assertEquals('application/json', $result->getContentType()); $this->assertEquals('utf-8', $result->getCharset()); $this->assertEquals(['status' => 'error', 'message' => 'Unknown api endpoint'], $result->getPayload()); From c16f6f18bfaaaf388256f0751565dc104fc8dd67 Mon Sep 17 00:00:00 2001 From: Michal Lulco Date: Thu, 7 May 2020 15:47:23 +0200 Subject: [PATCH 3/4] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 442e491..e6d8290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip * Added API rate limit * Added custom headers to API console * Added field for timeout to API console +* OpenAPI handler #### Fixed From 3f7c849060ee2f428728733030cd84235f488c69 Mon Sep 17 00:00:00 2001 From: Michal Lulco Date: Sun, 10 May 2020 14:29:22 +0200 Subject: [PATCH 4/4] Added info about RESTful urls, fixed console RESTful urls --- CHANGELOG.md | 2 ++ README.md | 16 +++++++++++++++- src/Component/ApiConsoleControl.php | 4 ++-- src/Misc/ConsoleRequest.php | 16 ++++++++++++++-- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6d8290..2f6f9fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,14 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip * Added custom headers to API console * Added field for timeout to API console * OpenAPI handler +* Information about RESTful urls #### Fixed * Fixed sending empty string in multi params * UrlEncoding values sending through get param inputs * Fixed static url part `/api/` in console +* Fixed generating urls in console for RESTful urls using ApiLink and EndpointInterface ## 2.0.1 - 2020-03-24 diff --git a/README.md b/README.md index 798a729..58681cd 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,20 @@ And add route to you RouterFactory: $router[] = new Route('/api/v/[/][/]', 'Api:Api:default'); ``` +If you want to use RESTful urls you will need another route: +```php +$router[] = new Route('api/v//', [ + 'presenter' => 'Api:Api', + 'action' => 'default', + 'id' => [ + Route::FILTER_IN => function ($id) { + $_GET['id'] = $id; + return $id; + } + ], +]); +``` + After that you need only register your API handlers to *apiDecider* [ApiDecider](src/ApiDecider.php), register [ApiLink](src/Link/ApiLink.php) and [Tomaj\NetteApi\Misc\IpDetector](src/Misc/IpDetector.php). This can be done also with *config.neon*: ```neon @@ -73,7 +87,7 @@ Core of the Nette-Api are handlers. For this example you need to implement two c 2. App\MyApi\v1\Handlers\SendEmailHandler These handlers implement interface *[ApiHandlerInterface](src/Handlers/ApiHandlerInterface.php)* but for easier usage you can extend your handlers from [BaseHandler](src/Handlers/BaseHandler.php). -When someone reach your API these handlers will be triggered and *handle()* method will be called. +When someone reach your API, these handlers will be triggered and *handle()* method will be called. ```php namespace App\MyApi\v1\Handlers; diff --git a/src/Component/ApiConsoleControl.php b/src/Component/ApiConsoleControl.php index 9f01d72..56b4f14 100644 --- a/src/Component/ApiConsoleControl.php +++ b/src/Component/ApiConsoleControl.php @@ -56,7 +56,7 @@ protected function createComponentConsoleForm(): Form $defaults = []; $form->setRenderer(new BootstrapRenderer()); - + if ($this->apiLink) { $url = $this->apiLink->link($this->endpoint); } else { @@ -136,7 +136,7 @@ public function formSucceeded(Form $form, ArrayHash $values): void $additionalValues['timeout'] = $values['timeout']; - $consoleRequest = new ConsoleRequest($this->handler); + $consoleRequest = new ConsoleRequest($this->handler, $this->endpoint, $this->apiLink); $result = $consoleRequest->makeRequest($url, $method, (array) $values, $additionalValues, $token); /** @var Template $template */ diff --git a/src/Misc/ConsoleRequest.php b/src/Misc/ConsoleRequest.php index 94bb453..0e6b6ea 100644 --- a/src/Misc/ConsoleRequest.php +++ b/src/Misc/ConsoleRequest.php @@ -5,7 +5,9 @@ namespace Tomaj\NetteApi\Misc; use Nette\Http\FileUpload; +use Tomaj\NetteApi\EndpointInterface; use Tomaj\NetteApi\Handlers\ApiHandlerInterface; +use Tomaj\NetteApi\Link\ApiLink; use Tomaj\NetteApi\Params\InputParam; use Tomaj\NetteApi\Params\ParamInterface; @@ -14,9 +16,17 @@ class ConsoleRequest /** @var ApiHandlerInterface */ private $handler; - public function __construct(ApiHandlerInterface $handler) + /** @var EndpointInterface|null */ + private $endpoint; + + /** @var ApiLink|null */ + private $apiLink; + + public function __construct(ApiHandlerInterface $handler, ?EndpointInterface $endpoint = null, ?ApiLink $apiLink = null) { $this->handler = $handler; + $this->endpoint = $endpoint; + $this->apiLink = $apiLink; } public function makeRequest(string $url, string $method, array $values, array $additionalValues = [], ?string $token = null): ConsoleResponse @@ -32,7 +42,9 @@ public function makeRequest(string $url, string $method, array $values, array $a $getFields = $this->normalizeValues($getFields); $putFields = $this->normalizeValues($putFields); - if (count($getFields)) { + if ($this->endpoint && $this->apiLink) { + $url = $this->apiLink->link($this->endpoint, $getFields); + } elseif (count($getFields)) { $parts = []; foreach ($getFields as $key => $value) { $parts[] = "$key=$value";