Skip to content

Commit d8dd6ec

Browse files
committed
Add Helper + FT/TT
1 parent 254c297 commit d8dd6ec

File tree

14 files changed

+1508
-0
lines changed

14 files changed

+1508
-0
lines changed

src/.gitkeep

Whitespace-only changes.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
namespace Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper;
3+
4+
use Symfony\Component\Validator\Constraint;
5+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\TypeDoc;
6+
7+
/**
8+
* Class ConstraintPayloadDocHelper
9+
*/
10+
class ConstraintPayloadDocHelper
11+
{
12+
/** Use this key in constraint payload to define parameter documentation */
13+
const PAYLOAD_DOCUMENTATION_KEY = 'documentation';
14+
const PAYLOAD_DOCUMENTATION_TYPE_KEY = 'type';
15+
const PAYLOAD_DOCUMENTATION_REQUIRED_KEY = 'required';
16+
const PAYLOAD_DOCUMENTATION_NULLABLE_KEY = 'nullable';
17+
const PAYLOAD_DOCUMENTATION_DESCRIPTION_KEY = 'description';
18+
const PAYLOAD_DOCUMENTATION_DEFAULT_KEY = 'default';
19+
const PAYLOAD_DOCUMENTATION_EXAMPLE_KEY = 'example';
20+
21+
/**
22+
* @param TypeDoc $doc
23+
* @param Constraint $constraint
24+
*/
25+
public function appendPayloadDoc(TypeDoc $doc, Constraint $constraint)
26+
{
27+
if (!$this->hasPayloadDoc($constraint)) {
28+
return;
29+
}
30+
31+
$doc->setRequired(
32+
$this->getPayloadDocValue($constraint, self::PAYLOAD_DOCUMENTATION_REQUIRED_KEY) ?? $doc->isRequired()
33+
);
34+
$doc->setNullable(
35+
$this->getPayloadDocValue($constraint, self::PAYLOAD_DOCUMENTATION_NULLABLE_KEY) ?? $doc->isNullable()
36+
);
37+
38+
$doc->setExample(
39+
$this->getPayloadDocValue($constraint, self::PAYLOAD_DOCUMENTATION_EXAMPLE_KEY)
40+
);
41+
$doc->setDefault(
42+
$this->getPayloadDocValue($constraint, self::PAYLOAD_DOCUMENTATION_DEFAULT_KEY)
43+
);
44+
45+
$description = $this->getPayloadDocValue($constraint, self::PAYLOAD_DOCUMENTATION_DESCRIPTION_KEY);
46+
if (null !== $description) {
47+
$doc->setDescription($description);
48+
}
49+
}
50+
51+
/**
52+
* @param Constraint $constraint
53+
*
54+
* @return mixed|null Null in case type does not exist
55+
*/
56+
public function getTypeIfExist(Constraint $constraint)
57+
{
58+
return $this->getPayloadDocValue($constraint, self::PAYLOAD_DOCUMENTATION_TYPE_KEY);
59+
}
60+
61+
/**
62+
* @param Constraint $constraint
63+
*
64+
* @return bool
65+
*/
66+
protected function hasPayloadDoc(Constraint $constraint) : bool
67+
{
68+
return is_array($constraint->payload)
69+
&& array_key_exists(self::PAYLOAD_DOCUMENTATION_KEY, $constraint->payload);
70+
}
71+
72+
/**
73+
* @param Constraint $constraint
74+
* @param string $documentationKey
75+
*
76+
* @return mixed|null Return null if value does not exist
77+
*/
78+
protected function getPayloadDocValue(Constraint $constraint, string $documentationKey)
79+
{
80+
return $this->hasPayloadDocKey($constraint, $documentationKey)
81+
? $constraint->payload[self::PAYLOAD_DOCUMENTATION_KEY][$documentationKey]
82+
: null
83+
;
84+
}
85+
86+
/**
87+
* @param Constraint $constraint
88+
* @param string $documentationKey
89+
*
90+
* @return bool
91+
*/
92+
protected function hasPayloadDocKey(Constraint $constraint, string $documentationKey) : bool
93+
{
94+
return array_key_exists(
95+
$documentationKey,
96+
$constraint->payload[self::PAYLOAD_DOCUMENTATION_KEY]
97+
)
98+
;
99+
}
100+
}

