Skip to content
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
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

---

Expand Down Expand Up @@ -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 BaggageSchemaStamp 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`:

Expand All @@ -86,7 +87,7 @@ framework:
buses:
messenger.bus.default:
middleware:
- Macpaw\SchemaContextBundle\Messenger\Middleware\SchemaMiddleware
- Macpaw\SchemaContextBundle\Messenger\Middleware\BaggageMiddleware
```

## Testing
Expand All @@ -100,4 +101,3 @@ Feel free to open issues and submit pull requests.

## License
This bundle is released under the MIT license.

8 changes: 4 additions & 4 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ 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%'
$allowedAppNames: '%schema_context.allowed_app_names%'
tags:
- { name: kernel.event_subscriber }

Macpaw\SchemaContextBundle\Messenger\Middleware\SchemaMiddleware:
Macpaw\SchemaContextBundle\Messenger\Middleware\BaggageMiddleware:
tags:
- { name: messenger.middleware }
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
) {
}

Expand All @@ -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);
Expand All @@ -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);
}
}
43 changes: 43 additions & 0 deletions src/Messenger/Middleware/BaggageSchemaMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Macpaw\SchemaContextBundle\Messenger\Middleware;

use Macpaw\SchemaContextBundle\Messenger\Stamp\BaggageSchemaStamp;
use Macpaw\SchemaContextBundle\Service\BaggageCodec;
use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;

class BaggageSchemaMiddleware implements MiddlewareInterface
{
public function __construct(
private BaggageSchemaResolver $baggageSchemaResolver,
private BaggageCodec $baggageCodec
) {
}

public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
$stamp = $envelope->last(BaggageSchemaStamp::class);

if ($stamp instanceof BaggageSchemaStamp) {
$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 BaggageSchemaStamp($schema, $baggage));
}

return $stack->next()->handle($envelope, $stack);
}
}
37 changes: 0 additions & 37 deletions src/Messenger/Middleware/SchemaMiddleware.php

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

use Symfony\Component\Messenger\Stamp\StampInterface;

class SchemaStamp implements StampInterface
class BaggageSchemaStamp implements StampInterface
{
public function __construct(public string $schema)
public function __construct(public string $schema, public string $baggage)
{
}
}
49 changes: 49 additions & 0 deletions src/Service/BaggageCodec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Macpaw\SchemaContextBundle\Service;

class BaggageCodec
{
/**
* @param array<string,string|null> $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<string,string|null>
*/
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;
}
}
Loading
Loading