Skip to content

Allow passing prepared objects instead of array definition #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/SpecBaseObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public function __construct(array $data)
}
$this->_properties[$property] = [];
foreach ($data[$property] as $key => $item) {
if ($type[1] === 'string') {
if ($type[1] === Type::STRING) {
if (!is_string($item)) {
$this->_errors[] = "property '$property' must be map<string, string>, but entry '$key' is of type " . \gettype($item) . '.';
}
Expand Down Expand Up @@ -127,9 +127,14 @@ public function __construct(array $data)
*/
private function instantiate($type, $data)
{
if (isset($data['$ref'])) {
if ($data instanceof $type) {
return $data;
}

if (is_array($data) && isset($data['$ref'])) {
return new Reference($data, $type);
}

try {
return new $type($data);
} catch (\TypeError $e) {
Expand Down
12 changes: 11 additions & 1 deletion src/spec/MediaType.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,17 @@ public function __construct(array $data)

if (!empty($encoding)) {
foreach ($encoding as $property => $encodingData) {
$encoding[$property] = new Encoding($encodingData, $this->schema->properties[$property] ?? null);
if ($encodingData instanceof Encoding) {
$encoding[$property] = $encodingData;
} elseif (is_array($encodingData)) {
$encoding[$property] = new Encoding($encodingData, $this->schema->properties[$property] ?? null);
} else {
$givenType = gettype($encodingData);
if ($givenType === 'object') {
$givenType = get_class($encodingData);
}
throw new TypeErrorException(sprintf('Encoding MUST be either array or Encoding object, "%s" given', $givenType));
}
}
$this->encoding = $encoding;
}
Expand Down
2 changes: 1 addition & 1 deletion src/spec/PathItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public function resolveReferences(ReferenceContext $context = null)
$pathItem = $this->_ref->resolve($context);
$this->_ref = null;
// The properties of the referenced structure are merged with the local Path Item Object.
foreach(self::attributes() as $attribute => $type) {
foreach (self::attributes() as $attribute => $type) {
if (!isset($pathItem->$attribute)) {
continue;
}
Expand Down
12 changes: 10 additions & 2 deletions src/spec/Paths.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,24 @@ class Paths implements SpecObjectInterface, DocumentContextInterface, ArrayAcces

/**
* Create an object from spec data.
* @param array $data spec data read from YAML or JSON
* @param PathItem[]|array[] $data spec data read from YAML or JSON
* @throws TypeErrorException in case invalid data is supplied.
*/
public function __construct(array $data)
{
foreach ($data as $path => $object) {
if ($object === null) {
$this->_paths[$path] = null;
} else {
} elseif (is_array($object)) {
$this->_paths[$path] = new PathItem($object);
} elseif ($object instanceof PathItem) {
$this->_paths[$path] = $object;
} else {
$givenType = gettype($object);
if ($givenType === 'object') {
$givenType = get_class($object);
}
throw new TypeErrorException(sprintf('Path MUST be either array or PathItem object, "%s" given', $givenType));
}
}
}
Expand Down
17 changes: 13 additions & 4 deletions src/spec/Responses.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use ArrayAccess;
use ArrayIterator;
use cebe\openapi\DocumentContextInterface;
use cebe\openapi\exceptions\TypeErrorException;
use cebe\openapi\exceptions\UnresolvableReferenceException;
use cebe\openapi\json\JsonPointer;
use cebe\openapi\ReferenceContext;
Expand Down Expand Up @@ -37,19 +38,27 @@ class Responses implements SpecObjectInterface, DocumentContextInterface, ArrayA

/**
* Create an object from spec data.
* @param array $data spec data read from YAML or JSON
* @throws \cebe\openapi\exceptions\TypeErrorException in case invalid data is supplied.
* @param Response[]|Reference[]|array[] $data spec data read from YAML or JSON
* @throws TypeErrorException in case invalid data is supplied.
*/
public function __construct(array $data)
{
foreach ($data as $statusCode => $response) {
// From Spec: This field MUST be enclosed in quotation marks (for example, "200") for compatibility between JSON and YAML.
$statusCode = (string) $statusCode;
if (preg_match('~^(?:default|[1-5](?:[0-9][0-9]|XX))$~', $statusCode)) {
if (isset($response['$ref'])) {
if ($response instanceof Response || $response instanceof Reference) {
$this->_responses[$statusCode] = $response;
} elseif (is_array($response) && isset($response['$ref'])) {
$this->_responses[$statusCode] = new Reference($response, Response::class);
} else {
} elseif (is_array($response)) {
$this->_responses[$statusCode] = new Response($response);
} else {
$givenType = gettype($response);
if ($givenType === 'object') {
$givenType = get_class($response);
}
throw new TypeErrorException(sprintf('Response MUST be either an array, a Response or a Reference object, "%s" given', $givenType));
}
} else {
$this->_errors[] = "Responses: $statusCode is not a valid HTTP status code.";
Expand Down
6 changes: 6 additions & 0 deletions src/spec/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ public function __construct(array $data)
$e
);
}
} elseif (!($data['additionalProperties'] instanceof Schema || is_bool($data['additionalProperties']))) {
$givenType = gettype($data['additionalProperties']);
if ($givenType === 'object') {
$givenType = get_class($data['additionalProperties']);
}
throw new TypeErrorException(sprintf('Schema::$additionalProperties MUST be either array, boolean or a Schema object, "%s" given', $givenType));
}
}
parent::__construct($data);
Expand Down
52 changes: 51 additions & 1 deletion tests/spec/MediaTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,54 @@ public function testRead()
$this->assertEquals($expectedCat, $mediaType->examples['cat']->value);

}
}

public function testCreateionFromObjects()
{
$mediaType = new MediaType([
'schema' => new \cebe\openapi\spec\Schema([
'type' => \cebe\openapi\spec\Type::OBJECT,
'properties' => [
'id' => new \cebe\openapi\spec\Schema(['type' => 'string', 'format' => 'uuid']),
'profileImage' => new \cebe\openapi\spec\Schema(['type' => 'string', 'format' => 'binary']),
],
]),
'encoding' => [
'id' => [],
'profileImage' => new \cebe\openapi\spec\Encoding([
'contentType' => 'image/png, image/jpeg',
'headers' => [
'X-Rate-Limit-Limit' => new \cebe\openapi\spec\Header([
'description' => 'The number of allowed requests in the current period',
'schema' => new \cebe\openapi\spec\Schema(['type' => 'integer']),
]),
],
]),
],
]);

// default value should be extracted
$this->assertEquals('text/plain', $mediaType->encoding['id']->contentType);
// object should be passed.
$this->assertInstanceOf(\cebe\openapi\spec\Encoding::class, $mediaType->encoding['profileImage']);
}

public function badEncodingProvider()
{
yield [['encoding' => ['id' => 'foo']], 'Encoding MUST be either array or Encoding object, "string" given'];
yield [['encoding' => ['id' => 42]], 'Encoding MUST be either array or Encoding object, "integer" given'];
yield [['encoding' => ['id' => false]], 'Encoding MUST be either array or Encoding object, "boolean" given'];
yield [['encoding' => ['id' => new stdClass()]], 'Encoding MUST be either array or Encoding object, "stdClass" given'];
// The last one can be supported in future, but now SpecBaseObjects::__construct() requires array explicitly
}

/**
* @dataProvider badEncodingProvider
*/
public function testPathsCanNotBeCreatedFromBullshit($config, $expectedException)
{
$this->expectException(\cebe\openapi\exceptions\TypeErrorException::class);
$this->expectExceptionMessage($expectedException);

new MediaType($config);
}
}
44 changes: 44 additions & 0 deletions tests/spec/PathTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use cebe\openapi\spec\PathItem;
use cebe\openapi\spec\Paths;
use cebe\openapi\spec\Reference;
use cebe\openapi\spec\Response;
use cebe\openapi\spec\Responses;

/**
* @covers \cebe\openapi\spec\Paths
Expand Down Expand Up @@ -64,6 +66,48 @@ public function testRead()
}
}

public function testCreateionFromObjects()
{
$paths = new Paths([
'/pets' => new PathItem([
'get' => new Operation([
'responses' => new Responses([
200 => new Response(['description' => 'A list of pets.']),
404 => ['description' => 'The pets list is gone 🙀'],
])
])
])
]);

$this->assertTrue($paths->hasPath('/pets'));
$this->assertInstanceOf(PathItem::class, $paths->getPath('/pets'));
$this->assertInstanceOf(PathItem::class, $paths['/pets']);
$this->assertInstanceOf(Operation::class, $paths->getPath('/pets')->get);

$this->assertSame('A list of pets.', $paths->getPath('/pets')->get->responses->getResponse(200)->description);
$this->assertSame('The pets list is gone 🙀', $paths->getPath('/pets')->get->responses->getResponse(404)->description);
}

public function badPathsConfigProvider()
{
yield [['/pets' => 'foo'], 'Path MUST be either array or PathItem object, "string" given'];
yield [['/pets' => 42], 'Path MUST be either array or PathItem object, "integer" given'];
yield [['/pets' => false], 'Path MUST be either array or PathItem object, "boolean" given'];
yield [['/pets' => new stdClass()], 'Path MUST be either array or PathItem object, "stdClass" given'];
// The last one can be supported in future, but now SpecBaseObjects::__construct() requires array explicitly
}

/**
* @dataProvider badPathsConfigProvider
*/
public function testPathsCanNotBeCreatedFromBullshit($config, $expectedException)
{
$this->expectException(\cebe\openapi\exceptions\TypeErrorException::class);
$this->expectExceptionMessage($expectedException);

new Paths($config);
}

public function testInvalidPath()
{
/** @var $paths Paths */
Expand Down
31 changes: 31 additions & 0 deletions tests/spec/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,35 @@ public function testResponseCodes()
$this->assertFalse($result);

}

public function testCreateionFromObjects()
{
$responses = new Responses([
200 => new Response(['description' => 'A list of pets.']),
404 => ['description' => 'The pets list is gone 🙀'],
]);

$this->assertSame('A list of pets.', $responses->getResponse(200)->description);
$this->assertSame('The pets list is gone 🙀', $responses->getResponse(404)->description);
}

public function badResponseProvider()
{
yield [['200' => 'foo'], 'Response MUST be either an array, a Response or a Reference object, "string" given'];
yield [['200' => 42], 'Response MUST be either an array, a Response or a Reference object, "integer" given'];
yield [['200' => false], 'Response MUST be either an array, a Response or a Reference object, "boolean" given'];
yield [['200' => new stdClass()], 'Response MUST be either an array, a Response or a Reference object, "stdClass" given'];
// The last one can be supported in future, but now SpecBaseObjects::__construct() requires array explicitly
}

/**
* @dataProvider badResponseProvider
*/
public function testPathsCanNotBeCreatedFromBullshit($config, $expectedException)
{
$this->expectException(\cebe\openapi\exceptions\TypeErrorException::class);
$this->expectExceptionMessage($expectedException);

new Responses($config);
}
}
47 changes: 47 additions & 0 deletions tests/spec/SchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,51 @@ public function testDiscriminator()
'monster' => 'https://gigantic-server.com/schemas/Monster/schema.json',
], $schema->discriminator->mapping);
}

public function testCreateionFromObjects()
{
$schema = new Schema([
'allOf' => [
new Schema(['type' => 'integer']),
new Schema(['type' => 'string']),
],
'additionalProperties' => new Schema([
'type' => 'object',
]),
'discriminator' => new Discriminator([
'mapping' => ['A' => 'B'],
]),
]);

$this->assertSame('integer', $schema->allOf[0]->type);
$this->assertSame('string', $schema->allOf[1]->type);
$this->assertInstanceOf(Schema::class, $schema->additionalProperties);
$this->assertSame('object', $schema->additionalProperties->type);
$this->assertSame(['A' => 'B'], $schema->discriminator->mapping);
}


public function badSchemaProvider()
{
yield [['properties' => ['a' => 'foo']], 'Unable to instantiate cebe\openapi\spec\Schema Object with data \'foo\''];
yield [['properties' => ['a' => 42]], 'Unable to instantiate cebe\openapi\spec\Schema Object with data \'42\''];
yield [['properties' => ['a' => false]], 'Unable to instantiate cebe\openapi\spec\Schema Object with data \'\''];
yield [['properties' => ['a' => new stdClass()]], "Unable to instantiate cebe\openapi\spec\Schema Object with data 'stdClass Object\n(\n)\n'"];

yield [['additionalProperties' => 'foo'], 'Schema::$additionalProperties MUST be either array, boolean or a Schema object, "string" given'];
yield [['additionalProperties' => 42], 'Schema::$additionalProperties MUST be either array, boolean or a Schema object, "integer" given'];
yield [['additionalProperties' => new stdClass()], 'Schema::$additionalProperties MUST be either array, boolean or a Schema object, "stdClass" given'];
// The last one can be supported in future, but now SpecBaseObjects::__construct() requires array explicitly
}

/**
* @dataProvider badSchemaProvider
*/
public function testPathsCanNotBeCreatedFromBullshit($config, $expectedException)
{
$this->expectException(\cebe\openapi\exceptions\TypeErrorException::class);
$this->expectExceptionMessage($expectedException);

new Schema($config);
}
}