-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from hellofresh/feature/second-level-id
Added second level ID callback implementation
- Loading branch information
Showing
3 changed files
with
372 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
<?php | ||
namespace HelloFresh\Stats\HTTPMetricAlterCallback; | ||
|
||
use HelloFresh\Stats\Bucket; | ||
use HelloFresh\Stats\Bucket\MetricOperation; | ||
use HelloFresh\Stats\HTTPMetricAlterCallback; | ||
use Psr\Http\Message\RequestInterface; | ||
|
||
/** | ||
* HTTPMetricAlterCallback implementation for filtering IDs on the second level of HTTP path, | ||
* e.g. to build for all requests like "GET /users/1", "GET /users/2", "GET /users/3" metric | ||
* like "get.users.-id-". See usage examples in README for the library. | ||
*/ | ||
class HasIDAtSecondLevel implements HTTPMetricAlterCallback | ||
{ | ||
const SECTION_TEST_TRUE = 'true'; | ||
const SECTION_TEST_IS_NUMERIC = 'numeric'; | ||
const SECTION_TEST_IS_NOT_EMPTY = 'not_empty'; | ||
const SECTIONS_DELIMITER = ':'; | ||
|
||
/** @var array */ | ||
protected $map = []; | ||
|
||
/** @var callable[] */ | ||
protected $sectionsTest = []; | ||
|
||
/** | ||
* HasIDAtSecondLevel constructor. | ||
* | ||
* @param array $map sections test map with key as the first section of request path | ||
* and value as section test callback. | ||
*/ | ||
public function __construct(array $map) | ||
{ | ||
$this->map = $map; | ||
|
||
$this->registerSectionTest(static::SECTION_TEST_TRUE, function ($pathSection) { | ||
return true; | ||
})->registerSectionTest(static::SECTION_TEST_IS_NUMERIC, function ($pathSection) { | ||
return is_numeric($pathSection); | ||
})->registerSectionTest(static::SECTION_TEST_IS_NOT_EMPTY, function ($pathSection) { | ||
return $pathSection != Bucket::METRIC_EMPTY_PLACEHOLDER; | ||
}); | ||
} | ||
|
||
/** | ||
* Creates HasIDAtSecondLevel instance by building sections test map from string value. | ||
* Main use-case for this builder method is for settings loaded from config file or environment variable. | ||
* | ||
* @param string $map | ||
* @param array $registerSectionTests section tests that must be registered for a new instance | ||
* @return self | ||
*/ | ||
public static function createFromStringMap($map, array $registerSectionTests = []) | ||
{ | ||
$parts = []; | ||
foreach (explode("\n", $map) as $line) { | ||
$line = trim($line); | ||
if ($line !== '') { | ||
foreach (explode(static::SECTIONS_DELIMITER, $line) as $part) { | ||
$part = trim($part); | ||
if ($part !== '') { | ||
$parts[] = $part; | ||
} | ||
} | ||
} | ||
} | ||
|
||
if (count($parts) % 2 !== 0) { | ||
throw new \InvalidArgumentException('Invalid sections format'); | ||
} | ||
|
||
$instance = new static([]); | ||
foreach ($registerSectionTests as $name => $callback) { | ||
$instance->registerSectionTest($name, $callback); | ||
} | ||
|
||
$arrayMap = []; | ||
for ($i = 0; $i < count($parts); $i += 2) { | ||
$pathSection = $parts[$i]; | ||
$sectionTestName = $parts[$i + 1]; | ||
if (!isset($instance->sectionsTest[$sectionTestName])) { | ||
throw new \InvalidArgumentException('Unknown section test callback name: ' . $sectionTestName); | ||
} | ||
$arrayMap[$pathSection] = $sectionTestName; | ||
} | ||
|
||
$instance->map = $arrayMap; | ||
|
||
return $instance; | ||
} | ||
|
||
/** | ||
* @param string $name | ||
* @param callable $callback section test callback that accepts string test section as parameter and returns bool | ||
* if given parameter passes the test. | ||
* | ||
* @return $this | ||
*/ | ||
public function registerSectionTest($name, callable $callback) | ||
{ | ||
$this->sectionsTest[$name] = $callback; | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
public function __invoke(MetricOperation $metricParts, RequestInterface $request) | ||
{ | ||
$firstFragment = '/'; | ||
foreach (explode('/', $request->getUri()->getPath()) as $fragment) { | ||
if ($fragment !== '') { | ||
$firstFragment = $fragment; | ||
break; | ||
} | ||
} | ||
|
||
if (isset($this->map[$firstFragment]) && isset($this->sectionsTest[$this->map[$firstFragment]])) { | ||
if (call_user_func($this->sectionsTest[$this->map[$firstFragment]], $metricParts[2])) { | ||
$metricParts[2] = Bucket::METRIC_ID_PLACEHOLDER; | ||
} | ||
} | ||
|
||
return $metricParts; | ||
} | ||
} |
190 changes: 190 additions & 0 deletions
190
tests/HTTPMetricAlterCallback/HasIDAtSecondLevelTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
<?php | ||
namespace HelloFresh\Stats\HTTPMetricAlterCallback; | ||
|
||
use HelloFresh\Stats\Bucket; | ||
use HelloFresh\Stats\Bucket\MetricOperation; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class HasIDAtSecondLevelTest extends TestCase | ||
{ | ||
/** | ||
* @dataProvider defaultSectionTestsProvider | ||
* | ||
* @param MetricOperation $inputMetric | ||
* @param MetricOperation $result | ||
* @param array $map | ||
*/ | ||
public function testDefaultSectionTests(MetricOperation $inputMetric, MetricOperation $result, array $map) | ||
{ | ||
$uri = $this->getMockBuilder('\Psr\Http\Message\UriInterface')->getMock(); | ||
|
||
$uri->expects($this->atLeastOnce()) | ||
->method('getPath') | ||
->will($this->returnValue(sprintf('/%s/%s', $inputMetric[1], $inputMetric[2]))); | ||
|
||
/** @var \PHPUnit_Framework_MockObject_MockObject|\Psr\Http\Message\RequestInterface $request */ | ||
$request = $this->getMockBuilder('\Psr\Http\Message\RequestInterface')->getMock(); | ||
|
||
$request->expects($this->atLeastOnce()) | ||
->method('getUri') | ||
->will($this->returnValue($uri)); | ||
|
||
$callback = new HasIDAtSecondLevel($map); | ||
$this->assertEquals($result->toArray(), $callback($inputMetric, $request)->toArray()); | ||
} | ||
|
||
public function testRegisterSectionTest() | ||
{ | ||
$inputMetric = new MetricOperation(['get', 'users', 'edit']); | ||
|
||
$uri = $this->getMockBuilder('\Psr\Http\Message\UriInterface')->getMock(); | ||
|
||
$uri->expects($this->atLeastOnce()) | ||
->method('getPath') | ||
->will($this->returnValue(sprintf('/%s/%s', $inputMetric[1], $inputMetric[2]))); | ||
|
||
/** @var \PHPUnit_Framework_MockObject_MockObject|\Psr\Http\Message\RequestInterface $request */ | ||
$request = $this->getMockBuilder('\Psr\Http\Message\RequestInterface')->getMock(); | ||
|
||
$request->expects($this->atLeastOnce()) | ||
->method('getUri') | ||
->will($this->returnValue($uri)); | ||
|
||
$callback = new HasIDAtSecondLevel(['users' => 'edit']); | ||
$this->assertEquals($inputMetric->toArray(), $callback($inputMetric, $request)->toArray()); | ||
|
||
$callback->registerSectionTest('edit', function ($pathSection) { | ||
return $pathSection == 'edit'; | ||
}); | ||
$this->assertEquals( | ||
(new MetricOperation(['get', 'users', Bucket::METRIC_ID_PLACEHOLDER]))->toArray(), | ||
$callback($inputMetric, $request)->toArray() | ||
); | ||
} | ||
|
||
/** | ||
* @dataProvider createFromStringMapProvider | ||
* @expectedException \InvalidArgumentException | ||
* @expectedExceptionMessage Invalid sections format | ||
* | ||
* @param string $map | ||
*/ | ||
public function testCreateFromStringMap_InvalidFormat($map) | ||
{ | ||
HasIDAtSecondLevel::createFromStringMap($map); | ||
} | ||
|
||
/** | ||
* @expectedException \InvalidArgumentException | ||
* @expectedExceptionMessage Unknown section test callback name: foo | ||
*/ | ||
public function testCreateFromStringMap_UnknownSectionTest() | ||
{ | ||
HasIDAtSecondLevel::createFromStringMap('users:foo'); | ||
} | ||
|
||
public function testCreateFromStringMap() | ||
{ | ||
$inputMetric1 = new MetricOperation(['get', 'users', '1']); | ||
$inputMetric2 = new MetricOperation(['get', 'users', 'foo']); | ||
|
||
$uri = $this->getMockBuilder('\Psr\Http\Message\UriInterface')->getMock(); | ||
|
||
$uri->expects($this->at(0)) | ||
->method('getPath') | ||
->will($this->returnValue(sprintf('/%s/%s', $inputMetric1[1], $inputMetric1[2]))); | ||
$uri->expects($this->at(1)) | ||
->method('getPath') | ||
->will($this->returnValue(sprintf('/%s/%s', $inputMetric2[1], $inputMetric2[2]))); | ||
|
||
/** @var \PHPUnit_Framework_MockObject_MockObject|\Psr\Http\Message\RequestInterface $request */ | ||
$request = $this->getMockBuilder('\Psr\Http\Message\RequestInterface')->getMock(); | ||
|
||
$request->expects($this->atLeastOnce()) | ||
->method('getUri') | ||
->will($this->returnValue($uri)); | ||
|
||
$callback = HasIDAtSecondLevel::createFromStringMap('users:foo', ['foo' => function ($pathSection) { | ||
return $pathSection == 'foo'; | ||
}]); | ||
|
||
$this->assertEquals( | ||
$inputMetric1->toArray(), | ||
$callback($inputMetric1, $request)->toArray() | ||
); | ||
$this->assertEquals( | ||
(new MetricOperation(['get', 'users', Bucket::METRIC_ID_PLACEHOLDER]))->toArray(), | ||
$callback($inputMetric2, $request)->toArray() | ||
); | ||
} | ||
|
||
public function defaultSectionTestsProvider() | ||
{ | ||
return [ | ||
// GET /users/1 | ||
[ | ||
new MetricOperation(['get', 'users', '1']), | ||
new MetricOperation(['get', 'users', Bucket::METRIC_ID_PLACEHOLDER]), | ||
['users' => HasIDAtSecondLevel::SECTION_TEST_TRUE], | ||
], | ||
[ | ||
new MetricOperation(['get', 'users', '1']), | ||
new MetricOperation(['get', 'users', Bucket::METRIC_ID_PLACEHOLDER]), | ||
['users' => HasIDAtSecondLevel::SECTION_TEST_IS_NUMERIC], | ||
], | ||
[ | ||
new MetricOperation(['get', 'users', '1']), | ||
new MetricOperation(['get', 'users', Bucket::METRIC_ID_PLACEHOLDER]), | ||
['users' => HasIDAtSecondLevel::SECTION_TEST_IS_NOT_EMPTY], | ||
], | ||
// GET /users/edit | ||
[ | ||
new MetricOperation(['get', 'users', 'edit']), | ||
new MetricOperation(['get', 'users', Bucket::METRIC_ID_PLACEHOLDER]), | ||
['users' => HasIDAtSecondLevel::SECTION_TEST_TRUE], | ||
], | ||
[ | ||
new MetricOperation(['get', 'users', 'edit']), | ||
new MetricOperation(['get', 'users', 'edit']), | ||
['users' => HasIDAtSecondLevel::SECTION_TEST_IS_NUMERIC], | ||
], | ||
[ | ||
new MetricOperation(['get', 'users', 'edit']), | ||
new MetricOperation(['get', 'users', Bucket::METRIC_ID_PLACEHOLDER]), | ||
['users' => HasIDAtSecondLevel::SECTION_TEST_IS_NOT_EMPTY], | ||
], | ||
// GET /users | ||
[ | ||
new MetricOperation(['get', 'users']), | ||
new MetricOperation(['get', 'users', Bucket::METRIC_ID_PLACEHOLDER]), | ||
['users' => HasIDAtSecondLevel::SECTION_TEST_TRUE], | ||
], | ||
[ | ||
new MetricOperation(['get', 'users']), | ||
new MetricOperation(['get', 'users', Bucket::METRIC_EMPTY_PLACEHOLDER]), | ||
['users' => HasIDAtSecondLevel::SECTION_TEST_IS_NUMERIC], | ||
], | ||
[ | ||
new MetricOperation(['get', 'users']), | ||
new MetricOperation(['get', 'users', Bucket::METRIC_EMPTY_PLACEHOLDER]), | ||
['users' => HasIDAtSecondLevel::SECTION_TEST_IS_NOT_EMPTY], | ||
], | ||
// does not match | ||
[ | ||
new MetricOperation(['get', 'clients']), | ||
new MetricOperation(['get', 'clients', Bucket::METRIC_EMPTY_PLACEHOLDER]), | ||
['users' => HasIDAtSecondLevel::SECTION_TEST_TRUE], | ||
], | ||
]; | ||
} | ||
|
||
public function createFromStringMapProvider() | ||
{ | ||
return [ | ||
['foo'], | ||
['foo:bar:baz'], | ||
["foo\n"], | ||
["foo:bar\nbaz"], | ||
]; | ||
} | ||
} |