Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
9 changes: 9 additions & 0 deletions config/auto-doc.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@
'204' => 'Operation successfully done',
'404' => 'This entity not found',
],

/*
|--------------------------------------------------------------------------
| Error Template
|--------------------------------------------------------------------------
|
| You can use your custom description view for errors.
*/
'error' => 'auto-doc::error',
],

/*
Expand Down
1 change: 1 addition & 0 deletions resources/views/error.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ $message }}
4 changes: 4 additions & 0 deletions src/AutoDocServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ public function boot()
__DIR__ . '/../resources/views/swagger-description.blade.php' => resource_path('views/vendor/auto-doc/swagger-description.blade.php'),
], 'view');

$this->publishes([
__DIR__ . '/../resources/views/error.blade.php' => resource_path('views/vendor/auto-doc/error.blade.php'),
], 'view');

if (!$this->app->routesAreCached()) {
require __DIR__ . '/Http/routes.php';
}
Expand Down
2 changes: 1 addition & 1 deletion src/Drivers/LocalDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function saveData(): void
public function getDocumentation(): array
{
if (!file_exists($this->prodFilePath)) {
throw new FileNotFoundException();
throw new FileNotFoundException($this->prodFilePath);
}

$fileContent = file_get_contents($this->prodFilePath);
Expand Down
2 changes: 1 addition & 1 deletion src/Drivers/RemoteDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function getDocumentation(): array
list($content, $statusCode) = $this->makeHttpRequest('get', $this->getUrl());

if (empty($content) || $statusCode !== 200) {
throw new FileNotFoundException();
throw new FileNotFoundException('Documentation file not found.');
}

return json_decode($content, true);
Expand Down
2 changes: 1 addition & 1 deletion src/Drivers/StorageDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function saveData(): void
public function getDocumentation(): array
{
if (!$this->disk->exists($this->prodFilePath)) {
throw new FileNotFoundException();
throw new FileNotFoundException("Documentation file not found :{$this->prodFilePath}");
}

$fileContent = $this->disk->get($this->prodFilePath);
Expand Down
34 changes: 30 additions & 4 deletions src/Services/SwaggerService.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use RonasIT\AutoDoc\Traits\GetDependenciesTrait;
use RonasIT\AutoDoc\Validators\SwaggerSpecValidator;
use Symfony\Component\HttpFoundation\Response;
use Exception;

/**
* @property SwaggerDriverContract $driver
Expand Down Expand Up @@ -160,6 +161,20 @@ protected function generateEmptyData(): array
return $data;
}

protected function generateBaseDataObject(): array
{
return [
'openapi' => self::OPEN_API_VERSION,
'servers' => [
['url' => URL::query($this->config['basePath'])],
],
'paths' => [],
'components' => [
'schemas' => $this->config['definitions'],
],
];
}

protected function generateSecurityDefinition(): ?array
{
if (empty($this->security)) {
Expand Down Expand Up @@ -817,9 +832,20 @@ public function saveProductionData()

public function getDocFileContent()
{
$documentation = $this->driver->getDocumentation();
try {
$documentation = $this->driver->getDocumentation();

$this->openAPIValidator->validate($documentation);
$this->openAPIValidator->validate($documentation);
} catch (Exception $exception) {
$data = $this->generateBaseDataObject();

$infoConfig = $this->config['info'];
$infoConfig['description'] = Arr::get($this->config, 'defaults.error');

$data['info'] = $this->prepareInfo($infoConfig, ['message' => $exception->getMessage()]);

return $data;
}

$additionalDocs = config('auto-doc.additional_paths', []);

Expand Down Expand Up @@ -946,7 +972,7 @@ protected function getDefaultValueByType($type)
return $values[$type];
}

protected function prepareInfo(array $info): array
protected function prepareInfo(array $info, array $descriptionData = []): array
{
if (empty($info)) {
return $info;
Expand All @@ -963,7 +989,7 @@ protected function prepareInfo(array $info): array
}

if (!empty($info['description'])) {
$info['description'] = view($info['description'])->render();
$info['description'] = view($info['description'], $descriptionData)->render();
}

return $info;
Expand Down
66 changes: 9 additions & 57 deletions tests/SwaggerServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,11 @@
namespace RonasIT\AutoDoc\Tests;

use Illuminate\Http\Testing\File;
use Illuminate\Support\Facades\View;
use PHPUnit\Framework\Attributes\DataProvider;
use RonasIT\AutoDoc\Exceptions\EmptyContactEmailException;
use RonasIT\AutoDoc\Exceptions\InvalidDriverClassException;
use RonasIT\AutoDoc\Exceptions\LegacyConfigException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\DuplicateFieldException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\DuplicateParamException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\DuplicatePathPlaceholderException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\InvalidFieldValueException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\InvalidPathException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\InvalidStatusCodeException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\InvalidSwaggerSpecException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\InvalidSwaggerVersionException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\MissingExternalRefException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\MissingFieldException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\MissingLocalRefException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\MissingPathParamException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\MissingPathPlaceholderException;
use RonasIT\AutoDoc\Exceptions\SpecValidation\MissingRefFileException;
use RonasIT\AutoDoc\Exceptions\SwaggerDriverClassNotFoundException;
use RonasIT\AutoDoc\Exceptions\UnsupportedDocumentationViewerException;
use RonasIT\AutoDoc\Exceptions\WrongSecurityConfigException;
Expand Down Expand Up @@ -90,215 +77,178 @@ public static function getConstructorInvalidTmpData(): array
return [
[
'tmpDoc' => 'documentation/invalid_version',
'exception' => InvalidSwaggerVersionException::class,
'exceptionMessage' => "Unrecognized Swagger version '1.0'. Expected 3.1.0.",
'exceptionMessage' => "Validation failed. Unrecognized Swagger version '1.0'. Expected 3.1.0.",
],
[
'tmpDoc' => 'documentation/invalid_format__array_parameter__no_items',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. paths./users.post.parameters.0 is an "
. "array, so it must include an 'items' field.",
],
[
'tmpDoc' => 'documentation/invalid_format__array_response_body__no_items',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. paths./users.get.responses.200.schema is an array, "
. "so it must include an 'items' field.",
],
[
'tmpDoc' => 'documentation/invalid_format__array_response_header__no_items',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. paths./users.get.responses.default.headers."
. "Last-Modified is an array, so it must include an 'items' field.",
],
[
'tmpDoc' => 'documentation/invalid_format__body_and_form_params',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}.post' "
. "has body and formData parameters. Only one or the other is allowed.",
],
[
'tmpDoc' => 'documentation/invalid_format__duplicate_header_params',
'exception' => DuplicateParamException::class,
'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}.get' "
. "has multiple in:header parameters with name:foo.",
],
[
'tmpDoc' => 'documentation/invalid_format__duplicate_path_params',
'exception' => DuplicateParamException::class,
'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}.get' has "
. "multiple in:path parameters with name:username",
. "multiple in:path parameters with name:username.",
],
[
'tmpDoc' => 'documentation/invalid_format__duplicate_path_placeholders',
'exception' => DuplicatePathPlaceholderException::class,
'exceptionMessage' => "Validation failed. Path '/users/{username}/profile/{username}/image/{img_id}' "
. "has multiple path placeholders with name: username.",
],
[
'tmpDoc' => 'documentation/invalid_format__duplicate_operation_id',
'exception' => DuplicateFieldException::class,
'exceptionMessage' => "Validation failed. Found multiple fields 'paths.*.*.operationId' "
. "with values: addPet.",
],
[
'tmpDoc' => 'documentation/invalid_format__duplicate_tag',
'exception' => DuplicateFieldException::class,
'exceptionMessage' => "Validation failed. Found multiple fields 'tags.*.name' with values: user.",
],
[
'tmpDoc' => 'documentation/invalid_format__file_invalid_consumes',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}/profile/image.post' "
. "has body and formData parameters. Only one or the other is allowed.",
],
[
'tmpDoc' => 'documentation/invalid_format__file_no_consumes',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}/profile/image.post' "
. "has body and formData parameters. Only one or the other is allowed.",
],
[
'tmpDoc' => 'documentation/invalid_format__multiple_body_params',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}.get' has 2 body "
. "parameters. Only one is allowed.",
],
[
'tmpDoc' => 'documentation/invalid_format__no_path_params',
'exception' => MissingPathParamException::class,
'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}/{foo}.get' has "
. "no params for placeholders: username, foo.",
],
[
'tmpDoc' => 'documentation/invalid_format__path_param_no_placeholder',
'exception' => MissingPathPlaceholderException::class,
'exceptionMessage' => "Validation failed. Operation 'paths./users/{username}.post' has no "
. "placeholders for params: foo.",
],
[
'tmpDoc' => 'documentation/invalid_format__invalid_value__path',
'exception' => InvalidPathException::class,
'exceptionMessage' => "Validation failed. Incorrect 'paths.users'. Paths should only have path "
. "names that starts with `/`.",
],
[
'tmpDoc' => 'documentation/invalid_format__invalid_value__status_code',
'exception' => InvalidStatusCodeException::class,
'exceptionMessage' => "Validation failed. Operation at 'paths./users.get.responses.8888' should "
. "only have three-digit status codes, `default`, and vendor extensions (`x-*`) as properties.",
],
[
'tmpDoc' => 'documentation/invalid_format__invalid_value__parameter_in',
'exception' => InvalidFieldValueException::class,
'exceptionMessage' => "Validation failed. Field 'paths./auth/login.post.parameters.0.in' "
. "has an invalid value: invalid_in. Allowed values: body, formData, query, path, header.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_field__paths',
'exception' => MissingFieldException::class,
'exceptionMessage' => "Validation failed. '' should have required fields: paths.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_field__operation_responses',
'exception' => MissingFieldException::class,
'exceptionMessage' => "Validation failed. 'paths./auth/login.post' should have required "
. "fields: responses.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_field__parameter_in',
'exception' => MissingFieldException::class,
'exceptionMessage' => "Validation failed. 'paths./auth/login.post.parameters.0' should "
. "have required fields: in.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_field__response_description',
'exception' => MissingFieldException::class,
'exceptionMessage' => "Validation failed. 'paths./auth/login.post.responses.200' should "
. "have required fields: description.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_field__definition_type',
'exception' => MissingFieldException::class,
'exceptionMessage' => "Validation failed. 'components.schemas.authloginObject' should have "
. "required fields: type.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_field__info_version',
'exception' => MissingFieldException::class,
'exceptionMessage' => "Validation failed. 'info' should have required fields: version.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_field__items_type',
'exception' => MissingFieldException::class,
'exceptionMessage' => "Validation failed. 'paths./pet/findByStatus.get.parameters.0.schema.items' "
. "should have required fields: type.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_field__header_type',
'exception' => MissingFieldException::class,
'exceptionMessage' => "Validation failed. 'paths./user/login.get.responses.200.headers.X-Rate-Limit' "
. "should have required fields: type.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_field__tag_name',
'exception' => MissingFieldException::class,
'exceptionMessage' => "Validation failed. 'tags.0' should have required fields: name.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_local_ref',
'exception' => MissingLocalRefException::class,
'exceptionMessage' => "Validation failed. Ref 'loginObject' is used in \$ref but not defined "
. "in 'definitions' field.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_external_ref',
'exception' => MissingExternalRefException::class,
'exceptionMessage' => "Validation failed. Ref 'authloginObject' is used in \$ref but not defined "
. "in 'tests/fixtures/SwaggerServiceTest/documentation/with_definitions.json' file.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_ref_file',
'exception' => MissingRefFileException::class,
'exceptionMessage' => "Validation failed. Filename 'invalid-filename.json' is used in \$ref but "
. "file doesn't exist.",
],
[
'tmpDoc' => 'documentation/invalid_format__invalid_schema_type',
'exception' => InvalidFieldValueException::class,
'exceptionMessage' => "Validation failed. Field 'paths./users.get.responses.200.schema.type' "
. "has an invalid value: something. Allowed values: array, boolean, integer, number, "
. "string, object, null, undefined, file.",
],
[
'tmpDoc' => 'documentation/invalid_format__missing_path_parameter',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. Path parameters cannot be optional. "
. "Set required=true for the 'username' parameters at operation 'paths./users.get'.",
],
[
'tmpDoc' => 'documentation/invalid_format__security_definition__type',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. Field 'securityDefinitions.0.type' has an invalid value: invalid. Allowed values: basic, apiKey, oauth2.",
],
[
'tmpDoc' => 'documentation/invalid_format__security_definition__flow',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. Field 'securityDefinitions.0.flow' has an invalid value: invalid. Allowed values: implicit, password, application, accessCode.",
],
[
'tmpDoc' => 'documentation/invalid_format__security_definition__in',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. Field 'securityDefinitions.0.in' has an invalid value: invalid. Allowed values: query, header.",
],
[
'tmpDoc' => 'documentation/invalid_format__request_body__invalid_content',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. Operation 'paths./users/{id}.post' has invalid content types: image/png.",
],
[
'tmpDoc' => 'documentation/invalid_format__response__invalid_items',
'exception' => InvalidSwaggerSpecException::class,
'exceptionMessage' => "Validation failed. 'paths./users/{id}.post.responses.200.schema.items' should have required fields: type.",
],
];
Expand All @@ -307,15 +257,17 @@ public static function getConstructorInvalidTmpData(): array
#[DataProvider('getConstructorInvalidTmpData')]
public function testGetDocFileContentInvalidTmpData(
string $tmpDoc,
string $exception,
string $exceptionMessage,
) {
$this->mockDriverGetDocumentation($this->getJsonFixture($tmpDoc));

$this->expectException($exception);
$this->expectExceptionMessage($exceptionMessage);

app(SwaggerService::class)->getDocFileContent();

View::addLocation(__DIR__ . '/../resources/views');

$rendered = view('error', ['message' => $exceptionMessage])->render();

$this->assertStringContainsString($exceptionMessage, html_entity_decode($rendered));
}

public function testEmptyContactEmail()
Expand Down