Skip to content

Commit

Permalink
Merge pull request #90 from chadicus/fea/conflicts-with
Browse files Browse the repository at this point in the history
Add "conflictsWith" functionality to Filterer library
  • Loading branch information
Sravanthi001 committed Nov 19, 2019
2 parents a8dec17 + 5ca6340 commit de16cb0
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 26 deletions.
26 changes: 26 additions & 0 deletions src/FilterOptions.php
@@ -0,0 +1,26 @@
<?php

namespace TraderInteractive;

final class FilterOptions
{
/**
* @var string
*/
const DEFAULT_VALUE = 'default';

/**
* @var string
*/
const CUSTOM_ERROR = 'error';

/**
* @var string
*/
const IS_REQUIRED = 'required';

/**
* @var string
*/
const CONFLICTS_WITH = 'conflictsWith';
}
93 changes: 67 additions & 26 deletions src/Filterer.php
Expand Up @@ -46,9 +46,9 @@ final class Filterer implements FiltererInterface
* @var array
*/
const DEFAULT_OPTIONS = [
'allowUnknowns' => false,
'defaultRequired' => false,
'responseType' => self::RESPONSE_TYPE_ARRAY,
FiltererOptions::ALLOW_UNKNOWNS => false,
FiltererOptions::DEFAULT_REQUIRED => false,
FiltererOptions::RESPONSE_TYPE => self::RESPONSE_TYPE_ARRAY,
];

/**
Expand Down Expand Up @@ -123,12 +123,15 @@ public function execute(array $input) : FilterResponse
$leftOverInput = array_diff_key($input, $this->specification);

$errors = [];
$conflicts = [];
foreach ($inputToFilter as $field => $input) {
$filters = $this->specification[$field];
self::assertFiltersIsAnArray($filters, $field);
$customError = self::validateCustomError($filters, $field);
unset($filters['required']);//doesn't matter if required since we have this one
unset($filters['default']);//doesn't matter if there is a default since we have a value
unset($filters[FilterOptions::IS_REQUIRED]);//doesn't matter if required since we have this one
unset($filters[FilterOptions::DEFAULT_VALUE]);//doesn't matter if there is a default since we have a value
$conflicts = self::extractConflicts($filters, $field, $conflicts);

foreach ($filters as $filter) {
self::assertFilterIsNotArray($filter, $field);

Expand Down Expand Up @@ -156,15 +159,16 @@ public function execute(array $input) : FilterResponse
foreach ($leftOverSpec as $field => $filters) {
self::assertFiltersIsAnArray($filters, $field);
$required = self::getRequired($filters, $this->defaultRequired, $field);
if (array_key_exists('default', $filters)) {
$inputToFilter[$field] = $filters['default'];
if (array_key_exists(FilterOptions::DEFAULT_VALUE, $filters)) {
$inputToFilter[$field] = $filters[FilterOptions::DEFAULT_VALUE];
continue;
}

$errors = self::handleRequiredFields($required, $field, $errors);
}

$errors = self::handleAllowUnknowns($this->allowUnknowns, $leftOverInput, $errors);
$errors = self::handleConflicts($inputToFilter, $conflicts, $errors);

return new FilterResponse($inputToFilter, $errors, $leftOverInput);
}
Expand All @@ -179,6 +183,40 @@ public function getAliases() : array
return $this->filterAliases ?? self::$registeredFilterAliases;
}

private static function extractConflicts(array &$filters, string $field, array $conflicts) : array
{
$conflictsWith = $filters[FilterOptions::CONFLICTS_WITH] ?? null;
unset($filters[FilterOptions::CONFLICTS_WITH]);
if ($conflictsWith === null) {
return $conflicts;
}

if (!is_array($conflictsWith)) {
$conflictsWith = [$conflictsWith];
}

$conflicts[$field] = $conflictsWith;

return $conflicts;
}

private static function handleConflicts(array $inputToFilter, array $conflicts, array $errors)
{
foreach (array_keys($inputToFilter) as $field) {
if (!array_key_exists($field, $conflicts)) {
continue;
}

foreach ($conflicts[$field] as $conflictsWith) {
if (array_key_exists($conflictsWith, $inputToFilter)) {
$errors[] = "Field '{$field}' cannot be given if field '{$conflictsWith}' is present.";
}
}
}

return $errors;
}

/**
* @return array
*
Expand Down Expand Up @@ -219,8 +257,8 @@ public function withSpecification(array $specification) : FiltererInterface
private function getOptions() : array
{
return [
'defaultRequired' => $this->defaultRequired,
'allowUnknowns' => $this->allowUnknowns,
FiltererOptions::DEFAULT_REQUIRED => $this->defaultRequired,
FiltererOptions::ALLOW_UNKNOWNS => $this->allowUnknowns,
];
}

Expand Down Expand Up @@ -296,7 +334,7 @@ private function getOptions() : array
public static function filter(array $specification, array $input, array $options = [])
{
$options += self::DEFAULT_OPTIONS;
$responseType = $options['responseType'];
$responseType = $options[FiltererOptions::RESPONSE_TYPE];

$filterer = new Filterer($specification, $options);
$filterResponse = $filterer->execute($input);
Expand Down Expand Up @@ -484,9 +522,11 @@ private static function handleRequiredFields(bool $required, string $field, arra

private static function getRequired($filters, $defaultRequired, $field) : bool
{
$required = isset($filters['required']) ? $filters['required'] : $defaultRequired;
$required = $filters[FilterOptions::IS_REQUIRED] ?? $defaultRequired;
if ($required !== false && $required !== true) {
throw new InvalidArgumentException("'required' for field '{$field}' was not a bool");
throw new InvalidArgumentException(
sprintf("'%s' for field '%s' was not a bool", FilterOptions::IS_REQUIRED, $field)
);
}

return $required;
Expand All @@ -508,11 +548,8 @@ private static function handleCustomError(
) : array {
$error = $customError;
if ($error === null) {
$error = sprintf(
"Field '%s' with value '{value}' failed filtering, message '%s'",
$field,
$e->getMessage()
);
$errorFormat = "Field '%s' with value '{value}' failed filtering, message '%s'";
$error = sprintf($errorFormat, $field, $e->getMessage());
}

$errors[$field] = str_replace('{value}', trim(var_export($value, true), "'"), $error);
Expand Down Expand Up @@ -547,33 +584,37 @@ private static function assertFilterIsNotArray($filter, string $field)
private static function validateCustomError(array &$filters, string $field)
{
$customError = null;
if (array_key_exists('error', $filters)) {
$customError = $filters['error'];
if (array_key_exists(FilterOptions::CUSTOM_ERROR, $filters)) {
$customError = $filters[FilterOptions::CUSTOM_ERROR];
if (!is_string($customError) || trim($customError) === '') {
throw new InvalidArgumentException("error for field '{$field}' was not a non-empty string");
throw new InvalidArgumentException(
sprintf("%s for field '%s' was not a non-empty string", FilterOptions::CUSTOM_ERROR, $field)
);
}

unset($filters['error']);//unset so its not used as a filter
unset($filters[FilterOptions::CUSTOM_ERROR]);//unset so its not used as a filter
}

return $customError;
}

private static function getAllowUnknowns(array $options) : bool
{
$allowUnknowns = $options['allowUnknowns'];
$allowUnknowns = $options[FiltererOptions::ALLOW_UNKNOWNS];
if ($allowUnknowns !== false && $allowUnknowns !== true) {
throw new InvalidArgumentException("'allowUnknowns' option was not a bool");
throw new InvalidArgumentException(sprintf("'%s' option was not a bool", FiltererOptions::ALLOW_UNKNOWNS));
}

return $allowUnknowns;
}

private static function getDefaultRequired(array $options) : bool
{
$defaultRequired = $options['defaultRequired'];
$defaultRequired = $options[FiltererOptions::DEFAULT_REQUIRED];
if ($defaultRequired !== false && $defaultRequired !== true) {
throw new InvalidArgumentException("'defaultRequired' option was not a bool");
throw new InvalidArgumentException(
sprintf("'%s' option was not a bool", FiltererOptions::DEFAULT_REQUIRED)
);
}

return $defaultRequired;
Expand Down Expand Up @@ -602,6 +643,6 @@ private static function generateFilterResponse(string $responseType, FilterRespo
];
}

throw new InvalidArgumentException("'responseType' was not a recognized value");
throw new InvalidArgumentException(sprintf("'%s' was not a recognized value", FiltererOptions::RESPONSE_TYPE));
}
}
21 changes: 21 additions & 0 deletions src/FiltererOptions.php
@@ -0,0 +1,21 @@
<?php

namespace TraderInteractive;

final class FiltererOptions
{
/**
* @var string
*/
const ALLOW_UNKNOWNS = 'allowUnknowns';

