Skip to content

Adds custom exceptions for invalid direction and filter operators and… #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,10 @@ FilterClause {
- If the `$count` query string value is not a boolean
- If the formatting of `$orderby` is not valid (should be a property, space and the direction)
- If the direction of the `$orderby` query string value is neither `asc` or `desc` (case-insensitive)
- This will throw an InvalidDirectionException, inheriting InvalidArgumentException.
- If the formatting of `$filter` is not valid (should be a property, space, operator, space and value)
- If the operator of the `$filter` query string value is not `eq`, `ne`, `gt`, `ge`, `lt`, `le` or `in` (case-insensitive)
- This will throw an InvalidFilterOperatorException, inheriting InvalidArgumentException.
- `LogicException`
- If an unforeseen edge case is triggered by an input value. For example when a regex operation fails. Should never be thrown under normal operation.
- If an edge case is found, please report them as an issue. Currently, I cannot write test cases for them as I don't know how to trigger them.
Expand Down
11 changes: 11 additions & 0 deletions src/Exceptions/InvalidDirectionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace GlobyApp\OdataQueryParser\Exceptions;

class InvalidDirectionException extends \InvalidArgumentException
{
public function __construct(string $message)
{
parent::__construct($message);
}
}
11 changes: 11 additions & 0 deletions src/Exceptions/InvalidFilterOperatorException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace GlobyApp\OdataQueryParser\Exceptions;

class InvalidFilterOperatorException extends \InvalidArgumentException
{
public function __construct(string $message)
{
parent::__construct($message);
}
}
20 changes: 11 additions & 9 deletions src/OdataQueryParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use GlobyApp\OdataQueryParser\Datatype\OrderByClause;
use GlobyApp\OdataQueryParser\Enum\FilterOperator;
use GlobyApp\OdataQueryParser\Enum\OrderDirection;
use GlobyApp\OdataQueryParser\Exceptions\InvalidDirectionException;
use GlobyApp\OdataQueryParser\Exceptions\InvalidFilterOperatorException;
use InvalidArgumentException;
use LogicException;

