Skip to content

Commit

Permalink
MetaResolver: Check that Callback returns correct Args class type
Browse files Browse the repository at this point in the history
  • Loading branch information
mabar committed Jan 4, 2025
1 parent 5f574a9 commit 72e2620
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Metadata
- Improved accuracy of metadata-related error messages
- `ReflectorMetaSource` is marked as `@internal`
- Check that `Callback` returns correct `Args` class type
- `Rule`
- `resolveArgs()` accepts `ArgsFieldContext` instead of `ArgsContext`
- all rules initialize `Type` lazily (performance optimization)
Expand Down
23 changes: 16 additions & 7 deletions src/Meta/MetaResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,20 @@ private function resolveCallbackMeta(
): CallbackRuntimeMeta
{
$type = $meta->getType();
$args = $type::resolveArgs($meta->getArgs(), $context, $reflector);

return new CallbackRuntimeMeta(
$type,
$type::resolveArgs($meta->getArgs(), $context, $reflector),
$declaringClass,
);
$argsType = $type::getArgsType();
if (!is_a($args, $argsType)) {
$realArgsType = get_class($args);

throw InvalidArgument::create()
->withMessage(
"'{$type}::resolveArgs()' should return '$argsType' (as defined in 'getArgsType()' method)"
. ", but returns '$realArgsType'.",
);
}

return new CallbackRuntimeMeta($type, $args, $declaringClass);
}

/**
Expand Down Expand Up @@ -405,13 +413,14 @@ public function resolveRuleMeta(RuleCompileMeta $meta, ArgsFieldContext $context
$rule = $this->ruleManager->getRule($type);
$args = $rule->resolveArgs($meta->getArgs(), $context);

if (!is_a($args, $rule->getArgsType())) {
$argsType = $rule->getArgsType();
if (!is_a($args, $argsType)) {
$ruleClass = get_class($rule);
$realArgsType = get_class($args);

throw InvalidArgument::create()
->withMessage(
"'{$ruleClass}->resolveArgs()' should return '{$rule->getArgsType()}' (as defined in 'getArgsType()' method)"
"'{$ruleClass}->resolveArgs()' should return '$argsType' (as defined in 'getArgsType()' method)"
. ", but returns '$realArgsType'.",
);
}
Expand Down
44 changes: 44 additions & 0 deletions tests/Doubles/Callbacks/WrongArgsTypeCallback.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Callbacks;

use Orisai\Exceptions\Logic\NotImplemented;
use Orisai\ObjectMapper\Args\Args;
use Orisai\ObjectMapper\Args\EmptyArgs;
use Orisai\ObjectMapper\Callbacks\Callback;
use Orisai\ObjectMapper\Context\ArgsContext;
use Orisai\ObjectMapper\Context\BaseFieldContext;
use Orisai\ObjectMapper\Processing\ObjectHolder;
use Orisai\ObjectMapper\Rules\NullArgs;
use ReflectionClass;
use Reflector;

/**
* @implements Callback<NullArgs>
*/
final class WrongArgsTypeCallback implements Callback
{

public static function resolveArgs(array $args, ArgsContext $context, Reflector $reflector): Args
{
return new NullArgs(false);
}

public static function getArgsType(): string
{
/** @phpstan-ignore-next-line */
return EmptyArgs::class;
}

public static function invoke(
$data,
Args $args,
ObjectHolder $holder,
BaseFieldContext $context,
ReflectionClass $declaringClass
): void
{
throw NotImplemented::create();
}

}
28 changes: 28 additions & 0 deletions tests/Doubles/Callbacks/WrongArgsTypeCallbackValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Callbacks;

use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Orisai\ObjectMapper\Callbacks\CallbackDefinition;

/**
* @Annotation
* @NamedArgumentConstructor()
* @Target({"PROPERTY"})
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class WrongArgsTypeCallbackValue implements CallbackDefinition
{

public function getType(): string
{
return WrongArgsTypeCallback::class;
}

public function getArgs(): array
{
return [];
}

}
14 changes: 0 additions & 14 deletions tests/Doubles/Invalid/WrongArgsTypeVO.php

This file was deleted.

18 changes: 18 additions & 0 deletions tests/Doubles/Invalid/WrongCallbackArgsTypeVO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Invalid;

use Orisai\ObjectMapper\MappedObject;
use Orisai\ObjectMapper\Rules\StringValue;
use Tests\Orisai\ObjectMapper\Doubles\Callbacks\WrongArgsTypeCallbackValue;

final class WrongCallbackArgsTypeVO implements MappedObject
{

/**
* @WrongArgsTypeCallbackValue()
* @StringValue()
*/
public string $field;

}
14 changes: 14 additions & 0 deletions tests/Doubles/Invalid/WrongRuleArgsTypeVO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Invalid;

