Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m9s
Added ProviderResourceBinding model, migrations, policies, and supporting framework for canonical resource identity mapping as defined in Spec 381.
215 lines
6.8 KiB
PHP
215 lines
6.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\Resources;
|
|
|
|
use Illuminate\Contracts\Support\Arrayable;
|
|
use InvalidArgumentException;
|
|
use JsonSerializable;
|
|
|
|
/**
|
|
* @implements Arrayable<string, mixed>
|
|
*/
|
|
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<string, mixed> $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<string>
|
|
*/
|
|
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;
|
|
}
|
|
}
|