Expand Down Expand Up @@ -37,10 +39,10 @@
* @param string $url The URL to parse the query strings from. It should be a "complete" or "full" URL
* @param bool $withDollar When set to false, parses the odata keys without requiring the $ in front of odata keys
*
* @return OdataQuery|null OdataQuery object, parsed version of the input url, or null, if there is no query string
* @return OdataQuery OdataQuery object, parsed version of the input url
* @throws InvalidArgumentException The URL, or parts of it are malformed and could not be processed
*/
public static function parse(string $url, bool $withDollar = true): ?OdataQuery
public static function parse(string $url, bool $withDollar = true): OdataQuery
{
// Verify the URL is valid
if (filter_var($url, FILTER_VALIDATE_URL) === false) {
Expand All @@ -51,7 +53,7 @@
$queryString = self::extractQueryString($url);
if ($queryString === null) {
// There is no query string, so there cannot be a result
return null;
return new OdataQuery();
}

$parsedQueryString = self::parseQueryString($queryString);
Expand All @@ -76,16 +78,16 @@
* @return string|null The query string from the input URL. Null if there is no query string.
* @throws InvalidArgumentException The URL is malformed and the query string could not be extracted
*/
protected static function extractQueryString(string $url): ?string

Check warning on line 81 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.2)

Escaped Mutant for Mutator "ProtectedVisibility": --- Original +++ New @@ @@ * @return string|null The query string from the input URL. Null if there is no query string. * @throws InvalidArgumentException The URL is malformed and the query string could not be extracted */ - protected static function extractQueryString(string $url) : ?string + private static function extractQueryString(string $url) : ?string { $queryString = parse_url($url, PHP_URL_QUERY); if ($queryString === false) {

Check warning on line 81 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.3)

Escaped Mutant for Mutator "ProtectedVisibility": --- Original +++ New @@ @@ * @return string|null The query string from the input URL. Null if there is no query string. * @throws InvalidArgumentException The URL is malformed and the query string could not be extracted */ - protected static function extractQueryString(string $url) : ?string + private static function extractQueryString(string $url) : ?string { $queryString = parse_url($url, PHP_URL_QUERY); if ($queryString === false) {
{
$queryString = parse_url($url, PHP_URL_QUERY);

if ($queryString === false) {

Check warning on line 85 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.2)

Escaped Mutant for Mutator "FalseValue": --- Original +++ New @@ @@ protected static function extractQueryString(string $url) : ?string { $queryString = parse_url($url, PHP_URL_QUERY); - if ($queryString === false) { + if ($queryString === true) { throw new InvalidArgumentException("URL could not be parsed. Ensure the URL is not malformed."); } // The URL query string parser should return a string or null query string

Check warning on line 85 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.3)

Escaped Mutant for Mutator "FalseValue": --- Original +++ New @@ @@ protected static function extractQueryString(string $url) : ?string { $queryString = parse_url($url, PHP_URL_QUERY); - if ($queryString === false) { + if ($queryString === true) { throw new InvalidArgumentException("URL could not be parsed. Ensure the URL is not malformed."); } // The URL query string parser should return a string or null query string
throw new InvalidArgumentException("URL could not be parsed. Ensure the URL is not malformed.");
}

// The URL query string parser should return a string or null query string
if (!($queryString === null || is_string($queryString))) {

Check warning on line 90 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.2)

Escaped Mutant for Mutator "LogicalOrAllSubExprNegation": --- Original +++ New @@ @@ throw new InvalidArgumentException("URL could not be parsed. Ensure the URL is not malformed."); } // The URL query string parser should return a string or null query string - if (!($queryString === null || is_string($queryString))) { + if (!(!($queryString === null) || !is_string($queryString))) { throw new InvalidArgumentException("URL query string should be a string."); } return $queryString;

Check warning on line 90 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.3)

Escaped Mutant for Mutator "LogicalOrAllSubExprNegation": --- Original +++ New @@ @@ throw new InvalidArgumentException("URL could not be parsed. Ensure the URL is not malformed."); } // The URL query string parser should return a string or null query string - if (!($queryString === null || is_string($queryString))) { + if (!(!($queryString === null) || !is_string($queryString))) { throw new InvalidArgumentException("URL query string should be a string."); } return $queryString;
throw new InvalidArgumentException("URL query string should be a string.");
}

Expand All @@ -99,14 +101,14 @@
*
* @return array<string, string> The components of the query string, split up into an array
*/
public static function parseQueryString(string $queryString): array

Check warning on line 104 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.2)

Escaped Mutant for Mutator "PublicVisibility": --- Original +++ New @@ @@ * * @return array<string, string> The components of the query string, split up into an array */ - public static function parseQueryString(string $queryString) : array + protected static function parseQueryString(string $queryString) : array { $result = []; parse_str($queryString, $result);

Check warning on line 104 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.3)

Escaped Mutant for Mutator "PublicVisibility": --- Original +++ New @@ @@ * * @return array<string, string> The components of the query string, split up into an array */ - public static function parseQueryString(string $queryString) : array + protected static function parseQueryString(string $queryString) : array { $result = []; parse_str($queryString, $result);
{
$result = [];
parse_str($queryString, $result);

// Verify that the parsed result only has string key and values
foreach ($result as $key => $value) {

Check warning on line 110 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.2)

Escaped Mutant for Mutator "Foreach_": --- Original +++ New @@ @@ $result = []; parse_str($queryString, $result); // Verify that the parsed result only has string key and values - foreach ($result as $key => $value) { + foreach (array() as $key => $value) { if (!is_string($key) || !is_string($value)) { throw new InvalidArgumentException("Parsed query string has non-string values."); }

Check warning on line 110 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.3)

Escaped Mutant for Mutator "Foreach_": --- Original +++ New @@ @@ $result = []; parse_str($queryString, $result); // Verify that the parsed result only has string key and values - foreach ($result as $key => $value) { + foreach (array() as $key => $value) { if (!is_string($key) || !is_string($value)) { throw new InvalidArgumentException("Parsed query string has non-string values."); }
if (!is_string($key) || !is_string($value)) {

Check warning on line 111 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.2)

Escaped Mutant for Mutator "LogicalOr": --- Original +++ New @@ @@ parse_str($queryString, $result); // Verify that the parsed result only has string key and values foreach ($result as $key => $value) { - if (!is_string($key) || !is_string($value)) { + if (!is_string($key) && !is_string($value)) { throw new InvalidArgumentException("Parsed query string has non-string values."); } }

Check warning on line 111 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.3)

Escaped Mutant for Mutator "LogicalOr": --- Original +++ New @@ @@ parse_str($queryString, $result); // Verify that the parsed result only has string key and values foreach ($result as $key => $value) { - if (!is_string($key) || !is_string($value)) { + if (!is_string($key) && !is_string($value)) { throw new InvalidArgumentException("Parsed query string has non-string values."); } }
throw new InvalidArgumentException("Parsed query string has non-string values.");
}
}
Expand Down Expand Up @@ -140,7 +142,7 @@
*
* @return string The key with or without dollar sign prepended
*/
protected static function buildKeyConstant(string $key, bool $withDollar): string

