mirror of
https://github.com/shlinkio/shlink.git
synced 2024-11-22 00:47:25 -06:00
Extract device long URL validation to its own validation class
This commit is contained in:
parent
822652cac3
commit
3e26f1113d
@ -6,16 +6,23 @@ namespace Shlinkio\Shlink\Core\ShortUrl\Entity;
|
||||
|
||||
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
|
||||
use Shlinkio\Shlink\Core\Model\DeviceType;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\DeviceLongUrlPair;
|
||||
|
||||
class DeviceLongUrl extends AbstractEntity
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ShortUrl $shortUrl,
|
||||
private ShortUrl $shortUrl; // @phpstan-ignore-line
|
||||
|
||||
private function __construct(
|
||||
public readonly DeviceType $deviceType,
|
||||
private string $longUrl,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromPair(DeviceLongUrlPair $pair): self
|
||||
{
|
||||
return new self($pair->deviceType, $pair->longUrl);
|
||||
}
|
||||
|
||||
public function longUrl(): string
|
||||
{
|
||||
return $this->longUrl;
|
||||
|
@ -4,14 +4,21 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\ShortUrl\Helper;
|
||||
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Util\UrlValidatorInterface;
|
||||
|
||||
class ShortUrlTitleResolutionHelper implements ShortUrlTitleResolutionHelperInterface
|
||||
{
|
||||
public function __construct(private UrlValidatorInterface $urlValidator)
|
||||
public function __construct(private readonly UrlValidatorInterface $urlValidator)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of TitleResolutionModelInterface
|
||||
* @param T $data
|
||||
* @return T
|
||||
* @throws InvalidUrlException
|
||||
*/
|
||||
public function processTitleAndValidateUrl(TitleResolutionModelInterface $data): TitleResolutionModelInterface
|
||||
{
|
||||
if ($data->hasTitle()) {
|
||||
|
@ -9,6 +9,9 @@ use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
interface ShortUrlTitleResolutionHelperInterface
|
||||
{
|
||||
/**
|
||||
* @template T of TitleResolutionModelInterface
|
||||
* @param T $data
|
||||
* @return T
|
||||
* @throws InvalidUrlException
|
||||
*/
|
||||
public function processTitleAndValidateUrl(TitleResolutionModelInterface $data): TitleResolutionModelInterface;
|
||||
|
@ -12,5 +12,5 @@ interface TitleResolutionModelInterface
|
||||
|
||||
public function doValidateUrl(): bool;
|
||||
|
||||
public function withResolvedTitle(string $title): self;
|
||||
public function withResolvedTitle(string $title): static;
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ final class ShortUrlCreation implements TitleResolutionModelInterface
|
||||
);
|
||||
}
|
||||
|
||||
public function withResolvedTitle(string $title): self
|
||||
public function withResolvedTitle(string $title): static
|
||||
{
|
||||
return new self(
|
||||
longUrl: $this->longUrl,
|
||||
|
@ -76,7 +76,7 @@ final class ShortUrlEdition implements TitleResolutionModelInterface
|
||||
);
|
||||
}
|
||||
|
||||
public function withResolvedTitle(string $title): self
|
||||
public function withResolvedTitle(string $title): static
|
||||
{
|
||||
return new self(
|
||||
longUrlPropWasProvided: $this->longUrlPropWasProvided,
|
||||
|
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\ShortUrl\Model\Validation;
|
||||
|
||||
use Laminas\Validator\AbstractValidator;
|
||||
use Laminas\Validator\ValidatorChain;
|
||||
use Shlinkio\Shlink\Core\Model\DeviceType;
|
||||
|
||||
use function array_keys;
|
||||
use function array_values;
|
||||
use function Functional\contains;
|
||||
use function Functional\every;
|
||||
use function is_array;
|
||||
use function Shlinkio\Shlink\Core\enumValues;
|
||||
|
||||
class DeviceLongUrlsValidator extends AbstractValidator
|
||||
{
|
||||
private const NOT_ARRAY = 'NOT_ARRAY';
|
||||
private const INVALID_DEVICE = 'INVALID_DEVICE';
|
||||
private const INVALID_LONG_URL = 'INVALID_LONG_URL';
|
||||
|
||||
protected array $messageTemplates = [
|
||||
self::NOT_ARRAY => 'Provided value is not an array.',
|
||||
self::INVALID_DEVICE => 'You have provided at least one invalid device identifier.',
|
||||
self::INVALID_LONG_URL => 'At least one of the long URLs are invalid.',
|
||||
];
|
||||
|
||||
public function __construct(private readonly ValidatorChain $longUrlValidators)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function isValid(mixed $value): bool
|
||||
{
|
||||
if (! is_array($value)) {
|
||||
$this->error(self::NOT_ARRAY);
|
||||
return false;
|
||||
}
|
||||
|
||||
$validValues = enumValues(DeviceType::class);
|
||||
$keys = array_keys($value);
|
||||
if (! every($keys, static fn ($key) => contains($validValues, $key))) {
|
||||
$this->error(self::INVALID_DEVICE);
|
||||
return false;
|
||||
}
|
||||
|
||||
$longUrls = array_values($value);
|
||||
$result = every($longUrls, $this->longUrlValidators->isValid(...));
|
||||
if (! $result) {
|
||||
$this->error(self::INVALID_LONG_URL);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -10,16 +10,9 @@ use Laminas\InputFilter\InputFilter;
|
||||
use Laminas\Validator;
|
||||
use Shlinkio\Shlink\Common\Validation;
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
use Shlinkio\Shlink\Core\Model\DeviceType;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
use function array_keys;
|
||||
use function array_values;
|
||||
use function Functional\contains;
|
||||
use function Functional\every;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
use function Shlinkio\Shlink\Core\enumValues;
|
||||
use function str_replace;
|
||||
use function substr;
|
||||
use function trim;
|
||||
@ -64,37 +57,20 @@ class ShortUrlInputFilter extends InputFilter
|
||||
|
||||
private function initialize(bool $requireLongUrl, bool $multiSegmentEnabled): void
|
||||
{
|
||||
$notEmptyValidator = new Validator\NotEmpty([
|
||||
$longUrlInput = $this->createInput(self::LONG_URL, $requireLongUrl);
|
||||
$longUrlInput->getValidatorChain()->attach(new Validator\NotEmpty([
|
||||
Validator\NotEmpty::OBJECT,
|
||||
Validator\NotEmpty::SPACE,
|
||||
Validator\NotEmpty::NULL,
|
||||
Validator\NotEmpty::EMPTY_ARRAY,
|
||||
Validator\NotEmpty::BOOLEAN,
|
||||
Validator\NotEmpty::STRING,
|
||||
]);
|
||||
|
||||
$longUrlInput = $this->createInput(self::LONG_URL, $requireLongUrl);
|
||||
$longUrlInput->getValidatorChain()->attach($notEmptyValidator);
|
||||
]));
|
||||
$this->add($longUrlInput);
|
||||
|
||||
$deviceLongUrlsInput = $this->createInput(self::DEVICE_LONG_URLS, false);
|
||||
$deviceLongUrlsInput->getValidatorChain()->attach( // TODO Extract callback to own validator
|
||||
new Validator\Callback(function (mixed $value) use ($notEmptyValidator): bool {
|
||||
if (! is_array($value)) {
|
||||
// TODO Set proper error: Not array
|
||||
return false;
|
||||
}
|
||||
|
||||
$validValues = enumValues(DeviceType::class);
|
||||
$keys = array_keys($value);
|
||||
if (! every($keys, static fn ($key) => contains($validValues, $key))) {
|
||||
// TODO Set proper error: Provided invalid device type
|
||||
return false;
|
||||
}
|
||||
|
||||
$longUrls = array_values($value);
|
||||
return every($longUrls, $notEmptyValidator->isValid(...));
|
||||
}),
|
||||
$deviceLongUrlsInput->getValidatorChain()->attach(
|
||||
new DeviceLongUrlsValidator($longUrlInput->getValidatorChain()),
|
||||
);
|
||||
$this->add($deviceLongUrlsInput);
|
||||
|
||||
|
@ -49,7 +49,7 @@ class PersistenceShortUrlRelationResolver implements ShortUrlRelationResolverInt
|
||||
|
||||
/**
|
||||
* @param string[] $tags
|
||||
* @return Collection|Tag[]
|
||||
* @return Collection<int, Tag>
|
||||
*/
|
||||
public function resolveTags(array $tags): Collections\Collection
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ interface ShortUrlRelationResolverInterface
|
||||
|
||||
/**
|
||||
* @param string[] $tags
|
||||
* @return Collection|Tag[]
|
||||
* @return Collection<int, Tag>
|
||||
*/
|
||||
public function resolveTags(array $tags): Collection;
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ class SimpleShortUrlRelationResolver implements ShortUrlRelationResolverInterfac
|
||||
|
||||
/**
|
||||
* @param string[] $tags
|
||||
* @return Collection|Tag[]
|
||||
* @return Collection<int, Tag>
|
||||
*/
|
||||
public function resolveTags(array $tags): Collections\Collection
|
||||
{
|
||||
|
@ -34,7 +34,6 @@ class ShortUrlService implements ShortUrlServiceInterface
|
||||
?ApiKey $apiKey = null,
|
||||
): ShortUrl {
|
||||
if ($shortUrlEdit->longUrlWasProvided()) {
|
||||
/** @var ShortUrlEdition $shortUrlEdit */
|
||||
$shortUrlEdit = $this->titleResolutionHelper->processTitleAndValidateUrl($shortUrlEdit);
|
||||
}
|
||||
|
||||
|
@ -31,22 +31,21 @@ class UrlShortener implements UrlShortenerInterface
|
||||
* @throws NonUniqueSlugException
|
||||
* @throws InvalidUrlException
|
||||
*/
|
||||
public function shorten(ShortUrlCreation $meta): ShortUrl
|
||||
public function shorten(ShortUrlCreation $creation): ShortUrl
|
||||
{
|
||||
// First, check if a short URL exists for all provided params
|
||||
$existingShortUrl = $this->findExistingShortUrlIfExists($meta);
|
||||
$existingShortUrl = $this->findExistingShortUrlIfExists($creation);
|
||||
if ($existingShortUrl !== null) {
|
||||
return $existingShortUrl;
|
||||
}
|
||||
|
||||
/** @var \Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation $meta */
|
||||
$meta = $this->titleResolutionHelper->processTitleAndValidateUrl($meta);
|
||||
$creation = $this->titleResolutionHelper->processTitleAndValidateUrl($creation);
|
||||
|
||||
/** @var ShortUrl $newShortUrl */
|
||||
$newShortUrl = $this->em->wrapInTransaction(function () use ($meta) {
|
||||
$shortUrl = ShortUrl::create($meta, $this->relationResolver);
|
||||
$newShortUrl = $this->em->wrapInTransaction(function () use ($creation): ShortUrl {
|
||||
$shortUrl = ShortUrl::create($creation, $this->relationResolver);
|
||||
|
||||
$this->verifyShortCodeUniqueness($meta, $shortUrl);
|
||||
$this->verifyShortCodeUniqueness($creation, $shortUrl);
|
||||
$this->em->persist($shortUrl);
|
||||
|
||||
return $shortUrl;
|
||||
|
@ -15,5 +15,5 @@ interface UrlShortenerInterface
|
||||
* @throws NonUniqueSlugException
|
||||
* @throws InvalidUrlException
|
||||
*/
|
||||
public function shorten(ShortUrlCreation $meta): ShortUrl;
|
||||
public function shorten(ShortUrlCreation $creation): ShortUrl;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class ApiKey extends AbstractEntity
|
||||
private string $key;
|
||||
private ?Chronos $expirationDate = null;
|
||||
private bool $enabled;
|
||||
/** @var Collection|ApiKeyRole[] */
|
||||
/** @var Collection<string, ApiKeyRole> */
|
||||
private Collection $roles;
|
||||
private ?string $name = null;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user