-
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
1,363 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
186 changes: 186 additions & 0 deletions
186
samples/server/petstore/php-slim4/lib/Mock/OpenApiDataMockerMiddleware.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.