use Orisai\ObjectMapper\MappedObject;
use Tests\Orisai\ObjectMapper\Doubles\Rules\WrongArgsTypeRuleValue;

final class WrongRuleArgsTypeVO implements MappedObject
{

/** @WrongArgsTypeRuleValue() */
public string $field;

}
7 changes: 4 additions & 3 deletions tests/Doubles/Rules/WrongArgsTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,26 @@
use Orisai\ObjectMapper\Context\ArgsContext;
use Orisai\ObjectMapper\Context\FieldContext;
use Orisai\ObjectMapper\Context\TypeContext;
use Orisai\ObjectMapper\Rules\NullArgs;
use Orisai\ObjectMapper\Rules\Rule;
use Orisai\ObjectMapper\Types\SimpleValueType;
use Orisai\ObjectMapper\Types\Type;

/**
* @implements Rule<EmptyArgs>
* @implements Rule<NullArgs>
*/
final class WrongArgsTypeRule implements Rule
{

public function resolveArgs(array $args, ArgsContext $context): Args
{
return new EmptyArgs();
return new NullArgs(false);
}

public function getArgsType(): string
{
/** @phpstan-ignore-next-line */
return 'nonsense';
return EmptyArgs::class;
}

public function processValue($value, Args $args, FieldContext $context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target({"PROPERTY", "ANNOTATION"})
* @Target({"PROPERTY"})
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class WrongArgsTypeValue implements RuleDefinition
final class WrongArgsTypeRuleValue implements RuleDefinition
{

public function getType(): string
Expand Down
26 changes: 21 additions & 5 deletions tests/Unit/Meta/MetaResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
use Tests\Orisai\ObjectMapper\Doubles\Invalid\FieldTraitMetaInvalidScopeRootVO;
use Tests\Orisai\ObjectMapper\Doubles\Invalid\MultipleIdenticalFieldNamesVO;
use Tests\Orisai\ObjectMapper\Doubles\Invalid\StaticMappedPropertyVO;
use Tests\Orisai\ObjectMapper\Doubles\Invalid\WrongArgsTypeVO;
use Tests\Orisai\ObjectMapper\Doubles\Invalid\WrongCallbackArgsTypeVO;
use Tests\Orisai\ObjectMapper\Doubles\Invalid\WrongRuleArgsTypeVO;
use Tests\Orisai\ObjectMapper\Doubles\Rules\WrongArgsTypeRule;
use Tests\Orisai\ObjectMapper\Toolkit\ProcessingTestCase;

Expand Down Expand Up @@ -194,17 +195,32 @@ public function testFieldNamesFromTrait(): void
$this->metaLoader->load(FieldNamesFromTraitVO::class);
}

public function testNotMatchingArgsType(): void
public function testWrongRuleArgsType(): void
{
$this->ruleManager->addRule(new WrongArgsTypeRule());

$this->expectException(InvalidArgument::class);
$this->expectExceptionMessage(
"'Tests\Orisai\ObjectMapper\Doubles\Rules\WrongArgsTypeRule->resolveArgs()' should return 'nonsense'"
. " (as defined in 'getArgsType()' method), but returns 'Orisai\ObjectMapper\Args\EmptyArgs'.",
"'Tests\Orisai\ObjectMapper\Doubles\Rules\WrongArgsTypeRule->resolveArgs()'"
. " should return 'Orisai\ObjectMapper\Args\EmptyArgs'"
. " (as defined in 'getArgsType()' method),"
. " but returns 'Orisai\ObjectMapper\Rules\NullArgs'.",
);

$this->metaLoader->load(WrongArgsTypeVO::class);
$this->metaLoader->load(WrongRuleArgsTypeVO::class);
}

public function testWrongCallbackArgsType(): void
{
$this->expectException(InvalidArgument::class);
$this->expectExceptionMessage(
"'Tests\Orisai\ObjectMapper\Doubles\Callbacks\WrongArgsTypeCallback::resolveArgs()'"
. " should return 'Orisai\ObjectMapper\Args\EmptyArgs'"
. " (as defined in 'getArgsType()' method),"
. " but returns 'Orisai\ObjectMapper\Rules\NullArgs'.",
);

$this->metaLoader->load(WrongCallbackArgsTypeVO::class);
}

}

0 comments on commit 72e2620

Please sign in to comment.