Skip to content

Commit c1e1ac5

Browse files
authored
Add transformer (#3)
1 parent 0a806ca commit c1e1ac5

File tree

2 files changed

+374
-0
lines changed

2 files changed

+374
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
namespace Yoanm\JsonRpcParamsSymfonyConstraintDoc\Infra\Transformer;
3+
4+
use Symfony\Component\Validator\Constraint;
5+
use Symfony\Component\Validator\Constraints as Assert;
6+
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\ConstraintPayloadDocHelper;
7+
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\DocTypeHelper;
8+
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\MinMaxHelper;
9+
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\StringDocHelper;
10+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ArrayDoc;
11+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\CollectionDoc;
12+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\TypeDoc;
13+
14+
/**
15+
* Class ConstraintToParamsDocTransformer
16+
*/
17+
class ConstraintToParamsDocTransformer
18+
{
19+
/** @var DocTypeHelper */
20+
private $docTypeHelper;
21+
/** @var StringDocHelper */
22+
private $stringDocHelper;
23+
/** @var MinMaxHelper */
24+
private $minMaxHelper;
25+
/** @var ConstraintPayloadDocHelper */
26+
private $constraintPayloadDocHelper;
27+
28+
/**
29+
* @param DocTypeHelper $docTypeHelper
30+
* @param ConstraintPayloadDocHelper $constraintPayloadDocHelper
31+
*/
32+
public function __construct(
33+
DocTypeHelper $docTypeHelper,
34+
StringDocHelper $stringDocHelper,
35+
MinMaxHelper $minMaxHelper,
36+
ConstraintPayloadDocHelper $constraintPayloadDocHelper
37+
) {
38+
$this->docTypeHelper = $docTypeHelper;
39+
$this->minMaxHelper = $minMaxHelper;
40+
$this->stringDocHelper = $stringDocHelper;
41+
$this->constraintPayloadDocHelper = $constraintPayloadDocHelper;
42+
}
43+
44+
/**
45+
* @param Constraint $constraint
46+
*
47+
* @return TypeDoc
48+
*/
49+
public function transform(Constraint $constraint)
50+
{
51+
return $this->docFromConstraint($constraint);
52+
}
53+
54+
/**
55+
* @param Constraint $constraintOrConstraintList
56+
* @param string|int|null $paramNameOrIndex
57+
*
58+
* @return TypeDoc
59+
*/
60+
private function docFromConstraint(Constraint $constraint, $paramNameOrIndex = null)
61+
{
62+
$constraintList = [$constraint];
63+
$constraintDoc = $this->docTypeHelper->guess($constraintList);
64+
if (null !== $paramNameOrIndex) {
65+
$constraintDoc->setName($paramNameOrIndex);
66+
}
67+
68+
foreach ($constraintList as $constraint) {
69+
$this->appendToDoc($constraintDoc, $constraint);
70+
}
71+
72+
return $constraintDoc;
73+
}
74+
75+
/**
76+
* @param TypeDoc $doc
77+
* @param Constraint $constraint
78+
*/
79+
private function appendToDoc(TypeDoc $doc, Constraint $constraint)
80+
{
81+
if ($doc instanceof ArrayDoc && $constraint instanceof Assert\All) {
82+
$this->appendAllConstraintToDoc($doc, $constraint);
83+
} else {
84+
$this->stringDocHelper->append($doc, $constraint);
85+
$this->appendCollectionDoc($doc, $constraint);
86+
87+
$this->minMaxHelper->append($doc, $constraint);
88+
$this->appendValidItemListDoc($doc, $constraint);
89+
90+
if ($constraint instanceof Assert\Existence) {
91+
$doc->setRequired($constraint instanceof Assert\Required);
92+
foreach ($constraint->constraints as $subConstraint) {
93+
$this->appendToDoc($doc, $subConstraint);
94+
}
95+
} elseif ($constraint instanceof Assert\NotNull) {
96+
$doc->setNullable(false);
97+
}
98+
99+
// /!\ Payload doc will override values even if already defined
100+
$this->constraintPayloadDocHelper->appendPayloadDoc($doc, $constraint);
101+
}
102+
}
103+
104+
/**
105+
* @param TypeDoc $doc
106+
* @param Constraint $constraint
107+
*/
108+
private function appendCollectionDoc(TypeDoc $doc, Constraint $constraint)
109+
{
110+
// If not a collection => give up
111+
if (!$doc instanceof CollectionDoc) {
112+
return;
113+
}
114+
115+
if ($constraint instanceof Assert\Collection) {
116+
foreach ($constraint->fields as $fieldName => $constraintOrConstrainList) {
117+
$doc->addSibling(
118+
$this->docFromConstraint($constraintOrConstrainList, $fieldName)
119+
);
120+
}
121+
122+
$doc->setAllowExtraSibling($constraint->allowExtraFields === true);
123+
$doc->setAllowMissingSibling($constraint->allowMissingFields === true);
124+
}
125+
}
126+
127+
/**
128+
* @param TypeDoc $doc
129+
* @param Constraint $constraint
130+
*/
131+
private function appendValidItemListDoc(TypeDoc $doc, Constraint $constraint)
132+
{
133+
if ($constraint instanceof Assert\Choice) {
134+
if ($constraint->callback && is_callable($constraint->callback)) {
135+
$choiceList = call_user_func($constraint->callback);
136+
} else {
137+
$choiceList = $constraint->choices ?? [];
138+
}
139+
foreach ($choiceList as $fieldName => $choice) {
140+
$doc->addAllowedValue($choice);
141+
}
142+
}
143+
}
144+
145+
/**
146+
* @param ArrayDoc $doc
147+
* @param Assert\All $constraint
148+
*/
149+
private function appendAllConstraintToDoc(ArrayDoc $doc, Assert\All $constraint)
150+
{
151+
$itemDoc = $this->docTypeHelper->guess($constraint->constraints);
152+
foreach ($constraint->constraints as $subConstraint) {
153+
$this->appendToDoc($itemDoc, $subConstraint);
154+
}
155+
156+
$doc->setItemValidation($itemDoc);
157+
}
158+
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
<?php
2+
namespace Tests\Functional\Infra\Transformer;
3+
4+
use PHPUnit\Framework\TestCase;
5+
use Prophecy\Argument;
6+
use Prophecy\Prophecy\ObjectProphecy;
7+
use Symfony\Component\Validator\Constraints as Assert;
8+
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\ConstraintPayloadDocHelper;
9+
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\DocTypeHelper;
10+
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\MinMaxHelper;
11+
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\StringDocHelper;
12+
use Yoanm\JsonRpcParamsSymfonyConstraintDoc\Infra\Transformer\ConstraintToParamsDocTransformer;
13+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ArrayDoc;
14+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\IntegerDoc;
15+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ObjectDoc;
16+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\StringDoc;
17+
use Yoanm\JsonRpcServerDoc\Domain\Model\Type\TypeDoc;
18+
19+
/**
20+
* @covers Yoanm\JsonRpcParamsSymfonyConstraintDoc\Infra\Transformer\ConstraintToParamsDocTransformer
21+
*
22+
* @group Helper
23+
*/
24+
class ConstraintToParamsDocTransformerTest extends TestCase
25+
{
26+
/** @var DocTypeHelper|ObjectProphecy */
27+
private $docTypeHelper;
28+
/** @var StringDocHelper|ObjectProphecy */
29+
private $stringDocHelper;
30+
/** @var MinMaxHelper|ObjectProphecy */
31+
private $minMaxHelper;
32+
/** @var ConstraintPayloadDocHelper|ObjectProphecy */
33+
private $constraintPayloadDocHelper;
34+
35+
/** @var ConstraintToParamsDocTransformer */
36+
private $transformer;
37+
38+
public function setUp()
39+
{
40+
$this->docTypeHelper = $this->prophesize(DocTypeHelper::class);
41+
$this->stringDocHelper = $this->prophesize(StringDocHelper::class);
42+
$this->minMaxHelper = $this->prophesize(MinMaxHelper::class);
43+
$this->constraintPayloadDocHelper = $this->prophesize(ConstraintPayloadDocHelper::class);
44+
45+
$this->transformer = new ConstraintToParamsDocTransformer(
46+
$this->docTypeHelper->reveal(),
47+
$this->stringDocHelper->reveal(),
48+
$this->minMaxHelper->reveal(),
49+
$this->constraintPayloadDocHelper->reveal()
50+
);
51+
}
52+
53+
public function testShouldHandleNotNullConstraint()
54+
{
55+
$constraint = new Assert\NotNull();
56+
$doc = new TypeDoc();
57+
58+
$this->assertTrue($doc->isNullable());
59+
60+
$this->docTypeHelper->guess([$constraint])
61+
->willReturn($doc)
62+
->shouldBeCalled()
63+
;
64+
65+
$this->assertFalse($this->transformer->transform($constraint)->isNullable());
66+
}
67+
68+
public function testShouldHandleChoiceConstraint()
69+
{
70+
$choiceList = ['a', 'b'];
71+
$constraint = new Assert\Choice($choiceList);
72+
$doc = new TypeDoc();
73+
74+
$this->docTypeHelper->guess([$constraint])
75+
->willReturn($doc)
76+
->shouldBeCalled()
77+
;
78+
79+
$this->assertSame($choiceList, $this->transformer->transform($constraint)->getAllowedValueList());
80+
}
81+
82+
public function testShouldHandleChoiceConstraintWithCallback()
83+
{
84+
$choiceList = ['a', 'b'];
85+
$choiceCallback = function () use ($choiceList) {
86+
return $choiceList;
87+
};
88+
89+
$constraint = new Assert\Choice(['callback' => $choiceCallback]);
90+
$doc = new TypeDoc();
91+
92+
$this->docTypeHelper->guess([$constraint])
93+
->willReturn($doc)
94+
->shouldBeCalled()
95+
;
96+
97+
$this->assertSame($choiceList, $this->transformer->transform($constraint)->getAllowedValueList());
98+
}
99+
100+
public function testShouldHandleAllConstraint()
101+
{
102+
$subConstraintList = [new Assert\Type('string')];
103+
$constraint = new Assert\All($subConstraintList);
104+
$doc = new ArrayDoc();
105+
$subDoc = new StringDoc();
106+
107+
$this->docTypeHelper->guess([$constraint])
108+
->willReturn($doc)
109+
->shouldBeCalled()
110+
;
111+
$this->docTypeHelper->guess($subConstraintList)
112+
->willReturn($subDoc)
113+
->shouldBeCalled()
114+
;
115+
116+
$newDoc = $this->transformer->transform($constraint);
117+
118+
$this->assertSame($doc, $newDoc);
119+
$this->assertSame($subDoc, $doc->getItemValidation());
120+
}
121+
122+
public function testShouldHandleCollectionConstraintAsArrayDoc()
123+
{
124+
$fieldsConstraintList = [0 => new Assert\Type('string'), 1 => new Assert\Type('integer')];
125+
$constraint = new Assert\Collection([
126+
'fields' => $fieldsConstraintList,
127+
'allowExtraFields' => true,
128+
'allowMissingFields' => true,
129+
]);
130+
$doc = new ArrayDoc();
131+
$subDoc = new StringDoc();
132+
$subDoc2 = new IntegerDoc();
133+
134+
$this->docTypeHelper->guess([$constraint])
135+
->willReturn($doc)
136+
->shouldBeCalled()
137+
;
138+
$this->docTypeHelper->guess(Argument::cetera())
139+
->willReturn($subDoc, $subDoc2)
140+
->shouldBeCalled()
141+
;
142+
143+
$newDoc = $this->transformer->transform($constraint);
144+
145+
$this->assertSame($doc, $newDoc);
146+
$this->assertSame([$subDoc, $subDoc2], $doc->getSiblingList());
147+
$this->assertSame(0, $subDoc->getName());
148+
$this->assertSame(1, $subDoc2->getName());
149+
$this->assertTrue($doc->isAllowMissingSibling());
150+
$this->assertTrue($doc->isAllowExtraSibling());
151+
}
152+
153+
public function testShouldHandleCollectionConstraintAsObjectDoc()
154+
{
155+
$fieldsConstraintList = ['key1' => new Assert\Type('string'), 'key2' => new Assert\Type('integer')];
156+
$constraint = new Assert\Collection([
157+
'fields' => $fieldsConstraintList,
158+
'allowExtraFields' => true,
159+
'allowMissingFields' => true,
160+
]);
161+
$doc = new ObjectDoc();
162+
$subDoc = new StringDoc();
163+
$subDoc2 = new IntegerDoc();
164+
165+
$this->docTypeHelper->guess([$constraint])
166+
->willReturn($doc)
167+
->shouldBeCalled()
168+
;
169+
$this->docTypeHelper->guess(Argument::cetera())
170+
->willReturn($subDoc, $subDoc2)
171+
->shouldBeCalled()
172+
;
173+
174+
$newDoc = $this->transformer->transform($constraint);
175+
176+
$this->assertSame($doc, $newDoc);
177+
$this->assertSame([$subDoc, $subDoc2], $doc->getSiblingList());
178+
$this->assertSame('key1', $subDoc->getName());
179+
$this->assertSame('key2', $subDoc2->getName());
180+
$this->assertTrue($doc->isAllowMissingSibling());
181+
$this->assertTrue($doc->isAllowExtraSibling());
182+
}
183+
184+
185+
186+
public function testPayloadDocShouldOverrideAnything()
187+
{
188+
$constraint = new Assert\NotNull();
189+
190+
$doc = new IntegerDoc();
191+
$this->assertTrue($doc->isNullable());
192+
193+
$this->docTypeHelper->guess([$constraint])
194+
->willReturn($doc)
195+
->shouldBeCalled()
196+
;
197+
$self = $this;
198+
$this->constraintPayloadDocHelper->appendPayloadDoc($doc, $constraint)
199+
->will(function ($args) use ($self) {
200+
/** @var IntegerDoc $doc */
201+
$doc = $args[0];
202+
$self->assertFalse($doc->isNullable());
203+
204+
// Set it back to true
205+
$doc->setNullable(true);
206+
})
207+
->shouldBeCalled()
208+
;
209+
210+
$newDoc = $this->transformer->transform($constraint);
211+
212+
$this->assertSame($doc, $newDoc);
213+
// Check is still true even if constraint is NotNul
214+
$this->assertTrue($doc->isNullable());
215+
}
216+
}

0 commit comments

Comments
 (0)