Skip to content

Commit

Permalink
feat: introduce InvalidSource thrown when using invalid JSON/YAML
Browse files Browse the repository at this point in the history
JSON or YAML given to a source may be invalid, in which case an
exception can be caught and manipulated.

```php
try {
    $source = \CuyZ\Valinor\Mapper\Source\Source::json('invalid JSON');
} catch (\CuyZ\Valinor\Mapper\Source\Exception\InvalidSource $error) {
    // Let the application handle the exception in the desired way.
    // It is possible to get the original source with `$error->source()`
}
```
  • Loading branch information
romm committed Mar 28, 2023
1 parent b9e3816 commit 0739d12
Show file tree
Hide file tree
Showing 14 changed files with 113 additions and 53 deletions.
16 changes: 15 additions & 1 deletion docs/pages/how-to/transform-input.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Transforming input

Any source can be given to the mapper, be it an array, some json, yaml or even a
Any source can be given to the mapper, be it an array, some JSON, YAML or even a
file:

```php
Expand Down Expand Up @@ -30,6 +30,20 @@ $mapper->map(
);
```

!!! info

JSON or YAML given to a source may be invalid, in which case an exception
can be caught and manipulated.

```php
try {
$source = \CuyZ\Valinor\Mapper\Source\Source::json('invalid JSON');
} catch (\CuyZ\Valinor\Mapper\Source\Exception\InvalidSource $exception) {
// Let the application handle the exception in the desired way.
// It is possible to get the original source with `$exception->source()`
}
```

## Modifiers

Sometimes the source is not in the same format and/or organised in the same
Expand Down
4 changes: 2 additions & 2 deletions src/Mapper/Source/Exception/FileExtensionNotHandled.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

namespace CuyZ\Valinor\Mapper\Source\Exception;

use RuntimeException;
use LogicException;

/** @internal */
final class FileExtensionNotHandled extends RuntimeException implements SourceException
final class FileExtensionNotHandled extends LogicException
{
public function __construct(string $extension)
{
Expand Down
11 changes: 8 additions & 3 deletions src/Mapper/Source/Exception/InvalidJson.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
use RuntimeException;

/** @internal */
final class InvalidJson extends RuntimeException implements SourceException
final class InvalidJson extends RuntimeException implements InvalidSource
{
public function __construct()
public function __construct(private string $source)
{
parent::__construct(
"The given value is not a valid JSON entry.",
'Invalid JSON source.',
1566307185
);
}

public function source(): string
{
return $this->source;
}
}
13 changes: 13 additions & 0 deletions src/Mapper/Source/Exception/InvalidSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Mapper\Source\Exception;

use Throwable;

/** @api */
interface InvalidSource extends Throwable
{
public function source(): mixed;
}
11 changes: 8 additions & 3 deletions src/Mapper/Source/Exception/InvalidYaml.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
use RuntimeException;

/** @internal */
final class InvalidYaml extends RuntimeException implements SourceException
final class InvalidYaml extends RuntimeException implements InvalidSource
{
public function __construct()
public function __construct(private string $source)
{
parent::__construct(
"The given value is not a valid YAML entry.",
'Invalid YAML source.',
1629990223
);
}

public function source(): string
{
return $this->source;
}
}
10 changes: 0 additions & 10 deletions src/Mapper/Source/Exception/SourceException.php

This file was deleted.

14 changes: 8 additions & 6 deletions src/Mapper/Source/Exception/SourceNotIterable.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@

namespace CuyZ\Valinor\Mapper\Source\Exception;

use CuyZ\Valinor\Utility\ValueDumper;
use RuntimeException;

/** @internal */
final class SourceNotIterable extends RuntimeException implements SourceException
final class SourceNotIterable extends RuntimeException implements InvalidSource
{
public function __construct(mixed $value)
public function __construct(private string $source)
{
$value = ValueDumper::dump($value);

parent::__construct(
"Invalid source $value, expected an iterable.",
'Invalid source, expected an iterable.',
1566307291
);
}

public function source(): string
{
return $this->source;
}
}
4 changes: 2 additions & 2 deletions src/Mapper/Source/Exception/UnableToReadFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

namespace CuyZ\Valinor\Mapper\Source\Exception;

use RuntimeException;
use LogicException;

/** @internal */
final class UnableToReadFile extends RuntimeException implements SourceException
final class UnableToReadFile extends LogicException
{
public function __construct(string $filename)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Mapper/Source/Exception/YamlExtensionNotEnabled.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

namespace CuyZ\Valinor\Mapper\Source\Exception;

use RuntimeException;
use LogicException;

/**
* @internal
*
* @codeCoverageIgnore
* @infection-ignore-all
*/
final class YamlExtensionNotEnabled extends RuntimeException implements SourceException
final class YamlExtensionNotEnabled extends LogicException
{
public function __construct()
{
Expand Down
8 changes: 6 additions & 2 deletions src/Mapper/Source/JsonSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace CuyZ\Valinor\Mapper\Source;

use CuyZ\Valinor\Mapper\Source\Exception\InvalidJson;
use CuyZ\Valinor\Mapper\Source\Exception\InvalidSource;
use CuyZ\Valinor\Mapper\Source\Exception\SourceNotIterable;
use Iterator;
use IteratorAggregate;
Expand All @@ -23,16 +24,19 @@ final class JsonSource implements IteratorAggregate
/** @var iterable<mixed> */
private iterable $source;

/**
* @throws InvalidSource
*/
public function __construct(string $jsonSource)
{
$source = json_decode($jsonSource, true);

if ($source === null) {
throw new InvalidJson();
throw new InvalidJson($jsonSource);
}

if (! is_iterable($source)) {
throw new SourceNotIterable($source);
throw new SourceNotIterable($jsonSource);
}

$this->source = $source;
Expand Down
7 changes: 7 additions & 0 deletions src/Mapper/Source/Source.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace CuyZ\Valinor\Mapper\Source;

use CuyZ\Valinor\Mapper\Source\Exception\InvalidSource;
use CuyZ\Valinor\Mapper\Source\Modifier\CamelCaseKeys;
use CuyZ\Valinor\Mapper\Source\Modifier\PathMapping;
use IteratorAggregate;
Expand Down Expand Up @@ -39,11 +40,17 @@ public static function array(array $data): Source
return new Source($data);
}

/**
* @throws InvalidSource
*/
public static function json(string $jsonSource): Source
{
return new Source(new JsonSource($jsonSource));
}

/**
* @throws InvalidSource
*/
public static function yaml(string $yamlSource): Source
{
return new Source(new YamlSource($yamlSource));
Expand Down
8 changes: 6 additions & 2 deletions src/Mapper/Source/YamlSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace CuyZ\Valinor\Mapper\Source;

use CuyZ\Valinor\Mapper\Source\Exception\InvalidSource;
use CuyZ\Valinor\Mapper\Source\Exception\InvalidYaml;
use CuyZ\Valinor\Mapper\Source\Exception\SourceNotIterable;
use CuyZ\Valinor\Mapper\Source\Exception\YamlExtensionNotEnabled;
Expand All @@ -26,6 +27,9 @@ final class YamlSource implements IteratorAggregate
/** @var iterable<mixed> */
private iterable $source;

/**
* @throws InvalidSource
*/
public function __construct(string $yamlSource)
{
/** @infection-ignore-all */
Expand All @@ -38,11 +42,11 @@ public function __construct(string $yamlSource)
$source = @yaml_parse($yamlSource);

if ($source === false) {
throw new InvalidYaml();
throw new InvalidYaml($yamlSource);
}

if (! is_iterable($source)) {
throw new SourceNotIterable($source);
throw new SourceNotIterable($yamlSource);
}

$this->source = $source;
Expand Down
28 changes: 18 additions & 10 deletions tests/Unit/Mapper/Source/JsonSourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,27 @@ public function test_valid_json_is_parsed_correctly(): void

public function test_invalid_json_throws_exception(): void
{
$this->expectException(InvalidJson::class);
$this->expectExceptionCode(1566307185);
$this->expectExceptionMessage('The given value is not a valid JSON entry.');

new JsonSource('@');
try {
new JsonSource('some invalid JSON entry');

self::fail();
} catch (InvalidJson $exception) {
self::assertSame(1566307185, $exception->getCode());
self::assertSame('Invalid JSON source.', $exception->getMessage());
self::assertSame('some invalid JSON entry', $exception->source());
}
}

public function test_invalid_json_type_throws_exception(): void
{
$this->expectException(SourceNotIterable::class);
$this->expectExceptionCode(1566307291);
$this->expectExceptionMessage('Invalid source true, expected an iterable.');

new JsonSource('true');
try {
new JsonSource('true');

self::fail();
} catch (SourceNotIterable $exception) {
self::assertSame(1566307291, $exception->getCode());
self::assertSame('Invalid source, expected an iterable.', $exception->getMessage());
self::assertSame('true', $exception->source());
}
}
}
28 changes: 18 additions & 10 deletions tests/Unit/Mapper/Source/YamlSourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,27 @@ public function test_valid_yaml_is_parsed_correctly(): void

public function test_invalid_yaml_throws_exception(): void
{
$this->expectException(InvalidYaml::class);
$this->expectExceptionCode(1629990223);
$this->expectExceptionMessage('The given value is not a valid YAML entry.');

new YamlSource('@');
try {
new YamlSource('@ invalid yaml');

self::fail();
} catch (InvalidYaml $exception) {
self::assertSame(1629990223, $exception->getCode());
self::assertSame('Invalid YAML source.', $exception->getMessage());
self::assertSame('@ invalid yaml', $exception->source());
}
}

public function test_invalid_yaml_type_throws_exception(): void
{
$this->expectException(SourceNotIterable::class);
$this->expectExceptionCode(1566307291);
$this->expectExceptionMessage("Invalid source 'foo', expected an iterable.");

new YamlSource('foo');
try {
new YamlSource('foo');

self::fail();
} catch (SourceNotIterable $exception) {
self::assertSame(1566307291, $exception->getCode());
self::assertSame('Invalid source, expected an iterable.', $exception->getMessage());
self::assertSame('foo', $exception->source());
}
}
}

0 comments on commit 0739d12

Please sign in to comment.