Yuriy Belenko 04ac754d3e [Slim4] Add object support to Data Mocker (#4853)
* [Slim4] Add new method to Mocker interface

* [Slim4] Add tests and implement mockObject method

* [Slim4] Accept items as object in mockArray method

* [Slim4] Add test for array of objects

* [Slim4] Refresh samples

* [Slim4] Remove abandoned debug comment

* [Slim4] Improve tests code coverage
2019-12-22 15:03:22 +08:00

466 lines
18 KiB
PHP

<?php
/**
* OpenApiDataMocker
*
* 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 OpenAPIServer\Mock\OpenApiDataMockerInterface as IMocker;
use StdClass;
use InvalidArgumentException;
/**
* OpenApiDataMocker Class Doc Comment
*
* @package OpenAPIServer\Mock
* @author OpenAPI Generator team
* @link https://github.com/openapitools/openapi-generator
*/
final class OpenApiDataMocker implements IMocker
{
/**
* Mocks OpenApi Data.
* @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#data-types
*
* @param $dataType string OpenApi data type. Use constants from OpenApiDataMockerInterface class
* @param $dataFormat string (optional) OpenApi data format
* @param $options array|null (optional) OpenApi data options
*
* @throws \InvalidArgumentException when invalid arguments passed
*
* @return mixed
*/
public function mock($dataType, $dataFormat = null, $options = [])
{
switch ($dataType) {
case IMocker::DATA_TYPE_INTEGER:
case IMocker::DATA_TYPE_NUMBER:
$minimum = $options['minimum'] ?? null;
$maximum = $options['maximum'] ?? null;
$exclusiveMinimum = $options['exclusiveMinimum'] ?? false;
$exclusiveMaximum = $options['exclusiveMaximum'] ?? false;
if ($dataType === IMocker::DATA_TYPE_INTEGER) {
return $this->mockInteger($dataFormat, $minimum, $maximum, $exclusiveMinimum, $exclusiveMaximum);
}
return $this->mockNumber($dataFormat, $minimum, $maximum, $exclusiveMinimum, $exclusiveMaximum);
case IMocker::DATA_TYPE_STRING:
$minLength = $options['minLength'] ?? 0;
$maxLength = $options['maxLength'] ?? null;
return $this->mockString($dataFormat, $minLength, $maxLength);
case IMocker::DATA_TYPE_BOOLEAN:
return $this->mockBoolean();
case IMocker::DATA_TYPE_ARRAY:
$items = $options['items'] ?? null;
$minItems = $options['minItems'] ?? 0;
$maxItems = $options['maxItems'] ?? null;
$uniqueItems = $options['uniqueItems'] ?? false;
return $this->mockArray($items, $minItems, $maxItems, $uniqueItems);
case IMocker::DATA_TYPE_OBJECT:
$properties = $options['properties'] ?? null;
$minProperties = $options['minProperties'] ?? 0;
$maxProperties = $options['maxProperties'] ?? null;
$additionalProperties = $options['additionalProperties'] ?? null;
$required = $options['required'] ?? null;
return $this->mockObject($properties, $minProperties, $maxProperties, $additionalProperties, $required);
default:
throw new InvalidArgumentException('"dataType" must be one of ' . implode(', ', [
IMocker::DATA_TYPE_INTEGER,
IMocker::DATA_TYPE_NUMBER,
IMocker::DATA_TYPE_STRING,
IMocker::DATA_TYPE_BOOLEAN,
IMocker::DATA_TYPE_ARRAY,
IMocker::DATA_TYPE_OBJECT,
]));
}
}
/**
* Shortcut to mock integer type
* Equivalent to mockData(DATA_TYPE_INTEGER);
*
* @param string|null $dataFormat (optional) int32 or int64
* @param number|null $minimum (optional) Default is 0
* @param number|null $maximum (optional) Default is mt_getrandmax()
* @param bool|null $exclusiveMinimum (optional) Default is false
* @param bool|null $exclusiveMaximum (optional) Default is false
*
* @throws \InvalidArgumentException when $maximum less than $minimum or invalid arguments provided
*
* @return int
*/
public function mockInteger(
$dataFormat = null,
$minimum = null,
$maximum = null,
$exclusiveMinimum = false,
$exclusiveMaximum = false
) {
return $this->getRandomNumber($minimum, $maximum, $exclusiveMinimum, $exclusiveMaximum, 0);
}
/**
* Shortcut to mock number type
* Equivalent to mockData(DATA_TYPE_NUMBER);
*
* @param string|null $dataFormat (optional) float or double
* @param number|null $minimum (optional) Default is 0
* @param number|null $maximum (optional) Default is mt_getrandmax()
* @param bool|null $exclusiveMinimum (optional) Default is false
* @param bool|null $exclusiveMaximum (optional) Default is false
*
* @throws \InvalidArgumentException when $maximum less than $minimum or invalid arguments provided
*
* @return float
*/
public function mockNumber(
$dataFormat = null,
$minimum = null,
$maximum = null,
$exclusiveMinimum = false,
$exclusiveMaximum = false
) {
return $this->getRandomNumber($minimum, $maximum, $exclusiveMinimum, $exclusiveMaximum, 4);
}
/**
* Shortcut to mock string type
* Equivalent to mockData(DATA_TYPE_STRING);
*
* @param string|null $dataFormat (optional) one of byte, binary, date, date-time, password
* @param int|null $minLength (optional) Default is 0
* @param int|null $maxLength (optional) Default is 100 chars
* @param array $enum (optional) This array should have at least one element.
* Elements in the array should be unique.
* @param string|null $pattern (optional) This string should be a valid regular expression, according to the ECMA 262 regular expression dialect.
* Recall: regular expressions are not implicitly anchored.
*
* @throws \InvalidArgumentException when invalid arguments passed
*
* @return string
*/
public function mockString(
$dataFormat = null,
$minLength = 0,
$maxLength = null,
$enum = null,
$pattern = null
) {
if ($enum !== null) {
if (
is_array($enum) === false
|| empty($enum)
|| count($enum) > count(array_unique($enum))
) {
throw new InvalidArgumentException('"enum" must be an array. This array should have at least one element. Elements in the array should be unique.');
}
// return random variant
return $enum[mt_rand(0, count($enum) - 1)];
}
if ($minLength !== 0 && $minLength !== null) {
if (is_int($minLength) === false) {
throw new InvalidArgumentException('"minLength" must be an integer');
} elseif ($minLength < 0) {
throw new InvalidArgumentException('"minLength" must be greater than, or equal to, 0');
}
} else {
$minLength = 0;
}
if ($maxLength !== null) {
if (is_int($maxLength) === false) {
throw new InvalidArgumentException('"maxLength" must be an integer');
} elseif ($maxLength < 0) {
throw new InvalidArgumentException('"maxLength" must be greater than, or equal to, 0');
}
} else {
// since we don't need huge texts by default, lets cut them down to 100 chars
$maxLength = 100;
}
if ($maxLength < $minLength) {
throw new InvalidArgumentException('"maxLength" value cannot be less than "minLength"');
}
return str_pad('', mt_rand($minLength, $maxLength), 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ', \STR_PAD_RIGHT);
}
/**
* Shortcut to mock boolean type
* Equivalent to mockData(DATA_TYPE_BOOLEAN);
*
* @return bool
*/
public function mockBoolean()
{
return (bool) mt_rand(0, 1);
}
/**
* Shortcut to mock array type
* Equivalent to mockData(DATA_TYPE_ARRAY);
*
* @param object|array $items Object or assoc array of described items
* @param int|null $minItems (optional) An array instance is valid against "minItems" if its size is greater than, or equal to, the value of this keyword.
* @param int|null $maxItems (optional) An array instance is valid against "maxItems" if its size is less than, or equal to, the value of this keyword
* @param bool|null $uniqueItems (optional) If it has boolean value true, the instance validates successfully if all of its elements are unique
*
* @throws \InvalidArgumentException when invalid arguments passed
*
* @return array
*/
public function mockArray(
$items,
$minItems = 0,
$maxItems = null,
$uniqueItems = false
) {
$arr = [];
$minSize = 0;
$maxSize = \PHP_INT_MAX;
if (
(is_array($items) === false && is_object($items) === false)
|| (is_array($items) && array_key_exists('type', $items) === false)
|| (is_object($items) && isset($items->type) === false)
) {
new InvalidArgumentException('"items" must be object or assoc array with "type" key');
}
if ($minItems !== null) {
if (is_integer($minItems) === false || $minItems < 0) {
throw new InvalidArgumentException('"mitItems" must be an integer. This integer must be greater than, or equal to, 0');
}
$minSize = $minItems;
}
if ($maxItems !== null) {
if (is_integer($maxItems) === false || $maxItems < 0) {
throw new InvalidArgumentException('"maxItems" must be an integer. This integer must be greater than, or equal to, 0.');
}
if ($maxItems < $minItems) {
throw new InvalidArgumentException('"maxItems" value cannot be less than "minItems"');
}
$maxSize = $maxItems;
}
$options = $this->extractSchemaProperties($items);
$dataType = $options['type'];
$dataFormat = $options['format'] ?? null;
// always genarate smallest possible array to avoid huge JSON responses
$arrSize = ($maxSize < 1) ? $maxSize : max($minSize, 1);
while (count($arr) < $arrSize) {
$arr[] = $this->mock($dataType, $dataFormat, $options);
}
return $arr;
}
/**
* Shortcut to mock object type.
* Equivalent to mockData(DATA_TYPE_OBJECT);
*
* @param object|array $properties Object or array of described properties
* @param int|null $minProperties (optional) An object instance is valid against "minProperties" if its number of properties is greater than, or equal to, the value of this keyword.
* @param int|null $maxProperties (optional) An object instance is valid against "maxProperties" if its number of properties is less than, or equal to, the value of this keyword.
* @param bool|object|array|null $additionalProperties (optional) If "additionalProperties" is true, validation always succeeds.
* If "additionalProperties" is false, validation succeeds only if the instance is an object and all properties on the instance were covered by "properties" and/or "patternProperties".
* If "additionalProperties" is an object, validate the value as a schema to all of the properties that weren't validated by "properties" nor "patternProperties".
* @param array|null $required (optional) This array MUST have at least one element. Elements of this array must be strings, and MUST be unique.
* An object instance is valid if its property set contains all elements in this array value.
*
* @throws \InvalidArgumentException when invalid arguments passed
*
* @return object
*/
public function mockObject(
$properties,
$minProperties = 0,
$maxProperties = null,
$additionalProperties = null,
$required = null
) {
$obj = new StdClass();
if (is_object($properties) === false && is_array($properties) === false) {
throw new InvalidArgumentException('The value of "properties" must be an array or object');
}
foreach ($properties as $propName => $propValue) {
if (is_object($propValue) === false && is_array($propValue) === false) {
throw new InvalidArgumentException('Each value of "properties" must be an array or object');
}
}
if ($minProperties !== null) {
if (is_integer($minProperties) === false || $minProperties < 0) {
throw new InvalidArgumentException('"minProperties" must be an integer. This integer must be greater than, or equal to, 0');
}
}
if ($maxProperties !== null) {
if (is_integer($maxProperties) === false || $maxProperties < 0) {
throw new InvalidArgumentException('"maxProperties" must be an integer. This integer must be greater than, or equal to, 0.');
}
if ($maxProperties < $minProperties) {
throw new InvalidArgumentException('"maxProperties" value cannot be less than "minProperties"');
}
}
if ($additionalProperties !== null) {
if (is_bool($additionalProperties) === false && is_object($additionalProperties) === false && is_array($additionalProperties) === false) {
throw new InvalidArgumentException('The value of "additionalProperties" must be a boolean or object or array.');
}
}
if ($required !== null) {
if (
is_array($required) === false
|| count($required) > count(array_unique($required))
) {
throw new InvalidArgumentException('The value of "required" must be an array. Elements of this array must be unique.');
}
foreach ($required as $requiredPropName) {
if (is_string($requiredPropName) === false) {
throw new InvalidArgumentException('Elements of "required" array must be strings');
}
}
}
foreach ($properties as $propName => $propValue) {
$options = $this->extractSchemaProperties($propValue);
$dataType = $options['type'];
$dataFormat = $options['dataFormat'] ?? null;
$obj->$propName = $this->mock($dataType, $dataFormat, $options);
}
return $obj;
}
/**
* @internal Extract OAS properties from array or object.
* @codeCoverageIgnore
*
* @param array|object $val Processed array or object
*
* @return array
*/
private function extractSchemaProperties($val)
{
$props = [
'type' => null,
'format' => null,
];
foreach (
[
'type',
'format',
'minimum',
'maximum',
'exclusiveMinimum',
'exclusiveMaximum',
'minLength',
'maxLength',
'pattern',
'enum',
'items',
'minItems',
'maxItems',
'uniqueItems',
'properties',
'minProperties',
'maxProperties',
'additionalProperties',
'required',
'example',
] as $propName
) {
if (is_array($val) && array_key_exists($propName, $val)) {
$props[$propName] = $val[$propName];
} elseif (is_object($val) && isset($val->$propName)) {
$props[$propName] = $val->$propName;
}
}
return $props;
}
/**
* @internal
* @codeCoverageIgnore
*
* @return float|int
*/
protected function getRandomNumber(
$minimum = null,
$maximum = null,
$exclusiveMinimum = false,
$exclusiveMaximum = false,
$maxDecimals = 4
) {
$min = 0;
$max = mt_getrandmax();
if ($minimum !== null) {
if (is_numeric($minimum) === false) {
throw new InvalidArgumentException('"minimum" must be a number');
}
$min = $minimum;
}
if ($maximum !== null) {
if (is_numeric($maximum) === false) {
throw new InvalidArgumentException('"maximum" must be a number');
}
$max = $maximum;
}
if ($exclusiveMinimum !== false) {
if (is_bool($exclusiveMinimum) === false) {
throw new InvalidArgumentException('"exclusiveMinimum" must be a boolean');
} elseif ($minimum === null) {
throw new InvalidArgumentException('If "exclusiveMinimum" is present, "minimum" must also be present');
}
$min += 1;
}
if ($exclusiveMaximum !== false) {
if (is_bool($exclusiveMaximum) === false) {
throw new InvalidArgumentException('"exclusiveMaximum" must be a boolean');
} elseif ($maximum === null) {
throw new InvalidArgumentException('If "exclusiveMaximum" is present, "maximum" must also be present');
}
$max -= 1;
}
if ($max < $min) {
throw new InvalidArgumentException('"maximum" value cannot be less than "minimum"');
}
if ($maxDecimals > 0) {
return round($min + mt_rand() / mt_getrandmax() * ($max - $min), $maxDecimals);
}
return mt_rand($min, $max);
}
}