Compare commits
3 commits
00c330342d
...
d6819a29fe
Author | SHA1 | Date | |
---|---|---|---|
d6819a29fe | |||
25e0441c1c | |||
b503c9edb9 |
14 changed files with 483 additions and 112 deletions
18
src/IRpcActionHandler.php
Normal file
18
src/IRpcActionHandler.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
// IRpcActionHandler.php
|
||||
// Created: 2024-08-15
|
||||
// Updated: 2024-08-16
|
||||
|
||||
namespace Aiwass;
|
||||
|
||||
/**
|
||||
* Provides the interface for IRpcServer::register().
|
||||
*/
|
||||
interface IRpcActionHandler {
|
||||
/**
|
||||
* Registers axctions on a given IRpcServer instance.
|
||||
*
|
||||
* @param IRpcServer $server Target RPC server.
|
||||
*/
|
||||
public function registerRpcActions(IRpcServer $server): void;
|
||||
}
|
68
src/IRpcServer.php
Normal file
68
src/IRpcServer.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
// IRpcServer.php
|
||||
// Created: 2024-08-16
|
||||
// Updated: 2024-08-16
|
||||
|
||||
namespace Aiwass;
|
||||
|
||||
use RuntimeException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Provides a common interface for RPC servers.
|
||||
*/
|
||||
interface IRpcServer {
|
||||
/**
|
||||
* Creates a proxy for this RPC server with a specified namespace.
|
||||
*
|
||||
* @param string $prefix Prefix to apply to the scoped RPC server.
|
||||
* @return IRpcServer A scoped RPC server instance.
|
||||
*/
|
||||
function scopeTo(string $prefix): IRpcServer;
|
||||
|
||||
/**
|
||||
* Registers a handler class.
|
||||
*
|
||||
* @param IRpcActionHandler $handler Handler to register.
|
||||
*/
|
||||
function register(IRpcActionHandler $handler): void;
|
||||
|
||||
/**
|
||||
* Registers an action.
|
||||
*
|
||||
* @param bool $isProcedure true if the action is a procedure (HTTP POST), false if it is a query (HTTP GET).
|
||||
* @param string $name Unique name of the action.
|
||||
* @param callable $handler Handler for the action.
|
||||
* @throws RuntimeException If a handler with the same name is already registered.
|
||||
* @throws InvalidArgumentException If $handler is not a callable type.
|
||||
*/
|
||||
function registerAction(bool $isProcedure, string $name, $handler): void;
|
||||
|
||||
/**
|
||||
* Registers a query action (HTTP GET).
|
||||
*
|
||||
* @param string $name Unique name of the action.
|
||||
* @param callable $handler Handler for the action.
|
||||
* @throws RuntimeException If a handler with the same name is already registered.
|
||||
* @throws InvalidArgumentException If $handler is not a callable type.
|
||||
*/
|
||||
function registerQueryAction(string $name, $handler): void;
|
||||
|
||||
/**
|
||||
* Registers a procedure action (HTTP POST).
|
||||
*
|
||||
* @param string $name Unique name of the action.
|
||||
* @param callable $handler Handler for the action.
|
||||
* @throws RuntimeException If a handler with the same name is already registered.
|
||||
* @throws InvalidArgumentException If $handler is not a callable type.
|
||||
*/
|
||||
function registerProcedureAction(string $name, $handler): void;
|
||||
|
||||
/**
|
||||
* Retrieves information about an action.
|
||||
*
|
||||
* @param string $name Name of the action.
|
||||
* @return ?RpcActionInfo An object containing information about the action, or null if it does not exist.
|
||||
*/
|
||||
function getActionInfo(string $name): ?RpcActionInfo;
|
||||
}
|
67
src/RpcAction.php
Normal file
67
src/RpcAction.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
// RpcAction.php
|
||||
// Created: 2024-08-15
|
||||
// Updated: 2024-08-16
|
||||
|
||||
namespace Aiwass;
|
||||
|
||||
use Attribute;
|
||||
use ReflectionAttribute;
|
||||
use ReflectionObject;
|
||||
|
||||
/**
|
||||
* Provides base for attributes that mark methods in a class as RPC action handlers.
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
class RpcAction {
|
||||
/**
|
||||
* @param bool $isProcedure true if the action is a procedure, false if query.
|
||||
* @param string $name Action name.
|
||||
*/
|
||||
public function __construct(
|
||||
private bool $isProcedure,
|
||||
private string $name
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Returns whether the action is a procedure.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isProcedure(): bool {
|
||||
return $this->isProcedure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads attributes from methods in a IRpcActionHandler instance and registers them to a given IRpcServer instance.
|
||||
*
|
||||
* @param IRpcServer $server RPC server instance.
|
||||
* @param IRpcActionHandler $handler Handler instance.
|
||||
*/
|
||||
public static function register(IRpcServer $server, IRpcActionHandler $handler): void {
|
||||
$objectInfo = new ReflectionObject($handler);
|
||||
$methodInfos = $objectInfo->getMethods();
|
||||
|
||||
foreach($methodInfos as $methodInfo) {
|
||||
$attrInfos = $methodInfo->getAttributes(RpcAction::class, ReflectionAttribute::IS_INSTANCEOF);
|
||||
|
||||
foreach($attrInfos as $attrInfo) {
|
||||
$handlerInfo = $attrInfo->newInstance();
|
||||
$server->registerAction(
|
||||
$handlerInfo->isProcedure(),
|
||||
$handlerInfo->getName(),
|
||||
$methodInfo->getClosure($methodInfo->isStatic() ? null : $handler)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
src/RpcActionHandler.php
Normal file
14
src/RpcActionHandler.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
// RpcActionHandler.php
|
||||
// Created: 2024-08-15
|
||||
// Updated: 2024-08-15
|
||||
|
||||
namespace Aiwass;
|
||||
|
||||
/**
|
||||
* Provides an abstract class version of IRpcActionHandler that already includes the trait as well,
|
||||
* letting you only have to use one use statement rather than two!
|
||||
*/
|
||||
abstract class RpcActionHandler implements IRpcActionHandler {
|
||||
use RpcActionHandlerTrait;
|
||||
}
|
16
src/RpcActionHandlerTrait.php
Normal file
16
src/RpcActionHandlerTrait.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
// RpcActionHandlerTrait.php
|
||||
// Created: 2024-08-15
|
||||
// Updated: 2024-08-16
|
||||
|
||||
namespace Aiwass;
|
||||
|
||||
/**
|
||||
* Provides an implementation of IRpcActionHandler::registerRpcActions that uses the attributes.
|
||||
* For more advanced use, everything can be use'd separately and RpcActionAttribute::register called manually.
|
||||
*/
|
||||
trait RpcActionHandlerTrait {
|
||||
public function registerRpcActions(IRpcServer $server): void {
|
||||
RpcAction::register($server, $this);
|
||||
}
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
// RpcActionInfo.php
|
||||
// Created: 2024-08-13
|
||||
// Updated: 2024-08-13
|
||||
// Updated: 2024-08-16
|
||||
|
||||
namespace Aiwass;
|
||||
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionFunction;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Provides information about an RPC action.
|
||||
|
@ -55,4 +57,37 @@ final class RpcActionInfo {
|
|||
public function getHandler(): Closure {
|
||||
return Closure::fromCallable($this->handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the handler and ensures required args are specified.
|
||||
*
|
||||
* @param array<string, mixed> $args Arguments to invoke the handler with.
|
||||
* @throws RuntimeException If any given data was invalid.
|
||||
* @return mixed Result of the handler
|
||||
*/
|
||||
public function invokeHandler(array $args = []): mixed {
|
||||
$handlerInfo = new ReflectionFunction($this->getHandler());
|
||||
if($handlerInfo->isVariadic())
|
||||
throw new RuntimeException('variadic');
|
||||
|
||||
$handlerArgs = [];
|
||||
$handlerParams = $handlerInfo->getParameters();
|
||||
foreach($handlerParams as $paramInfo) {
|
||||
$paramName = $paramInfo->getName();
|
||||
if(!array_key_exists($paramName, $args)) {
|
||||
if($paramInfo->isOptional())
|
||||
continue;
|
||||
|
||||
if(!$paramInfo->allowsNull())
|
||||
throw new RuntimeException('required');
|
||||
}
|
||||
|
||||
$handlerArgs[$paramName] = $args[$paramName] ?? null;
|
||||
}
|
||||
|
||||
if(count($handlerArgs) !== count($args))
|
||||
throw new RuntimeException('params');
|
||||
|
||||
return $handlerInfo->invokeArgs($handlerArgs);
|
||||
}
|
||||
}
|
||||
|
|
20
src/RpcProcedure.php
Normal file
20
src/RpcProcedure.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
// RpcProcedure.php
|
||||
// Created: 2024-08-15
|
||||
// Updated: 2024-08-16
|
||||
|
||||
namespace Aiwass;
|
||||
|
||||
use Attribute;
|
||||
/**
|
||||
* Provides an attribute for marking methods in a class as an RPC Query action.
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
class RpcProcedure extends RpcAction {
|
||||
/**
|
||||
* @param string $name Action name.
|
||||
*/
|
||||
public function __construct(string $name) {
|
||||
parent::__construct(true, $name);
|
||||
}
|
||||
}
|
20
src/RpcQuery.php
Normal file
20
src/RpcQuery.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
// RpcQuery.php
|
||||
// Created: 2024-08-15
|
||||
// Updated: 2024-08-16
|
||||
|
||||
namespace Aiwass;
|
||||
|
||||
use Attribute;
|
||||
/**
|
||||
* Provides an attribute for marking methods in a class as an RPC Query action.
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
class RpcQuery extends RpcAction {
|
||||
/**
|
||||
* @param string $name Action name.
|
||||
*/
|
||||
public function __construct(string $name) {
|
||||
parent::__construct(false, $name);
|
||||
}
|
||||
}
|
|
@ -1,15 +1,12 @@
|
|||
<?php
|
||||
// RpcRouteHandler.php
|
||||
// Created: 2024-08-13
|
||||
// Updated: 2024-08-13
|
||||
// Updated: 2024-08-16
|
||||
|
||||
namespace Aiwass;
|
||||
|
||||
use Exception;
|
||||
use ReflectionFunction;
|
||||
use ReflectionIntersectionType;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionUnionType;
|
||||
use RuntimeException;
|
||||
use Index\Http\{HttpResponseBuilder,HttpRequest};
|
||||
use Index\Http\Content\FormContent;
|
||||
use Index\Http\Routing\{HttpGet,HttpPost,RouteHandler};
|
||||
|
@ -19,10 +16,11 @@ use Index\Http\Routing\{HttpGet,HttpPost,RouteHandler};
|
|||
*/
|
||||
class RpcRouteHandler extends RouteHandler {
|
||||
/**
|
||||
* @param RpcServer $server An RPC server instance.
|
||||
* @param IRpcServer $server An RPC server instance.
|
||||
*/
|
||||
public function __construct(
|
||||
private RpcServer $server
|
||||
private IVerificationProvider $verification,
|
||||
private IRpcServer $server
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
@ -31,7 +29,7 @@ class RpcRouteHandler extends RouteHandler {
|
|||
* @param HttpResponseBuilder $response Response that it being built.
|
||||
* @param HttpRequest $request Request that is being handled.
|
||||
* @param string $action Name of the action specified in the URL.
|
||||
* @return int|array{error: string, message?: string}|mixed Response to the request.
|
||||
* @return int|string Response to the request.
|
||||
*/
|
||||
#[HttpGet('/_aiwass/([A-Za-z0-9\-_\.:]+)')]
|
||||
#[HttpPost('/_aiwass/([A-Za-z0-9\-_\.:]+)')]
|
||||
|
@ -61,49 +59,29 @@ class RpcRouteHandler extends RouteHandler {
|
|||
$userToken = (string)$request->getHeaderLine('X-Aiwass-Verify');
|
||||
if($userToken === '') {
|
||||
$response->setStatusCode(403);
|
||||
return AiwassMsgPack::encode(['error' => 'aiwass:verify', 'message' => 'X-Aiwass-Verify header is missing.']);
|
||||
return AiwassMsgPack::encode(['error' => 'aiwass:verify']);
|
||||
}
|
||||
|
||||
$actInfo = $this->server->getActionInfo($action);
|
||||
if($actInfo === null) {
|
||||
$response->setStatusCode(404);
|
||||
return AiwassMsgPack::encode(['error' => 'aiwass:unknown', 'message' => 'No action with that name exists.']);
|
||||
return AiwassMsgPack::encode(['error' => 'aiwass:unknown']);
|
||||
}
|
||||
|
||||
if($actInfo->isProcedure() !== $expectProcedure) {
|
||||
$response->setStatusCode(405);
|
||||
$response->setHeader('Allow', $actInfo->isProcedure() ? 'POST' : 'GET');
|
||||
return AiwassMsgPack::encode(['error' => 'aiwass:method', 'message' => 'This action cannot be called using this request method.']);
|
||||
return AiwassMsgPack::encode(['error' => 'aiwass:method']);
|
||||
}
|
||||
|
||||
if(!$this->server->getVerificationProvider()->verify($userToken, $expectProcedure, $action, $paramString))
|
||||
return AiwassMsgPack::encode(['error' => 'aiwass:verify', 'message' => 'Request token verification failed.']);
|
||||
if(!$this->verification->verify($userToken, $expectProcedure, $action, $paramString))
|
||||
return AiwassMsgPack::encode(['error' => 'aiwass:verify']);
|
||||
|
||||
$handlerInfo = new ReflectionFunction($actInfo->getHandler());
|
||||
if($handlerInfo->isVariadic())
|
||||
return AiwassMsgPack::encode(['error' => 'aiwass:variadic', 'message' => 'Handler was declared as a variadic method and is thus impossible to be called.']);
|
||||
|
||||
$handlerArgs = [];
|
||||
|
||||
$handlerParams = $handlerInfo->getParameters();
|
||||
foreach($handlerParams as $paramInfo) {
|
||||
$paramName = $paramInfo->getName();
|
||||
if(!array_key_exists($paramName, $params)) {
|
||||
if($paramInfo->isOptional())
|
||||
continue;
|
||||
|
||||
if(!$paramInfo->allowsNull())
|
||||
return AiwassMsgPack::encode(['error' => 'aiwass:param:required', 'message' => 'A required parameter is missing.', 'param' => $paramName]);
|
||||
}
|
||||
|
||||
$handlerArgs[$paramName] = $params[$paramName] ?? null;
|
||||
}
|
||||
|
||||
if(count($handlerArgs) !== count($params)) {
|
||||
try {
|
||||
return AiwassMsgPack::encode($actInfo->invokeHandler($params));
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->setStatusCode(400);
|
||||
return AiwassMsgPack::encode(['error' => 'aiwass:params', 'message' => 'Unsupported arguments were specified.']);
|
||||
return AiwassMsgPack::encode(['error' => sprintf('aiwass:%s', $ex->getMessage())]);
|
||||
}
|
||||
|
||||
return AiwassMsgPack::encode($handlerInfo->invokeArgs($handlerArgs));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
// RpcServer.php
|
||||
// Created: 2024-08-13
|
||||
// Updated: 2024-08-13
|
||||
// Updated: 2024-08-16
|
||||
|
||||
namespace Aiwass;
|
||||
|
||||
|
@ -11,44 +11,16 @@ use RuntimeException;
|
|||
/**
|
||||
* Implements an RPC server.
|
||||
*/
|
||||
class RpcServer {
|
||||
class RpcServer implements IRpcServer {
|
||||
use RpcServerTrait;
|
||||
|
||||
/** @var array<string, RpcActionInfo> */
|
||||
private array $actions = [];
|
||||
|
||||
/**
|
||||
* @param IVerificationProvider $verify A verification provider.
|
||||
*/
|
||||
public function __construct(
|
||||
private IVerificationProvider $verify
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves this verification provider for this server.
|
||||
*
|
||||
* @return IVerificationProvider Verification provider implementation.
|
||||
*/
|
||||
public function getVerificationProvider(): IVerificationProvider {
|
||||
return $this->verify;
|
||||
public function scopeTo(string $prefix): IRpcServer {
|
||||
return new RpcServerScoped($this, $prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an RPC route handler.
|
||||
*
|
||||
* @return RpcRouteHandler RPC route handler.
|
||||
*/
|
||||
public function createRouteHandler(): RpcRouteHandler {
|
||||
return new RpcRouteHandler($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an action.
|
||||
*
|
||||
* @param bool $isProcedure true if the action is a procedure (HTTP POST), false if it is a query (HTTP GET).
|
||||
* @param string $name Unique name of the action.
|
||||
* @param callable $handler Handler for the action.
|
||||
* @throws RuntimeException If a handler with the same name is already registered.
|
||||
* @throws InvalidArgumentException If $handler is not a callable type.
|
||||
*/
|
||||
public function registerAction(bool $isProcedure, string $name, $handler): void {
|
||||
if(array_key_exists($name, $this->actions))
|
||||
throw new RuntimeException('an action with that name has already been registered');
|
||||
|
@ -56,47 +28,7 @@ class RpcServer {
|
|||
$this->actions[$name] = new RpcActionInfo($isProcedure, $name, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a query action (HTTP GET).
|
||||
*
|
||||
* @param string $name Unique name of the action.
|
||||
* @param callable $handler Handler for the action.
|
||||
* @throws RuntimeException If a handler with the same name is already registered.
|
||||
* @throws InvalidArgumentException If $handler is not a callable type.
|
||||
*/
|
||||
public function registerQueryAction(string $name, $handler): void {
|
||||
$this->registerAction(false, $name, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a procedure action (HTTP POST).
|
||||
*
|
||||
* @param string $name Unique name of the action.
|
||||
* @param callable $handler Handler for the action.
|
||||
* @throws RuntimeException If a handler with the same name is already registered.
|
||||
* @throws InvalidArgumentException If $handler is not a callable type.
|
||||
*/
|
||||
public function registerProcedureAction(string $name, $handler): void {
|
||||
$this->registerAction(true, $name, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves information about an action.
|
||||
*
|
||||
* @param string $name Name of the action.
|
||||
* @return ?RpcActionInfo An object containing information about the action, or null if it does not exist.
|
||||
*/
|
||||
public function getActionInfo(string $name): ?RpcActionInfo {
|
||||
return $this->actions[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an RPC server with a Hmac verification provider.
|
||||
*
|
||||
* @param callable(): string $getSecretKey A method that returns the secret key to use.
|
||||
* @return RpcServer The RPC server.
|
||||
*/
|
||||
public static function createHmac($getSecretKey): RpcServer {
|
||||
return new RpcServer(new HmacVerificationProvider($getSecretKey));
|
||||
}
|
||||
}
|
||||
|
|
30
src/RpcServerScoped.php
Normal file
30
src/RpcServerScoped.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
// RpcServerScoped.php
|
||||
// Created: 2024-08-16
|
||||
// Updated: 2024-08-16
|
||||
|
||||
namespace Aiwass;
|
||||
|
||||
/**
|
||||
* Provides a scoped RPC server implementation.
|
||||
*/
|
||||
class RpcServerScoped implements IRpcServer {
|
||||
use RpcServerTrait;
|
||||
|
||||
public function __construct(
|
||||
private IRpcServer $base,
|
||||
private string $prefix
|
||||
) {}
|
||||
|
||||
public function scopeTo(string $prefix): IRpcServer {
|
||||
return new RpcServerScoped($this->base, $this->prefix . $prefix);
|
||||
}
|
||||
|
||||
public function registerAction(bool $isProcedure, string $name, $handler): void {
|
||||
$this->base->registerAction($isProcedure, $this->prefix . $name, $handler);
|
||||
}
|
||||
|
||||
public function getActionInfo(string $name): ?RpcActionInfo {
|
||||
return $this->base->getActionInfo($this->prefix . $name);
|
||||
}
|
||||
}
|
23
src/RpcServerTrait.php
Normal file
23
src/RpcServerTrait.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
// RpcServerTrait.php
|
||||
// Created: 2024-08-16
|
||||
// Updated: 2024-08-16
|
||||
|
||||
namespace Aiwass;
|
||||
|
||||
/**
|
||||
* Provides implementations for IRpcServer methods that are likely going to be identical across implementations.
|
||||
*/
|
||||
trait RpcServerTrait {
|
||||
public function register(IRpcActionHandler $handler): void {
|
||||
$handler->registerRpcActions($this);
|
||||
}
|
||||
|
||||
public function registerQueryAction(string $name, $handler): void {
|
||||
$this->registerAction(false, $name, $handler);
|
||||
}
|
||||
|
||||
public function registerProcedureAction(string $name, $handler): void {
|
||||
$this->registerAction(true, $name, $handler);
|
||||
}
|
||||
}
|
108
tests/AttributesTest.php
Normal file
108
tests/AttributesTest.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
// AttributesTest.php
|
||||
// Created: 2024-08-16
|
||||
// Updated: 2024-08-16
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use RuntimeException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\{CoversClass,UsesClass};
|
||||
use Aiwass\{RpcAction,RpcActionHandler,RpcProcedure,RpcQuery,RpcServer};
|
||||
|
||||
#[CoversClass(RpcAction::class)]
|
||||
#[CoversClass(RpcActionHandler::class)]
|
||||
#[CoversClass(RpcProcedure::class)]
|
||||
#[CoversClass(RpcQuery::class)]
|
||||
#[UsesClass(RpcServer::class)]
|
||||
final class AttributesTest extends TestCase {
|
||||
public function testAttributes(): void {
|
||||
$handler = new class($this) extends RpcActionHandler {
|
||||
private static $that;
|
||||
|
||||
public function __construct(private $self) {
|
||||
self::$that = $self;
|
||||
}
|
||||
|
||||
#[RpcAction(true, 'aiwass:test:proc1')]
|
||||
public function procedureRegisteredUsingActionAttribute() {
|
||||
return 'procedure registered using action attribute';
|
||||
}
|
||||
|
||||
#[RpcAction(false, 'aiwass:test:query1')]
|
||||
public function queryRegisteredUsingActionAttribute(string $beans) {
|
||||
$this->self->assertEquals('it is beans', $beans);
|
||||
return 'query registered using action attribute';
|
||||
}
|
||||
|
||||
#[RpcQuery('aiwass:test:query2')]
|
||||
public static function staticQueryRegisteredUsingQueryAttribute(string $required, string $optional = 'the') {
|
||||
self::$that->assertEquals('internet', $required);
|
||||
self::$that->assertEquals('the', $optional);
|
||||
return 'static query registered using query attribute';
|
||||
}
|
||||
|
||||
#[RpcProcedure('aiwass:test:proc2')]
|
||||
public function dynamicProcedureRegisteredUsingProcedureAttribute(string $optional = 'meow') {
|
||||
$this->self->assertEquals('meow', $optional);
|
||||
return 'dynamic procedure registered using procedure attribute';
|
||||
}
|
||||
|
||||
#[RpcQuery('aiwass:test:query3')]
|
||||
#[RpcProcedure('aiwass:test:proc3')]
|
||||
public function multiple() {
|
||||
return 'a dynamic method registered as both a query and a procedure';
|
||||
}
|
||||
|
||||
public function hasNoAttr() {
|
||||
return 'not an action handler';
|
||||
}
|
||||
};
|
||||
|
||||
$server = new RpcServer;
|
||||
$server->register($handler);
|
||||
|
||||
$this->assertNull($server->getActionInfo('aiwass:none'));
|
||||
|
||||
$proc1 = $server->getActionInfo('aiwass:test:proc1');
|
||||
$this->assertNotNull($proc1);
|
||||
$this->assertTrue($proc1->isProcedure());
|
||||
$this->assertEquals('aiwass:test:proc1', $proc1->getName());
|
||||
$this->assertEquals('procedure registered using action attribute', $proc1->invokeHandler());
|
||||
|
||||
$proc2 = $server->getActionInfo('aiwass:test:proc2');
|
||||
$this->assertNotNull($proc2);
|
||||
$this->assertTrue($proc2->isProcedure());
|
||||
$this->assertEquals('aiwass:test:proc2', $proc2->getName());
|
||||
$this->assertEquals('dynamic procedure registered using procedure attribute', $proc2->invokeHandler());
|
||||
|
||||
$query1 = $server->getActionInfo('aiwass:test:query1');
|
||||
$this->assertNotNull($query1);
|
||||
$this->assertFalse($query1->isProcedure());
|
||||
$this->assertEquals('aiwass:test:query1', $query1->getName());
|
||||
$this->assertEquals('query registered using action attribute', $query1->invokeHandler(['beans' => 'it is beans']));
|
||||
|
||||
$query2 = $server->getActionInfo('aiwass:test:query2');
|
||||
$this->assertNotNull($query2);
|
||||
$this->assertFalse($query2->isProcedure());
|
||||
$this->assertEquals('aiwass:test:query2', $query2->getName());
|
||||
$this->assertEquals('static query registered using query attribute', $query2->invokeHandler(['required' => 'internet']));
|
||||
|
||||
$query3 = $server->getActionInfo('aiwass:test:query3');
|
||||
$proc3 = $server->getActionInfo('aiwass:test:proc3');
|
||||
$this->assertNotNull($query3);
|
||||
$this->assertNotNull($proc3);
|
||||
$this->assertFalse($query3->isProcedure());
|
||||
$this->asserttrue($proc3->isProcedure());
|
||||
$this->assertEquals('aiwass:test:query3', $query3->getName());
|
||||
$this->assertEquals('aiwass:test:proc3', $proc3->getName());
|
||||
$this->assertEquals($query3->getHandler(), $proc3->getHandler());
|
||||
$this->assertEquals('a dynamic method registered as both a query and a procedure', $query3->invokeHandler());
|
||||
$this->assertEquals('a dynamic method registered as both a query and a procedure', $proc3->invokeHandler());
|
||||
|
||||
$this->assertNull($server->getActionInfo('doesnotexist'));
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$query1->invokeHandler(['notbeans' => 'it is not beans']);
|
||||
}
|
||||
}
|
42
tests/ScopedServerTest.php
Normal file
42
tests/ScopedServerTest.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
// ScopedServerTest.php
|
||||
// Created: 2024-08-16
|
||||
// Updated: 2024-08-16
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\{CoversClass,UsesClass};
|
||||
use Aiwass\{RpcServer,RpcServerScoped};
|
||||
|
||||
#[CoversClass(RpcServerScoped::class)]
|
||||
#[UsesClass(RpcServer::class)]
|
||||
final class ScopedServerTest extends TestCase {
|
||||
public function testServerScoping(): void {
|
||||
$base = new RpcServer;
|
||||
$base->registerQueryAction('test', fn() => 'test');
|
||||
|
||||
$scopedToBeans = $base->scopeTo('beans:');
|
||||
$scopedToBeans->registerQueryAction('test', fn() => 'test in beans');
|
||||
|
||||
$scopedToGarf = $base->scopeTo('garf:');
|
||||
$scopedToGarfield = $scopedToGarf->scopeTo('ield:');
|
||||
$scopedToGarfield->registerQueryAction('test', fn() => 'test in garfield');
|
||||
|
||||
$baseTest = $base->getActionInfo('test');
|
||||
$this->assertNotNull($baseTest);
|
||||
$this->assertEquals('test', $baseTest->getName());
|
||||
|
||||
$baseBeansTest = $base->getActionInfo('beans:test');
|
||||
$scopedBeansTest = $scopedToBeans->getActionInfo('test');
|
||||
$this->assertNotNull($baseBeansTest);
|
||||
$this->assertSame($baseBeansTest, $scopedBeansTest);
|
||||
$this->assertEquals('beans:test', $scopedBeansTest->getName());
|
||||
|
||||
$baseGarfieldTest = $base->getActionInfo('garf:ield:test');
|
||||
$scopedGarfieldTest = $scopedToGarfield->getActionInfo('test');
|
||||
$this->assertNotNull($baseGarfieldTest);
|
||||
$this->assertSame($baseGarfieldTest, $scopedGarfieldTest);
|
||||
$this->assertEquals('garf:ield:test', $scopedGarfieldTest->getName());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue