55namespace Chubbyphp \Tests \Parsing \Unit \Schema ;
66
77use Chubbyphp \Parsing \ErrorsException ;
8+ use Chubbyphp \Parsing \Schema \BoolSchema ;
89use Chubbyphp \Parsing \Schema \FloatSchema ;
910use Chubbyphp \Parsing \Schema \IntSchema ;
1011use Chubbyphp \Parsing \Schema \ObjectConstructorSchema ;
@@ -17,6 +18,7 @@ public function __construct(
1718 public readonly string $ field1 ,
1819 public readonly int $ field2 ,
1920 public readonly ?float $ field3 = null ,
21+ public readonly ?float $ field4 = null ,
2022 ) {}
2123
2224 public function jsonSerialize (): array
@@ -25,6 +27,7 @@ public function jsonSerialize(): array
2527 'field1 ' => $ this ->field1 ,
2628 'field2 ' => $ this ->field2 ,
2729 'field3 ' => $ this ->field3 ,
30+ 'field4 ' => $ this ->field4 ,
2831 ];
2932 }
3033}
@@ -47,7 +50,12 @@ final class ObjectConstructorSchemaTest extends TestCase
4750{
4851 public function testImmutability (): void
4952 {
50- $ schema = new ObjectConstructorSchema (['field1 ' => new StringSchema (), 'field2 ' => new IntSchema (), 'field3 ' => new FloatSchema ()], ObjectConstructorDemo::class);
53+ $ schema = new ObjectConstructorSchema ([
54+ 'field1 ' => new StringSchema (),
55+ 'field2 ' => new IntSchema (),
56+ 'field3 ' => new FloatSchema ()->nullable (),
57+ 'field4 ' => new FloatSchema ()->nullable (),
58+ ], ObjectConstructorDemo::class);
5159
5260 self ::assertNotSame ($ schema , $ schema ->nullable ());
5361 self ::assertNotSame ($ schema , $ schema ->nullable (false ));
@@ -108,28 +116,30 @@ public function testConstructWithAdditionalFieldSchema(): void
108116 new ObjectConstructorSchema ([
109117 'field1 ' => new StringSchema (),
110118 'field2 ' => new IntSchema (),
111- 'field3 ' => new FloatSchema (),
112- 'field4 ' => new FloatSchema (),
113- ], ObjectConstructorDemo::class)->optional (['field3 ' ]);
119+ 'field3 ' => new FloatSchema ()->nullable (),
120+ 'field4 ' => new FloatSchema ()->nullable (),
121+ 'field5 ' => new FloatSchema (),
122+ ], ObjectConstructorDemo::class)->optional (['field3 ' , 'field4 ' ]);
114123
115124 throw new \Exception ('code should not be reached ' );
116125 } catch (\InvalidArgumentException $ invalidArgumentException ) {
117126 self ::assertSame (
118- 'Additional fieldToSchema for " ' .ObjectConstructorDemo::class.'" __construct parameters: "field4 " ' ,
127+ 'Additional fieldToSchema for " ' .ObjectConstructorDemo::class.'" __construct parameters: "field5 " ' ,
119128 $ invalidArgumentException ->getMessage ()
120129 );
121130 }
122131 }
123132
124133 public function testSuccessWithAllParameters (): void
125134 {
126- $ input = ['field1 ' => 'test ' , 'field2 ' => 5 , 'field3 ' => 3.14159 ];
135+ $ input = ['field1 ' => 'test ' , 'field2 ' => 5 , 'field3 ' => 3.14159 , ' field4 ' => 1.61803 ];
127136
128137 $ schema = new ObjectConstructorSchema ([
129138 'field1 ' => new StringSchema (),
130139 'field2 ' => new IntSchema (),
131- 'field3 ' => new FloatSchema (),
132- ], ObjectConstructorDemo::class)->optional (['field3 ' ]);
140+ 'field3 ' => new FloatSchema ()->nullable (),
141+ 'field4 ' => new FloatSchema ()->nullable (),
142+ ], ObjectConstructorDemo::class)->optional (['field3 ' , 'field4 ' ]);
133143
134144 /** @var ObjectConstructorDemo $object */
135145 $ object = $ schema ->parse ($ input );
@@ -141,20 +151,26 @@ public function testSuccessWithAllParameters(): void
141151
142152 public function testSuccessWithAllParametersOptionalConsidered (): void
143153 {
144- $ input = ['field1 ' => 'test ' , 'field2 ' => 5 ];
154+ $ input = ['field1 ' => 'test ' , 'field2 ' => 5 , ' field4 ' => 1.61803 ];
145155
146156 $ schema = new ObjectConstructorSchema ([
147157 'field1 ' => new StringSchema (),
148158 'field2 ' => new IntSchema (),
149- 'field3 ' => new FloatSchema (),
150- ], ObjectConstructorDemo::class)->optional (['field3 ' ]);
159+ 'field3 ' => new FloatSchema ()->nullable (),
160+ 'field4 ' => new FloatSchema ()->nullable (),
161+ ], ObjectConstructorDemo::class)->optional (['field3 ' , 'field4 ' ]);
151162
152163 /** @var ObjectConstructorDemo $object */
153164 $ object = $ schema ->parse ($ input );
154165
155166 self ::assertInstanceOf (ObjectConstructorDemo::class, $ object );
156167
157- self ::assertSame ([...$ input , 'field3 ' => null ], $ object ->jsonSerialize ());
168+ self ::assertSame ([
169+ 'field1 ' => 'test ' ,
170+ 'field2 ' => 5 ,
171+ 'field3 ' => null ,
172+ 'field4 ' => 1.61803 ,
173+ ], $ object ->jsonSerialize ());
158174 }
159175
160176 public function testSuccessWithRequiredParameters (): void
@@ -164,14 +180,14 @@ public function testSuccessWithRequiredParameters(): void
164180 $ schema = new ObjectConstructorSchema ([
165181 'field1 ' => new StringSchema (),
166182 'field2 ' => new IntSchema (),
167- ], ObjectConstructorDemo::class)->optional (['field3 ' ]);
183+ ], ObjectConstructorDemo::class)->optional (['field3 ' , ' field4 ' ]);
168184
169185 /** @var ObjectConstructorDemo $object */
170186 $ object = $ schema ->parse ($ input );
171187
172188 self ::assertInstanceOf (ObjectConstructorDemo::class, $ object );
173189
174- self ::assertSame ([...$ input , 'field3 ' => null ], $ object ->jsonSerialize ());
190+ self ::assertSame ([...$ input , 'field3 ' => null , ' field4 ' => null ], $ object ->jsonSerialize ());
175191 }
176192
177193 public function testFailedWithInvalidValue (): void
@@ -181,8 +197,9 @@ public function testFailedWithInvalidValue(): void
181197 $ schema = new ObjectConstructorSchema ([
182198 'field1 ' => new StringSchema (),
183199 'field2 ' => new IntSchema (),
184- 'field3 ' => new FloatSchema (),
185- ], ObjectConstructorDemo::class)->optional (['field3 ' ]);
200+ 'field3 ' => new FloatSchema ()->nullable (),
201+ 'field4 ' => new FloatSchema ()->nullable (),
202+ ], ObjectConstructorDemo::class)->optional (['field3 ' , 'field4 ' ]);
186203
187204 try {
188205 $ schema ->parse ($ input );
@@ -211,8 +228,9 @@ public function testFailedWithInvalidValueNotCatchedByFieldSchema(): void
211228 $ schema = new ObjectConstructorSchema ([
212229 'field1 ' => new StringSchema (),
213230 'field2 ' => new IntSchema (),
214- 'field3 ' => new StringSchema (),
215- ], ObjectConstructorDemo::class)->optional (['field3 ' ]);
231+ 'field3 ' => new StringSchema ()->nullable (),
232+ 'field4 ' => new FloatSchema ()->nullable (),
233+ ], ObjectConstructorDemo::class)->optional (['field3 ' , 'field4 ' ]);
216234
217235 try {
218236 $ schema ->parse ($ input );
@@ -246,8 +264,9 @@ public function testFailedWithUnknownException(): void
246264 $ schema = new ObjectConstructorSchema ([
247265 'field1 ' => new StringSchema (),
248266 'field2 ' => new IntSchema (),
249- 'field3 ' => new FloatSchema ()->postParse (static fn () => throw $ exception ),
250- ], ObjectConstructorDemo::class)->optional (['field3 ' ]);
267+ 'field3 ' => new FloatSchema ()->nullable ()->postParse (static fn () => throw $ exception ),
268+ 'field4 ' => new FloatSchema ()->nullable (),
269+ ], ObjectConstructorDemo::class)->optional (['field3 ' , 'field4 ' ]);
251270
252271 try {
253272 $ schema ->parse ($ input );
@@ -274,4 +293,153 @@ public function testFailedWithTypeErrorNotMatchingPattern(): void
274293 self ::assertSame ('some unrelated type error ' , $ e ->getMessage ());
275294 }
276295 }
296+
297+ public function testParseSuccessWithPreParse (): void
298+ {
299+ $ input = ['field1 ' => 'test ' , 'field2 ' => 5 , 'field3 ' => null , 'field4 ' => null ];
300+
301+ $ schema = new ObjectConstructorSchema ([
302+ 'field1 ' => new StringSchema (),
303+ 'field2 ' => new IntSchema (),
304+ 'field3 ' => new FloatSchema ()->nullable (),
305+ 'field4 ' => new FloatSchema ()->nullable (),
306+ ], ObjectConstructorDemo::class)
307+ ->optional (['field3 ' , 'field4 ' ])
308+ ->preParse (static fn () => $ input )
309+ ;
310+
311+ self ::assertSame ($ input , $ schema ->parse (null )->jsonSerialize ());
312+ }
313+
314+ public function testParseSuccessWithPostParse (): void
315+ {
316+ $ input = ['field1 ' => 'test ' , 'field2 ' => 5 ];
317+
318+ $ schema = new ObjectConstructorSchema ([
319+ 'field1 ' => new StringSchema (),
320+ 'field2 ' => new IntSchema (),
321+ 'field3 ' => new FloatSchema ()->nullable (),
322+ 'field4 ' => new FloatSchema ()->nullable (),
323+ ], ObjectConstructorDemo::class)
324+ ->optional (['field3 ' , 'field4 ' ])
325+ ->postParse (static fn (ObjectConstructorDemo $ output ) => new ObjectConstructorDemo (...[...$ output ->jsonSerialize (), 'field2 ' => 10 ]))
326+ ;
327+
328+ self ::assertSame (
329+ [...$ input , 'field2 ' => 10 , 'field3 ' => null , 'field4 ' => null ],
330+ (array ) $ schema ->parse ($ input )
331+ );
332+ }
333+
334+ public function testParseFailedWithCatch (): void
335+ {
336+ $ schema = (new ObjectConstructorSchema ([
337+ 'field1 ' => new StringSchema (),
338+ 'field2 ' => new IntSchema (),
339+ 'field3 ' => new FloatSchema ()->nullable (),
340+ 'field4 ' => new FloatSchema ()->nullable (),
341+ ], ObjectConstructorDemo::class))
342+ ->optional (['field3 ' , 'field4 ' ])
343+ ->catch (static function (mixed $ input , ErrorsException $ errorsException ) {
344+ self ::assertNull ($ input );
345+ self ::assertSame ([
346+ [
347+ 'path ' => '' ,
348+ 'error ' => [
349+ 'code ' => 'object.type ' ,
350+ 'template ' => 'Type should be "array|\stdClass|\Traversable", {{given}} given ' ,
351+ 'variables ' => [
352+ 'given ' => 'NULL ' ,
353+ ],
354+ ],
355+ ],
356+ ], $ errorsException ->errors ->jsonSerialize ());
357+
358+ return 'catched ' ;
359+ })
360+ ;
361+
362+ self ::assertSame ('catched ' , $ schema ->parse (null ));
363+ }
364+
365+ public function testSafeParseSuccess (): void
366+ {
367+ $ input = ['field1 ' => 'test ' , 'field2 ' => 1 ];
368+
369+ $ schema = (new ObjectConstructorSchema ([
370+ 'field1 ' => new StringSchema (),
371+ 'field2 ' => new IntSchema (),
372+ 'field3 ' => new FloatSchema ()->nullable (),
373+ 'field4 ' => new FloatSchema ()->nullable (),
374+ ], ObjectConstructorDemo::class))
375+ ->optional (['field3 ' , 'field4 ' ])
376+ ;
377+
378+ self ::assertSame (
379+ [...$ input , 'field3 ' => null , 'field4 ' => null ],
380+ $ schema ->safeParse ($ input )->data ->jsonSerialize ()
381+ );
382+ }
383+
384+ public function testSafeParseFailed (): void
385+ {
386+ $ schema = (new ObjectConstructorSchema ([
387+ 'field1 ' => new StringSchema (),
388+ 'field2 ' => new IntSchema (),
389+ 'field3 ' => new FloatSchema ()->nullable (),
390+ 'field4 ' => new FloatSchema ()->nullable (),
391+ ], ObjectConstructorDemo::class))
392+ ->optional (['field3 ' , 'field4 ' ])
393+ ;
394+
395+ self ::assertSame ([
396+ [
397+ 'path ' => '' ,
398+ 'error ' => [
399+ 'code ' => 'object.type ' ,
400+ 'template ' => 'Type should be "array|\stdClass|\Traversable", {{given}} given ' ,
401+ 'variables ' => [
402+ 'given ' => 'NULL ' ,
403+ ],
404+ ],
405+ ],
406+ ], $ schema ->safeParse (null )->exception ->errors ->jsonSerialize ());
407+ }
408+
409+ public function testGetFieldToSchema (): void
410+ {
411+ $ fieldToSchema = ['field1 ' => new StringSchema (), 'field2 ' => new IntSchema ()];
412+
413+ $ schema = new ObjectConstructorSchema ($ fieldToSchema , ObjectConstructorDemo::class);
414+
415+ self ::assertSame ($ fieldToSchema , $ schema ->getFieldToSchema ());
416+
417+ $ fieldToSchema2 = [...$ schema ->getFieldToSchema (), 'field3 ' => new BoolSchema ()];
418+
419+ $ schema2 = new ObjectConstructorSchema ($ fieldToSchema2 , ObjectConstructorDemo::class);
420+
421+ self ::assertSame ($ fieldToSchema2 , $ schema2 ->getFieldToSchema ());
422+ }
423+
424+ public function testGetFieldSchemaSuccess (): void
425+ {
426+ $ field2Schema = new IntSchema ();
427+
428+ $ schema = new ObjectConstructorSchema ([
429+ 'field1 ' => new StringSchema (),
430+ 'field2 ' => $ field2Schema ,
431+ ], ObjectConstructorDemo::class);
432+
433+ self ::assertSame ($ field2Schema , $ schema ->getFieldSchema ('field2 ' ));
434+ }
435+
436+ public function testGetFieldSchemaFailed (): void
437+ {
438+ $ schema = new ObjectConstructorSchema ([
439+ 'field1 ' => new StringSchema (),
440+ 'field2 ' => new IntSchema (),
441+ ], ObjectConstructorDemo::class);
442+
443+ self ::assertNull ($ schema ->getFieldSchema ('field3 ' ));
444+ }
277445}
0 commit comments