src/App/Helper/DocTypeHelper.php

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
<?php
2+
namespace Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper;
3+
4+
use Symfony\Component\Validator\Constraint;
5+
use Symfony\Component\Validator\Constraints as Assert;
6+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ArrayDoc;
7+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\BooleanDoc;
8+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\CollectionDoc;
9+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\FloatDoc;
10+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\IntegerDoc;
11+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\NumberDoc;
12+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ObjectDoc;
13+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ScalarDoc;
14+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\StringDoc;
15+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\TypeDoc;
16+
17+
/**
18+
* Class DocTypeHelper
19+
*/
20+
class DocTypeHelper
21+
{
22+
/** @var ConstraintPayloadDocHelper */
23+
private $constraintPayloadDocHelper;
24+
25+
/**
26+
* @param ConstraintPayloadDocHelper $constraintPayloadDocHelper
27+
*/
28+
public function __construct(ConstraintPayloadDocHelper $constraintPayloadDocHelper)
29+
{
30+
$this->constraintPayloadDocHelper = $constraintPayloadDocHelper;
31+
}
32+
33+
/**
34+
* @param Constraint[] $constraintList
35+
*
36+
* @return TypeDoc
37+
*/
38+
public function guess(array $constraintList) : TypeDoc
39+
{
40+
return $this->getDocFromTypeConstraintOrPayloadDocIfExist($constraintList)
41+
?? $this->guessTypeFromConstraintList($constraintList)
42+
?? new TypeDoc();
43+
}
44+
45+
/**
46+
* @param Constraint[] $constraintList
47+
*
48+
* @return TypeDoc|null
49+
*/
50+
private function getDocFromTypeConstraintOrPayloadDocIfExist(array $constraintList)
51+
{
52+
$doc = null;
53+
// Check if a Type constraint exist or if a constraint have a type documentation
54+
foreach ($constraintList as $constraint) {
55+
if (null !== ($typeFromPayload = $this->constraintPayloadDocHelper->getTypeIfExist($constraint))) {
56+
$doc = $this->getDocFromType($typeFromPayload);
57+
} elseif ($constraint instanceof Assert\Type) {
58+
$doc = $this->getDocFromType(strtolower($constraint->type));
59+
}
60+
61+
if (null !== $doc) {
62+
break;
63+
}
64+
}
65+
66+
return $doc;
67+
}
68+
69+
/**
70+
* @param array $constraintList
71+
*
72+
* @return TypeDoc|null
73+
*/
74+
private function guessTypeFromConstraintList(array $constraintList)
75+
{
76+
$doc = $abstractTypeFound = null;
77+
foreach ($constraintList as $constraint) {
78+
$doc = $this->guessTypeFromConstraint($constraint);
79+
if (null !== $doc) {
80+
if ($this->isAbstractType($doc)) {
81+
// Abstract type => continue to see if better type can be found
82+
$abstractTypeFound = $doc;
83+
$doc = null;
84+
} else {
85+
break;
86+
}
87+
}
88+
}
89+
// Try to fallback on abstractType if found
90+
if (null === $doc && null !== $abstractTypeFound) {
91+
$doc = $abstractTypeFound;
92+
}
93+
94+
return $doc;
95+
}
96+
97+
/**
98+
* @param Constraint $constraint
99+
*
100+
* @return TypeDoc|null
101+
*/
102+
private function guessTypeFromConstraint(Constraint $constraint)
103+
{
104+
// Try to guess primary types
105+
switch (true) {
106+
case $constraint instanceof Assert\Existence:
107+
return $this->guess($constraint->constraints);
108+
case $constraint instanceof Assert\Length:// << Applied on string only
109+
case $constraint instanceof Assert\Date: // << validator expect a string with specific format
110+
case $constraint instanceof Assert\Time: // << validator expect a string with specific format
111+
case $constraint instanceof Assert\Bic:
112+
case $constraint instanceof Assert\CardScheme:
113+
case $constraint instanceof Assert\Country:
114+
case $constraint instanceof Assert\Currency:
115+
case $constraint instanceof Assert\Email:
116+
case $constraint instanceof Assert\File:
117+
case $constraint instanceof Assert\Iban:
118+
case $constraint instanceof Assert\Ip:
119+
case $constraint instanceof Assert\Isbn:
120+
case $constraint instanceof Assert\Issn:
121+
case $constraint instanceof Assert\Language:
122+
case $constraint instanceof Assert\Locale:
123+
case $constraint instanceof Assert\Luhn:
124+
case $constraint instanceof Assert\Regex:
125+
case $constraint instanceof Assert\Url:
126+
case $constraint instanceof Assert\Uuid:
127+
return new StringDoc();
128+
case $constraint instanceof Assert\DateTime:
129+
if ('U' === $constraint->format) {
130+
return new ScalarDoc();// Don't know if value will be an number as string or as integer
131+
}
132+
133+
return new StringDoc();
134+
case $constraint instanceof Assert\IsTrue:
135+
case $constraint instanceof Assert\IsFalse:
136+
return new BooleanDoc();
137+
case $constraint instanceof Assert\Collection:
138+
// If only integer => array, else object
139+
$integerKeyList = array_filter(array_keys($constraint->fields), 'is_int');
140+
if (count($constraint->fields) === count($integerKeyList)) {
141+
return new ArrayDoc();
142+
}
143+
144+
return new ObjectDoc();
145+
case $constraint instanceof Assert\Choice
146+
&& true === $constraint->multiple: // << expect an array multiple choices
147+
case $constraint instanceof Assert\All: // << Applied only on array
148+
return new ArrayDoc();
149+
}
150+
151+
// If primary type is still not defined
152+
switch (true) {
153+
case $constraint instanceof Assert\Range:
154+
if (
155+
(null !== $constraint->min && is_float($constraint->min))
156+
|| (null !== $constraint->max && is_float($constraint->max))
157+
) {
158+
return new FloatDoc();
159+
}
160+
161+
return new NumberDoc();
162+
case $constraint instanceof Assert\GreaterThan:
163+
case $constraint instanceof Assert\GreaterThanOrEqual:
164+
case $constraint instanceof Assert\LessThan:
165+
case $constraint instanceof Assert\LessThanOrEqual:
166+
if (null !== $constraint->value && is_float($constraint->value)) {
167+
return new FloatDoc();
168+
}
169+
170+
return new NumberDoc();
171+
case $constraint instanceof Assert\Count:
172+
return new CollectionDoc();
173+
}
174+
175+
return null;
176+
}
177+
178+
/**
179+
* @param string $type
180+
*
181+
* @return TypeDoc|null
182+
*/
183+
private function getDocFromType(string $type)
184+
{
185+
switch (true) {
186+
case 'scalar' === $type:
187+
return new ScalarDoc();
188+
case 'string' === $type:
189+
return new StringDoc();
190+
case 'bool' === $type || 'boolean' === $type:
191+
return new BooleanDoc();
192+
case 'int' === $type || 'integer' === $type:
193+
return new IntegerDoc();
194+
case in_array($type, ['float', 'long', 'double', 'real', 'numeric']):
195+
return new FloatDoc();
196+
case 'array' === $type:
197+
return new ArrayDoc();
198+
case 'object' === $type:
199+
return new ObjectDoc();
200+
}
201+
202+
return null;
203+
}
204+
205+
/**
206+
* @param TypeDoc $doc
207+
*
208+
* @return bool
209+
*/
210+
private function isAbstractType(TypeDoc $doc) : bool
211+
{
212+
// use get_class to avoid inheritance issue
213+
$class = get_class($doc);
214+
215+
return CollectionDoc::class === $class
216+
|| NumberDoc::class === $class
217+
|| ScalarDoc::class === $class
218+
;
219+
}
220+
}

0 commit comments

Comments
 (0)