Skip to content

Feature/shorthand support #1

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 4 commits into from
Oct 28, 2020
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
20 changes: 20 additions & 0 deletions src/Exception/ExceptionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/**
* @see https://github.com/open-code-modeling/json-schema-to-php for the canonical source repository
* @copyright https://github.com/open-code-modeling/json-schema-to-php/blob/master/COPYRIGHT.md
* @license https://github.com/open-code-modeling/json-schema-to-php/blob/master/LICENSE.md MIT License
*/

declare(strict_types = 1);

namespace OpenCodeModeling\JsonSchemaToPhp\Exception;

/**
* Base exception interface
*
* All exceptions must implements this exception to catch exceptions of this library
*/
interface ExceptionInterface
{
}
115 changes: 115 additions & 0 deletions src/Exception/InvalidShorthand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

/**
* @see https://github.com/open-code-modeling/json-schema-to-php for the canonical source repository
* @copyright https://github.com/open-code-modeling/json-schema-to-php/blob/master/COPYRIGHT.md
* @license https://github.com/open-code-modeling/json-schema-to-php/blob/master/LICENSE.md MIT License
*/

declare(strict_types=1);

namespace OpenCodeModeling\JsonSchemaToPhp\Exception;

final class InvalidShorthand extends LogicException
{
/**
* @var array<string, mixed>
*/
private array $shorthand;

/**
* @param string $schemaProperty
* @param array<string, mixed> $shorthand
* @return static
*/
public static function cannotParseProperty(string $schemaProperty, array $shorthand): self
{
$self = new static(
\sprintf(
'I tried to parse JSONSchema for property: "%s", but it is neither a string nor an object.',
$schemaProperty
)
);
$self->shorthand = $shorthand;

return $self;
}

/**
* @param array<string, mixed> $shorthand $shorthand
* @return static
*/
public static function emptyString(array $shorthand): self
{
$self = new static('Shorthand contains an empty or non string property. Cannot deal with that!');
$self->shorthand = $shorthand;

return $self;
}

/**
* @param array<string, mixed> $shorthand $shorthand
* @return static
*/
public static function refNotString(array $shorthand): self
{
$self = new static(
'Detected a top level shorthand reference using a "$ref" property, but the value of the property is not a string.'
);
$self->shorthand = $shorthand;

return $self;
}

/**
* @param array<string, mixed> $shorthand $shorthand
* @return static
*/
public static function itemsNotString(array $shorthand): self
{
$self = new static(
'Detected a top level shorthand array using an "$items" property, but the value of the property is not a string.'
);
$self->shorthand = $shorthand;

return $self;
}

/**
* @param array<string, mixed> $shorthand $shorthand
* @return static
*/
public static function refWithOtherProperties(array $shorthand): self
{
$self = new static(
'Shorthand contains a top level ref property "$ref", but it is not the only property!'
. ' A top level reference cannot have other properties then "$ref".'
);
$self->shorthand = $shorthand;

return $self;
}

/**
* @param array<string, mixed> $shorthand $shorthand
* @return static
*/
public static function itemsWithOtherProperties(array $shorthand): self
{
$self = new static(
'Shorthand %s contains a top level array property "$items", but it is not the only property!'
. ' A top level array cannot have other properties then "$items".'
);
$self->shorthand = $shorthand;

return $self;
}

/**
* @return array<string, mixed>
*/
public function shorthand(): array
{
return $this->shorthand;
}
}
17 changes: 17 additions & 0 deletions src/Exception/LogicException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

/**
* @see https://github.com/open-code-modeling/json-schema-to-php for the canonical source repository
* @copyright https://github.com/open-code-modeling/json-schema-to-php/blob/master/COPYRIGHT.md
* @license https://github.com/open-code-modeling/json-schema-to-php/blob/master/LICENSE.md MIT License
*/

declare(strict_types = 1);

namespace OpenCodeModeling\JsonSchemaToPhp\Exception;

use LogicException as PhpLogicException;

class LogicException extends PhpLogicException implements ExceptionInterface
{
}
22 changes: 22 additions & 0 deletions src/Exception/RuntimeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/**
* @see https://github.com/open-code-modeling/json-schema-to-php for the canonical source repository
* @copyright https://github.com/open-code-modeling/json-schema-to-php/blob/master/COPYRIGHT.md
* @license https://github.com/open-code-modeling/json-schema-to-php/blob/master/LICENSE.md MIT License
*/

declare(strict_types = 1);

namespace OpenCodeModeling\JsonSchemaToPhp\Exception;

use RuntimeException as PhpRuntimeException;

/**
* Runtime exception
*
* Use this exception if the code has not the capacity to handle the request.
*/
class RuntimeException extends PhpRuntimeException implements ExceptionInterface
{
}
185 changes: 185 additions & 0 deletions src/Shorthand/Shorthand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php

/**
* @see https://github.com/open-code-modeling/json-schema-to-php for the canonical source repository
* @copyright https://github.com/open-code-modeling/json-schema-to-php/blob/master/COPYRIGHT.md
* @license https://github.com/open-code-modeling/json-schema-to-php/blob/master/LICENSE.md MIT License
*/

declare(strict_types=1);

namespace OpenCodeModeling\JsonSchemaToPhp\Shorthand;

