Skip to content

Commit

Permalink
Merge ab9bed2 into 2480a71
Browse files Browse the repository at this point in the history
  • Loading branch information
josefbenjac committed Jan 25, 2019
2 parents 2480a71 + ab9bed2 commit 81fd747
Show file tree
Hide file tree
Showing 64 changed files with 4,389 additions and 1,612 deletions.
291 changes: 243 additions & 48 deletions .docs/README.md
@@ -1,14 +1,27 @@
# Apitte\OpenApi
# Apitte - OpenApi plugin

## Content
- offers several ways to define [OpenAPI Specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md) (including generated)
- adds php objects / classes for better manipulation with OpenApi schema
- adds SwaggerUi panel to Tracy

- [Installation - how to register a plugin](#installation)
- [Configuration - all options](#configuration)
- [Usage - controller showtime](#usage)
## Content

- [Installation - how to register plugin](#installation)
- [Usage](#usage)
- [SchemaBuilder - how to create schema](#schemabuilder)
- [Definitions - how to define schema](#definitions)
- [Config](#config)
- [External Files](#external-files)
- [OpenApi Annotations](#openapi-annotations-experimental)
- [Core Annotations](#core-annotations)
- [Entity Annotations](#entity-annotations)
- [Custom - how to write custom definition](#custom-definition)
- [SwaggerUI Tracy panel - how to configure panel](#tracy-swaggerui-panel)
- [OpenApi Controller Endpoint - how to provide schema in api](#openapi-controller-endpoint)

## Installation

This plugin requires [Apitte/Core](https://github.com/apitte/core) library.
This plugin requires [apitte/core](https://github.com/apitte/core) library.

At first you have to register the main extension.

Expand All @@ -25,69 +38,251 @@ api:
Apitte\OpenApi\DI\OpenApiPlugin:
```

## Configuration
## Usage

You can configure Swagger UI with a few optional parameters.
### SchemaBuilder

```yaml
api:
plugins:
Apitte\OpenApi\DI\OpenApiPlugin:
swagger:
url: null # default url
expansion: list # list|full|none
filter: true # true|false|string
title: My API v2
You can find new service in DIC, called SchemaBuilder.

It contains default set of definitions.

When you run `build()`, these definitions are **merged together** and result is `OpenApi` object.

```php
$openApi = $schemaBuilder->build(); // OpenApi object
```

## Usage
You have objecs, so you can use.

```php
$version = $openApi->getInfo()->getVersion(); // string
$data = $openApi->toArray(); // array
```

### Definitions

There are many ways how to define open api schema.

You can write **raw** OpenApi.

- [Config](#config)
- [External Files](#external-files)
- [OpenApi Annotations](#openapi-annotations-experimental)

Or you can let the plugin do for you in **dynamic** way.

Let say you would like to display application's OpenAPI in some swagger gui. At first you
have to make a controller, secondly inject the `OpenApiService` and create a schema.
- [Core Annotations](#core-annotations)
- [Entity Annotations](#entity-annotations)

At least send the response with generated schema.
Also you can write your **own** definition.

- [Custom](#custom-definition)

-----------------
#### Config

You can easily define whole schema (or part) directly in extension config.

```yaml
Apitte\OpenApi\DI\OpenApiPlugin:
definition:
openapi: "3.0.2"
info:
title: My awesome OpenApi specification
version: "1.0.0"
...
```

#### External Files

Define whole OpenApi schema (or part) in external files.

```yaml
services:
- App\Controllers\OpenApiController
Apitte\OpenApi\DI\OpenApiPlugin:
files:
- %appDir%/openApi/petstore.neon
- %appDir%/openApi/petstoreExtended.yaml
- %appDir%/openApi/petstoreAdmin.json
```

Supported types are `neon`, `yaml` and `json`.

#### OpenApi-Annotations **(Experimental)**

This definition comes from core, but use only `OpenApi` annotation.

```php
namespace App\Controllers;
/**
* @Controller
* @ControllerPath("/")
* @OpenApi("
* openapi: '4.0.3'
* info:
* title: Defined by controller annotation
* version: '1.0.0'"
*)
*/
final class HomeController extends BaseV1Controller
{

/**
* @Path("/")
* @Method("GET")
* @OpenApi("
* summary: Defined specific endpoint
* operationId: listPets
* tags:
* - pets
* parameters:
* -
* name: limit
* in: query
* description: 'How many items to return at one time (max 100)'
* required: false
* schema:
* type: integer
* format: int32
* responses:
* '200':
* description: A paged array of pets
* headers:
* x-next:
* description: A link to the next page of responses
* schema:
* type: string
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Pets'
* default:
* description: unexpected error
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* ")
*/
public function index(ApiRequest $request, ApiResponse $response): ApiResponse
{
return $response->withEntity(ArrayEntity::from(['data' => ['Welcome']]));
}

}
```

use Apitte\Core\Annotation\Controller\Controller;
use Apitte\Core\Annotation\Controller\Method;
use Apitte\Core\Annotation\Controller\Path;
use Apitte\Core\Annotation\Controller\RootPath;
use Apitte\Core\Http\ApiRequest;
use Apitte\Core\Http\ApiResponse;
use Apitte\Core\UI\Controller\IController;
use Apitte\OpenApi\OpenApiService;
#### Core-Annotations

This definition is based on Doctrine Annotation, which are parts of core.

```php

/**
* @Controller
* @RootPath("/openapi")
* @ControllerPath("/users")
* @Tag(value="User")
*/
final class OpenApiController implements IController
final class UserDetailController extends BaseV1Controller
{

/** @var OpenApiService @inject */
public $openApiService;
/**
* @Path("/create")
* @Method("POST")
* @Request(required="true", description="Sample request")
* @Responses({
* @Response(code="200", description="Success"),
* @Response(code="404", description="Not found")
* })
*/
public function detail(ApiRequest $request): array
{
return [];
}

}
```

/**
* @Path("/schema")
* @Method("GET")
*/
public function index(ApiRequest $request, ApiResponse $response): ApiResponse
{
$schema = $this->openApiService->createSchema();
#### Entity-Annotations

return $response
->writeJsonBody($schema->toArray())
->withAddedHeader('Access-Control-Allow-Origin', '*');
}
Same as CoreDefinition but it use `entity` in `RequestBody` & `Response` annotation.

```php
/**
* @Controller
* @ControllerPath("/users")
*/
final class UserDetailController extends BaseV1Controller
{

/**
* @Path("/create")
* @Method("POST")
* @Request(required="true", description="Sample request", entity="App\Controllers\Entity\User")
* @Responses({
* @Response(code="200", description="Success", entity="App\Controllers\Entity\User"),
* @Response(code="404", description="Not found")
* })
*/
public function detail(ApiRequest $request): array
{
return [];
}

}
```

At the end, open your browser and locate to `localhost/<api-project>/openapi/schema`.
Entity is loaded by reflection, it loads all public properties using `EntityAdapter`.

You can redefine entity adapter by interface.


#### Custom Definition

If you need, you can add your definition using `IDefinition` interface.

### Tracy SwaggerUI Panel

You can configure Swagger UI with a few optional parameters.

```yaml
Apitte\OpenApi\DI\OpenApiPlugin:
swaggerUi:
url: null # default url
expansion: list # list|full|none
filter: true # true|false|string
title: My API v2
```

### OpenApi Controller Endpoint

You will probably need to provide your open api schema outside from your app.

Create controller for this case.

```php
/**
* @Controller
* @ControllerPath("/open-api")
*/
final class OpenApiController
{

/** @var ISchemaBuilder */
private $schemaBuilder;

public function __construct(ISchemaBuilder $schemaBuilder)
{
$this->schemaBuilder = $schemaBuilder;
}

/**
* @Path("/")
* @Method("GET")
*/
public function index(ApiRequest $request, ApiResponse $response): ApiResponse
{
$openApi = $this->schemaBuilder->build();
return $response->writeJsonBody($openApi->toArray());
}

}
```
21 changes: 18 additions & 3 deletions composer.json
Expand Up @@ -14,17 +14,32 @@
{
"name": "Milan Felix Šulc",
"homepage": "https://f3l1x.io"
},
{
"name": "Josef Benjač",
"homepage": "https://josefbenjac.com"
}
],
"require": {
"php": ">= 7.1",
"apitte/core": "^0.4.0",
"apitte/core": "dev-master as 0.4.0",
"tracy/tracy": "~2.4.14 || ~2.5.0",
"nette/di": "~2.4.12"
"nette/di": "~2.4.12",
"nette/utils": "~2.5.3"
},
"repositories": [
{
"type": "vcs",
"url": "git@github.com:benijo/core.git"
}
],
"suggest": {
"symfony/yaml": "Allows yaml definition"
},
"require-dev": {
"ninjify/qa": "^0.8.0",
"ninjify/nunjuck": "^0.2.0"
"ninjify/nunjuck": "^0.2.0",
"symfony/yaml": "^4.2"
},
"conflict": {
"nette/utils": "<2.5.2"
Expand Down
14 changes: 8 additions & 6 deletions ruleset.xml
Expand Up @@ -2,7 +2,8 @@
<ruleset name="Contributte">
<!-- Contributte Coding Standard -->
<rule ref="./vendor/ninjify/coding-standard/contributte.xml">
<exclude name="SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedProperty"/><!-- Schema is not implemented completely -->
<!-- Schema is not implemented completely -->
<exclude name="SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedProperty"/>
</rule>

<!-- Specific rules -->
Expand All @@ -14,13 +15,14 @@
</properties>
</rule>

<rule ref="PSR1.Classes.ClassDeclaration.MissingNamespace">
<exclude-pattern>*tests/cases/*</exclude-pattern>
</rule>
<rule ref="PSR1.Classes.ClassDeclaration.MissingNamespace">
<exclude-pattern>*tests/cases/*</exclude-pattern>
</rule>

<rule ref="Squiz.Classes.ClassFileName.NoMatch">
<exclude-pattern>*tests/cases/*</exclude-pattern>
</rule>
<exclude-pattern>*tests/cases/*</exclude-pattern>
</rule>


<!-- Exclude folders -->
<exclude-pattern>/tests/tmp</exclude-pattern>
Expand Down

0 comments on commit 81fd747

Please sign in to comment.