Skip to content

Commit

Permalink
Use devizzent/cebe-php-openapi to be compatible with OpenApi 3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
scaytrase committed Feb 18, 2023
1 parent f6af720 commit 6772c57
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 56 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"php": ">=7.2",
"ext-bcmath": "*",
"ext-json": "*",
"cebe/php-openapi": "^1.6",
"devizzent/cebe-php-openapi": "^1.0",
"league/uri": "^6.3",
"psr/cache": "^1.0 || ^2.0 || ^3.0",
"psr/http-message": "^1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function validate(OperationAddress $addr, MessageInterface $message): voi

// 0. Multipart body message MUST be described with a set of object properties
if ($schema->type !== CebeType::OBJECT) {
throw TypeMismatch::becauseTypeDoesNotMatch('object', $schema->type);
throw TypeMismatch::becauseTypeDoesNotMatch(['object'], $schema->type);
}

// 1. Parse message body
Expand Down
2 changes: 1 addition & 1 deletion src/PSR7/Validators/BodyValidator/MultipartValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public function validate(OperationAddress $addr, MessageInterface $message): voi

// 0. Multipart body message MUST be described with a set of object properties
if ($schema->type !== CebeType::OBJECT) {
throw TypeMismatch::becauseTypeDoesNotMatch('object', $schema->type);
throw TypeMismatch::becauseTypeDoesNotMatch(['object'], $schema->type);
}

