Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/Common/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,19 @@

abstract class Component extends DataTransferObject implements JsonSerializable
{
/**
* Whether constructed components should be validated. Disabled while decoding API
* responses, since responses may legitimately omit fields that are only required
* when creating a pass. Validation still runs for objects constructed directly.
*/
private static bool $validate = true;

public function __construct()
{
if (! self::$validate) {
return;
}

$validator = Validation::createValidatorBuilder()->enableAttributeMapping()->getValidator();
$errors = $validator->validate($this);

Expand All @@ -25,6 +36,18 @@ public function __construct()
}
}

public static function decode(array $data): static
{
$previous = self::$validate;
self::$validate = false;

try {
return parent::decode($data);
} finally {
self::$validate = $previous;
}
}

/**
* Convert the object into something JSON serializable.
*/
Expand Down
50 changes: 50 additions & 0 deletions tests/Common/ComponentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php declare(strict_types=1);

namespace Chiiya\Passes\Tests\Common;

use Chiiya\Passes\Exceptions\ValidationException;
use Chiiya\Passes\Google\Components\Common\Message;
use Chiiya\Passes\Google\Passes\LoyaltyObject;
use Chiiya\Passes\Tests\TestCase;
use PHPUnit\Framework\Attributes\Group;

class ComponentTest extends TestCase
{
#[Group('common')]
public function test_decode_does_not_run_validation(): void
{
// The API may return values our constraints don't expect; decoding a response
// must never fail validation (see chiiya/laravel-passes#40).
$message = Message::decode(['messageType' => 'SOME_FUTURE_TYPE']);

$this->assertSame('SOME_FUTURE_TYPE', $message->messageType);
}

#[Group('common')]
public function test_direct_construction_still_runs_validation(): void
{
$this->expectException(ValidationException::class);

new Message(messageType: 'SOME_FUTURE_TYPE');
}

#[Group('google')]
public function test_decode_skips_validation_for_nested_components(): void
{
// Regression for chiiya/laravel-passes#40: updating a loyalty pass decodes the
// response, whose classReference may carry values/fields our validation rejects.
$object = LoyaltyObject::decode([
'id' => 'issuer.object',
'classId' => 'issuer.class',
'state' => 'ACTIVE',
'classReference' => [
'id' => 'issuer.class',
'reviewStatus' => 'SOME_FUTURE_STATUS',
'programLogo' => ['sourceUri' => ['uri' => 'https://example.com/logo.png']],
],
]);

$this->assertSame('SOME_FUTURE_STATUS', $object->classReference->reviewStatus);
$this->assertNull($object->classReference->issuerName);
}
}