Skip to content

Commit 0a806ca

Browse files
authored
Merge pull request #2 from yoanm/release/1.0.0-dev
Add Helper + FT/TT
2 parents 254c297 + 6b8a9a1 commit 0a806ca

File tree

16 files changed

+1523
-3
lines changed

16 files changed

+1523
-3
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk/?branch=master) [![Build Status](https://scrutinizer-ci.com/g/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk/badges/build.png?b=master)](https://scrutinizer-ci.com/g/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk/build-status/master) [![Code Coverage](https://scrutinizer-ci.com/g/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk/?branch=master)
55

6-
[![Travis Build Status](https://img.shields.io/travis/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk/master.svg?label=travis)](https://travis-ci.org/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk) [![Travis PHP versions](https://img.shields.io/travis/php-v/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk.svg)](https://travis-ci.org/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk)
6+
[![Travis Build Status](https://img.shields.io/travis/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk/master.svg?label=travis)](https://travis-ci.com/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk) [![Travis PHP versions](https://img.shields.io/travis/php-v/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk.svg)](https://travis-ci.com/yoanm/php-jsonrpc-params-symfony-constraint-doc-sdk)
77

88
[![Latest Stable Version](https://img.shields.io/packagist/v/yoanm/jsonrpc-params-symfony-constraint-doc-sdk.svg)](https://packagist.org/packages/yoanm/jsonrpc-params-symfony-constraint-doc-sdk) [![Packagist PHP version](https://img.shields.io/packagist/php-v/yoanm/jsonrpc-params-symfony-constraint-doc-sdk.svg)](https://packagist.org/packages/yoanm/jsonrpc-params-symfony-constraint-doc-sdk)
99

composer.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,5 @@
3434
"squizlabs/php_codesniffer": "3.*",
3535
"phpunit/phpunit": "^6.0 || ^7.0",
3636
"yoanm/php-unit-extended": "^1.0"
37-
},
38-
"minimum-stability": "dev"
37+
}
3938
}

src/.gitkeep

Whitespace-only changes.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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+
}

src/App/Helper/DocTypeHelper.php

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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+
static $stringConstraintClassList = [
105+
Assert\Length::class, // << Applied on string only
106+
Assert\Date::class, // << validator expect a string with specific format
107+
Assert\Time::class, // << validator expect a string with specific format
108+
Assert\Bic::class,
109+
Assert\CardScheme::class,
110+
Assert\Country::class,
111+
Assert\Currency::class,
112+
Assert\Email::class,
113+
Assert\File::class,
114+
Assert\Iban::class,
115+
Assert\Ip::class,
116+
Assert\Isbn::class,
117+
Assert\Issn::class,
118+
Assert\Language::class,
119+
Assert\Locale::class,
120+
Assert\Luhn::class,
121+
Assert\Regex::class,
122+
Assert\Url::class,
123+
Assert\Uuid::class,
124+
];
125+
static $booleanConstraintClassList = [
126+
Assert\IsTrue::class,
127+
Assert\IsFalse::class,
128+
];
129+
$constraintClass = get_class($constraint);
130+
131+
// Try to guess primary types
132+
if (in_array($constraintClass, $stringConstraintClassList)) {
133+
return new StringDoc();
134+
} elseif (in_array($constraintClass, $booleanConstraintClassList)) {
135+
return new BooleanDoc();
136+
} elseif ($constraint instanceof Assert\DateTime) {
137+
if ('U' === $constraint->format) {
138+
return new ScalarDoc();// Don't know if value will be an number as string or as integer
139+
}
140+
141+
return new StringDoc();
142+
} elseif ($constraint instanceof Assert\Collection) {
143+
// If only integer => array, else object
144+
$integerKeyList = array_filter(array_keys($constraint->fields), 'is_int');
145+
if (count($constraint->fields) === count($integerKeyList)) {
146+
return new ArrayDoc();
147+
}
148+
149+
return new ObjectDoc();
150+
} elseif (Assert\All::class === $constraintClass // << Applied only on array
151+
|| ($constraint instanceof Assert\Choice
152+
&& true === $constraint->multiple // << expect an array multiple choices
153+
)
154+
) {
155+
return new ArrayDoc();
156+
}
157+
158+
// If primary type is still not defined
159+
static $numberOrFloatConstraintClassList = [
160+
Assert\GreaterThan::class,
161+
Assert\GreaterThanOrEqual::class,
162+
Assert\LessThan::class,
163+
Assert\LessThanOrEqual::class,
164+
];
165+
if ($constraint instanceof Assert\Range) {
166+
return $this->floatOrNumber([$constraint->min, $constraint->max]);
167+
} elseif (in_array($constraintClass, $numberOrFloatConstraintClassList)) {
168+
return $this->floatOrNumber([$constraint->value]);
169+
} elseif (Assert\Count::class == $constraintClass) {
170+
return new CollectionDoc();
171+
} elseif ($constraint instanceof Assert\Existence) {
172+
return $this->guess($constraint->constraints);
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+
if ('scalar' === $type) {
186+
return new ScalarDoc();
187+
} elseif ('string' === $type) {
188+
return new StringDoc();
189+
} elseif ('bool' === $type || 'boolean' === $type) {
190+
return new BooleanDoc();
191+
} elseif ('int' === $type || 'integer' === $type) {
192+
return new IntegerDoc();
193+
} elseif (in_array($type, ['float', 'long', 'double', 'real', 'numeric'])) {
194+
return new FloatDoc();
195+
} elseif ('array' === $type) {
196+
return new ArrayDoc();
197+
} elseif ('object' === $type) {
198+
return new ObjectDoc();
199+
}
200+
201+
return null;
202+
}
203+
204+
/**
205+
* @param TypeDoc $doc
206+
*
207+
* @return bool
208+
*/
209+
private function isAbstractType(TypeDoc $doc) : bool
210+
{
211+
// use get_class to avoid inheritance issue
212+
$class = get_class($doc);
213+
214+
return CollectionDoc::class === $class
215+
|| NumberDoc::class === $class
216+
|| ScalarDoc::class === $class
217+
;
218+
}
219+
220+
/**
221+
* @param array $basedOnList
222+
*
223+
* @return FloatDoc|NumberDoc
224+
*/
225+
private function floatOrNumber(array $basedOnList)
226+
{
227+
foreach ($basedOnList as $value) {
228+
if (null !== $value && is_float($value)) {
229+
return new FloatDoc();
230+
}
231+
}
232+
233+
return new NumberDoc();
234+
}
235+
}

0 commit comments

Comments
 (0)