if ($message->getBody()->getSize()) {
Expand Down
4 changes: 2 additions & 2 deletions src/PSR7/Validators/SerializedParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public function deserialize($value)
if ($this->isJsonContentType()) {
// Value MUST be a string.
if (! is_string($value)) {
throw TypeMismatch::becauseTypeDoesNotMatch('string', $value);
throw TypeMismatch::becauseTypeDoesNotMatch(['string'], $value);
}

$decodedValue = json_decode($value, true);
Expand Down Expand Up @@ -172,7 +172,7 @@ protected function convertToSerializationStyle($value, ?CebeSchema $schema)
}

if (! is_iterable($value)) {
throw TypeMismatch::becauseTypeDoesNotMatch('iterable', $value);
throw TypeMismatch::becauseTypeDoesNotMatch(['iterable'], $value);
}

foreach ($value as &$val) {
Expand Down
10 changes: 6 additions & 4 deletions src/Schema/Exception/TypeMismatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@
namespace League\OpenAPIValidation\Schema\Exception;

use function gettype;
use function implode;
use function sprintf;

// Validation for 'type' keyword failed against a given data
class TypeMismatch extends KeywordMismatch
{
/**
* @param mixed $value
* @param string[] $expected
* @param mixed $value
*
* @return TypeMismatch
*/
public static function becauseTypeDoesNotMatch(string $expected, $value): self
public static function becauseTypeDoesNotMatch(array $expected, $value): self
{
$exception = new self(sprintf("Value expected to be '%s', '%s' given.", $expected, gettype($value)));
$exception->data = $value;
$exception = new self(sprintf("Value expected to be '%s', but '%s' given.", implode(', ', $expected), gettype($value)));
$exception->data = $value;
$exception->keyword = 'type';

return $exception;
Expand Down
10 changes: 9 additions & 1 deletion src/Schema/Keywords/Nullable.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

use League\OpenAPIValidation\Schema\Exception\KeywordMismatch;

use function in_array;
use function is_string;

class Nullable extends BaseKeyword
{
/**
Expand All @@ -17,8 +20,13 @@ class Nullable extends BaseKeyword
*/
public function validate($data, bool $nullable): void
{
if (! $nullable && ($data === null)) {
if (! $nullable && ($data === null) && ! $this->nullableByType()) {
throw KeywordMismatch::fromKeyword('nullable', $data, 'Value cannot be null');
}
}

public function nullableByType(): bool
{
return ! is_string($this->parentSchema->type) && in_array('null', $this->parentSchema->type);
}
}
116 changes: 73 additions & 43 deletions src/Schema/Keywords/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use League\OpenAPIValidation\Schema\Exception\TypeMismatch;
use League\OpenAPIValidation\Schema\TypeFormats\FormatsContainer;
use RuntimeException;
use TypeError;

use function class_exists;
use function is_array;
Expand All @@ -32,51 +33,80 @@ class Type extends BaseKeyword
* An instance matches successfully if its primitive type is one of the
* types defined by keyword. Recall: "number" includes "integer".
*
* @param mixed $data
* @param mixed $data
* @param string|string[] $types
*
* @throws TypeMismatch
*/
public function validate($data, string $type, ?string $format = null): void
public function validate($data, $types, ?string $format = null): void
{
switch ($type) {
case CebeType::OBJECT:
if (! is_object($data) && ! (is_array($data) && ArrayHelper::isAssoc($data)) && $data !== []) {
throw TypeMismatch::becauseTypeDoesNotMatch(CebeType::OBJECT, $data);
}

break;
case CebeType::ARRAY:
if (! is_array($data) || ArrayHelper::isAssoc($data)) {
throw TypeMismatch::becauseTypeDoesNotMatch('array', $data);
}

break;
case CebeType::BOOLEAN:
if (! is_bool($data)) {
throw TypeMismatch::becauseTypeDoesNotMatch(CebeType::BOOLEAN, $data);
}

break;
case CebeType::NUMBER:
if (is_string($data) || ! is_numeric($data)) {
throw TypeMismatch::becauseTypeDoesNotMatch(CebeType::NUMBER, $data);
}

break;
case CebeType::INTEGER:
if (! is_int($data)) {
throw TypeMismatch::becauseTypeDoesNotMatch(CebeType::INTEGER, $data);
}

break;
case CebeType::STRING:
if (! is_string($data)) {
throw TypeMismatch::becauseTypeDoesNotMatch(CebeType::STRING, $data);
}

break;
default:
throw InvalidSchema::becauseTypeIsNotKnown($type);
if (! is_array($types) && ! is_string($types)) {
throw new TypeError('$types only can be array or string');
}

if (! is_array($types)) {
$types = [$types];
}

$matchedType = false;
foreach ($types as $type) {
switch ($type) {
case CebeType::OBJECT:
if (! is_object($data) && ! (is_array($data) && ArrayHelper::isAssoc($data)) && $data !== []) {
break;
}

$matchedType = $type;
break;
case CebeType::ARRAY:
if (! is_array($data) || ArrayHelper::isAssoc($data)) {
break;
}

$matchedType = $type;
break;
case CebeType::BOOLEAN:
if (! is_bool($data)) {
break;
}

$matchedType = $type;
break;
case CebeType::NUMBER:
if (is_string($data) || ! is_numeric($data)) {
break;
}

$matchedType = $type;
break;
case CebeType::INTEGER:
if (! is_int($data)) {
break;
}

$matchedType = $type;
break;
case CebeType::STRING:
if (! is_string($data)) {
break;
}

$matchedType = $type;
break;
case CebeType::NULL:
if ($data !== null) {
break;
}

$matchedType = $type;
break;
default:
throw InvalidSchema::becauseTypeIsNotKnown($type);
}
}

if ($matchedType === false) {
throw TypeMismatch::becauseTypeDoesNotMatch($types, $data);
}

// 2. Validate format now
Expand All @@ -85,7 +115,7 @@ public function validate($data, string $type, ?string $format = null): void
return;
}

$formatValidator = FormatsContainer::getFormat($type, $format); // callable or FQCN
$formatValidator = FormatsContainer::getFormat($matchedType, $format); // callable or FQCN
if ($formatValidator === null) {
return;
}
Expand All @@ -99,7 +129,7 @@ public function validate($data, string $type, ?string $format = null): void
}

if (! $formatValidator($data)) {
throw FormatMismatch::fromFormat($format, $data, $type);
throw FormatMismatch::fromFormat($format, $data, $matchedType);
}
}
}
2 changes: 1 addition & 1 deletion tests/PSR7/BaseValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protected function makeGoodResponse(string $path, string $method): ResponseInter
{
switch ($method . ' ' . $path) {
case 'get /path1':
$body = ['propA' => 1];
$body = ['propA' => 1, 'propD' => [1, 'string', null]];

return (new Response())
->withHeader('Content-Type', 'application/json')
Expand Down
2 changes: 1 addition & 1 deletion tests/PSR7/Validators/SerializedParameterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function testDeserializeThrowsSchemaMismatchExceptionIfValueIsNotStringWh
$subject = new SerializedParameter($this->createMock(Schema::class), 'application/json');

$this->expectException(SchemaMismatch::class);
$this->expectExceptionMessage("Value expected to be 'string', 'array' given");
$this->expectExceptionMessage("Value expected to be 'string', but 'array' given");

$subject->deserialize(['green', 'red']);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/stubs/api.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
openapi: 3.0.2
openapi: 3.1.0
info:
title: Weather API
version: 0.0.1
Expand Down
7 changes: 7 additions & 0 deletions tests/stubs/schemas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ components:
type: array
items:
type: string
propD:
type: array
items:
type:
- string
- integer
- 'null'
required:
- propA
- propB
Expand Down

0 comments on commit 6772c57

Please sign in to comment.