mirror of
https://github.com/shlinkio/shlink.git
synced 2024-10-22 20:25:35 +03:00
Capture error on real-time update when creating short URL
This commit is contained in:
parent
33911afcd6
commit
f078d95588
9 changed files with 71 additions and 19 deletions
|
@ -161,7 +161,7 @@ class CreateShortUrlCommand extends Command
|
||||||
$doValidateUrl = $input->getOption('validate-url');
|
$doValidateUrl = $input->getOption('validate-url');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$shortUrl = $this->urlShortener->shorten(ShortUrlCreation::fromRawData([
|
$result = $this->urlShortener->shorten(ShortUrlCreation::fromRawData([
|
||||||
ShortUrlInputFilter::LONG_URL => $longUrl,
|
ShortUrlInputFilter::LONG_URL => $longUrl,
|
||||||
ShortUrlInputFilter::VALID_SINCE => $input->getOption('valid-since'),
|
ShortUrlInputFilter::VALID_SINCE => $input->getOption('valid-since'),
|
||||||
ShortUrlInputFilter::VALID_UNTIL => $input->getOption('valid-until'),
|
ShortUrlInputFilter::VALID_UNTIL => $input->getOption('valid-until'),
|
||||||
|
@ -178,7 +178,7 @@ class CreateShortUrlCommand extends Command
|
||||||
|
|
||||||
$io->writeln([
|
$io->writeln([
|
||||||
sprintf('Processed long URL: <info>%s</info>', $longUrl),
|
sprintf('Processed long URL: <info>%s</info>', $longUrl),
|
||||||
sprintf('Generated short URL: <info>%s</info>', $this->stringifier->stringify($shortUrl)),
|
sprintf('Generated short URL: <info>%s</info>', $this->stringifier->stringify($result->shortUrl)),
|
||||||
]);
|
]);
|
||||||
return ExitCodes::EXIT_SUCCESS;
|
return ExitCodes::EXIT_SUCCESS;
|
||||||
} catch (InvalidUrlException | NonUniqueSlugException $e) {
|
} catch (InvalidUrlException | NonUniqueSlugException $e) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
|
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Model\UrlShorteningResult;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\UrlShortenerInterface;
|
use Shlinkio\Shlink\Core\ShortUrl\UrlShortenerInterface;
|
||||||
use ShlinkioTest\Shlink\CLI\CliTestUtilsTrait;
|
use ShlinkioTest\Shlink\CLI\CliTestUtilsTrait;
|
||||||
use Symfony\Component\Console\Tester\CommandTester;
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
|
@ -51,7 +52,9 @@ class CreateShortUrlCommandTest extends TestCase
|
||||||
public function properShortCodeIsCreatedIfLongUrlIsCorrect(): void
|
public function properShortCodeIsCreatedIfLongUrlIsCorrect(): void
|
||||||
{
|
{
|
||||||
$shortUrl = ShortUrl::createFake();
|
$shortUrl = ShortUrl::createFake();
|
||||||
$this->urlShortener->expects($this->once())->method('shorten')->withAnyParameters()->willReturn($shortUrl);
|
$this->urlShortener->expects($this->once())->method('shorten')->withAnyParameters()->willReturn(
|
||||||
|
UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl),
|
||||||
|
);
|
||||||
$this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn(
|
$this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn(
|
||||||
'stringified_short_url',
|
'stringified_short_url',
|
||||||
);
|
);
|
||||||
|
@ -106,7 +109,7 @@ class CreateShortUrlCommandTest extends TestCase
|
||||||
Assert::assertEquals(['foo', 'bar', 'baz', 'boo', 'zar'], $creation->tags);
|
Assert::assertEquals(['foo', 'bar', 'baz', 'boo', 'zar'], $creation->tags);
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
)->willReturn($shortUrl);
|
)->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl));
|
||||||
$this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn(
|
$this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn(
|
||||||
'stringified_short_url',
|
'stringified_short_url',
|
||||||
);
|
);
|
||||||
|
@ -129,7 +132,7 @@ class CreateShortUrlCommandTest extends TestCase
|
||||||
Assert::assertEquals($expectedDomain, $meta->domain);
|
Assert::assertEquals($expectedDomain, $meta->domain);
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
)->willReturn(ShortUrl::createFake());
|
)->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching(ShortUrl::createFake()));
|
||||||
$this->stringifier->method('stringify')->with($this->isInstanceOf(ShortUrl::class))->willReturn('');
|
$this->stringifier->method('stringify')->with($this->isInstanceOf(ShortUrl::class))->willReturn('');
|
||||||
|
|
||||||
$input['longUrl'] = 'http://domain.com/foo/bar';
|
$input['longUrl'] = 'http://domain.com/foo/bar';
|
||||||
|
@ -155,7 +158,7 @@ class CreateShortUrlCommandTest extends TestCase
|
||||||
Assert::assertEquals($expectedValidateUrl, $meta->doValidateUrl());
|
Assert::assertEquals($expectedValidateUrl, $meta->doValidateUrl());
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
)->willReturn($shortUrl);
|
)->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl));
|
||||||
$this->stringifier->method('stringify')->with($this->isInstanceOf(ShortUrl::class))->willReturn('');
|
$this->stringifier->method('stringify')->with($this->isInstanceOf(ShortUrl::class))->willReturn('');
|
||||||
|
|
||||||
$options['longUrl'] = 'http://domain.com/foo/bar';
|
$options['longUrl'] = 'http://domain.com/foo/bar';
|
||||||
|
|
37
module/Core/src/ShortUrl/Model/UrlShorteningResult.php
Normal file
37
module/Core/src/ShortUrl/Model/UrlShorteningResult.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Core\ShortUrl\Model;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
final class UrlShorteningResult
|
||||||
|
{
|
||||||
|
private function __construct(
|
||||||
|
public readonly ShortUrl $shortUrl,
|
||||||
|
private readonly ?Throwable $errorOnEventDispatching,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable(Throwable $errorOnEventDispatching): void $handler
|
||||||
|
*/
|
||||||
|
public function onEventDispatchingError(callable $handler): void
|
||||||
|
{
|
||||||
|
if ($this->errorOnEventDispatching !== null) {
|
||||||
|
$handler($this->errorOnEventDispatching);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function withoutErrorOnEventDispatching(ShortUrl $shortUrl): self
|
||||||
|
{
|
||||||
|
return new self($shortUrl, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function withErrorOnEventDispatching(ShortUrl $shortUrl, Throwable $errorOnEventDispatching): self
|
||||||
|
{
|
||||||
|
return new self($shortUrl, $errorOnEventDispatching);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||||
namespace Shlinkio\Shlink\Core\ShortUrl;
|
namespace Shlinkio\Shlink\Core\ShortUrl;
|
||||||
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Container\ContainerExceptionInterface;
|
||||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated;
|
use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated;
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||||
|
@ -13,6 +14,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortCodeUniquenessHelperInterface;
|
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortCodeUniquenessHelperInterface;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlTitleResolutionHelperInterface;
|
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlTitleResolutionHelperInterface;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Model\UrlShorteningResult;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface;
|
use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface;
|
use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface;
|
||||||
|
|
||||||
|
@ -31,12 +33,12 @@ class UrlShortener implements UrlShortenerInterface
|
||||||
* @throws NonUniqueSlugException
|
* @throws NonUniqueSlugException
|
||||||
* @throws InvalidUrlException
|
* @throws InvalidUrlException
|
||||||
*/
|
*/
|
||||||
public function shorten(ShortUrlCreation $creation): ShortUrl
|
public function shorten(ShortUrlCreation $creation): UrlShorteningResult
|
||||||
{
|
{
|
||||||
// First, check if a short URL exists for all provided params
|
// First, check if a short URL exists for all provided params
|
||||||
$existingShortUrl = $this->findExistingShortUrlIfExists($creation);
|
$existingShortUrl = $this->findExistingShortUrlIfExists($creation);
|
||||||
if ($existingShortUrl !== null) {
|
if ($existingShortUrl !== null) {
|
||||||
return $existingShortUrl;
|
return UrlShorteningResult::withoutErrorOnEventDispatching($existingShortUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
$creation = $this->titleResolutionHelper->processTitleAndValidateUrl($creation);
|
$creation = $this->titleResolutionHelper->processTitleAndValidateUrl($creation);
|
||||||
|
@ -51,9 +53,17 @@ class UrlShortener implements UrlShortenerInterface
|
||||||
return $shortUrl;
|
return $shortUrl;
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->eventDispatcher->dispatch(new ShortUrlCreated($newShortUrl->getId()));
|
try {
|
||||||
|
$this->eventDispatcher->dispatch(new ShortUrlCreated($newShortUrl->getId()));
|
||||||
|
} catch (ContainerExceptionInterface $e) {
|
||||||
|
// Ignore container errors when dispatching the event.
|
||||||
|
// When using openswoole, this event will try to enqueue a task, which cannot be done outside an HTTP
|
||||||
|
// request.
|
||||||
|
// If the short URL is created from CLI, the event dispatching will fail.
|
||||||
|
return UrlShorteningResult::withErrorOnEventDispatching($newShortUrl, $e);
|
||||||
|
}
|
||||||
|
|
||||||
return $newShortUrl;
|
return UrlShorteningResult::withoutErrorOnEventDispatching($newShortUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function findExistingShortUrlIfExists(ShortUrlCreation $creation): ?ShortUrl
|
private function findExistingShortUrlIfExists(ShortUrlCreation $creation): ?ShortUrl
|
||||||
|
|
|
@ -6,8 +6,8 @@ namespace Shlinkio\Shlink\Core\ShortUrl;
|
||||||
|
|
||||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Model\UrlShorteningResult;
|
||||||
|
|
||||||
interface UrlShortenerInterface
|
interface UrlShortenerInterface
|
||||||
{
|
{
|
||||||
|
@ -15,5 +15,5 @@ interface UrlShortenerInterface
|
||||||
* @throws NonUniqueSlugException
|
* @throws NonUniqueSlugException
|
||||||
* @throws InvalidUrlException
|
* @throws InvalidUrlException
|
||||||
*/
|
*/
|
||||||
public function shorten(ShortUrlCreation $creation): ShortUrl;
|
public function shorten(ShortUrlCreation $creation): UrlShorteningResult;
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,9 +58,9 @@ class UrlShortenerTest extends TestCase
|
||||||
)->willReturnArgument(0);
|
)->willReturnArgument(0);
|
||||||
$this->shortCodeHelper->method('ensureShortCodeUniqueness')->willReturn(true);
|
$this->shortCodeHelper->method('ensureShortCodeUniqueness')->willReturn(true);
|
||||||
|
|
||||||
$shortUrl = $this->urlShortener->shorten($meta);
|
$result = $this->urlShortener->shorten($meta);
|
||||||
|
|
||||||
self::assertEquals($longUrl, $shortUrl->getLongUrl());
|
self::assertEquals($longUrl, $result->shortUrl->getLongUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
|
@ -91,7 +91,7 @@ class UrlShortenerTest extends TestCase
|
||||||
|
|
||||||
$result = $this->urlShortener->shorten($meta);
|
$result = $this->urlShortener->shorten($meta);
|
||||||
|
|
||||||
self::assertSame($expected, $result);
|
self::assertSame($expected, $result->shortUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function provideExistingShortUrls(): iterable
|
public static function provideExistingShortUrls(): iterable
|
||||||
|
|
|
@ -26,9 +26,9 @@ abstract class AbstractCreateShortUrlAction extends AbstractRestAction
|
||||||
public function handle(Request $request): Response
|
public function handle(Request $request): Response
|
||||||
{
|
{
|
||||||
$shortUrlMeta = $this->buildShortUrlData($request);
|
$shortUrlMeta = $this->buildShortUrlData($request);
|
||||||
$shortUrl = $this->urlShortener->shorten($shortUrlMeta);
|
$result = $this->urlShortener->shorten($shortUrlMeta);
|
||||||
|
|
||||||
return new JsonResponse($this->transformer->transform($shortUrl));
|
return new JsonResponse($this->transformer->transform($result->shortUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,6 +17,7 @@ use Shlinkio\Shlink\Core\Exception\ValidationException;
|
||||||
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Model\UrlShorteningResult;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\UrlShortener;
|
use Shlinkio\Shlink\Core\ShortUrl\UrlShortener;
|
||||||
use Shlinkio\Shlink\Rest\Action\ShortUrl\CreateShortUrlAction;
|
use Shlinkio\Shlink\Rest\Action\ShortUrl\CreateShortUrlAction;
|
||||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||||
|
@ -53,7 +54,7 @@ class CreateShortUrlActionTest extends TestCase
|
||||||
|
|
||||||
$this->urlShortener->expects($this->once())->method('shorten')->with(
|
$this->urlShortener->expects($this->once())->method('shorten')->with(
|
||||||
ShortUrlCreation::fromRawData($expectedMeta),
|
ShortUrlCreation::fromRawData($expectedMeta),
|
||||||
)->willReturn($shortUrl);
|
)->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl));
|
||||||
$this->transformer->expects($this->once())->method('transform')->with($shortUrl)->willReturn(
|
$this->transformer->expects($this->once())->method('transform')->with($shortUrl)->willReturn(
|
||||||
['shortUrl' => 'stringified_short_url'],
|
['shortUrl' => 'stringified_short_url'],
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,6 +12,7 @@ use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
|
||||||
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Model\UrlShorteningResult;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\UrlShortenerInterface;
|
use Shlinkio\Shlink\Core\ShortUrl\UrlShortenerInterface;
|
||||||
use Shlinkio\Shlink\Rest\Action\ShortUrl\SingleStepCreateShortUrlAction;
|
use Shlinkio\Shlink\Rest\Action\ShortUrl\SingleStepCreateShortUrlAction;
|
||||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||||
|
@ -44,7 +45,7 @@ class SingleStepCreateShortUrlActionTest extends TestCase
|
||||||
])->withAttribute(ApiKey::class, $apiKey);
|
])->withAttribute(ApiKey::class, $apiKey);
|
||||||
$this->urlShortener->expects($this->once())->method('shorten')->with(
|
$this->urlShortener->expects($this->once())->method('shorten')->with(
|
||||||
ShortUrlCreation::fromRawData(['apiKey' => $apiKey, 'longUrl' => 'http://foobar.com']),
|
ShortUrlCreation::fromRawData(['apiKey' => $apiKey, 'longUrl' => 'http://foobar.com']),
|
||||||
)->willReturn(ShortUrl::createFake());
|
)->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching(ShortUrl::createFake()));
|
||||||
|
|
||||||
$resp = $this->action->handle($request);
|
$resp = $this->action->handle($request);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue