Skip to content

Commit 351aa18

Browse files
authored
implement getAll function in TextMap Extract (open-telemetry#1570)
* implement getAll function in TextMap Extract * refactor propagation interfaces to introduce ExtendedPropagationGetterInterface * Refactor getAll method in SanitizeCombinedHeadersPropagationGetter to streamline conditional logic for getter instance check
1 parent 39cd6e2 commit 351aa18

File tree

5 files changed

+172
-3
lines changed

5 files changed

+172
-3
lines changed

src/Context/Propagation/ArrayAccessGetterSetter.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
/**
1717
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.6.1/specification/context/api-propagators.md#textmap-propagator Getter and Setter.
1818
*
19-
* Default implementation of {@see PropagationGetterInterface} and {@see PropagationSetterInterface}.
19+
* Default implementation of {@see ExtendedPropagationGetterInterface} and {@see PropagationSetterInterface}.
2020
* This type is used if no custom getter/setter is provided to {@see TextMapPropagatorInterface::inject()} or {@see TextMapPropagatorInterface::extract()}.
2121
*/
22-
final class ArrayAccessGetterSetter implements PropagationGetterInterface, PropagationSetterInterface
22+
final class ArrayAccessGetterSetter implements ExtendedPropagationGetterInterface, PropagationSetterInterface
2323
{
2424
private static ?self $instance = null;
2525

@@ -78,6 +78,29 @@ public function get($carrier, string $key): ?string
7878
);
7979
}
8080

81+
/** {@inheritdoc} */
82+
public function getAll($carrier, string $key): array
83+
{
84+
if ($this->isSupportedCarrier($carrier)) {
85+
$value = $carrier[$this->resolveKey($carrier, $key)] ?? null;
86+
if (is_array($value) && $value) {
87+
return array_values(array_filter($value, 'is_string'));
88+
}
89+
90+
return is_string($value)
91+
? [$value]
92+
: [];
93+
}
94+
95+
throw new InvalidArgumentException(
96+
sprintf(
97+
'Unsupported carrier type: %s. Unable to get value associated with key:%s',
98+
get_debug_type($carrier),
99+
$key
100+
)
101+
);
102+
}
103+
81104
/** {@inheritdoc} */
82105
public function set(&$carrier, string $key, string $value): void
83106
{
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Context\Propagation;
6+
7+
/**
8+
* Interface for getting values from a carrier.
9+
* This interface extends the base PropagationGetterInterface to avoid breaking changes.
10+
*/
11+
interface ExtendedPropagationGetterInterface extends PropagationGetterInterface
12+
{
13+
/**
14+
* Gets all values of a given key from a carrier.
15+
*
16+
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.44.0/specification/context/api-propagators.md#getall
17+
*
18+
* @return list<string>
19+
*/
20+
public function getAll($carrier, string $key): array;
21+
}

src/Context/Propagation/SanitizeCombinedHeadersPropagationGetter.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* handle edge cases where the header has a trailing ';' or an empty trace state.
1313
* We also need to trim trailing separators from the header, found when a header is empty.
1414
*/
15-
final class SanitizeCombinedHeadersPropagationGetter implements PropagationGetterInterface
15+
final class SanitizeCombinedHeadersPropagationGetter implements ExtendedPropagationGetterInterface
1616
{
1717
private const LIST_MEMBERS_SEPARATOR = ',';
1818
private const SERVER_CONCAT_HEADERS_REGEX = '/;(?=[^,=;]*=|$)/';
@@ -40,4 +40,23 @@ public function get($carrier, string $key): ?string
4040
$value,
4141
);
4242
}
43+
44+
public function getAll($carrier, string $key): array
45+
{
46+
$value = $this->getter instanceof ExtendedPropagationGetterInterface
47+
? $this->getter->getAll($carrier, $key)
48+
: (array) $this->getter->get($carrier, $key);
49+
50+
if ($value === []) {
51+
return [];
52+
}
53+
54+
$value = preg_replace(
55+
[self::SERVER_CONCAT_HEADERS_REGEX, self::TRAILING_LEADING_SEPARATOR_REGEX],
56+
[self::LIST_MEMBERS_SEPARATOR],
57+
$value,
58+
);
59+
60+
return array_values($value);
61+
}
4362
}

