*/ final readonly class ResourceIdentity implements Arrayable, JsonSerializable { public const string ProviderResource = 'provider_resource'; public const string CanonicalBuiltin = 'canonical_builtin'; public const string CanonicalDefault = 'canonical_default'; public const string CanonicalVirtualTarget = 'canonical_virtual_target'; public const string Unsupported = 'unsupported'; public const string Unknown = 'unknown'; public function __construct( public string $providerKey, public string $identityKind, public ?string $providerResourceType = null, public ?string $providerResourceId = null, public ?string $canonicalDiscriminator = null, ) { $this->assertValid(); } public static function providerResource(string $providerKey, string $resourceType, string $resourceId): self { return new self( providerKey: $providerKey, identityKind: self::ProviderResource, providerResourceType: $resourceType, providerResourceId: $resourceId, ); } public static function canonicalBuiltin(string $providerKey, string $resourceType, string $discriminator): self { return new self( providerKey: $providerKey, identityKind: self::CanonicalBuiltin, providerResourceType: $resourceType, canonicalDiscriminator: $discriminator, ); } public static function canonicalDefault(string $providerKey, string $resourceType, string $discriminator): self { return new self( providerKey: $providerKey, identityKind: self::CanonicalDefault, providerResourceType: $resourceType, canonicalDiscriminator: $discriminator, ); } public static function virtualTarget(string $providerKey, string $resourceType, string $discriminator): self { return new self( providerKey: $providerKey, identityKind: self::CanonicalVirtualTarget, providerResourceType: $resourceType, canonicalDiscriminator: $discriminator, ); } public static function unsupported(string $providerKey, string $resourceType, string $discriminator): self { return new self( providerKey: $providerKey, identityKind: self::Unsupported, providerResourceType: $resourceType, canonicalDiscriminator: $discriminator, ); } public static function unknown(string $providerKey, string $resourceType, string $discriminator): self { return new self( providerKey: $providerKey, identityKind: self::Unknown, providerResourceType: $resourceType, canonicalDiscriminator: $discriminator, ); } /** * @param array $payload */ public static function fromArray(array $payload): self { return new self( providerKey: (string) ($payload['provider_key'] ?? ''), identityKind: (string) ($payload['identity_kind'] ?? ''), providerResourceType: self::nullableString($payload['provider_resource_type'] ?? null), providerResourceId: self::nullableString($payload['provider_resource_id'] ?? null), canonicalDiscriminator: self::nullableString($payload['canonical_discriminator'] ?? null), ); } public function stableIdentityValue(): ?string { return $this->identityKind === self::ProviderResource ? $this->providerResourceId : $this->canonicalDiscriminator; } public function fingerprint(): string { return hash('sha256', json_encode($this->payload(), JSON_THROW_ON_ERROR)); } /** * @return array{ * provider_key: string, * identity_kind: string, * provider_resource_type: ?string, * provider_resource_id: ?string, * canonical_discriminator: ?string, * fingerprint: string * } */ public function toArray(): array { $payload = $this->payload(); return $payload + [ 'fingerprint' => hash('sha256', json_encode($payload, JSON_THROW_ON_ERROR)), ]; } /** * @return array{ * provider_key: string, * identity_kind: string, * provider_resource_type: ?string, * provider_resource_id: ?string, * canonical_discriminator: ?string * } */ private function payload(): array { return [ 'provider_key' => $this->providerKey, 'identity_kind' => $this->identityKind, 'provider_resource_type' => $this->providerResourceType, 'provider_resource_id' => $this->providerResourceId, 'canonical_discriminator' => $this->canonicalDiscriminator, ]; } public function jsonSerialize(): array { return $this->toArray(); } private function assertValid(): void { if (trim($this->providerKey) === '') { throw new InvalidArgumentException('Resource identities require a provider key.'); } if (! in_array($this->identityKind, self::validKinds(), true)) { throw new InvalidArgumentException(sprintf('Unsupported resource identity kind [%s].', $this->identityKind)); } if ($this->providerResourceType !== null && trim($this->providerResourceType) === '') { throw new InvalidArgumentException('Provider resource type must be non-empty when supplied.'); } if ($this->identityKind === self::ProviderResource) { if ($this->providerResourceType === null || trim($this->providerResourceType) === '' || $this->providerResourceId === null || trim($this->providerResourceId) === '') { throw new InvalidArgumentException('Provider resource identities require provider resource type and ID.'); } return; } if ($this->canonicalDiscriminator === null || trim($this->canonicalDiscriminator) === '') { throw new InvalidArgumentException('Canonical resource identities require a discriminator.'); } } /** * @return list */ private static function validKinds(): array { return [ self::ProviderResource, self::CanonicalBuiltin, self::CanonicalDefault, self::CanonicalVirtualTarget, self::Unsupported, self::Unknown, ]; } private static function nullableString(mixed $value): ?string { return is_string($value) && trim($value) !== '' ? trim($value) : null; } }