Skip to content

Commit

Permalink
[BUGFIX] Convert non-list arrays in API response
Browse files Browse the repository at this point in the history
Resolves: #81
  • Loading branch information
eliashaeussler committed Aug 16, 2023
1 parent 26407a3 commit a236656
Show file tree
Hide file tree
Showing 14 changed files with 730 additions and 5 deletions.
51 changes: 51 additions & 0 deletions Classes/Exception/InvalidArrayPathException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS extension "personio_jobs".
*
* Copyright (C) 2023 Elias Häußler <e.haeussler@familie-redlich.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

namespace CPSIT\Typo3PersonioJobs\Exception;

use Exception;

/**
* InvalidArrayPathException
*
* @author Elias Häußler <e.haeussler@familie-redlich.de>
* @license GPL-2.0-or-later
*/
final class InvalidArrayPathException extends Exception
{
public static function forUnexpectedType(string $path, string $expected, string $actual): self

Check warning on line 36 in Classes/Exception/InvalidArrayPathException.php

View check run for this annotation

Codecov / codecov/patch

Classes/Exception/InvalidArrayPathException.php#L36

Added line #L36 was not covered by tests
{
return new self(
sprintf('Expected %s at array path "%s", got %s instead.', $expected, $path, $actual),
1692177655,
);

Check warning on line 41 in Classes/Exception/InvalidArrayPathException.php

View check run for this annotation

Codecov / codecov/patch

Classes/Exception/InvalidArrayPathException.php#L38-L41

Added lines #L38 - L41 were not covered by tests
}

public static function forInvalidPathSegment(string $path): self

Check warning on line 44 in Classes/Exception/InvalidArrayPathException.php

View check run for this annotation

Codecov / codecov/patch

Classes/Exception/InvalidArrayPathException.php#L44

Added line #L44 was not covered by tests
{
return new self(
sprintf('The array path segment "%s" is not valid.', $path),
1692178102,
);

Check warning on line 49 in Classes/Exception/InvalidArrayPathException.php

View check run for this annotation

Codecov / codecov/patch

Classes/Exception/InvalidArrayPathException.php#L46-L49

Added lines #L46 - L49 were not covered by tests
}
}
47 changes: 47 additions & 0 deletions Classes/Exception/MalformedXmlException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS extension "personio_jobs".
*
* Copyright (C) 2023 Elias Häußler <e.haeussler@familie-redlich.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

namespace CPSIT\Typo3PersonioJobs\Exception;

use Exception;

/**
* MalformedXmlException
*
* @author Elias Häußler <e.haeussler@familie-redlich.de>
* @license GPL-2.0-or-later
*/
final class MalformedXmlException extends Exception
{
public static function create(string $input, string $error): self

Check warning on line 36 in Classes/Exception/MalformedXmlException.php

View check run for this annotation

Codecov / codecov/patch

Classes/Exception/MalformedXmlException.php#L36

Added line #L36 was not covered by tests
{
return new self(
sprintf(
'The string "%s" does not contain valid XML: %s',
mb_strimwidth($input, 0, 100, ''),
$error,
),
1692170602,
);
}

Check warning on line 46 in Classes/Exception/MalformedXmlException.php

View check run for this annotation

Codecov / codecov/patch

Classes/Exception/MalformedXmlException.php#L38-L46

Added lines #L38 - L46 were not covered by tests
}
89 changes: 89 additions & 0 deletions Classes/Mapper/Source/XmlSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS extension "personio_jobs".
*
* Copyright (C) 2023 Elias Häußler <e.haeussler@familie-redlich.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

namespace CPSIT\Typo3PersonioJobs\Mapper\Source;

use ArrayObject;
use CPSIT\Typo3PersonioJobs\Exception\InvalidArrayPathException;
use CPSIT\Typo3PersonioJobs\Exception\MalformedXmlException;
use CPSIT\Typo3PersonioJobs\Utility\ArrayUtility;
use Mtownsend\XmlToArray\XmlToArray;
use Throwable;

