diff --git a/config/auto-doc.php b/config/auto-doc.php index e45520d2..a2126815 100644 --- a/config/auto-doc.php +++ b/config/auto-doc.php @@ -1,5 +1,9 @@ '/', /* @@ -19,7 +22,6 @@ | | Information fields */ - 'info' => [ /* @@ -29,7 +31,6 @@ | | You can use your custom documentation view */ - 'description' => 'swagger-description', 'version' => '0.0.0', 'title' => 'Name of Your Application', @@ -54,7 +55,6 @@ | Base path for API routes. If config is set, all routes which starts from | this value will be grouped. */ - 'basePath' => '/', 'schemes' => [], 'definitions' => [], @@ -67,7 +67,6 @@ | Library name, which used to secure the project. | Available values: "jwt", "laravel", "null" */ - 'security' => '', 'defaults' => [ @@ -76,7 +75,6 @@ | Default descriptions of code statuses |-------------------------------------------------------------------------- */ - 'code-descriptions' => [ '200' => 'Operation successfully done', '204' => 'Operation successfully done', @@ -86,31 +84,50 @@ /* |-------------------------------------------------------------------------- - | Data Collector Class + | Driver |-------------------------------------------------------------------------- | - | Class of data collector, which will collect and save documentation - | It can be your own data collector class which should be inherited from - | RonasIT\Support\AutoDoc\Interfaces\DataCollectorInterface interface, - | or our data collectors from next packages: - | - | ronasit/local-data-collector - | ronasit/laravel-remote-data-collector - | - | If config not set, will be using ronasit/local-data-collector + | The name of driver, which will collect and save documentation + | Feel free to use your own driver class which should be inherited from + | `RonasIT\Support\AutoDoc\Interfaces\SwaggerDriverInterface` interface, + | or one of our drivers from the `drivers` config: */ + 'driver' => 'local', - 'data_collector' => \RonasIT\Support\AutoDoc\DataCollectors\LocalDataCollector::class, + 'drivers' => [ + 'local' => [ + 'class' => LocalDriver::class, + 'production_path' => storage_path('documentation.json') + ], + 'remote' => [ + 'class' => RemoteDriver::class, + 'key' => 'project_name', + 'url' => 'http://example.com' + ], + 'storage' => [ + 'class' => StorageDriver::class, + + /* + |-------------------------------------------------------------------------- + | Storage disk + |-------------------------------------------------------------------------- + | + | One of the filesystems.disks config value + */ + 'disk' => 'public', + 'production_path' => 'documentation.json' + ] + ], - /* - |-------------------------------------------------------------------------- - | Swagger documentation visibility environments list - |-------------------------------------------------------------------------- - | - | The list of environments in which auto documentation will be displaying - */ + /* + |-------------------------------------------------------------------------- + | Swagger documentation visibility environments list + |-------------------------------------------------------------------------- + | + | The list of environments in which auto documentation will be displaying + */ 'display_environments' => [ 'local', - 'development', - ], + 'development' + ] ]; diff --git a/config/local-data-collector.php b/config/local-data-collector.php deleted file mode 100644 index 25ba12f8..00000000 --- a/config/local-data-collector.php +++ /dev/null @@ -1,5 +0,0 @@ - env('LOCAL_DATA_COLLECTOR_PROD_PATH') -]; diff --git a/src/AutoDocServiceProvider.php b/src/AutoDocServiceProvider.php index 85036397..4652cd20 100644 --- a/src/AutoDocServiceProvider.php +++ b/src/AutoDocServiceProvider.php @@ -13,10 +13,6 @@ public function boot() __DIR__ . '/../config/auto-doc.php' => config_path('auto-doc.php'), ], 'config'); - $this->publishes([ - __DIR__ . '/../config/local-data-collector.php' => config_path('local-data-collector.php'), - ], 'config'); - $this->publishes([ __DIR__ . '/Views/swagger-description.blade.php' => resource_path('views/swagger-description.blade.php'), ], 'view'); @@ -34,6 +30,5 @@ public function boot() public function register() { - } } diff --git a/src/DataCollectors/LocalDataCollector.php b/src/Drivers/LocalDriver.php similarity index 74% rename from src/DataCollectors/LocalDataCollector.php rename to src/Drivers/LocalDriver.php index 08f96dbe..bf1e53d0 100755 --- a/src/DataCollectors/LocalDataCollector.php +++ b/src/Drivers/LocalDriver.php @@ -1,21 +1,21 @@ prodFilePath = config('local-data-collector.production_path'); + $this->prodFilePath = config('auto-doc.drivers.local.production_path'); if (empty($this->prodFilePath)) { throw new MissedProductionFilePathException(); @@ -41,7 +41,7 @@ public function saveData() self::$data = []; } - public function getDocumentation() + public function getDocumentation(): stdClass { if (!file_exists($this->prodFilePath)) { throw new FileNotFoundException(); diff --git a/src/Drivers/RemoteDriver.php b/src/Drivers/RemoteDriver.php new file mode 100755 index 00000000..30944ac2 --- /dev/null +++ b/src/Drivers/RemoteDriver.php @@ -0,0 +1,62 @@ +key = config('auto-doc.drivers.remote.key'); + $this->remoteUrl = config('auto-doc.drivers.remote.url'); + } + + public function saveTmpData($tempData) + { + self::$data = $tempData; + } + + public function getTmpData() + { + return self::$data; + } + + public function saveData() + { + $curl = curl_init(); + + curl_setopt($curl, CURLOPT_URL,$this->getUrl()); + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($this->getTmpData())); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + + curl_exec($curl); + + curl_close($curl); + + self::$data = []; + } + + public function getDocumentation(): stdClass + { + $content = file_get_contents($this->getUrl()); + + if (empty($content)) { + throw new FileNotFoundException(); + } + + return json_decode($content); + } + + protected function getUrl() + { + return "{$this->remoteUrl}/documentations/{$this->key}"; + } +} diff --git a/src/Drivers/StorageDriver.php b/src/Drivers/StorageDriver.php new file mode 100755 index 00000000..c52f4fc2 --- /dev/null +++ b/src/Drivers/StorageDriver.php @@ -0,0 +1,52 @@ +disk = config('auto-doc.drivers.storage.disk'); + $this->filePath = config('auto-doc.drivers.storage.production_path'); + } + + public function saveTmpData($tempData) + { + self::$data = $tempData; + } + + public function getTmpData() + { + return self::$data; + } + + public function saveData() + { + $content = json_encode(self::$data); + + Storage::disk($this->disk)->put($this->filePath, $content); + + self::$data = []; + } + + public function getDocumentation(): stdClass + { + if (!Storage::disk($this->disk)->exists($this->filePath)) { + throw new FileNotFoundException(); + } + + $fileContent = Storage::disk($this->disk)->get($this->filePath); + + return json_decode($fileContent); + } +} diff --git a/src/Exceptions/DataCollectorClassNotFoundException.php b/src/Exceptions/DataCollectorClassNotFoundException.php deleted file mode 100644 index 304e5bef..00000000 --- a/src/Exceptions/DataCollectorClassNotFoundException.php +++ /dev/null @@ -1,17 +0,0 @@ -service = app(SwaggerService::class); - } - public function handle($request, Closure $next) { $response = $next($request); if ((config('app.env') == 'testing') && !self::$skipped && !empty($request->route())) { - $this->service->addData($request, $response); + app(SwaggerService::class)->addData($request, $response); } self::$skipped = false; diff --git a/src/Interfaces/DataCollectorInterface.php b/src/Interfaces/SwaggerDriverInterface.php similarity index 79% rename from src/Interfaces/DataCollectorInterface.php rename to src/Interfaces/SwaggerDriverInterface.php index c2145438..88a912cf 100755 --- a/src/Interfaces/DataCollectorInterface.php +++ b/src/Interfaces/SwaggerDriverInterface.php @@ -2,7 +2,9 @@ namespace RonasIT\Support\AutoDoc\Interfaces; -interface DataCollectorInterface +use stdClass; + +interface SwaggerDriverInterface { /** * Save temporary data @@ -24,7 +26,7 @@ public function saveData(); /** * Get production documentation */ - public function getDocumentation(); + public function getDocumentation(): stdClass; } diff --git a/src/Services/SwaggerService.php b/src/Services/SwaggerService.php index 6b09dbad..f06494f9 100755 --- a/src/Services/SwaggerService.php +++ b/src/Services/SwaggerService.php @@ -2,34 +2,35 @@ namespace RonasIT\Support\AutoDoc\Services; -use ReflectionClass; use Illuminate\Container\Container; -use Illuminate\Http\Request; +use Illuminate\Http\Testing\File; use Illuminate\Support\Arr; use Illuminate\Support\Str; -use RonasIT\Support\AutoDoc\Interfaces\DataCollectorInterface; -use RonasIT\Support\AutoDoc\Traits\GetDependenciesTrait; +use ReflectionClass; +use Illuminate\Http\Request; use RonasIT\Support\AutoDoc\Exceptions\WrongSecurityConfigException; -use RonasIT\Support\AutoDoc\Exceptions\DataCollectorClassNotFoundException; -use RonasIT\Support\AutoDoc\DataCollectors\LocalDataCollector; +use RonasIT\Support\AutoDoc\Traits\GetDependenciesTrait; use Symfony\Component\HttpFoundation\Response; -use Illuminate\Http\Testing\File; +use RonasIT\Support\AutoDoc\Interfaces\SwaggerDriverInterface; +use RonasIT\Support\AutoDoc\Exceptions\InvalidDriverClassException; +use RonasIT\Support\AutoDoc\Exceptions\SwaggerDriverClassNotFoundException; /** - * @property DataCollectorInterface $dataCollector + * @property SwaggerDriverInterface $driver */ class SwaggerService { use GetDependenciesTrait; - protected $dataCollector; + protected $driver; protected $data; + protected $config; protected $container; private $uri; private $method; /** - * @var \Illuminate\Http\Request + * @var Request */ private $request; private $item; @@ -37,53 +38,60 @@ class SwaggerService public function __construct(Container $container) { - $this->setDataCollector(); + $this->config = config('auto-doc'); + + $this->setDriver(); if (config('app.env') == 'testing') { $this->container = $container; - $this->security = config('auto-doc.security'); + $this->security = $this->config['security']; - $this->data = $this->dataCollector->getTmpData(); + $this->data = $this->driver->getTmpData(); if (empty($this->data)) { $this->data = $this->generateEmptyData(); - $this->dataCollector->saveTmpData($this->data); + $this->driver->saveTmpData($this->data); } } } - protected function setDataCollector() + protected function setDriver() { - $dataCollectorClass = config('auto-doc.data_collector'); + $driver = $this->config['driver']; + $className = Arr::get($this->config, "drivers.{$driver}.class"); - if (empty($dataCollectorClass)) { - $this->dataCollector = app(LocalDataCollector::class); - } elseif (!class_exists($dataCollectorClass)) { - throw new DataCollectorClassNotFoundException(); + if (!class_exists($className)) { + throw new SwaggerDriverClassNotFoundException($className); } else { - $this->dataCollector = app($dataCollectorClass); + $this->driver = app($className); + } + + if (!$this->driver instanceof SwaggerDriverInterface) { + throw new InvalidDriverClassException($driver); } } - protected function generateEmptyData() + protected function generateEmptyData(): array { $data = [ - 'swagger' => config('auto-doc.swagger.version'), + 'swagger' => Arr::get($this->config, 'swagger.version'), 'host' => $this->getAppUrl(), - 'basePath' => config('auto-doc.basePath'), - 'schemes' => config('auto-doc.schemes'), + 'basePath' => $this->config['basePath'], + 'schemes' => $this->config['schemes'], 'paths' => [], - 'definitions' => config('auto-doc.definitions') + 'definitions' => $this->config['definitions'] ]; - $info = $this->prepareInfo(config('auto-doc.info')); + $info = $this->prepareInfo($this->config['info']); + if (!empty($info)) { $data['info'] = $info; } $securityDefinitions = $this->generateSecurityDefinition(); + if (!empty($securityDefinitions)) { $data['securityDefinitions'] = $securityDefinitions; } @@ -102,23 +110,16 @@ protected function getAppUrl() protected function generateSecurityDefinition() { - $availableTypes = ['jwt', 'laravel']; - $security = $this->security; - - if (empty($security)) { + if (empty($this->security)) { return ''; } - if (!in_array($security, $availableTypes)) { - throw new WrongSecurityConfigException(); - } - return [ - $security => $this->generateSecurityDefinitionObject($security) + $this->security => $this->generateSecurityDefinitionObject($this->security) ]; } - protected function generateSecurityDefinitionObject($type) + protected function generateSecurityDefinitionObject($type): array { switch ($type) { case 'jwt': @@ -134,6 +135,8 @@ protected function generateSecurityDefinitionObject($type) 'name' => 'Cookie', 'in' => 'header' ]; + default: + throw new WrongSecurityConfigException(); } } @@ -146,7 +149,7 @@ public function addData(Request $request, $response) $this->parseRequest(); $this->parseResponse($response); - $this->dataCollector->saveTmpData($this->data); + $this->driver->saveTmpData($this->data); } protected function prepareItem() @@ -172,13 +175,13 @@ protected function prepareItem() protected function getUri() { $uri = $this->request->route()->uri(); - $basePath = preg_replace("/^\//", '', config('auto-doc.basePath')); + $basePath = preg_replace("/^\//", '', $this->config['basePath']); $preparedUri = preg_replace("/^{$basePath}/", '', $uri); return preg_replace("/^\//", '', $preparedUri); } - protected function getPathParams() + protected function getPathParams(): array { $params = []; @@ -228,6 +231,7 @@ protected function parseResponse($response) $produceList = $this->data['paths'][$this->uri][$this->method]['produces']; $produce = $response->headers->get('Content-type'); + if (is_null($produce)) { $produce = 'text/plain'; } @@ -264,20 +268,14 @@ protected function saveExample($code, $content, $produce) } } - protected function makeResponseExample($content, $mimeType, $description = '') + protected function makeResponseExample($content, $mimeType, $description = ''): array { - $responseExample = [ - 'description' => $description - ]; + $responseExample = ['description' => $description]; if ($mimeType === 'application/json') { - $responseExample['schema'] = [ - 'example' => json_decode($content, true) - ]; + $responseExample['schema'] = ['example' => json_decode($content, true)]; } elseif ($mimeType === 'application/pdf') { - $responseExample['schema'] = [ - 'example' => base64_encode($content) - ]; + $responseExample['schema'] = ['example' => base64_encode($content)]; } else { $responseExample['examples']['example'] = $content; } @@ -308,7 +306,7 @@ protected function saveGetRequestParameters($rules, array $annotations) $description = Arr::get($annotations, $parameter, implode(', ', $validation)); - $existedParameter = Arr::first($this->item['parameters'], function ($existedParameter, $key) use ($parameter) { + $existedParameter = Arr::first($this->item['parameters'], function ($existedParameter) use ($parameter) { return $existedParameter['name'] == $parameter; }); @@ -368,7 +366,7 @@ protected function saveDefinitions($objectName, $rules, array $annotations) $this->data['definitions'][$objectName . 'Object'] = $data; } - protected function getParameterType(array $validation) + protected function getParameterType(array $validation): string { $validationRules = [ 'array' => 'object', @@ -385,8 +383,7 @@ protected function getParameterType(array $validation) foreach ($validation as $item) { if (in_array($item, array_keys($validationRules))) { - $parameterType = $validationRules[$item]; - break; + return $validationRules[$item]; } } @@ -396,7 +393,7 @@ protected function getParameterType(array $validation) protected function saveParameterType(&$data, $parameter, $parameterType) { $data['properties'][$parameter] = [ - 'type' => $parameterType, + 'type' => $parameterType ]; } @@ -406,7 +403,7 @@ protected function saveParameterDescription(&$data, $parameter, array $rulesArra $data['properties'][$parameter]['description'] = $description; } - protected function requestHasMoreProperties($actionName) + protected function requestHasMoreProperties($actionName): bool { $requestParametersCount = count($this->request->all()); @@ -419,11 +416,11 @@ protected function requestHasMoreProperties($actionName) return $requestParametersCount > $objectParametersCount; } - protected function requestHasBody() + protected function requestHasBody(): bool { $parameters = $this->data['paths'][$this->uri][$this->method]['parameters']; - $bodyParamExisted = Arr::where($parameters, function ($value, $key) { + $bodyParamExisted = Arr::where($parameters, function ($value) { return $value['name'] == 'body'; }); @@ -450,7 +447,7 @@ public function getConcreteRequest() $route->parametersWithoutNulls(), $instance, $method ); - return Arr::first($parameters, function ($key, $parameter) { + return Arr::first($parameters, function ($key) { return preg_match('/Request/', $key); }); } @@ -497,6 +494,7 @@ protected function saveSecurity() protected function addSecurityToOperation() { $security = &$this->data['paths'][$this->uri][$this->method]['security']; + if (empty($security)) { $security[] = [ "{$this->security}" => [] @@ -515,7 +513,7 @@ protected function getSummary($request, array $annotations) return $summary; } - protected function requestSupportAuth() + protected function requestSupportAuth(): bool { switch ($this->security) { case 'jwt' : @@ -527,7 +525,6 @@ protected function requestSupportAuth() } return !empty($header); - } protected function parseRequestName($request) @@ -559,10 +556,10 @@ protected function getResponseDescription($code) return $localDescription; } - return config("auto-doc.defaults.code-descriptions.{$code}", $defaultDescription); + return Arr::get($this->config, "defaults.code-descriptions.{$code}", $defaultDescription); } - protected function getActionName($uri) + protected function getActionName($uri): string { $action = preg_replace('[\/]', '', $uri); @@ -571,7 +568,7 @@ protected function getActionName($uri) protected function saveTempData() { - $exportFile = config('auto-doc.files.temporary'); + $exportFile = Arr::get($this->config, 'files.temporary'); $data = json_encode($this->data); file_put_contents($exportFile, $data); @@ -579,25 +576,27 @@ protected function saveTempData() public function saveProductionData() { - $this->dataCollector->saveData(); + $this->driver->saveData(); } public function getDocFileContent() { - return $this->dataCollector->getDocumentation(); + return $this->driver->getDocumentation(); } - protected function camelCaseToUnderScore($input) + protected function camelCaseToUnderScore($input): string { preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches); $ret = $matches[0]; + foreach ($ret as &$match) { $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match); } + return implode('_', $ret); } - protected function generateExample($properties) + protected function generateExample($properties): array { $parameters = $this->replaceObjectValues($this->request->all()); $example = []; @@ -607,7 +606,7 @@ protected function generateExample($properties) return $example; } - protected function replaceObjectValues($parameters) + protected function replaceObjectValues($parameters): array { $classNamesValues = [ File::class => '[uploaded_file]', @@ -629,12 +628,37 @@ protected function replaceObjectValues($parameters) return $returnParameters; } + protected function getClassAnnotations($class): array + { + $reflection = new ReflectionClass($class); + + $annotations = $reflection->getDocComment(); + + $blocks = explode("\n", $annotations); + + $result = []; + + foreach ($blocks as $block) { + if (Str::contains($block, '@')) { + $index = strpos($block, '@'); + $block = substr($block, $index); + $exploded = explode(' ', $block); + + $paramName = str_replace('@', '', array_shift($exploded)); + $paramValue = implode(' ', $exploded); + + $result[$paramName] = $paramValue; + } + } + + return $result; + } + /** * NOTE: All functions below are temporary solution for * this issue: https://github.com/OAI/OpenAPI-Specification/issues/229 * We hope swagger developers will resolve this problem in next release of Swagger OpenAPI * */ - protected function replaceNullValues($parameters, $types, &$example) { foreach ($parameters as $parameter => $value) { @@ -683,30 +707,4 @@ protected function prepareInfo($info) return $info; } - - protected function getClassAnnotations($class): array - { - $reflection = new ReflectionClass($class); - - $annotations = $reflection->getDocComment(); - - $blocks = explode("\n", $annotations); - - $result = []; - - foreach ($blocks as $block) { - if (Str::contains($block, '@')) { - $index = strpos($block, '@'); - $block = substr($block, $index); - $exploded = explode(' ', $block); - - $paramName = str_replace('@', '', array_shift($exploded)); - $paramValue = implode(' ', $exploded); - - $result[$paramName] = $paramValue; - } - } - - return $result; - } }