Check warning on line 145 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.2)

Escaped Mutant for Mutator "ProtectedVisibility": --- Original +++ New @@ @@ * * @return string The key with or without dollar sign prepended */ - protected static function buildKeyConstant(string $key, bool $withDollar) : string + private static function buildKeyConstant(string $key, bool $withDollar) : string { return $withDollar ? '$' . $key : $key; }

Check warning on line 145 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.3)

Escaped Mutant for Mutator "ProtectedVisibility": --- Original +++ New @@ @@ * * @return string The key with or without dollar sign prepended */ - protected static function buildKeyConstant(string $key, bool $withDollar) : string + private static function buildKeyConstant(string $key, bool $withDollar) : string { return $withDollar ? '$' . $key : $key; }
{
return $withDollar ? '$'.$key : $key;
}
Expand All @@ -153,7 +155,7 @@
*
* @return bool Whether the odata key is present in the input query string
*/
protected static function hasKey(string $key, array $queryString): bool

Check warning on line 158 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.2)

Escaped Mutant for Mutator "ProtectedVisibility": --- Original +++ New @@ @@ * * @return bool Whether the odata key is present in the input query string */ - protected static function hasKey(string $key, array $queryString) : bool + private static function hasKey(string $key, array $queryString) : bool { return array_key_exists($key, $queryString); }

Check warning on line 158 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.3)