use OpenCodeModeling\JsonSchemaToPhp\Exception\InvalidShorthand;
use OpenCodeModeling\JsonSchemaToPhp\Exception\LogicException;

final class Shorthand
{
/**
* @param array<string, mixed> $shorthand
* @return array<string, mixed>
*/
public static function convertToJsonSchema(array $shorthand): array
{
$schema = [
'type' => 'object',
'properties' => [

],
'required' => [],
'additionalProperties' => false,
];

foreach ($shorthand as $property => $shorthandDefinition) {
if (! \is_string($property) || empty($property)) {
throw InvalidShorthand::emptyString($shorthand);
}
$schemaProperty = $property;

switch (true) {
case \mb_substr($property, -1) === '?':
$schemaProperty = \mb_substr($property, 0, -1);
break;
case $schemaProperty === '$ref':
if (\count($shorthand) > 1) {
throw InvalidShorthand::refWithOtherProperties($shorthand);
}

if (! \is_string($shorthandDefinition)) {
throw InvalidShorthand::refNotString($shorthand);
}

$shorthandDefinition = \str_replace('#/definitions/', '', $shorthandDefinition);

return [
'$ref' => "#/definitions/$shorthandDefinition",
];
case $schemaProperty === '$items':
if (\count($shorthand) > 1) {
throw InvalidShorthand::itemsWithOtherProperties($shorthand);
}

if (! \is_string($shorthandDefinition)) {
throw InvalidShorthand::itemsNotString($shorthand);
}

if (\mb_substr($shorthandDefinition, -2) !== '[]') {
$shorthandDefinition .= '[]';
}

return self::convertShorthandStringToJsonSchema($shorthandDefinition);
case $schemaProperty === '$title':
$schema['title'] = $shorthandDefinition;
continue 2;
default:
$schema['required'][] = $schemaProperty;
break;
}

if (\is_array($shorthandDefinition)) {
$schema['properties'][$schemaProperty] = self::convertToJsonSchema($shorthandDefinition);
} elseif (\is_string($shorthandDefinition)) {
$schema['properties'][$schemaProperty] = self::convertShorthandStringToJsonSchema($shorthandDefinition);
} else {
throw InvalidShorthand::cannotParseProperty($schemaProperty, $shorthand);
}
}

return $schema;
}

/**
* @param string $shorthandStr
* @return array<string, mixed>
*/
private static function convertShorthandStringToJsonSchema(string $shorthandStr): array
{
if ($shorthandStr === '') {
return ['type' => 'string'];
}

$parts = \explode('|', $shorthandStr);

if ($parts[0] === 'enum') {
return ['enum' => \array_slice($parts, 1)];
}

if (\mb_substr($parts[0], -2) === '[]') {
$itemsParts = [\mb_substr($parts[0], 0, -2)];
\array_push($itemsParts, ...\array_slice($parts, 1));

return [
'type' => 'array',
'items' => self::convertShorthandStringToJsonSchema(\implode('|', $itemsParts)),
];
}

switch ($parts[0]) {
case 'string':
case 'integer':
case 'number':
case 'boolean':
$type = $parts[0];

if (isset($parts[1]) && $parts[1] === 'null') {
$type = [$type, 'null'];

\array_splice($parts, 1, 1);
}

$schema = ['type' => $type];

if (\count($parts) > 1) {
$parts = \array_slice($parts, 1);

foreach ($parts as $part) {
[$validationKey, $validationValue] = self::parseShorthandValidation($part);

$schema[$validationKey] = $validationValue;
}
}

return $schema;
default:
return [
'$ref' => '#/definitions/'.$parts[0],
];
}
}

/**
* @param string $shorthandValidation
* @return array<mixed>
*/
private static function parseShorthandValidation(string $shorthandValidation): array
{
$parts = \explode(':', $shorthandValidation);

if (\count($parts) !== 2) {
throw new LogicException(\sprintf(
'Cannot parse shorthand validation: "%s". Expected format "validationKey:value". Please check again!',
$shorthandValidation
));
}

[$validationKey, $value] = $parts;

if ($value === 'true') {
return [$validationKey, true];
}

if ($value === 'false') {
return [$validationKey, false];
}

if ((string) (int) $value === $value) {
return [$validationKey, (int) $value];
}

if ((string) (float) $value === $value) {
return [$validationKey, (float) $value];
}

return [$validationKey, $value];
}
}
13 changes: 12 additions & 1 deletion src/Type/ArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace OpenCodeModeling\JsonSchemaToPhp\Type;

final class ArrayType implements TypeDefinition
final class ArrayType implements TypeDefinition, TitleAware
{
use PopulateRequired;

Expand All @@ -36,6 +36,7 @@ final class ArrayType implements TypeDefinition
protected ?string $name = null;
protected bool $isRequired = false;
protected bool $nullable = false;
protected ?string $title = null;

private function __construct()
{
Expand Down Expand Up @@ -231,6 +232,16 @@ public function additionalItems(): ?TypeSet
return $this->additionalItems;
}

public function title(): ?string
{
return $this->title;
}

public function setTitle(string $title): void
{
$this->title = $title;
}

public static function type(): string
{
return self::TYPE_ARRAY;
Expand Down
Loading