/**
* @var string
*/
const DEFAULT_REQUIRED = 'defaultRequired';

/**
* @var string
*/
const RESPONSE_TYPE = 'responseType';
}
58 changes: 58 additions & 0 deletions tests/FiltererTest.php
Expand Up @@ -238,6 +238,64 @@ public function provideValidFilterData() : array
'options' => [],
'result' => [true, ['field' => 'a string with newlines and extra spaces'], null, []],
],
'conflicts with single' => [
'spec' => [
'fieldOne' => [FilterOptions::CONFLICTS_WITH => 'fieldThree', ['string']],
'fieldTwo' => [['string']],
'fieldThree' => [FilterOptions::CONFLICTS_WITH => 'fieldOne', ['string']],
],
'input' => [
'fieldOne' => 'abc',
'fieldTwo' => '123',
'fieldThree' => 'xyz',
],
'options' => [],
'result' => [
false,
null,
"Field 'fieldOne' cannot be given if field 'fieldThree' is present.\n"
. "Field 'fieldThree' cannot be given if field 'fieldOne' is present.",
[],
],
],
'conflicts with multiple' => [
'spec' => [
'fieldOne' => [FilterOptions::CONFLICTS_WITH => ['fieldTwo', 'fieldThree'], ['string']],
'fieldTwo' => [['string']],
'fieldThree' => [['string']],
],
'input' => [
'fieldOne' => 'abc',
'fieldTwo' => '123',
],
'options' => [],
'result' => [
false,
null,
"Field 'fieldOne' cannot be given if field 'fieldTwo' is present.",
[],
],
],
'conflicts with not present' => [
'spec' => [
'fieldOne' => [FilterOptions::CONFLICTS_WITH => 'fieldThree', ['string']],
'fieldTwo' => [['string']],
],
'input' => [
'fieldOne' => 'abc',
'fieldTwo' => '123',
],
'options' => [],
'result' => [
true,
[
'fieldOne' => 'abc',
'fieldTwo' => '123',
],
null,
[],
],
],
];
}

Expand Down

0 comments on commit de16cb0

Please sign in to comment.