tests/Unit/Context/Propagation/ArrayAccessGetterSetterTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,42 @@ public function test_set_empty_key(): void
138138
$this->expectExceptionMessage('Unable to set value with an empty key');
139139
$map->set($carrier, '', 'alpha');
140140
}
141+
142+
public function test_get_all_from_carrier(): void
143+
{
144+
$carrier = ['a' => ['alpha', 'beta'], 'b' => 'bravo'];
145+
$map = new ArrayAccessGetterSetter();
146+
147+
$this->assertSame(['alpha', 'beta'], $map->getAll($carrier, 'a'));
148+
$this->assertSame(['bravo'], $map->getAll($carrier, 'b'));
149+
$this->assertSame(['a', 'b'], $map->keys($carrier));
150+
}
151+
152+
public function test_get_all_from_not_existing_key(): void
153+
{
154+
$carrier = ['a' => 'alpha'];
155+
$map = new ArrayAccessGetterSetter();
156+
157+
$this->assertSame([], $map->getAll($carrier, 'b'));
158+
$this->assertSame(['a'], $map->keys($carrier));
159+
}
160+
161+
public function test_get_all_numeric_key_from_carrier(): void
162+
{
163+
$carrier = [1 => ['alpha', 'beta']];
164+
$map = new ArrayAccessGetterSetter();
165+
166+
$this->assertSame(['alpha', 'beta'], $map->getAll($carrier, '1'));
167+
$this->assertSame(['1'], $map->keys($carrier));
168+
}
169+
170+
public function test_get_all_from_unsupported_carrier(): void
171+
{
172+
$carrier = new stdClass();
173+
$map = new ArrayAccessGetterSetter();
174+
175+
$this->expectException(InvalidArgumentException::class);
176+
$this->expectExceptionMessage('Unsupported carrier type: ' . \get_class($carrier) . '. Unable to get value associated with key:a');
177+
$map->getAll($carrier, 'a');
178+
}
141179
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Tests\Unit\Context\Propagation;
6+
7+
use Mockery;
8+
use Mockery\Adapter\Phpunit\MockeryTestCase;
9+
use OpenTelemetry\Context\Propagation\ExtendedPropagationGetterInterface;
10+
use OpenTelemetry\Context\Propagation\PropagationGetterInterface;
11+
use OpenTelemetry\Context\Propagation\SanitizeCombinedHeadersPropagationGetter;
12+
use PHPUnit\Framework\Attributes\CoversClass;
13+
14+
#[CoversClass(SanitizeCombinedHeadersPropagationGetter::class)]
15+
class SanitizeCombinedHeadersPropagationGetterTest extends MockeryTestCase
16+
{
17+
/** @var Mockery\MockInterface&PropagationGetterInterface */
18+
private $propagationGetter;
19+
20+
/** @var Mockery\MockInterface&ExtendedPropagationGetterInterface */
21+
private $extendedPropagationGetter;
22+
23+
protected function setUp(): void
24+
{
25+
$this->propagationGetter = Mockery::mock(PropagationGetterInterface::class);
26+
$this->extendedPropagationGetter = Mockery::mock(ExtendedPropagationGetterInterface::class);
27+
}
28+
29+
public function test_get_all_from_carrier_with_semicolons(): void
30+
{
31+
$carrier = ['a' => ['key1=value1;key2=value2', 'key3=value3']];
32+
33+
$this->extendedPropagationGetter->shouldReceive('getAll')->with($carrier, 'a')->andReturn(['key1=value1;key2=value2', 'key3=value3']);
34+
$getter = new SanitizeCombinedHeadersPropagationGetter($this->extendedPropagationGetter);
35+
36+
$this->assertSame(['key1=value1,key2=value2', 'key3=value3'], $getter->getAll($carrier, 'a'));
37+
}
38+
39+
public function test_get_all_from_carrier_with_leading_commas(): void
40+
{
41+
$carrier = ['a' => [',,alpha,beta']];
42+
43+
$this->extendedPropagationGetter->shouldReceive('getAll')->with($carrier, 'a')->andReturn([',,alpha,beta']);
44+
$getter = new SanitizeCombinedHeadersPropagationGetter($this->extendedPropagationGetter);
45+
46+
$this->assertSame(['alpha,beta'], $getter->getAll($carrier, 'a'));
47+
}
48+
49+
public function test_get_all_from_not_existing_key(): void
50+
{
51+
$carrier = ['a' => 'alpha'];
52+
53+
$this->extendedPropagationGetter->shouldReceive('getAll')->with($carrier, 'b')->andReturn([]);
54+
$getter = new SanitizeCombinedHeadersPropagationGetter($this->extendedPropagationGetter);
55+
56+
$this->assertSame([], $getter->getAll($carrier, 'b'));
57+
}
58+
59+
public function test_get_all_from_carrier_without_implement_extended_getter(): void
60+
{
61+
$carrier = ['a' => 'alpha'];
62+
63+
$this->propagationGetter->shouldReceive('get')->with($carrier, 'a')->andReturn('alpha');
64+
$getter = new SanitizeCombinedHeadersPropagationGetter($this->propagationGetter);
65+
66+
$this->assertSame(['alpha'], $getter->getAll($carrier, 'a'));
67+
}
68+
}

0 commit comments

Comments
 (0)