/**
* XmlSource
*
* @author Elias Häußler <e.haeussler@familie-redlich.de>
* @license GPL-2.0-or-later
*
* @extends ArrayObject<string, mixed>
*/
final class XmlSource extends ArrayObject
{
/**
* @param array<string, mixed> $source
*/
public function __construct(array $source)
{
parent::__construct($source);

Check warning on line 48 in Classes/Mapper/Source/XmlSource.php

View check run for this annotation

Codecov / codecov/patch

Classes/Mapper/Source/XmlSource.php#L48

Added line #L48 was not covered by tests
}

/**
* @throws MalformedXmlException
*/
public static function fromXml(string $xml): self

Check warning on line 54 in Classes/Mapper/Source/XmlSource.php

View check run for this annotation

Codecov / codecov/patch

Classes/Mapper/Source/XmlSource.php#L54

Added line #L54 was not covered by tests
{
set_error_handler(static fn (int $code, string $message) => self::handleParseError($xml, $message));

Check warning on line 56 in Classes/Mapper/Source/XmlSource.php

View check run for this annotation

Codecov / codecov/patch

Classes/Mapper/Source/XmlSource.php#L56

Added line #L56 was not covered by tests

try {
$source = XmlToArray::convert($xml);
} catch (Throwable $exception) {
self::handleParseError($xml, $exception->getMessage());

Check warning on line 61 in Classes/Mapper/Source/XmlSource.php

View check run for this annotation

Codecov / codecov/patch

Classes/Mapper/Source/XmlSource.php#L59-L61

Added lines #L59 - L61 were not covered by tests
} finally {
restore_error_handler();

Check warning on line 63 in Classes/Mapper/Source/XmlSource.php

View check run for this annotation

Codecov / codecov/patch

Classes/Mapper/Source/XmlSource.php#L63

Added line #L63 was not covered by tests
}

return new self($source);

Check warning on line 66 in Classes/Mapper/Source/XmlSource.php

View check run for this annotation

Codecov / codecov/patch

Classes/Mapper/Source/XmlSource.php#L66

Added line #L66 was not covered by tests
}

/**
* @throws InvalidArrayPathException
*/
public function asCollection(string $node): self

Check warning on line 72 in Classes/Mapper/Source/XmlSource.php

View check run for this annotation

Codecov / codecov/patch

Classes/Mapper/Source/XmlSource.php#L72

Added line #L72 was not covered by tests
{
$clone = clone $this;
$clone->exchangeArray(
ArrayUtility::convertToCollection((array)$clone, $node),
);

Check warning on line 77 in Classes/Mapper/Source/XmlSource.php

View check run for this annotation

Codecov / codecov/patch

Classes/Mapper/Source/XmlSource.php#L74-L77

Added lines #L74 - L77 were not covered by tests

return $clone;

Check warning on line 79 in Classes/Mapper/Source/XmlSource.php

View check run for this annotation

Codecov / codecov/patch

Classes/Mapper/Source/XmlSource.php#L79

Added line #L79 was not covered by tests
}

/**
* @throws MalformedXmlException
*/
private static function handleParseError(string $xml, string $message): never

Check warning on line 85 in Classes/Mapper/Source/XmlSource.php

View check run for this annotation

Codecov / codecov/patch

Classes/Mapper/Source/XmlSource.php#L85

Added line #L85 was not covered by tests
{
throw MalformedXmlException::create($xml, $message);

Check warning on line 87 in Classes/Mapper/Source/XmlSource.php

View check run for this annotation

Codecov / codecov/patch

Classes/Mapper/Source/XmlSource.php#L87

Added line #L87 was not covered by tests
}
}
15 changes: 10 additions & 5 deletions Classes/Service/PersonioApiService.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@
use CPSIT\Typo3PersonioJobs\Domain\Model\Job;
use CPSIT\Typo3PersonioJobs\Domain\Model\JobDescription;
use CPSIT\Typo3PersonioJobs\Event\AfterJobsMappedEvent;
use CPSIT\Typo3PersonioJobs\Exception\InvalidArrayPathException;
use CPSIT\Typo3PersonioJobs\Exception\MalformedApiResponseException;
use CPSIT\Typo3PersonioJobs\Exception\MalformedXmlException;
use CPSIT\Typo3PersonioJobs\Mapper\Source\XmlSource;
use CPSIT\Typo3PersonioJobs\Utility\FrontendUtility;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Source\Source;
use CuyZ\Valinor\Mapper\Tree\Message\Messages;
use CuyZ\Valinor\Mapper\TreeMapper;
use CuyZ\Valinor\MapperBuilder;
use DateTimeInterface;
use Mtownsend\XmlToArray\XmlToArray;
use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Core\Http\RequestFactory;
use TYPO3\CMS\Core\Http\Uri;
Expand All @@ -62,17 +63,21 @@ public function __construct(

/**
* @return list<Job>
* @throws InvalidArrayPathException
* @throws MalformedApiResponseException
* @throws MalformedXmlException
*/
public function getJobs(): array
{
$requestUri = $this->apiUrl->withPath('/xml');
$response = $this->requestFactory->request((string)$requestUri);
$array = XmlToArray::convert((string)$response->getBody());
$source = Source::array($array['position'] ?? []);
$source = XmlSource::fromXml((string)$response->getBody())
->asCollection('position')
->asCollection('position.*.jobDescriptions.jobDescription')
;

try {
$jobs = $this->mapper->map('list<' . Job::class . '>', $source);
$jobs = $this->mapper->map('list<' . Job::class . '>', $source['position']);

$this->eventDispatcher->dispatch(new AfterJobsMappedEvent($requestUri, $jobs));

Expand Down
4 changes: 4 additions & 0 deletions Classes/Service/PersonioImportService.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
use CPSIT\Typo3PersonioJobs\Domain\Repository\JobRepository;
use CPSIT\Typo3PersonioJobs\Enums\ImportOperation;
use CPSIT\Typo3PersonioJobs\Event\AfterJobsImportedEvent;
use CPSIT\Typo3PersonioJobs\Exception\InvalidArrayPathException;
use CPSIT\Typo3PersonioJobs\Exception\InvalidParametersException;
use CPSIT\Typo3PersonioJobs\Exception\MalformedXmlException;
use CPSIT\Typo3PersonioJobs\Helper\SlugHelper;
use Generator;
use Psr\EventDispatcher\EventDispatcherInterface;
Expand Down Expand Up @@ -59,7 +61,9 @@ public function __construct(

/**
* @param int<0, max> $storagePid
* @throws InvalidArrayPathException
* @throws InvalidParametersException
* @throws MalformedXmlException
*/
public function import(
int $storagePid,
Expand Down
111 changes: 111 additions & 0 deletions Classes/Utility/ArrayUtility.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS extension "personio_jobs".
*
* Copyright (C) 2023 Elias Häußler <e.haeussler@familie-redlich.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

namespace CPSIT\Typo3PersonioJobs\Utility;

use CPSIT\Typo3PersonioJobs\Exception\InvalidArrayPathException;

/**
* ArrayUtility
*
* @author Elias Häußler <e.haeussler@familie-redlich.de>
* @license GPL-2.0-or-later
*/
final class ArrayUtility
{
/**
* @param array<string, mixed> $array
* @return array<string, mixed>
* @throws InvalidArrayPathException
*/
public static function convertToCollection(array $array, string $path): array

Check warning on line 41 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L41

Added line #L41 was not covered by tests
{
$reference = &$array;
$pathSegments = str_getcsv($path, '.');
$remainingSegments = $pathSegments;
$currentPathSegments = [];

Check warning on line 46 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L43-L46

Added lines #L43 - L46 were not covered by tests

foreach ($pathSegments as $pathSegment) {
$currentPathSegments[] = array_shift($remainingSegments);

Check warning on line 49 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L48-L49

Added lines #L48 - L49 were not covered by tests

// Validate path segment
if (!is_string($pathSegment) || trim($pathSegment) === '') {
throw InvalidArrayPathException::forInvalidPathSegment(implode('.', $currentPathSegments));

Check warning on line 53 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L52-L53

Added lines #L52 - L53 were not covered by tests
}

// Handle non-array values
if (!is_array($reference)) {
throw InvalidArrayPathException::forUnexpectedType(
implode('.', $currentPathSegments),
'array',
gettype($reference),
);

Check warning on line 62 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L57-L62

Added lines #L57 - L62 were not covered by tests
}

// Handle placeholder for lists
if ($pathSegment === '*') {
$reference = self::convertListToCollection($reference, implode('.', $remainingSegments));

Check warning on line 67 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L66-L67

Added lines #L66 - L67 were not covered by tests

return $array;

Check warning on line 69 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L69

Added line #L69 was not covered by tests
}

// Create node value if not exists
if (!array_key_exists($pathSegment, $reference)) {
$reference[$pathSegment] = [];

Check warning on line 74 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L73-L74

Added lines #L73 - L74 were not covered by tests
}

$reference = &$reference[$pathSegment];

Check warning on line 77 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L77

Added line #L77 was not covered by tests
}

// Handle non-array values
if (!is_array($reference)) {
throw InvalidArrayPathException::forUnexpectedType($path, 'array', gettype($reference));

Check warning on line 82 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L81-L82

Added lines #L81 - L82 were not covered by tests
}

// Convert array to list
if (!array_is_list($reference)) {
$reference = [$reference];

Check warning on line 87 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L86-L87

Added lines #L86 - L87 were not covered by tests
}

return $array;

Check warning on line 90 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L90

Added line #L90 was not covered by tests
}

/**
* @param array<mixed> $array
* @return array<int, mixed>
* @throws InvalidArrayPathException
*/
private static function convertListToCollection(array $array, string $path): array

Check warning on line 98 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L98

Added line #L98 was not covered by tests
{
// Handle non-lists
if (!array_is_list($array)) {
throw InvalidArrayPathException::forUnexpectedType($path, 'list', 'array');

Check warning on line 102 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L101-L102

Added lines #L101 - L102 were not covered by tests
}

foreach ($array as $key => $value) {
$array[$key] = self::convertToCollection($value, $path);

Check warning on line 106 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L105-L106

Added lines #L105 - L106 were not covered by tests
}

return $array;

Check warning on line 109 in Classes/Utility/ArrayUtility.php

View check run for this annotation

Codecov / codecov/patch

Classes/Utility/ArrayUtility.php#L109

Added line #L109 was not covered by tests
}
}
Loading

0 comments on commit a236656

Please sign in to comment.