Escaped Mutant for Mutator "ProtectedVisibility": --- Original +++ New @@ @@ * * @return bool Whether the odata key is present in the input query string */ - protected static function hasKey(string $key, array $queryString) : bool + private static function hasKey(string $key, array $queryString) : bool { return array_key_exists($key, $queryString); }
{
return array_key_exists($key, $queryString);
}
Expand Down Expand Up @@ -223,7 +225,7 @@
if (!self::validateWithFilterValidate($queryString, self::$count, FILTER_VALIDATE_BOOLEAN)) {
// 0 and 1 are also valid values for a boolean
if (!(array_key_exists(self::$count, $queryString)
&& (trim($queryString[self::$count]) === '0' || trim($queryString[self::$count]) === '1'))) {

Check warning on line 228 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.2)

Escaped Mutant for Mutator "UnwrapTrim": --- Original +++ New @@ @@ { if (!self::validateWithFilterValidate($queryString, self::$count, FILTER_VALIDATE_BOOLEAN)) { // 0 and 1 are also valid values for a boolean - if (!(array_key_exists(self::$count, $queryString) && (trim($queryString[self::$count]) === '0' || trim($queryString[self::$count]) === '1'))) { + if (!(array_key_exists(self::$count, $queryString) && ($queryString[self::$count] === '0' || trim($queryString[self::$count]) === '1'))) { return null; } }

Check warning on line 228 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.2)

Escaped Mutant for Mutator "UnwrapTrim": --- Original +++ New @@ @@ { if (!self::validateWithFilterValidate($queryString, self::$count, FILTER_VALIDATE_BOOLEAN)) { // 0 and 1 are also valid values for a boolean - if (!(array_key_exists(self::$count, $queryString) && (trim($queryString[self::$count]) === '0' || trim($queryString[self::$count]) === '1'))) { + if (!(array_key_exists(self::$count, $queryString) && (trim($queryString[self::$count]) === '0' || $queryString[self::$count] === '1'))) { return null; } }

Check warning on line 228 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.3)

Escaped Mutant for Mutator "UnwrapTrim": --- Original +++ New @@ @@ { if (!self::validateWithFilterValidate($queryString, self::$count, FILTER_VALIDATE_BOOLEAN)) { // 0 and 1 are also valid values for a boolean - if (!(array_key_exists(self::$count, $queryString) && (trim($queryString[self::$count]) === '0' || trim($queryString[self::$count]) === '1'))) { + if (!(array_key_exists(self::$count, $queryString) && ($queryString[self::$count] === '0' || trim($queryString[self::$count]) === '1'))) { return null; } }

Check warning on line 228 in src/OdataQueryParser.php

View workflow job for this annotation

GitHub Actions / Test suite and coverage (8.3)

Escaped Mutant for Mutator "Identical": --- Original +++ New @@ @@ { if (!self::validateWithFilterValidate($queryString, self::$count, FILTER_VALIDATE_BOOLEAN)) { // 0 and 1 are also valid values for a boolean - if (!(array_key_exists(self::$count, $queryString) && (trim($queryString[self::$count]) === '0' || trim($queryString[self::$count]) === '1'))) { + if (!(array_key_exists(self::$count, $queryString) && (trim($queryString[self::$count]) !== '0' || trim($queryString[self::$count]) === '1'))) { return null; } }
return null;
}
}
Expand Down Expand Up @@ -285,7 +287,7 @@
* @param array<string, string> $queryString The query string to get the order by clauses from
*
* @return OrderByClause[] The parsed order by clauses
* @throws InvalidArgumentException If the direction is not asc or desc, or the clause split found a clause that was incorrectly formed
* @throws InvalidFilterOperatorException If the direction is not asc or desc, or the clause split found a clause that was incorrectly formed
*/
private static function getOrderBy(array $queryString): array
{
Expand Down Expand Up @@ -322,14 +324,14 @@
* @param string $direction The string representation of the order direction
*
* @return OrderDirection The parsed order direction
* @throws InvalidArgumentException If the direction is not asc or desc
* @throws InvalidDirectionException If the direction is not asc or desc
*/
private static function parseDirection(string $direction): OrderDirection
{
return match (strtolower($direction)) {
"asc" => OrderDirection::ASC,
"desc" => OrderDirection::DESC,
default => throw new InvalidArgumentException("Direction should be either asc or desc"),
default => throw new InvalidDirectionException("Direction should be either asc or desc"),
};
}

Expand All @@ -339,7 +341,7 @@
* @param array<string, string> $queryString The query string to find the filter key in
*
* @return FilterClause[] The parsed list of filter clauses
* @throws InvalidArgumentException If an invalid operator is found, or the clause split found a clause that was incorrectly formed
* @throws InvalidFilterOperatorException If an invalid operator is found, or the clause split found a clause that was incorrectly formed
*/
private static function getFilter(array $queryString): array
{
Expand Down Expand Up @@ -377,7 +379,7 @@
* @param string $operator The string representation of the filter operator
*
* @return FilterOperator The parsed filter operator
* @throws InvalidArgumentException If the filter operator is not valid
* @throws InvalidFilterOperatorException If the filter operator is not valid
*/
private static function parseFilterOperator(string $operator): FilterOperator
{
Expand All @@ -389,7 +391,7 @@
"lt" => FilterOperator::LESS_THAN,
"le" => FilterOperator::LESS_THAN_EQUALS,
"in" => FilterOperator::IN,
default => throw new InvalidArgumentException("Filter operator should be eq, ne, gt, ge, lt, le or in."),
default => throw new InvalidFilterOperatorException("Filter operator should be eq, ne, gt, ge, lt, le or in."),
};
}

Expand Down
10 changes: 5 additions & 5 deletions tests/FilterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ public function testShouldReturnEqualClauseMixedCase(): void

public function testShouldReturnEqualClauseWithFloat(): void
{
$this->assertIsFloat(OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20eq%2042.42")?->getFilter()[0]->getValue());
$this->assertIsFloat(OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20eq%2042.42")->getFilter()[0]->getValue());
}

public function testShouldReturnEqualClauseWithInteger(): void
{
$this->assertIsInt(OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20eq%2042")?->getFilter()[0]->getValue());
$this->assertIsInt(OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20eq%2042")->getFilter()[0]->getValue());
}

public function testShouldReturnEqualClauseWithSpacedStrings(): void
Expand Down Expand Up @@ -220,21 +220,21 @@ public function testShouldReturnIntegersIfInFloats(): void

public function testShouldReturnFloatIfCheckingInFloat(): void
{
$inArray = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20in%20(19.5,%2020)")?->getFilter()[0]->getValue();
$inArray = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20in%20(19.5,%2020)")->getFilter()[0]->getValue();
$this->assertIsArray($inArray);
$this->assertArrayHasKey(0, $inArray);
$this->assertIsFloat($inArray[0]);
}

public function testBooleanTrueValue(): void
{
$bool = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20eq%20true")?->getFilter()[0]->getValue();
$bool = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20eq%20true")->getFilter()[0]->getValue();
$this->assertTrue($bool);
}

public function testBooleanFalseValue(): void
{
$bool = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20eq%20false")?->getFilter()[0]->getValue();
$bool = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20eq%20false")->getFilter()[0]->getValue();
$this->assertFalse($bool);
}

Expand Down
2 changes: 1 addition & 1 deletion tests/ParseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function testShouldReturnExceptionIfUrlIsNotValid(): void {
}

public function testShouldReturnAnEmptyArrayIfNoQueryParameters(): void {
$expected = null;
$expected = new OdataQueryParser\OdataQuery();
$actual = OdataQueryParser\OdataQueryParser::parse("https://example.com");

$this->assertEquals($expected, $actual);
Expand Down
2 changes: 1 addition & 1 deletion tests/SkipTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public function testShouldNotThrowExceptionIfSkipIsEqualToZeroWithSpace(): void

public function testShouldReturnAnIntegerForTheSkipValue(): void
{
$this->assertIsInt(OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=42')?->getSkip());
$this->assertIsInt(OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=42')->getSkip());
}

public function testShouldThrowAnInvalidArgumentExceptionIfSkipParameterIsLowerThanZeroInNonDollarMode(): void
Expand Down
2 changes: 1 addition & 1 deletion tests/TopTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public function testShouldReturnTheTopValueIfProvidedInTheQueryParameters(): voi

public function testShouldReturnAnIntegerTopValue(): void
{
$this->assertIsInt(OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$top=42')?->getTop());
$this->assertIsInt(OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$top=42')->getTop());
}

public function testShouldReturnTheTopValueIfProvidedInTheQueryParametersAndFilledWithSpaces(): void
Expand Down