Skip to content

Commit

Permalink
[Slim4] Refresh samples
Browse files Browse the repository at this point in the history
  • Loading branch information
ybelenko committed Jan 14, 2020
1 parent c3083d6 commit fbf03cf
Show file tree
Hide file tree
Showing 7 changed files with 1,363 additions and 7 deletions.
4 changes: 4 additions & 0 deletions samples/server/petstore/php-slim4/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Command | Target
`$ composer test` | All tests
`$ composer test-apis` | Apis tests
`$ composer test-models` | Models tests
`$ composer test-mock` | Mock feature tests
`$ composer test-utils` | Utils tests

#### Config

Expand Down Expand Up @@ -99,6 +101,8 @@ Switch on option in `./index.php`:
+++ $app->addErrorMiddleware(true, true, true);
```

## [Mock Server Documentation](./docs/MockServer.md)

## API Endpoints

All URIs are relative to *http://petstore.swagger.io:80/v2*
Expand Down
135 changes: 135 additions & 0 deletions samples/server/petstore/php-slim4/docs/MockServer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# php-base - PHP Slim 4 Server library for OpenAPI Petstore

## Mock Server Documentation

### Mocker Options
To enable mock server uncomment these lines in `index.php` config file:

```php
/**
* Mocker Middleware options.
*/
$config['mockerOptions'] = [
'dataMocker' => new OpenApiDataMocker(),

'getMockResponseCallback' => function (ServerRequestInterface $request, array $responses) {
// check if client clearly asks for mocked response
if (
$request->hasHeader('X-OpenAPIServer-Mock')
&& $request->getHeader('X-OpenAPIServer-Mock')[0] === 'ping'
) {
if (array_key_exists('default', $responses)) {
return $responses['default'];
}

// return first response
return $responses[array_key_first($responses)];
}

return false;
},

'afterCallback' => function ($request, $response) {
// mark mocked response to distinguish real and fake responses
return $response->withHeader('X-OpenAPIServer-Mock', 'pong');
},
];
```

* `dataMocker` is mocker class instance. To create custom data mocker extend `OpenAPIServer\Mock\OpenApiDataMockerInterface`.
* `getMockResponseCallback` is callback before mock data generation. Above example shows how to enable mock feature for only requests with `{{X-OpenAPIServer}}-mock: ping` HTTP header. Adjust requests filtering to fit your project requirements. This function must return single response schema from `$responses` array parameter. **Mock feature is disabled when callback returns anything beside array.**
* `afterCallback` is callback executed after mock data generation. Most obvious use case is append specific HTTP headers to distinguish real and fake responses. **This function must always return response instance.**

### Supported features

All data types supported except specific string formats: `email`, `uuid`, `password` which are poorly implemented.

#### Data Types Support

| Data Type | Data Format | Supported |
|:---------:|:-----------:|:------------------:|
| `integer` | `int32` | :white_check_mark: |
| `integer` | `int64` | :white_check_mark: |
| `number` | `float` | :white_check_mark: |
| `number` | `double` | |
| `string` | `byte` | :white_check_mark: |
| `string` | `binary` | :white_check_mark: |
| `boolean` | | :white_check_mark: |
| `string` | `date` | :white_check_mark: |
| `string` | `date-time` | :white_check_mark: |
| `string` | `password` | :white_check_mark: |
| `string` | `email` | :white_check_mark: |
| `string` | `uuid` | :white_check_mark: |

#### Data Options Support

| Data Type | Option | Supported |
|:-----------:|:----------------------:|:------------------:|
| `string` | `minLength` | :white_check_mark: |
| `string` | `maxLength` | :white_check_mark: |
| `string` | `enum` | :white_check_mark: |
| `string` | `pattern` | |
| `integer` | `minimum` | :white_check_mark: |
| `integer` | `maximum` | :white_check_mark: |
| `integer` | `exclusiveMinimum` | :white_check_mark: |
| `integer` | `exclusiveMaximum` | :white_check_mark: |
| `number` | `minimum` | :white_check_mark: |
| `number` | `maximum` | :white_check_mark: |
| `number` | `exclusiveMinimum` | :white_check_mark: |
| `number` | `exclusiveMaximum` | :white_check_mark: |
| `array` | `items` | :white_check_mark: |
| `array` | `additionalItems` | |
| `array` | `minItems` | :white_check_mark: |
| `array` | `maxItems` | :white_check_mark: |
| `array` | `uniqueItems` | |
| `object` | `properties` | :white_check_mark: |
| `object` | `maxProperties` | |
| `object` | `minProperties` | |
| `object` | `patternProperties` | |
| `object` | `additionalProperties` | |
| `object` | `required` | |
| `*` | `$ref` | :white_check_mark: |
| `*` | `allOf` | |
| `*` | `anyOf` | |
| `*` | `oneOf` | |
| `*` | `not` | |

### Known Limitations

Avoid circular refs in your schema. Schema below can cause infinite loop and `Out of Memory` PHP error:
```yml
# ModelA has reference to ModelB while ModelB has reference to ModelA.
# Mock server will produce huge nested JSON example and ended with `Out of Memory` error.
definitions:
ModelA:
type: object
properties:
model_b:
$ref: '#/definitions/ModelB'
ModelB:
type: array
items:
$ref: '#/definitions/ModelA'
```
Don't ref scalar types, because generator will not produce models which mock server can find. So schema below will cause error:
```yml
# generated build contains only `OuterComposite` model class which referenced to not existed `OuterNumber`, `OuterString`, `OuterBoolean` classes
# mock server cannot mock `OuterComposite` model and throws exception
definitions:
OuterComposite:
type: object
properties:
my_number:
$ref: '#/definitions/OuterNumber'
my_string:
$ref: '#/definitions/OuterString'
my_boolean:
$ref: '#/definitions/OuterBoolean'
OuterNumber:
type: number
OuterString:
type: string
OuterBoolean:
type: boolean
```
32 changes: 32 additions & 0 deletions samples/server/petstore/php-slim4/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
require_once __DIR__ . '/vendor/autoload.php';

use OpenAPIServer\SlimRouter;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use OpenAPIServer\Mock\OpenApiDataMocker;

$config = [];

Expand Down Expand Up @@ -50,6 +53,35 @@
// 'error' => null,
];

/**
* Mocker Middleware options.
*/
$config['mockerOptions'] = [
// 'dataMocker' => new OpenApiDataMocker(),

// 'getMockResponseCallback' => function (ServerRequestInterface $request, array $responses) {
// // check if client clearly asks for mocked response
// if (
// $request->hasHeader('X-OpenAPIServer-Mock')
// && $request->getHeader('X-OpenAPIServer-Mock')[0] === 'ping'
// ) {
// if (array_key_exists('default', $responses)) {
// return $responses['default'];
// }

// // return first response
// return $responses[array_key_first($responses)];
// }

// return false;
// },

// 'afterCallback' => function ($request, $response) {
// // mark mocked response to distinguish real and fake responses
// return $response->withHeader('X-OpenAPIServer-Mock', 'pong');
// },
];

$router = new SlimRouter($config);
$app = $router->getSlimApp();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ public function mockObject(
foreach ($properties as $propName => $propValue) {
$options = $this->extractSchemaProperties($propValue);
$dataType = $options['type'];
$dataFormat = $options['dataFormat'] ?? null;
$dataFormat = $options['format'] ?? null;
$ref = $options['$ref'] ?? null;
$data = $this->mockFromRef($ref);
$obj->$propName = ($data) ? $data : $this->mock($dataType, $dataFormat, $options);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php

/**
* OpenApiDataMockerMiddleware
*
* PHP version 7.1
*
* @package OpenAPIServer
* @author OpenAPI Generator team
* @link https://github.com/openapitools/openapi-generator
*/

/**
* OpenAPI Petstore
*
* This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\
* The version of the OpenAPI document: 1.0.0
* Generated by: https://github.com/openapitools/openapi-generator.git
*/

/**
* NOTE: This class is auto generated by the openapi generator program.
* https://github.com/openapitools/openapi-generator
* Do not edit the class manually.
*/
namespace OpenAPIServer\Mock;

use Slim\Factory\AppFactory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use OpenAPIServer\Mock\OpenApiDataMockerInterface;
use InvalidArgumentException;

/**
* OpenApiDataMockerMiddleware Class Doc Comment
*
* @package OpenAPIServer\Mock
* @author OpenAPI Generator team
* @link https://github.com/openapitools/openapi-generator
*/
final class OpenApiDataMockerMiddleware implements MiddlewareInterface
{
/**
* @var OpenApiDataMockerInterface DataMocker.
*/
private $mocker;

/**
* @var array Array of responses schemas.
*/
private $responses;

/**
* @var callable|null Custom callback to select mocked response.
*/
private $getMockResponseCallback;

/**
* @var callable|null Custom after callback.
*/
private $afterCallback;

/**
* Class constructor.
*
* @param OpenApiDataMockerInterface $mocker DataMocker.
* @param array $responses Array of responses schemas.
* @param callable|null $getMockResponseCallback Custom callback to select mocked response.
* Mock feature is disabled when this argument is null.
* @example $getMockResponseCallback = function (ServerRequestInterface $request, array $responses) {
* // check if client clearly asks for mocked response
* if (
* $request->hasHeader('X-OpenAPIServer-Mock')
* && $request->header('X-OpenAPIServer-Mock')[0] === 'ping'
* ) {
* return $responses[array_key_first($responses)];
* }
* return false;
* };
* @param callable|null $afterCallback After callback.
* Function must return response instance.
* @example $afterCallback = function (ServerRequestInterface $request, ResponseInterface $response) {
* // mark mocked response to distinguish real and fake responses
* return $response->withHeader('X-OpenAPIServer-Mock', 'pong');
* };
*/
public function __construct(
OpenApiDataMockerInterface $mocker,
array $responses,
$getMockResponseCallback = null,
$afterCallback = null
) {
$this->mocker = $mocker;
$this->responses = $responses;
if (is_callable($getMockResponseCallback)) {
$this->getMockResponseCallback = $getMockResponseCallback;
} elseif ($getMockResponseCallback !== null) {
// wrong argument type
throw new InvalidArgumentException('\$getMockResponseCallback must be closure or null');
}

if (is_callable($afterCallback)) {
$this->afterCallback = $afterCallback;
} elseif ($afterCallback !== null) {
// wrong argument type
throw new InvalidArgumentException('\$afterCallback must be closure or null');
}
}

/**
* Parse incoming JSON input into a native PHP format
*
* @param ServerRequestInterface $request HTTP request
* @param RequestHandlerInterface $handler Request handler
*
* @return ResponseInterface HTTP response
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$customCallback = $this->getMockResponseCallback;
$customAfterCallback = $this->afterCallback;
$mockedResponse = (is_callable($customCallback)) ? $customCallback($request, $this->responses) : null;
if (
is_array($mockedResponse)
&& array_key_exists('code', $mockedResponse)
&& array_key_exists('jsonSchema', $mockedResponse)
) {
// response schema succesfully selected, we can mock it now
$statusCode = ($mockedResponse['code'] === 0) ? 200 : $mockedResponse['code'];
$contentType = '*/*';
$response = AppFactory::determineResponseFactory()->createResponse($statusCode);
$responseSchema = json_decode($mockedResponse['jsonSchema'], true);

if (is_array($responseSchema) && array_key_exists('headers', $responseSchema)) {
// response schema contains headers definitions, apply them one by one
foreach ($responseSchema['headers'] as $headerName => $headerDefinition) {
$response = $response->withHeader($headerName, $this->mocker->mockFromSchema($headerDefinition['schema']));
}
}

if (
is_array($responseSchema)
&& array_key_exists('content', $responseSchema)
&& !empty($responseSchema['content'])
) {
// response schema contains body definition
$responseContentSchema = null;
foreach ($responseSchema['content'] as $schemaContentType => $schemaDefinition) {
// we can respond in JSON format when any(*/*) content-type allowed
// or JSON(application/json) content-type specifically defined
if (
$schemaContentType === '*/*'
|| strtolower(substr($schemaContentType, 0, 16)) === 'application/json'
) {
$contentType = 'application/json';
$responseContentSchema = $schemaDefinition['schema'];
}
}

if ($contentType === 'application/json') {
$responseBody = $this->mocker->mockFromSchema($responseContentSchema);
$response->getBody()->write(json_encode($responseBody));
} else {
// notify developer that only application/json response supported so far
$response->getBody()->write('Mock feature supports only "application/json" content-type!');
}
}

// after callback applied only when mocked response schema has been selected
if (is_callable($customAfterCallback)) {
$response = $customAfterCallback($request, $response);
}

// no reason to execute following middlewares (auth, validation etc.)
// return mocked response and end connection
return $response
->withHeader('Content-Type', $contentType);
}

// no response selected, mock feature disabled
// execute following middlewares
return $handler->handle($request);
}
}
Loading

0 comments on commit fbf03cf

Please sign in to comment.