66
77use Tempest \Mapper \Mapper ;
88use Tempest \Router \Request ;
9+ use Tempest \Validation \Exceptions \PropertyValidationException ;
10+ use Tempest \Validation \Exceptions \ValidationException ;
11+ use Tempest \Validation \Validator ;
912
1013use function Tempest \map ;
14+ use function Tempest \reflect ;
1115
1216final readonly class RequestToObjectMapper implements Mapper
1317{
@@ -19,6 +23,30 @@ public function canMap(mixed $from, mixed $to): bool
1923 public function map (mixed $ from , mixed $ to ): array |object
2024 {
2125 /** @var Request $from */
22- return map ($ from ->body )->to ($ to );
26+ $ object = map ($ from ->body )->to ($ to );
27+
28+ // We perform a new round of validation on the newly constructed object
29+ // because we want to be sure that required uninitialized properties are also validated.
30+ // This doesn't happen in the ArrayToObject mapper because we are more lenient there by design
31+ // TODO: The better approach would be to have this RequestToObjectMapper be totally independent of ArrayToObjectMapper
32+ $ validator = new Validator ();
33+
34+ $ failingRules = [];
35+
36+ foreach (reflect ($ object )->getPublicProperties () as $ property ) {
37+ $ value = $ property ->isInitialized ($ object ) ? $ property ->getValue ($ object ) : null ;
38+
39+ try {
40+ $ validator ->validateProperty ($ property , $ value );
41+ } catch (PropertyValidationException $ validationException ) {
42+ $ failingRules [$ property ->getName ()] = $ validationException ->failingRules ;
43+ }
44+ }
45+
46+ if ($ failingRules !== []) {
47+ throw new ValidationException ($ object , $ failingRules );
48+ }
49+
50+ return $ object ;
2351 }
2452}
0 commit comments