Skip to content
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
11 changes: 11 additions & 0 deletions src/JsonSchemaAwareCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);

namespace EventEngine\JsonSchema;

use EventEngine\Schema\TypeSchema;

interface JsonSchemaAwareCollection
{
public static function __itemSchema(): TypeSchema;
}
67 changes: 67 additions & 0 deletions src/JsonSchemaAwareCollectionLogic.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);

namespace EventEngine\JsonSchema;

use EventEngine\Data\ImmutableRecord;
use EventEngine\JsonSchema\RecordLogic\TypeDetector;
use EventEngine\Schema\TypeSchema;

trait JsonSchemaAwareCollectionLogic
{
private static function __itemType(): ?string
{
return null;
}

public static function __itemSchema(): TypeSchema
{
if(null === self::$__itemSchema) {
$itemType = self::__itemType();

if(null === $itemType) {
return JsonSchema::any();
}

if(self::isScalarType($itemType)) {
return JsonSchema::schemaFromScalarPhpType($itemType, false);
}

self::$__itemSchema = TypeDetector::getTypeFromClass($itemType, self::__allowNestedSchema());
}

return self::$__itemSchema;
}

private static function isScalarType(string $type): bool
{
switch ($type) {
case ImmutableRecord::PHP_TYPE_STRING:
case ImmutableRecord::PHP_TYPE_INT:
case ImmutableRecord::PHP_TYPE_FLOAT:
case ImmutableRecord::PHP_TYPE_BOOL:
return true;
default:
return false;
}
}

/**
* If item type is a class and that class implements JsonSchemaAwareRecord the resulting JsonSchema
* for that type can either be a TypeRef (no nested schema allowed - default logic) or an object schema derived
* from JsonSchemaAwareRecord::__schema (enabled by returning true from the method)
*
* @return bool
*/
private static function __allowNestedSchema(): bool
{
return false;
}

/**
* Static item schema cache
*
* @var TypeSchema
*/
private static $__itemSchema;
}
2 changes: 1 addition & 1 deletion src/JsonSchemaAwareRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ public static function __type(): string;
* @return TypeSchema JSON Schema of the type
*/
public static function __schema(): TypeSchema;
}
}
49 changes: 34 additions & 15 deletions src/JsonSchemaAwareRecordLogic.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use EventEngine\Data\ImmutableRecord;
use EventEngine\Data\ImmutableRecordLogic;
use EventEngine\JsonSchema\Exception\InvalidArgumentException;
use EventEngine\JsonSchema\RecordLogic\TypeDetector;
use EventEngine\Schema\TypeSchema;

trait JsonSchemaAwareRecordLogic
Expand All @@ -30,6 +31,28 @@ public static function __schema(): TypeSchema
return self::generateSchemaFromPropTypeMap();
}

/**
* Override method to return a list property keys that are optional
*
* @return array
*/
private static function __optionalProperties(): array
{
return [];
}

/**
* If a property type is a class and that class implements JsonSchemaAwareRecord the resulting JsonSchema
* for that type can either be a TypeRef (no nested schema allowed - default logic) or an object schema derived
* from JsonSchemaAwareRecord::__schema (enabled by returning true from the method)
*
* @return bool
*/
private static function __allowNestedSchema(): bool
{
return false;
}

/**
* @param array $arrayPropTypeMap Map of array property name to array item type
* @return Type
Expand Down Expand Up @@ -70,20 +93,26 @@ private static function generateSchemaFromPropTypeMap(array $arrayPropTypeMap =
} elseif ($arrayItemType === ImmutableRecord::PHP_TYPE_ARRAY) {
throw new InvalidArgumentException("Array item type of property $prop must not be 'array', only a scalar type or an existing class can be used as array item type.");
} else {
$arrayItemSchema = JsonSchema::typeRef(self::getTypeFromClass($arrayItemType));
$arrayItemSchema = self::getTypeFromClass($arrayItemType);
}

$props[$prop] = JsonSchema::array($arrayItemSchema);
} else {
$props[$prop] = JsonSchema::typeRef(self::getTypeFromClass($type));
$props[$prop] = self::getTypeFromClass($type);
}

if ($isNullable) {
$props[$prop] = JsonSchema::nullOr($props[$prop]);
}
}

self::$__schema = JsonSchema::object($props);
$optionalProps = [];
foreach (self::__optionalProperties() as $optProp) {
$optionalProps[$optProp] = $props[$optProp];
unset($props[$optProp]);
}

self::$__schema = JsonSchema::object($props, $optionalProps);
}

return self::$__schema;
Expand All @@ -94,19 +123,9 @@ private static function convertClassToTypeName(string $class): string
return \substr(\strrchr($class, '\\'), 1);
}

private static function getTypeFromClass(string $classOrType): string
private static function getTypeFromClass(string $classOrType): Type
{
if (! \class_exists($classOrType)) {
return $classOrType;
}

$refObj = new \ReflectionClass($classOrType);

if ($refObj->implementsInterface(ImmutableRecord::class)) {
return \call_user_func([$classOrType, '__type']);
}

return self::convertClassToTypeName($classOrType);
return TypeDetector::getTypeFromClass($classOrType, self::__allowNestedSchema());
}

/**
Expand Down
66 changes: 66 additions & 0 deletions src/RecordLogic/TypeDetector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);

namespace EventEngine\JsonSchema\RecordLogic;

use EventEngine\JsonSchema\JsonSchema;
use EventEngine\JsonSchema\JsonSchemaAwareCollection;
use EventEngine\JsonSchema\JsonSchemaAwareRecord;
use EventEngine\JsonSchema\Type;

final class TypeDetector
{
public static function getTypeFromClass(string $classOrType, bool $allowNestedSchema = true): Type
{
if (! \class_exists($classOrType)) {
return JsonSchema::typeRef($classOrType);
}

$refObj = new \ReflectionClass($classOrType);

if ($refObj->implementsInterface(JsonSchemaAwareRecord::class)) {

if($allowNestedSchema) {
return \call_user_func([$classOrType, '__schema']);
}

return new Type\TypeRef(\call_user_func([$classOrType, '__type']));
}

if($refObj->implementsInterface(JsonSchemaAwareCollection::class)) {
return JsonSchema::array(\call_user_func([$classOrType, '__itemSchema']));
}

if($scalarSchemaType = self::determineScalarTypeIfPossible($classOrType)) {
return $scalarSchemaType;
}

return self::convertClassToType($classOrType);
}

private static function determineScalarTypeIfPossible(string $class): ?Type
{
if(is_callable([$class, 'fromString'])) {
return JsonSchema::string();
}

if(is_callable([$class, 'fromInt'])) {
return JsonSchema::integer();
}

if(is_callable([$class, 'fromFloat'])) {
return JsonSchema::float();
}

if(is_callable([$class, 'fromBool'])) {
return JsonSchema::boolean();
}

return null;
}

private static function convertClassToType(string $class): Type
{
return new Type\TypeRef(\substr(\strrchr($class, '\\'), 1));
}
}
11 changes: 11 additions & 0 deletions tests/BasicTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);

namespace EventEngineTest\JsonSchema;

use PHPUnit\Framework\TestCase;

class BasicTestCase extends TestCase
{

}
Loading