diff --git a/module/Core/src/Exception/NonUniqueSlugException.php b/module/Core/src/Exception/NonUniqueSlugException.php index fb7e4503..9e22d354 100644 --- a/module/Core/src/Exception/NonUniqueSlugException.php +++ b/module/Core/src/Exception/NonUniqueSlugException.php @@ -4,10 +4,19 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Exception; +use Fig\Http\Message\StatusCodeInterface; +use Zend\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; +use Zend\ProblemDetails\Exception\ProblemDetailsExceptionInterface; + use function sprintf; -class NonUniqueSlugException extends InvalidArgumentException +class NonUniqueSlugException extends InvalidArgumentException implements ProblemDetailsExceptionInterface { + use CommonProblemDetailsExceptionTrait; + + private const TITLE = 'Invalid custom slug'; + public const TYPE = 'INVALID_SLUG'; + public static function fromSlug(string $slug, ?string $domain): self { $suffix = ''; @@ -15,6 +24,13 @@ class NonUniqueSlugException extends InvalidArgumentException $suffix = sprintf(' for domain "%s"', $domain); } - return new self(sprintf('Provided slug "%s" is not unique%s.', $slug, $suffix)); + $e = new self(sprintf('Provided slug "%s" is already in use%s.', $slug, $suffix)); + + $e->detail = $e->getMessage(); + $e->title = self::TITLE; + $e->type = self::TYPE; + $e->status = StatusCodeInterface::STATUS_BAD_REQUEST; + + return $e; } } diff --git a/module/Core/test/Exception/NonUniqueSlugExceptionTest.php b/module/Core/test/Exception/NonUniqueSlugExceptionTest.php index 71c4a276..d2008621 100644 --- a/module/Core/test/Exception/NonUniqueSlugExceptionTest.php +++ b/module/Core/test/Exception/NonUniqueSlugExceptionTest.php @@ -22,12 +22,12 @@ class NonUniqueSlugExceptionTest extends TestCase public function provideMessages(): iterable { yield 'without domain' => [ - 'Provided slug "foo" is not unique.', + 'Provided slug "foo" is already in use.', 'foo', null, ]; yield 'with domain' => [ - 'Provided slug "baz" is not unique for domain "bar".', + 'Provided slug "baz" is already in use for domain "bar".', 'baz', 'bar', ]; diff --git a/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php index d627b427..38daf905 100644 --- a/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php @@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; -use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Model\CreateShortUrlData; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; @@ -16,8 +15,6 @@ use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Util\RestUtils; use Zend\Diactoros\Response\JsonResponse; -use function sprintf; - abstract class AbstractCreateShortUrlAction extends AbstractRestAction { /** @var UrlShortenerInterface */ @@ -52,21 +49,13 @@ abstract class AbstractCreateShortUrlAction extends AbstractRestAction } $longUrl = $shortUrlData->getLongUrl(); + $tags = $shortUrlData->getTags(); $shortUrlMeta = $shortUrlData->getMeta(); - try { - $shortUrl = $this->urlShortener->urlToShortCode($longUrl, $shortUrlData->getTags(), $shortUrlMeta); - $transformer = new ShortUrlDataTransformer($this->domainConfig); + $shortUrl = $this->urlShortener->urlToShortCode($longUrl, $tags, $shortUrlMeta); + $transformer = new ShortUrlDataTransformer($this->domainConfig); - return new JsonResponse($transformer->transform($shortUrl)); - } catch (NonUniqueSlugException $e) { - $customSlug = $shortUrlMeta->getCustomSlug(); - $this->logger->warning('Provided non-unique slug. {e}', ['e' => $e]); - return new JsonResponse([ - 'error' => RestUtils::getRestErrorCodeFromException($e), - 'message' => sprintf('Provided slug %s is already in use. Try with a different one.', $customSlug), - ], self::STATUS_BAD_REQUEST); - } + return new JsonResponse($transformer->transform($shortUrl)); } /** diff --git a/module/Rest/src/Util/RestUtils.php b/module/Rest/src/Util/RestUtils.php index 279d7fca..24398eb9 100644 --- a/module/Rest/src/Util/RestUtils.php +++ b/module/Rest/src/Util/RestUtils.php @@ -19,7 +19,8 @@ class RestUtils /** @deprecated */ public const INVALID_URL_ERROR = Core\InvalidUrlException::TYPE; public const INVALID_ARGUMENT_ERROR = 'INVALID_ARGUMENT'; - public const INVALID_SLUG_ERROR = 'INVALID_SLUG'; + /** @deprecated */ + public const INVALID_SLUG_ERROR = Core\NonUniqueSlugException::TYPE; public const INVALID_CREDENTIALS_ERROR = 'INVALID_CREDENTIALS'; public const INVALID_AUTH_TOKEN_ERROR = 'INVALID_AUTH_TOKEN'; public const INVALID_AUTHORIZATION_ERROR = 'INVALID_AUTHORIZATION'; diff --git a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php index 1cc5c889..c4f8970d 100644 --- a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php @@ -8,8 +8,6 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; -use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; -use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Rest\Action\ShortUrl\CreateShortUrlAction; use Shlinkio\Shlink\Rest\Util\RestUtils; @@ -88,23 +86,4 @@ class CreateShortUrlActionTest extends TestCase yield ['127.0.0.1']; yield ['???/&%$&']; } - - /** @test */ - public function nonUniqueSlugReturnsError(): void - { - $this->urlShortener->urlToShortCode( - Argument::type(Uri::class), - Argument::type('array'), - ShortUrlMeta::createFromRawData(['customSlug' => 'foo']), - Argument::cetera() - )->willThrow(NonUniqueSlugException::class)->shouldBeCalledOnce(); - - $request = (new ServerRequest())->withParsedBody([ - 'longUrl' => 'http://www.domain.com/foo/bar', - 'customSlug' => 'foo', - ]); - $response = $this->action->handle($request); - $this->assertEquals(400, $response->getStatusCode()); - $this->assertStringContainsString(RestUtils::INVALID_SLUG_ERROR, (string) $response->getBody()); - } }