Files
JobcardSystem/app/Domain/Credential/TechnicalInformation.php
T

89 lines
3.7 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace App\Domain\Credential;
/** Normalizes and validates non-secret technical-information metadata. */
final class TechnicalInformation
{
public const CATEGORY_HOSTING = 'hosting';
public const CATEGORY_VPN = 'vpn';
public const CATEGORY_EMAIL = 'email';
public const CATEGORY_DOMAIN = 'domain';
public const CATEGORY_DATABASE = 'database';
public const CATEGORY_SSH = 'ssh';
public const CATEGORY_API = 'api';
public const CATEGORY_OTHER = 'other';
public const CATEGORY_MICROSOFT = 'microsoft';
public const CATEGORY_NETWORK = 'network';
public const CATEGORY_ROUTER = 'router';
public const CATEGORY_INFRASTRUCTURE = 'infrastructure';
/** @return list<string> */
public static function categories(): array
{
return [self::CATEGORY_HOSTING, self::CATEGORY_VPN, self::CATEGORY_EMAIL, self::CATEGORY_DOMAIN, self::CATEGORY_DATABASE, self::CATEGORY_SSH, self::CATEGORY_API, self::CATEGORY_OTHER, self::CATEGORY_MICROSOFT, self::CATEGORY_NETWORK, self::CATEGORY_ROUTER, self::CATEGORY_INFRASTRUCTURE];
}
/** @return array{category:string, label:string, username:string|null, notes:string|null} */
public function normalize(array $information): array
{
return [
'category' => strtolower($this->text($information['category'] ?? null) ?? ''),
'label' => $this->text($information['label'] ?? null) ?? '',
'username' => $this->text($information['username'] ?? null),
'notes' => $this->text($information['notes'] ?? null),
];
}
/** @return array{valid:bool,errors:array<string,string>,category:string,label:string,username:string|null,notes:string|null} */
public function validate(array $information): array
{
$normalized = $this->normalize($information);
$errors = [];
if (!in_array($normalized['category'], self::categories(), true)) {
$errors['category'] = 'Credential category is invalid.';
}
if ($normalized['label'] === '') {
$errors['label'] = 'Credential label is required.';
} elseif ($normalized['label'] !== '' && mb_strlen($normalized['label']) > 120 || ($normalized['label'] !== '' && $this->hasControlCharacter($normalized['label']))) {
$errors['label'] = 'Credential label must be 120 characters or fewer and contain no control characters.';
}
if ($normalized['username'] !== null && (mb_strlen($normalized['username']) > 190 || $this->hasControlCharacter($normalized['username']))) {
$errors['username'] = 'Credential username must be 190 characters or fewer and contain no control characters.';
}
if ($normalized['notes'] !== null && mb_strlen($normalized['notes']) > 2000) {
$errors['notes'] = 'Credential notes must be 2000 characters or fewer.';
}
return [...$normalized, 'valid' => $errors === [], 'errors' => $errors];
}
/** @return array<string,mixed> */
public function display(array $information): array
{
$safe = [];
foreach (['id', 'category', 'label', 'username', 'notes'] as $field) {
if (array_key_exists($field, $information)) $safe[$field] = $information[$field];
}
return $safe;
}
/** @return array<string,mixed> */
public function toDisplay(array $information): array
{
return $this->display($information);
}
private function text(mixed $value): ?string
{
if ($value === null) return null;
$text = trim(is_scalar($value) ? (string) $value : '');
return $text === '' ? null : $text;
}
private function hasControlCharacter(string $value): bool
{
return preg_match('/[\x00-\x1F\x7F]/', $value) === 1;
}
}