diff --git a/src/Common/Component.php b/src/Common/Component.php index 4ece05c..6e58e46 100644 --- a/src/Common/Component.php +++ b/src/Common/Component.php @@ -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); @@ -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. */ diff --git a/tests/Common/ComponentTest.php b/tests/Common/ComponentTest.php new file mode 100644 index 0000000..9110619 --- /dev/null +++ b/tests/Common/ComponentTest.php @@ -0,0 +1,50 @@ + '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); + } +}