Skip to content

Commit 44916ac

Browse files
committed
refactor(nexus): replace operation input classes; enhance routing and headers testing
1 parent 2700d67 commit 44916ac

47 files changed

Lines changed: 975 additions & 225 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/Exception/Failure/FailureConverter.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,20 @@ public static function mapExceptionToFailure(\Throwable $e, DataConverterInterfa
172172
$failure->setApplicationFailureInfo($info);
173173
break;
174174

175+
case $e instanceof NexusOperationFailure:
176+
$info = new NexusOperationFailureInfo();
177+
/** @psalm-suppress DeprecatedMethod */
178+
$info
179+
->setScheduledEventId($e->getScheduledEventId())
180+
->setEndpoint($e->getEndpoint())
181+
->setService($e->getService())
182+
->setOperation($e->getOperation())
183+
->setOperationId($e->getOperationToken())
184+
->setOperationToken($e->getOperationToken());
185+
186+
$failure->setNexusOperationExecutionFailureInfo($info);
187+
break;
188+
175189
default:
176190
$info = new ApplicationFailureInfo();
177191
$info->setType($e::class);

src/Interceptor/NexusOperationInbound/NexusOperationCancelInput.php renamed to src/Interceptor/NexusOperationInbound/CancelOperationInput.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,24 @@
1717
/**
1818
* @psalm-immutable
1919
*/
20-
final class NexusOperationCancelInput
20+
final class CancelOperationInput
2121
{
2222
/**
2323
* @no-named-arguments
2424
* @internal Don't use the constructor. Use {@see self::with()} instead.
2525
*/
2626
public function __construct(
27-
public readonly OperationContext $context,
28-
public readonly OperationCancelDetails $details,
27+
public readonly OperationContext $operationContext,
28+
public readonly OperationCancelDetails $cancelDetails,
2929
) {}
3030

3131
public function with(
32-
?OperationContext $context = null,
33-
?OperationCancelDetails $details = null,
32+
?OperationContext $operationContext = null,
33+
?OperationCancelDetails $cancelDetails = null,
3434
): self {
3535
return new self(
36-
$context ?? $this->context,
37-
$details ?? $this->details,
36+
$operationContext ?? $this->operationContext,
37+
$cancelDetails ?? $this->cancelDetails,
3838
);
3939
}
4040
}

src/Interceptor/NexusOperationInbound/NexusOperationStartInput.php renamed to src/Interceptor/NexusOperationInbound/StartOperationInput.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,26 @@
1717
/**
1818
* @psalm-immutable
1919
*/
20-
final class NexusOperationStartInput
20+
final class StartOperationInput
2121
{
2222
/**
2323
* @no-named-arguments
2424
* @internal Don't use the constructor. Use {@see self::with()} instead.
2525
*/
2626
public function __construct(
27-
public readonly OperationContext $context,
28-
public readonly OperationStartDetails $details,
27+
public readonly OperationContext $operationContext,
28+
public readonly OperationStartDetails $startDetails,
2929
public readonly mixed $input,
3030
) {}
3131

3232
public function with(
33-
?OperationContext $context = null,
34-
?OperationStartDetails $details = null,
33+
?OperationContext $operationContext = null,
34+
?OperationStartDetails $startDetails = null,
3535
mixed $input = null,
3636
): self {
3737
return new self(
38-
$context ?? $this->context,
39-
$details ?? $this->details,
38+
$operationContext ?? $this->operationContext,
39+
$startDetails ?? $this->startDetails,
4040
$input ?? $this->input,
4141
);
4242
}

src/Interceptor/NexusOperationInboundCallsInterceptor.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
namespace Temporal\Interceptor;
1313

14-
use Temporal\Interceptor\NexusOperationInbound\NexusOperationCancelInput;
15-
use Temporal\Interceptor\NexusOperationInbound\NexusOperationStartInput;
14+
use Temporal\Interceptor\NexusOperationInbound\CancelOperationInput;
15+
use Temporal\Interceptor\NexusOperationInbound\StartOperationInput;
1616
use Temporal\Interceptor\Trait\NexusOperationInboundCallsInterceptorTrait;
1717
use Temporal\Internal\Interceptor\Interceptor;
1818
use Temporal\Nexus\Handler\OperationStartResult;
@@ -26,9 +26,9 @@
2626
* {
2727
* use NexusOperationInboundCallsInterceptorTrait;
2828
*
29-
* public function startNexusOperation(NexusOperationStartInput $input, callable $next): OperationStartResult
29+
* public function startOperation(StartOperationInput $input, callable $next): OperationStartResult
3030
* {
31-
* if (($input->context->headers['authorization'] ?? null) !== 'expected-token') {
31+
* if (($input->operationContext->headers['authorization'] ?? null) !== 'expected-token') {
3232
* throw HandlerException::create(ErrorType::Unauthorized, 'Unauthorized');
3333
* }
3434
*
@@ -42,12 +42,12 @@
4242
interface NexusOperationInboundCallsInterceptor extends Interceptor
4343
{
4444
/**
45-
* @param callable(NexusOperationStartInput): OperationStartResult $next
45+
* @param callable(StartOperationInput): OperationStartResult $next
4646
*/
47-
public function startNexusOperation(NexusOperationStartInput $input, callable $next): OperationStartResult;
47+
public function startOperation(StartOperationInput $input, callable $next): OperationStartResult;
4848

4949
/**
50-
* @param callable(NexusOperationCancelInput): void $next
50+
* @param callable(CancelOperationInput): void $next
5151
*/
52-
public function cancelNexusOperation(NexusOperationCancelInput $input, callable $next): void;
52+
public function cancelOperation(CancelOperationInput $input, callable $next): void;
5353
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/**
4+
* This file is part of Temporal package.
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace Temporal\Interceptor\NexusOperationOutbound;
13+
14+
/**
15+
* @psalm-immutable
16+
*/
17+
final class GetInfoInput
18+
{
19+
/**
20+
* @no-named-arguments
21+
* @internal Don't use the constructor. Use {@see self::with()} instead.
22+
*/
23+
public function __construct() {}
24+
25+
public function with(): self
26+
{
27+
return new self();
28+
}
29+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
/**
4+
* This file is part of Temporal package.
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace Temporal\Interceptor;
13+
14+
use Temporal\Interceptor\NexusOperationOutbound\GetInfoInput;
15+
use Temporal\Interceptor\Trait\NexusOperationOutboundCallsInterceptorTrait;
16+
use Temporal\Internal\Interceptor\Interceptor;
17+
use Temporal\Nexus\NexusOperationContext;
18+
19+
/**
20+
* Intercepts calls a Nexus operation handler makes back into the Temporal APIs,
21+
* counterpart to Java's {@code NexusOperationOutboundCallsInterceptor}.
22+
*
23+
* Java additionally intercepts {@code getMetricsScope()} and {@code getWorkflowClient()};
24+
* neither has a clean PHP handler analogue (the metrics scope is RoadRunner-owned and the
25+
* WorkflowClient is kept on the internal channel), so only {@see self::getInfo()} is exposed.
26+
*
27+
* It's recommended to use `NexusOperationOutboundCallsInterceptorTrait` when implementing this
28+
* interface because the interface might be extended in the future. The trait will provide forward
29+
* compatibility.
30+
*
31+
* ```php
32+
* class MyNexusOperationOutboundCallsInterceptor implements NexusOperationOutboundCallsInterceptor
33+
* {
34+
* use NexusOperationOutboundCallsInterceptorTrait;
35+
*
36+
* public function getInfo(GetInfoInput $input, callable $next): NexusOperationContext
37+
* {
38+
* $info = $next($input);
39+
* // observe namespace/taskQueue, e.g. attach to a span
40+
* return $info;
41+
* }
42+
* }
43+
* ```
44+
*
45+
* @see NexusOperationOutboundCallsInterceptorTrait
46+
*/
47+
interface NexusOperationOutboundCallsInterceptor extends Interceptor
48+
{
49+
/**
50+
* Intercepts the call to read the Nexus operation info ({@see \Temporal\Nexus\Nexus::getOperationContext()}).
51+
*
52+
* @param callable(GetInfoInput): NexusOperationContext $next
53+
*/
54+
public function getInfo(GetInfoInput $input, callable $next): NexusOperationContext;
55+
}

src/Interceptor/Trait/NexusOperationInboundCallsInterceptorTrait.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
namespace Temporal\Interceptor\Trait;
1313

14-
use Temporal\Interceptor\NexusOperationInbound\NexusOperationCancelInput;
15-
use Temporal\Interceptor\NexusOperationInbound\NexusOperationStartInput;
14+
use Temporal\Interceptor\NexusOperationInbound\CancelOperationInput;
15+
use Temporal\Interceptor\NexusOperationInbound\StartOperationInput;
1616
use Temporal\Interceptor\NexusOperationInboundCallsInterceptor;
1717
use Temporal\Nexus\Handler\OperationStartResult;
1818

@@ -24,21 +24,21 @@
2424
trait NexusOperationInboundCallsInterceptorTrait
2525
{
2626
/**
27-
* Default implementation of the `startNexusOperation` method.
27+
* Default implementation of the `startOperation` method.
2828
*
29-
* @see NexusOperationInboundCallsInterceptor::startNexusOperation()
29+
* @see NexusOperationInboundCallsInterceptor::startOperation()
3030
*/
31-
public function startNexusOperation(NexusOperationStartInput $input, callable $next): OperationStartResult
31+
public function startOperation(StartOperationInput $input, callable $next): OperationStartResult
3232
{
3333
return $next($input);
3434
}
3535

3636
/**
37-
* Default implementation of the `cancelNexusOperation` method.
37+
* Default implementation of the `cancelOperation` method.
3838
*
39-
* @see NexusOperationInboundCallsInterceptor::cancelNexusOperation()
39+
* @see NexusOperationInboundCallsInterceptor::cancelOperation()
4040
*/
41-
public function cancelNexusOperation(NexusOperationCancelInput $input, callable $next): void
41+
public function cancelOperation(CancelOperationInput $input, callable $next): void
4242
{
4343
$next($input);
4444
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/**
4+
* This file is part of Temporal package.
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace Temporal\Interceptor\Trait;
13+
14+
use Temporal\Interceptor\NexusOperationOutbound\GetInfoInput;
15+
use Temporal\Interceptor\NexusOperationOutboundCallsInterceptor;
16+
use Temporal\Nexus\NexusOperationContext;
17+
18+
/**
19+
* Trait that provides a default interceptor implementation.
20+
*
21+
* @see NexusOperationOutboundCallsInterceptor
22+
*/
23+
trait NexusOperationOutboundCallsInterceptorTrait
24+
{
25+
/**
26+
* Default implementation of the `getInfo` method.
27+
*
28+
* @see NexusOperationOutboundCallsInterceptor::getInfo()
29+
*/
30+
public function getInfo(GetInfoInput $input, callable $next): NexusOperationContext
31+
{
32+
return $next($input);
33+
}
34+
}

src/Interceptor/WorkflowOutboundCalls/ExecuteNexusOperationInput.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ final class ExecuteNexusOperationInput
3030
* @internal Don't use the constructor. Use {@see self::with()} instead.
3131
*/
3232
public function __construct(
33+
public readonly string $endpoint,
3334
public readonly string $service,
3435
public readonly string $operation,
3536
public readonly array $args,
@@ -39,20 +40,25 @@ public function __construct(
3940
) {}
4041

4142
public function with(
43+
?string $endpoint = null,
4244
?string $service = null,
4345
?string $operation = null,
4446
?array $args = null,
4547
?NexusOperationOptions $options = null,
4648
null|Type|string|\ReflectionClass|\ReflectionType $returnType = null,
4749
?array $nexusHeaders = null,
4850
): self {
51+
if ($endpoint !== null && $endpoint === '') {
52+
throw new \InvalidArgumentException('$endpoint must be a non-empty string.');
53+
}
4954
if ($service !== null && $service === '') {
5055
throw new \InvalidArgumentException('$service must be a non-empty string.');
5156
}
5257
if ($operation !== null && $operation === '') {
5358
throw new \InvalidArgumentException('$operation must be a non-empty string.');
5459
}
5560
return new self(
61+
$endpoint ?? $this->endpoint,
5662
$service ?? $this->service,
5763
$operation ?? $this->operation,
5864
$args ?? $this->args,

src/Internal/Declaration/Instantiator/NexusServiceInstantiator.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@ public function instantiate(NexusServicePrototype $prototype): NexusServiceInsta
4444
return new NexusServiceInstance($prototype, $instance, $handlers);
4545
}
4646

47-
/**
48-
* @throws \ReflectionException
49-
*/
5047
private function resolveInstance(NexusServicePrototype $prototype): object
5148
{
5249
$factory = $prototype->getFactory();
@@ -69,7 +66,14 @@ private function resolveInstance(NexusServicePrototype $prototype): object
6966
$prototype->getID(),
7067
));
7168
}
72-
return $reflection->newInstance();
69+
try {
70+
return $reflection->newInstance();
71+
} catch (\ReflectionException $e) {
72+
throw new NexusException(\sprintf(
73+
'Service implementation for "%s" cannot be instantiated without arguments — bind via withInstance() or withFactory()',
74+
$prototype->getID(),
75+
), 0, $e);
76+
}
7377
}
7478

7579
/**

0 commit comments

Comments
 (0)