Skip to content

Commit 384c0c0

Browse files
committed
Add shorthand support
1 parent 4dde7ed commit 384c0c0

File tree

3 files changed

+423
-1
lines changed

3 files changed

+423
-1
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
}
3232
},
3333
"require": {
34-
"php": "^7.4 || ^8.0"
34+
"php": "^7.4 || ^8.0",
35+
"ext-json": "*"
3536
},
3637
"require-dev": {
3738
"jangregor/phpstan-prophecy": "^0.8.0",

src/Shorthand/Shorthand.php

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* @see https://github.com/open-code-modeling/json-schema-to-php for the canonical source repository
6+
* @copyright https://github.com/open-code-modeling/json-schema-to-php/blob/master/COPYRIGHT.md
7+
* @license https://github.com/open-code-modeling/json-schema-to-php/blob/master/LICENSE.md MIT License
8+
*/
9+
10+
namespace OpenCodeModeling\JsonSchemaToPhp\Shorthand;
11+
12+
use LogicException;
13+
use function array_push;
14+
use function array_slice;
15+
use function array_splice;
16+
use function count;
17+
use function explode;
18+
use function floatval;
19+
use function implode;
20+
use function intval;
21+
use function is_array;
22+
use function is_string;
23+
use function json_encode;
24+
use function mb_strlen;
25+
use function mb_substr;
26+
use function sprintf;
27+
use function str_replace;
28+
use function strlen;
29+
30+
final class Shorthand
31+
{
32+
public static function convertToJsonSchema(array $shorthand): array
33+
{
34+
$schema = [
35+
'type' => 'object',
36+
'properties' => [
37+
38+
],
39+
'required' => [],
40+
'additionalProperties' => false,
41+
];
42+
43+
foreach ($shorthand as $property => $shorthandDefinition) {
44+
if(!is_string($property) || empty($property)) {
45+
throw new LogicException(sprintf(
46+
'Shorthand %s contains an empty or non string property. Cannot deal with that!',
47+
json_encode($shorthand)
48+
));
49+
}
50+
51+
$schemaProperty = $property;
52+
53+
if(mb_substr($property, -1) === '?') {
54+
$schemaProperty = mb_substr($property, 0, strlen($property) - 1);
55+
} else if ($schemaProperty === '$ref') {
56+
if(count($shorthand) > 1) {
57+
throw new LogicException(sprintf(
58+
'Shorthand %s contains a top level ref property "$ref", but it is not the only property!
59+
\nA top level reference cannot have other properties then "$ref".',
60+
json_encode($shorthand)
61+
));
62+
}
63+
64+
if(!is_string($shorthandDefinition)) {
65+
throw new LogicException(sprintf(
66+
'Detected a top level shorthand reference using a "$ref" property, but the value of the property is not a string.',
67+
));
68+
}
69+
70+
$shorthandDefinition = str_replace('#/definitions/', '', $shorthandDefinition);
71+
72+
return [
73+
'$ref' => "#/definitions/$shorthandDefinition"
74+
];
75+
} else if ($schemaProperty === '$items') {
76+
if(count($shorthand) > 1) {
77+
throw new LogicException(sprintf(
78+
'Shorthand %s contains a top level array property "$items", but it is not the only property!
79+
\nA top level array cannot have other properties then "$items".',
80+
json_encode($shorthand)
81+
));
82+
}
83+
84+
if(!is_string($shorthandDefinition)) {
85+
throw new LogicException(sprintf(
86+
'Detected a top level shorthand array using an "$items" property, but the value of the property is not a string.',
87+
));
88+
}
89+
90+
if(mb_substr($shorthandDefinition, -2) !== '[]') {
91+
$shorthandDefinition .= '[]';
92+
}
93+
94+
return self::convertShorthandStringToJsonSchema($shorthandDefinition);
95+
} else if ($schemaProperty === '$title') {
96+
$schema['title'] = $shorthandDefinition;
97+
continue;
98+
} else {
99+
$schema['required'][] = $schemaProperty;
100+
}
101+
102+
if(is_array($shorthandDefinition)) {
103+
$schema['properties'][$schemaProperty] = self::convertToJsonSchema($shorthandDefinition);
104+
} else if (is_string($shorthandDefinition)) {
105+
$schema['properties'][$schemaProperty] = self::convertShorthandStringToJsonSchema($shorthandDefinition);
106+
} else {
107+
throw new LogicException(sprintf(
108+
'I tried to parse JSONSchema for property: "%s", but it is neither a string nor an object.',
109+
$schemaProperty
110+
));
111+
}
112+
}
113+
114+
return $schema;
115+
}
116+
117+
private static function convertShorthandStringToJsonSchema(string $shorthandStr): array
118+
{
119+
if($shorthandStr === '') {
120+
return ['type' => 'string'];
121+
}
122+
123+
$parts = explode('|', $shorthandStr);
124+
125+
if($parts[0] === 'enum') {
126+
return ['enum' => array_slice($parts, 1)];
127+
}
128+
129+
if(mb_substr($parts[0], -2) === '[]') {
130+
$itemsParts = [mb_substr($parts[0], 0, mb_strlen($parts[0]) - 2)];
131+
array_push($itemsParts, ...array_slice($parts, 1));
132+
133+
return [
134+
'type' => 'array',
135+
'items' => self::convertShorthandStringToJsonSchema(implode('|', $itemsParts)),
136+
];
137+
}
138+
139+
switch ($parts[0]) {
140+
case 'string':
141+
case 'integer':
142+
case 'number':
143+
case 'boolean':
144+
$type = $parts[0];
145+
146+
if(isset($parts[1]) && $parts[1] === 'null') {
147+
$type = [$type, 'null'];
148+
149+
array_splice($parts, 1, 1);
150+
}
151+
152+
$schema = ['type' => $type];
153+
154+
if(count($parts) > 1) {
155+
$parts = array_slice($parts, 1);
156+
157+
foreach ($parts as $part) {
158+
[$validationKey, $validationValue] = self::parseShorthandValidation($part);
159+
160+
$schema[$validationKey] = $validationValue;
161+
}
162+
}
163+
164+
return $schema;
165+
default:
166+
return [
167+
'$ref' => '#/definitions/'.$parts[0],
168+
];
169+
}
170+
}
171+
172+
private static function parseShorthandValidation(string $shorthandValidation): array
173+
{
174+
$parts = explode(':', $shorthandValidation);
175+
176+
if(count($parts) !== 2) {
177+
throw new LogicException(sprintf(
178+
'Cannot parse shorthand validation: "%s". Expected format "validationKey:value". Please check again!',
179+
$shorthandValidation
180+
));
181+
}
182+
183+
[$validationKey, $value] = $parts;
184+
185+
if($value === 'true') {
186+
return [$validationKey, true];
187+
}
188+
189+
if($value === 'false') {
190+
return [$validationKey, false];
191+
}
192+
193+
if((string)intval($value) === $value) {
194+
return [$validationKey, (int)$value];
195+
}
196+
197+
if((string)floatval($value) === $value) {
198+
return [$validationKey, (float)$value];
199+
}
200+
201+
return [$validationKey, $value];
202+
}
203+
}

0 commit comments

Comments
 (0)