From 5bf12fed66a025d3210f529210e76f8899699aab Mon Sep 17 00:00:00 2001 From: Aleksey Tupichenkov Date: Thu, 28 Aug 2025 16:25:02 +0300 Subject: [PATCH 1/3] feat: reworked to work with full baggage instead of only scheme --- README.md | 32 ++-- config/services.yaml | 8 +- ...istener.php => BaggageRequestListener.php} | 30 ++-- ...pClient.php => BaggageAwareHttpClient.php} | 27 +-- .../Middleware/BaggageMiddleware.php | 43 +++++ src/Messenger/Middleware/SchemaMiddleware.php | 37 ----- .../{SchemaStamp.php => BaggageStamp.php} | 4 +- src/Service/BaggageCodec.php | 49 ++++++ src/Service/BaggageSchemaResolver.php | 44 +++++ src/Service/SchemaResolver.php | 20 --- .../BaggageRequestListenerTest.php | 91 ++++++++++ .../SchemaRequestListenerTest.php | 55 ------ .../HttpClient/BaggageAwareHttpClientTest.php | 100 +++++++++++ .../HttpClient/SchemaAwareHttpClientTest.php | 55 ------ ...wareTest.php => BaggageMiddlewareTest.php} | 43 +++-- tests/Service/BaggageCodecTest.php | 157 ++++++++++++++++++ 16 files changed, 564 insertions(+), 231 deletions(-) rename src/EventListener/{SchemaRequestListener.php => BaggageRequestListener.php} (61%) rename src/HttpClient/{SchemaAwareHttpClient.php => BaggageAwareHttpClient.php} (62%) create mode 100644 src/Messenger/Middleware/BaggageMiddleware.php delete mode 100644 src/Messenger/Middleware/SchemaMiddleware.php rename src/Messenger/Stamp/{SchemaStamp.php => BaggageStamp.php} (56%) create mode 100644 src/Service/BaggageCodec.php create mode 100644 src/Service/BaggageSchemaResolver.php delete mode 100644 src/Service/SchemaResolver.php create mode 100644 tests/EventListener/BaggageRequestListenerTest.php delete mode 100644 tests/EventListener/SchemaRequestListenerTest.php create mode 100644 tests/HttpClient/BaggageAwareHttpClientTest.php delete mode 100644 tests/HttpClient/SchemaAwareHttpClientTest.php rename tests/Messenger/Middleware/{SchemaMiddlewareTest.php => BaggageMiddlewareTest.php} (55%) create mode 100644 tests/Service/BaggageCodecTest.php diff --git a/README.md b/README.md index adbe57d..fe5ccea 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ The **SchemaContextBundle** provides a lightweight way to manage dynamic schema ## Features - Extracts tenant schema param from baggage request header. -- Stores schema context in a global `SchemaResolver`. -- Injects schema info into Messenger messages via a middleware. -- Rehydrates schema on message consumption via a middleware. -- Provide decorator for Http clients to propagate schema header +- Stores schema and baggage context in a global `BaggageSchemaResolver`. +- Injects schema and baggage info into Messenger messages via a middleware. +- Rehydrates schema and baggage on message consumption via a middleware. +- Provide decorator for Http clients to propagate baggage header --- @@ -49,34 +49,35 @@ APP_NAME=develop ## Usage ```php -use Macpaw\SchemaContextBundle\Service\SchemaResolver; +use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver; -public function index(SchemaResolver $schemaResolver) +public function index(BaggageSchemaResolver $schemaResolver) { $schema = $schemaResolver->getSchema(); + $baggage = $schemaResolver->getBaggage(); // Use schema in logic } ``` -## Schema-Aware HTTP Client +## Baggage-Aware HTTP Client Decorate your http client in your service configuration: ```yaml services: - schema_aware_payment_http_client: - class: Macpaw\SchemaContextBundle\HttpClient\SchemaAwareHttpClient + baggage_aware_payment_http_client: + class: Macpaw\SchemaContextBundle\HttpClient\BaggageAwareHttpClient decorates: payment_http_client #http client to decorate arguments: - - '@schema_aware_payment_http_client.inner' - - '@Macpaw\SchemaContextBundle\Service\SchemaResolver' - - '%schema_context.header_name%' + - '@baggage_aware_payment_http_client.inner' + - '@Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver' + - '@Macpaw\SchemaContextBundle\Service\BaggageCodec' ``` ## Messenger Integration The bundle provides a middleware that automatically: -* Adds a SchemaStamp to dispatched messages +* Adds a BaggageStamp to dispatched messages -* Restores the schema context on message handling +* Restores the schema and baggage context on message handling Enable the middleware in your `messenger.yaml`: @@ -86,7 +87,7 @@ framework: buses: messenger.bus.default: middleware: - - Macpaw\SchemaContextBundle\Messenger\Middleware\SchemaMiddleware + - Macpaw\SchemaContextBundle\Messenger\Middleware\BaggageMiddleware ``` ## Testing @@ -100,4 +101,3 @@ Feel free to open issues and submit pull requests. ## License This bundle is released under the MIT license. - diff --git a/config/services.yaml b/config/services.yaml index 2acb1ef..2e4c682 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -4,13 +4,13 @@ services: autoconfigure: true public: false - Macpaw\SchemaContextBundle\Service\SchemaResolver: + Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver: public: true shared: true - Macpaw\SchemaContextBundle\EventListener\SchemaRequestListener: + Macpaw\SchemaContextBundle\EventListener\BaggageRequestListener: arguments: - $schemaResolver: '@Macpaw\SchemaContextBundle\Service\SchemaResolver' + $baggageSchemaResolver: '@Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver' $schemaRequestHeader: '%schema_context.header_name%' $defaultSchema: '%schema_context.default_schema%' $appName: '%schema_context.app_name%' @@ -18,6 +18,6 @@ services: tags: - { name: kernel.event_subscriber } - Macpaw\SchemaContextBundle\Messenger\Middleware\SchemaMiddleware: + Macpaw\SchemaContextBundle\Messenger\Middleware\BaggageMiddleware: tags: - { name: messenger.middleware } diff --git a/src/EventListener/SchemaRequestListener.php b/src/EventListener/BaggageRequestListener.php similarity index 61% rename from src/EventListener/SchemaRequestListener.php rename to src/EventListener/BaggageRequestListener.php index 02cfdf2..7ff35c4 100644 --- a/src/EventListener/SchemaRequestListener.php +++ b/src/EventListener/BaggageRequestListener.php @@ -4,15 +4,17 @@ namespace Macpaw\SchemaContextBundle\EventListener; -use Macpaw\SchemaContextBundle\Service\SchemaResolver; +use Macpaw\SchemaContextBundle\Service\BaggageCodec; +use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -class SchemaRequestListener implements EventSubscriberInterface +class BaggageRequestListener implements EventSubscriberInterface { public function __construct( - private SchemaResolver $schemaResolver, + private BaggageSchemaResolver $baggageSchemaResolver, + private BaggageCodec $baggageCodec, private string $schemaRequestHeader, private string $defaultSchema, private string $appName, @@ -35,24 +37,18 @@ public function onKernelRequest(RequestEvent $event): void $request = $event->getRequest(); $baggage = $request->headers->get('baggage'); + $schema = null; if ($baggage) { - foreach (explode(',', $baggage) as $part) { - [$key, $value] = array_map( - static fn(?string $v): ?string => $v !== null ? trim($v) : null, - explode('=', $part, 2) + [null, null] - ); - - if ($key === $this->schemaRequestHeader && $value !== null) { - $schema = $value; - break; - } - } - } + $baggage = $this->baggageCodec->decode($baggage); + $this->baggageSchemaResolver->setBaggage($baggage); - $schema ??= $this->defaultSchema; + $schema = $baggage[$this->schemaRequestHeader] ?? null; + } if ($schema !== null && $schema !== '') { - $this->schemaResolver->setSchema($schema); + $this->baggageSchemaResolver->setSchema($schema); + } else { + $this->baggageSchemaResolver->setSchema($this->defaultSchema); } } diff --git a/src/HttpClient/SchemaAwareHttpClient.php b/src/HttpClient/BaggageAwareHttpClient.php similarity index 62% rename from src/HttpClient/SchemaAwareHttpClient.php rename to src/HttpClient/BaggageAwareHttpClient.php index a820419..f4d4c45 100644 --- a/src/HttpClient/SchemaAwareHttpClient.php +++ b/src/HttpClient/BaggageAwareHttpClient.php @@ -4,17 +4,18 @@ namespace Macpaw\SchemaContextBundle\HttpClient; +use Macpaw\SchemaContextBundle\Service\BaggageCodec; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; -use Macpaw\SchemaContextBundle\Service\SchemaResolver; +use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver; use Symfony\Contracts\HttpClient\ResponseStreamInterface; -class SchemaAwareHttpClient implements HttpClientInterface +class BaggageAwareHttpClient implements HttpClientInterface { public function __construct( private HttpClientInterface $inner, - private SchemaResolver $schemaResolver, - private string $schemaRequestHeader + private BaggageSchemaResolver $baggageSchemaResolver, + private BaggageCodec $baggageCodec, ) { } @@ -23,18 +24,20 @@ public function __construct( */ public function request(string $method, string $url, array $options = []): ResponseInterface { - $schema = $this->schemaResolver->getSchema(); - $baggageHeader = $this->schemaRequestHeader . '=' . $schema; $headers = isset($options['headers']) && is_array($options['headers']) ? $options['headers'] : []; - if (isset($headers['baggage'])) { - $headers['baggage'] .= ',' . $baggageHeader; - } else { - $headers['baggage'] = $baggageHeader; - } + $baggage = isset($headers['baggage']) + ? $this->baggageCodec->decode($headers['baggage']) + : []; + + $baggage = [ + ...$baggage, + ...($this->baggageSchemaResolver->getBaggage() ?? []) + ]; + $headers['baggage'] = $this->baggageCodec->encode($baggage); $options['headers'] = $headers; return $this->inner->request($method, $url, $options); @@ -53,6 +56,6 @@ public function withOptions(array $options): static $wrapped = $this->inner->withOptions($options); /** @phpstan-ignore-next-line */ - return new self($wrapped, $this->schemaResolver, $this->schemaRequestHeader); + return new self($wrapped, $this->baggageSchemaResolver, $this->baggageCodec); } } diff --git a/src/Messenger/Middleware/BaggageMiddleware.php b/src/Messenger/Middleware/BaggageMiddleware.php new file mode 100644 index 0000000..ed316c6 --- /dev/null +++ b/src/Messenger/Middleware/BaggageMiddleware.php @@ -0,0 +1,43 @@ +last(BaggageStamp::class); + + if ($stamp instanceof BaggageStamp) { + $this->baggageSchemaResolver + ->setSchema($stamp->schema) + ->setBaggage($this->baggageCodec->decode($stamp->baggage)); + + return $stack->next()->handle($envelope, $stack); + } + + $schema = $this->baggageSchemaResolver->getSchema(); + $baggage = $this->baggageCodec->encode($this->baggageSchemaResolver->getBaggage() ?? []); + + if ($schema !== null && $schema !== '') { + $envelope = $envelope->with(new BaggageStamp($schema, $baggage)); + } + + return $stack->next()->handle($envelope, $stack); + } +} diff --git a/src/Messenger/Middleware/SchemaMiddleware.php b/src/Messenger/Middleware/SchemaMiddleware.php deleted file mode 100644 index 78fe890..0000000 --- a/src/Messenger/Middleware/SchemaMiddleware.php +++ /dev/null @@ -1,37 +0,0 @@ -last(SchemaStamp::class); - - if ($stamp instanceof SchemaStamp) { - $this->schemaResolver->setSchema($stamp->schema); - - return $stack->next()->handle($envelope, $stack); - } - - $schema = $this->schemaResolver->getSchema(); - - if ($schema !== null && $schema !== '') { - $envelope = $envelope->with(new SchemaStamp($schema)); - } - - return $stack->next()->handle($envelope, $stack); - } -} diff --git a/src/Messenger/Stamp/SchemaStamp.php b/src/Messenger/Stamp/BaggageStamp.php similarity index 56% rename from src/Messenger/Stamp/SchemaStamp.php rename to src/Messenger/Stamp/BaggageStamp.php index ac5fd3c..a078868 100644 --- a/src/Messenger/Stamp/SchemaStamp.php +++ b/src/Messenger/Stamp/BaggageStamp.php @@ -6,9 +6,9 @@ use Symfony\Component\Messenger\Stamp\StampInterface; -class SchemaStamp implements StampInterface +class BaggageStamp implements StampInterface { - public function __construct(public string $schema) + public function __construct(public string $schema, public string $baggage) { } } diff --git a/src/Service/BaggageCodec.php b/src/Service/BaggageCodec.php new file mode 100644 index 0000000..989d5aa --- /dev/null +++ b/src/Service/BaggageCodec.php @@ -0,0 +1,49 @@ + $baggage + */ + public function encode(array $baggage): string + { + $parts = []; + + foreach ($baggage as $key => $value) { + if ($value === null) { + $parts[] = trim($key); + } else { + $parts[] = trim($key) . '=' . trim($value); + } + } + + return implode(',', $parts); + } + + /** + * @return array + */ + public function decode(string $baggage): array + { + $result = []; + foreach (explode(',', $baggage) as $part) { + $part = trim($part); + if ($part === '') { + continue; + } + + if (str_contains($part, '=')) { + [$key, $value] = explode('=', $part, 2); + $result[trim($key)] = trim($value); + } else { + $result[$part] = null; + } + } + + return $result; + } +} diff --git a/src/Service/BaggageSchemaResolver.php b/src/Service/BaggageSchemaResolver.php new file mode 100644 index 0000000..28108f8 --- /dev/null +++ b/src/Service/BaggageSchemaResolver.php @@ -0,0 +1,44 @@ +|null + */ + private ?array $baggage = null; + private ?string $schema = null; + + /** + * @return array|null + */ + public function getBaggage(): ?array + { + return $this->baggage; + } + + /** + * @param array $baggage + */ + public function setBaggage(array $baggage): self + { + $this->baggage = $baggage; + + return $this; + } + + public function setSchema(string $schema): self + { + $this->schema = $schema; + + return $this; + } + + public function getSchema(): ?string + { + return $this->schema; + } +} diff --git a/src/Service/SchemaResolver.php b/src/Service/SchemaResolver.php deleted file mode 100644 index fbf538f..0000000 --- a/src/Service/SchemaResolver.php +++ /dev/null @@ -1,20 +0,0 @@ -schema = $schema; - } - - public function getSchema(): ?string - { - return $this->schema; - } -} diff --git a/tests/EventListener/BaggageRequestListenerTest.php b/tests/EventListener/BaggageRequestListenerTest.php new file mode 100644 index 0000000..4f6ac3b --- /dev/null +++ b/tests/EventListener/BaggageRequestListenerTest.php @@ -0,0 +1,91 @@ + 'X-Schema=tenant1']); + $kernel = $this->createMock(HttpKernelInterface::class); + $event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST); + + $listener->onKernelRequest($event); + + self::assertSame('tenant1', $resolver->getSchema()); + self::assertSame([ + 'X-Schema' => 'tenant1', + ], $resolver->getBaggage()); + } + + public function testBaggageFromHeaderIsSetWithMultiplyParameters(): void + { + $resolver = new BaggageSchemaResolver(); + $baggageCodec = new BaggageCodec(); + $listener = new BaggageRequestListener( + $resolver, + $baggageCodec, + 'X-Schema', + 'default', + 'test-app', + ['test-app'], + ); + + $request = new Request([], [], [], [], [], ['HTTP_BAGGAGE' => 'X-Schema= tenant1 ,test , foo=bar']); + $kernel = $this->createMock(HttpKernelInterface::class); + $event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST); + + $listener->onKernelRequest($event); + + self::assertSame('tenant1', $resolver->getSchema()); + self::assertSame([ + 'X-Schema' => 'tenant1', + 'test' => null, + 'foo' => 'bar', + ], $resolver->getBaggage()); + } + + public function testDefaultSchemaIsUsedIfHeaderMissing(): void + { + $resolver = new BaggageSchemaResolver(); + $baggageCodec = new BaggageCodec(); + $listener = new BaggageRequestListener( + $resolver, + $baggageCodec, + 'X-Schema', + 'fallback', + 'test-app', + ['test-app'], + ); + + $request = new Request(); + $kernel = $this->createMock(HttpKernelInterface::class); + $event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST); + + $listener->onKernelRequest($event); + + self::assertSame('fallback', $resolver->getSchema()); + self::assertNull($resolver->getBaggage()); + } +} diff --git a/tests/EventListener/SchemaRequestListenerTest.php b/tests/EventListener/SchemaRequestListenerTest.php deleted file mode 100644 index 45024ed..0000000 --- a/tests/EventListener/SchemaRequestListenerTest.php +++ /dev/null @@ -1,55 +0,0 @@ - 'X-Schema=tenant1']); - $kernel = $this->createMock(HttpKernelInterface::class); - $event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST); - - $listener->onKernelRequest($event); - - self::assertSame('tenant1', $resolver->getSchema()); - } - - public function testDefaultSchemaIsUsedIfHeaderMissing(): void - { - $resolver = new SchemaResolver(); - $listener = new SchemaRequestListener( - $resolver, - 'X-Schema', - 'fallback', - 'test-app', - ['test-app'], - ); - - $request = new Request(); - $kernel = $this->createMock(HttpKernelInterface::class); - $event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST); - - $listener->onKernelRequest($event); - - self::assertSame('fallback', $resolver->getSchema()); - } -} diff --git a/tests/HttpClient/BaggageAwareHttpClientTest.php b/tests/HttpClient/BaggageAwareHttpClientTest.php new file mode 100644 index 0000000..a10b48c --- /dev/null +++ b/tests/HttpClient/BaggageAwareHttpClientTest.php @@ -0,0 +1,100 @@ + $arrayBaggage + */ + #[DataProvider('baggageHeaderDataProvider')] + public function testItInjectsSchemaIntoBaggageHeader( + string $requestBaggage, + array $arrayBaggage, + string $expectedSentBaggage + ): void { + $mockClient = $this->createMock(HttpClientInterface::class); + $mockClient + ->expects($this->once()) + ->method('request') + ->with( + 'GET', + 'https://api.example.com/test', + $this->callback(function (array $options) use ($expectedSentBaggage) { + $headers = $options['headers'] ?? []; + $baggage = $headers['baggage'] ?? null; + + return $baggage === $expectedSentBaggage; + }) + ) + ->willReturn(new MockResponse('OK')); + + $baggageSchemaResolver = (new BaggageSchemaResolver())->setBaggage($arrayBaggage); + $baggageCodec = new BaggageCodec(); + + $client = new BaggageAwareHttpClient( + $mockClient, + $baggageSchemaResolver, + $baggageCodec, + ); + + $response = $client->request('GET', 'https://api.example.com/test', [ + 'headers' => [ + 'baggage' => $requestBaggage, + ], + ]); + + self::assertInstanceOf(ResponseInterface::class, $response); + } + + /** + * @return iterable, string}> + */ + public static function baggageHeaderDataProvider(): iterable + { + yield [ + '', + [], + '', + ]; + + yield [ + '', + [ + 'X-Schema' => 'tenant_42', + ], + 'X-Schema=tenant_42', + ]; + + yield [ + '', + [ + 'X-Schema' => 'tenant_42', + 'foo' => null, + 'bar' => 'baz', + ], + 'X-Schema=tenant_42,foo,bar=baz', + ]; + + yield [ + 'test = 123', + [ + 'X-Schema' => 'tenant_42', + 'foo' => null, + 'bar' => 'baz', + ], + 'test=123,X-Schema=tenant_42,foo,bar=baz', + ]; + } +} diff --git a/tests/HttpClient/SchemaAwareHttpClientTest.php b/tests/HttpClient/SchemaAwareHttpClientTest.php deleted file mode 100644 index e8439e3..0000000 --- a/tests/HttpClient/SchemaAwareHttpClientTest.php +++ /dev/null @@ -1,55 +0,0 @@ -createMock(HttpClientInterface::class); - $mockClient - ->expects($this->once()) - ->method('request') - ->with( - 'GET', - 'https://api.example.com/test', - $this->callback(function (array $options) use ($expectedSchema, $schemaRequestHeader) { - $headers = $options['headers'] ?? []; - $baggage = $headers['baggage'] ?? null; - - if (is_array($baggage)) { - $baggage = implode(',', $baggage); - } - - return is_string($baggage) && str_contains($baggage, $schemaRequestHeader . '=' . $expectedSchema); - }) - ) - ->willReturn($mockResponse); - - $schemaResolver = $this->createMock(SchemaResolver::class); - $schemaResolver->method('getSchema')->willReturn($expectedSchema); - - $client = new SchemaAwareHttpClient( - $mockClient, - $schemaResolver, - $schemaRequestHeader, - ); - - $response = $client->request('GET', 'https://api.example.com/test'); - - self::assertInstanceOf(ResponseInterface::class, $response); - } -} diff --git a/tests/Messenger/Middleware/SchemaMiddlewareTest.php b/tests/Messenger/Middleware/BaggageMiddlewareTest.php similarity index 55% rename from tests/Messenger/Middleware/SchemaMiddlewareTest.php rename to tests/Messenger/Middleware/BaggageMiddlewareTest.php index 0b6fcd4..29dadb4 100644 --- a/tests/Messenger/Middleware/SchemaMiddlewareTest.php +++ b/tests/Messenger/Middleware/BaggageMiddlewareTest.php @@ -4,21 +4,29 @@ namespace Macpaw\SchemaContextBundle\Tests\Messenger\Middleware; -use Macpaw\SchemaContextBundle\Messenger\Middleware\SchemaMiddleware; -use Macpaw\SchemaContextBundle\Messenger\Stamp\SchemaStamp; -use Macpaw\SchemaContextBundle\Service\SchemaResolver; +use Macpaw\SchemaContextBundle\Messenger\Middleware\BaggageMiddleware; +use Macpaw\SchemaContextBundle\Messenger\Stamp\BaggageStamp; +use Macpaw\SchemaContextBundle\Service\BaggageCodec; +use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; use Symfony\Component\Messenger\Middleware\StackInterface; -class SchemaMiddlewareTest extends TestCase +class BaggageMiddlewareTest extends TestCase { public function testSchemaIsSetFromStamp(): void { - $resolver = new SchemaResolver(); - $middleware = new SchemaMiddleware($resolver); - $stamp = new SchemaStamp('tenant1'); + $schema = 'tenant1'; + $rawBaggage = 'X-Schema=tenant1'; + $baggage = [ + 'X-Schema' => 'tenant1', + ]; + + $resolver = new BaggageSchemaResolver(); + $baggageCodec = new BaggageCodec(); + $middleware = new BaggageMiddleware($resolver, $baggageCodec); + $stamp = new BaggageStamp($schema, $rawBaggage); $envelope = (new Envelope(new \stdClass()))->with($stamp); $stack = $this->createMock(StackInterface::class); $nextMiddleware = new class implements MiddlewareInterface { @@ -34,15 +42,23 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope $middleware->handle($envelope, $stack); - $this->assertSame('tenant1', $resolver->getSchema()); + $this->assertSame($schema, $resolver->getSchema()); + $this->assertSame($baggage, $resolver->getBaggage()); } public function testSchemaStampIsInjectedIfMissing(): void { $schema = 'tenant1'; - $resolver = new SchemaResolver(); - $resolver->setSchema($schema); - $middleware = new SchemaMiddleware($resolver); + $rawBaggage = 'X-Schema=tenant1'; + $baggage = [ + 'X-Schema' => 'tenant1', + ]; + $resolver = new BaggageSchemaResolver(); + $resolver + ->setSchema($schema) + ->setBaggage($baggage); + $baggageCodec = new BaggageCodec(); + $middleware = new BaggageMiddleware($resolver, $baggageCodec); $originalEnvelope = new Envelope(new \stdClass()); $stack = $this->createMock(StackInterface::class); @@ -59,9 +75,10 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope $resultEnvelope = $middleware->handle($originalEnvelope, $stack); - $stamp = $resultEnvelope->last(SchemaStamp::class); + $stamp = $resultEnvelope->last(BaggageStamp::class); - $this->assertInstanceOf(SchemaStamp::class, $stamp); + $this->assertInstanceOf(BaggageStamp::class, $stamp); $this->assertSame($schema, $stamp->schema); + $this->assertSame($rawBaggage, $stamp->baggage); } } diff --git a/tests/Service/BaggageCodecTest.php b/tests/Service/BaggageCodecTest.php new file mode 100644 index 0000000..47ba305 --- /dev/null +++ b/tests/Service/BaggageCodecTest.php @@ -0,0 +1,157 @@ + $arrayBaggage + */ + #[DataProvider('decodeDataProvider')] + public function testDecode(array $arrayBaggage, string $expectedResult): void + { + $resolver = new BaggageCodec(); + $result = $resolver->encode($arrayBaggage); + + self::assertSame($expectedResult, $result); + } + + /** + * @param array{string, ?string} $expectedResult + */ + #[DataProvider('encodeDataProvider')] + public function testEncode(string $rawBaggage, array $expectedResult): void + { + $resolver = new BaggageCodec(); + $result = $resolver->decode($rawBaggage); + + self::assertSame($expectedResult, $result); + } + + /** + * @param array $arrayBaggage + * @param array $expectedResult + */ + #[DataProvider('encodeAndDecodeDataProvider')] + public function testEncodeAndDecode(array $arrayBaggage, array $expectedResult): void + { + $resolver = new BaggageCodec(); + $result = $resolver->decode($resolver->encode($arrayBaggage)); + + self::assertSame($expectedResult, $result); + } + + /** + * @return iterable, string}> + */ + public static function decodeDataProvider(): iterable + { + yield [ + [], + '', + ]; + + yield [ + ['foo' => null], + 'foo', + ]; + + yield [ + [' foo ' => null], + 'foo', + ]; + + yield [ + ['foo' => null, 'bar' => 'baz'], + 'foo,bar=baz', + ]; + + yield [ + [' foo ' => null, ' bar ' => ' baz '], + 'foo,bar=baz', + ]; + + yield [ + ['foo' => null, 'bar' => 'baz', 'X-Schema' => '123'], + 'foo,bar=baz,X-Schema=123', + ]; + } + + /** + * @return iterable}> + */ + public static function encodeDataProvider(): iterable + { + yield [ + '', + [], + ]; + + yield [ + 'foo', + ['foo' => null], + ]; + + yield [ + ' foo ', + ['foo' => null], + ]; + + yield [ + 'foo,bar=baz', + ['foo' => null, 'bar' => 'baz'], + ]; + + yield [ + ' foo , bar = baz ', + ['foo' => null, 'bar' => 'baz'], + ]; + + yield [ + 'foo,bar=baz,X-Schema=123', + ['foo' => null, 'bar' => 'baz', 'X-Schema' => '123'], + ]; + } + + /** + * @return iterable, array}> + */ + public static function encodeAndDecodeDataProvider(): iterable + { + yield [ + [], + [], + ]; + + yield [ + ['foo' => null], + ['foo' => null], + ]; + + yield [ + [' foo ' => null], + ['foo' => null], + ]; + + yield [ + ['foo' => null, 'bar' => 'baz'], + ['foo' => null, 'bar' => 'baz'], + ]; + + yield [ + [' foo ' => null, ' bar ' => ' baz '], + ['foo' => null, 'bar' => 'baz'], + ]; + + yield [ + ['foo' => null, 'bar' => 'baz', 'X-Schema' => '123'], + ['foo' => null, 'bar' => 'baz', 'X-Schema' => '123'], + ]; + } +} From 214a154c5cde94dc8108380f641f9814e38bf5ae Mon Sep 17 00:00:00 2001 From: Aleksey Tupichenkov Date: Fri, 29 Aug 2025 14:24:05 +0300 Subject: [PATCH 2/3] chore: rename BaggageStamp to BaggageSchemaStamp for make it more obviously --- README.md | 2 +- src/Messenger/Middleware/BaggageMiddleware.php | 8 ++++---- .../Stamp/{BaggageStamp.php => BaggageSchemaStamp.php} | 2 +- tests/Messenger/Middleware/BaggageMiddlewareTest.php | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) rename src/Messenger/Stamp/{BaggageStamp.php => BaggageSchemaStamp.php} (82%) diff --git a/README.md b/README.md index fe5ccea..1e7efa9 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ services: ## Messenger Integration The bundle provides a middleware that automatically: -* Adds a BaggageStamp to dispatched messages +* Adds a BaggageSchemaStamp to dispatched messages * Restores the schema and baggage context on message handling diff --git a/src/Messenger/Middleware/BaggageMiddleware.php b/src/Messenger/Middleware/BaggageMiddleware.php index ed316c6..ec83d32 100644 --- a/src/Messenger/Middleware/BaggageMiddleware.php +++ b/src/Messenger/Middleware/BaggageMiddleware.php @@ -4,7 +4,7 @@ namespace Macpaw\SchemaContextBundle\Messenger\Middleware; -use Macpaw\SchemaContextBundle\Messenger\Stamp\BaggageStamp; +use Macpaw\SchemaContextBundle\Messenger\Stamp\BaggageSchemaStamp; use Macpaw\SchemaContextBundle\Service\BaggageCodec; use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver; use Symfony\Component\Messenger\Envelope; @@ -21,9 +21,9 @@ public function __construct( public function handle(Envelope $envelope, StackInterface $stack): Envelope { - $stamp = $envelope->last(BaggageStamp::class); + $stamp = $envelope->last(BaggageSchemaStamp::class); - if ($stamp instanceof BaggageStamp) { + if ($stamp instanceof BaggageSchemaStamp) { $this->baggageSchemaResolver ->setSchema($stamp->schema) ->setBaggage($this->baggageCodec->decode($stamp->baggage)); @@ -35,7 +35,7 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope $baggage = $this->baggageCodec->encode($this->baggageSchemaResolver->getBaggage() ?? []); if ($schema !== null && $schema !== '') { - $envelope = $envelope->with(new BaggageStamp($schema, $baggage)); + $envelope = $envelope->with(new BaggageSchemaStamp($schema, $baggage)); } return $stack->next()->handle($envelope, $stack); diff --git a/src/Messenger/Stamp/BaggageStamp.php b/src/Messenger/Stamp/BaggageSchemaStamp.php similarity index 82% rename from src/Messenger/Stamp/BaggageStamp.php rename to src/Messenger/Stamp/BaggageSchemaStamp.php index a078868..8f64aa6 100644 --- a/src/Messenger/Stamp/BaggageStamp.php +++ b/src/Messenger/Stamp/BaggageSchemaStamp.php @@ -6,7 +6,7 @@ use Symfony\Component\Messenger\Stamp\StampInterface; -class BaggageStamp implements StampInterface +class BaggageSchemaStamp implements StampInterface { public function __construct(public string $schema, public string $baggage) { diff --git a/tests/Messenger/Middleware/BaggageMiddlewareTest.php b/tests/Messenger/Middleware/BaggageMiddlewareTest.php index 29dadb4..acba9a2 100644 --- a/tests/Messenger/Middleware/BaggageMiddlewareTest.php +++ b/tests/Messenger/Middleware/BaggageMiddlewareTest.php @@ -5,7 +5,7 @@ namespace Macpaw\SchemaContextBundle\Tests\Messenger\Middleware; use Macpaw\SchemaContextBundle\Messenger\Middleware\BaggageMiddleware; -use Macpaw\SchemaContextBundle\Messenger\Stamp\BaggageStamp; +use Macpaw\SchemaContextBundle\Messenger\Stamp\BaggageSchemaStamp; use Macpaw\SchemaContextBundle\Service\BaggageCodec; use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver; use PHPUnit\Framework\TestCase; @@ -26,7 +26,7 @@ public function testSchemaIsSetFromStamp(): void $resolver = new BaggageSchemaResolver(); $baggageCodec = new BaggageCodec(); $middleware = new BaggageMiddleware($resolver, $baggageCodec); - $stamp = new BaggageStamp($schema, $rawBaggage); + $stamp = new BaggageSchemaStamp($schema, $rawBaggage); $envelope = (new Envelope(new \stdClass()))->with($stamp); $stack = $this->createMock(StackInterface::class); $nextMiddleware = new class implements MiddlewareInterface { @@ -75,9 +75,9 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope $resultEnvelope = $middleware->handle($originalEnvelope, $stack); - $stamp = $resultEnvelope->last(BaggageStamp::class); + $stamp = $resultEnvelope->last(BaggageSchemaStamp::class); - $this->assertInstanceOf(BaggageStamp::class, $stamp); + $this->assertInstanceOf(BaggageSchemaStamp::class, $stamp); $this->assertSame($schema, $stamp->schema); $this->assertSame($rawBaggage, $stamp->baggage); } From 8247bd390109e8f20e6686c42b8e37edb32cb5d0 Mon Sep 17 00:00:00 2001 From: Aleksey Tupichenkov Date: Fri, 29 Aug 2025 14:30:40 +0300 Subject: [PATCH 3/3] chore: rename BaggageMiddleware to BaggageSchemaMiddleware for make it more obviously --- ...{BaggageMiddleware.php => BaggageSchemaMiddleware.php} | 2 +- ...MiddlewareTest.php => BaggageSchemaMiddlewareTest.php} | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/Messenger/Middleware/{BaggageMiddleware.php => BaggageSchemaMiddleware.php} (95%) rename tests/Messenger/Middleware/{BaggageMiddlewareTest.php => BaggageSchemaMiddlewareTest.php} (90%) diff --git a/src/Messenger/Middleware/BaggageMiddleware.php b/src/Messenger/Middleware/BaggageSchemaMiddleware.php similarity index 95% rename from src/Messenger/Middleware/BaggageMiddleware.php rename to src/Messenger/Middleware/BaggageSchemaMiddleware.php index ec83d32..5a064d2 100644 --- a/src/Messenger/Middleware/BaggageMiddleware.php +++ b/src/Messenger/Middleware/BaggageSchemaMiddleware.php @@ -11,7 +11,7 @@ use Symfony\Component\Messenger\Middleware\MiddlewareInterface; use Symfony\Component\Messenger\Middleware\StackInterface; -class BaggageMiddleware implements MiddlewareInterface +class BaggageSchemaMiddleware implements MiddlewareInterface { public function __construct( private BaggageSchemaResolver $baggageSchemaResolver, diff --git a/tests/Messenger/Middleware/BaggageMiddlewareTest.php b/tests/Messenger/Middleware/BaggageSchemaMiddlewareTest.php similarity index 90% rename from tests/Messenger/Middleware/BaggageMiddlewareTest.php rename to tests/Messenger/Middleware/BaggageSchemaMiddlewareTest.php index acba9a2..d4a6944 100644 --- a/tests/Messenger/Middleware/BaggageMiddlewareTest.php +++ b/tests/Messenger/Middleware/BaggageSchemaMiddlewareTest.php @@ -4,7 +4,7 @@ namespace Macpaw\SchemaContextBundle\Tests\Messenger\Middleware; -use Macpaw\SchemaContextBundle\Messenger\Middleware\BaggageMiddleware; +use Macpaw\SchemaContextBundle\Messenger\Middleware\BaggageSchemaMiddleware; use Macpaw\SchemaContextBundle\Messenger\Stamp\BaggageSchemaStamp; use Macpaw\SchemaContextBundle\Service\BaggageCodec; use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver; @@ -13,7 +13,7 @@ use Symfony\Component\Messenger\Middleware\MiddlewareInterface; use Symfony\Component\Messenger\Middleware\StackInterface; -class BaggageMiddlewareTest extends TestCase +class BaggageSchemaMiddlewareTest extends TestCase { public function testSchemaIsSetFromStamp(): void { @@ -25,7 +25,7 @@ public function testSchemaIsSetFromStamp(): void $resolver = new BaggageSchemaResolver(); $baggageCodec = new BaggageCodec(); - $middleware = new BaggageMiddleware($resolver, $baggageCodec); + $middleware = new BaggageSchemaMiddleware($resolver, $baggageCodec); $stamp = new BaggageSchemaStamp($schema, $rawBaggage); $envelope = (new Envelope(new \stdClass()))->with($stamp); $stack = $this->createMock(StackInterface::class); @@ -58,7 +58,7 @@ public function testSchemaStampIsInjectedIfMissing(): void ->setSchema($schema) ->setBaggage($baggage); $baggageCodec = new BaggageCodec(); - $middleware = new BaggageMiddleware($resolver, $baggageCodec); + $middleware = new BaggageSchemaMiddleware($resolver, $baggageCodec); $originalEnvelope = new Envelope(new \stdClass()); $stack = $this->createMock(StackInterface::class);