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
4 changes: 2 additions & 2 deletions .horde.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ dependencies:
required:
php: ^8.2
composer:
horde/core: ^3
horde/dav: ^2
horde/exception: ^3
horde/perms: ^3
horde/serialize: ^3
Expand All @@ -55,6 +53,8 @@ dependencies:
psr/event-dispatcher: ^1.0
optional:
composer:
horde/core: ^3
horde/dav: ^2
horde/http: ^3
horde/activesync: ^3
horde/eventdispatcher: ^3
Expand Down
47 changes: 47 additions & 0 deletions src/Dispatch/ApiCallContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

/**
* Copyright 2026 The Horde Project (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*/

namespace Horde\Rpc\Dispatch;

/**
* Out-of-band context for an API call.
*
* Transports populate this with protocol-specific information (identity,
* auth state, protocol name). Providers may inspect it to filter discovery
* results or adjust behavior. Null context means "vanilla response."
*/
final readonly class ApiCallContext
{
/**
* @param array<string, mixed> $attributes
*/
public function __construct(
private array $attributes = [],
) {}

public function getAttribute(string $name, mixed $default = null): mixed
{
return $this->attributes[$name] ?? $default;
}

public function withAttribute(string $name, mixed $value): self
{
return new self(array_merge($this->attributes, [$name => $value]));
}

/**
* @return array<string, mixed>
*/
public function getAttributes(): array
{
return $this->attributes;
}
}
49 changes: 49 additions & 0 deletions src/Dispatch/ApiProviderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

/**
* Copyright 2026 The Horde Project (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*/

namespace Horde\Rpc\Dispatch;

/**
* Provides the list of available API methods.
*
* Decouples method registration from any specific protocol layer.
* Implementations may wrap a Horde registry, a simple array map,
* a PSR-11 container, or any other method source.
*/
interface ApiProviderInterface
{
/**
* Check if a method is available.
*
* When a context is provided, the provider may use it to filter
* availability (e.g. hiding methods from unauthenticated callers).
*/
public function hasMethod(string $method, ?ApiCallContext $context = null): bool;

/**
* Get method descriptor for introspection/validation.
*
* Returns null if the method does not exist or is not visible
* in the given context.
*/
public function getMethodDescriptor(string $method, ?ApiCallContext $context = null): ?MethodDescriptor;

/**
* List all available methods.
*
* When a context is provided, the provider may filter the returned
* list (e.g. omitting methods that require authentication when the
* caller is anonymous).
*
* @return list<MethodDescriptor>
*/
public function listMethods(?ApiCallContext $context = null): array;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*/

namespace Horde\Rpc\JsonRpc\Dispatch;
namespace Horde\Rpc\Dispatch;

/**
* Generic provider backed by a callable map.
*
* Each method is a namecallable pair. Implements both ApiProviderInterface
* Each method is a name-callable pair. Implements both ApiProviderInterface
* (method registry) and MethodInvokerInterface (method execution), making it
* a convenient all-in-one for simple APIs.
*
Expand All @@ -34,7 +34,7 @@ final class CallableMapProvider implements ApiProviderInterface, MethodInvokerIn
private readonly array $descriptors;

/**
* @param array<string, callable> $methods Namecallable map
* @param array<string, callable> $methods Name-callable map
* @param array<string, MethodDescriptor> $descriptors Optional descriptors keyed by method name.
* Methods without an explicit descriptor get a minimal auto-generated one.
*/
Expand All @@ -49,22 +49,22 @@ public function __construct(array $methods, array $descriptors = [])
$this->descriptors = $merged;
}

public function hasMethod(string $method): bool
public function hasMethod(string $method, ?ApiCallContext $context = null): bool
{
return isset($this->methods[$method]);
}

public function getMethodDescriptor(string $method): ?MethodDescriptor
public function getMethodDescriptor(string $method, ?ApiCallContext $context = null): ?MethodDescriptor
{
return $this->descriptors[$method] ?? null;
}

public function listMethods(): array
public function listMethods(?ApiCallContext $context = null): array
{
return array_values($this->descriptors);
}

public function invoke(string $method, array $params): Result
public function invoke(string $method, array $params, ?ApiCallContext $context = null): Result
{
return new Result(($this->methods[$method])(...$params));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*/

namespace Horde\Rpc\JsonRpc\Dispatch;
namespace Horde\Rpc\Dispatch;

/**
* Metadata describing an available JSON-RPC method.
* Metadata describing an available API method.
*/
final readonly class MethodDescriptor
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*/

namespace Horde\Rpc\JsonRpc\Dispatch;
namespace Horde\Rpc\Dispatch;

/**
* Executes a method by name with given parameters.
Expand All @@ -22,5 +22,5 @@ interface MethodInvokerInterface
/**
* Invoke a method by name with the given parameters.
*/
public function invoke(string $method, array $params): Result;
public function invoke(string $method, array $params, ?ApiCallContext $context = null): Result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*/

namespace Horde\Rpc\JsonRpc\Dispatch;
namespace Horde\Rpc\Dispatch;

/**
* The outcome of a dispatched method invocation.
Expand Down
41 changes: 0 additions & 41 deletions src/JsonRpc/Dispatch/ApiProviderInterface.php

This file was deleted.

3 changes: 3 additions & 0 deletions src/JsonRpc/Dispatch/Dispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

namespace Horde\Rpc\JsonRpc\Dispatch;

use Horde\Rpc\Dispatch\ApiProviderInterface;
use Horde\Rpc\Dispatch\MethodInvokerInterface;
use Horde\Rpc\Dispatch\Result;
use Horde\Rpc\JsonRpc\Exception\MethodNotFoundException;
use Horde\Rpc\JsonRpc\Protocol\Request;

Expand Down
1 change: 1 addition & 0 deletions src/JsonRpc/Dispatch/DispatcherInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Horde\Rpc\JsonRpc\Dispatch;

use Horde\Rpc\Dispatch\Result;
use Horde\Rpc\JsonRpc\Protocol\Request;

/**
Expand Down
13 changes: 9 additions & 4 deletions src/JsonRpc/Dispatch/HordeRegistryApiProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@

use Horde_Exception;
use Horde_Registry;
use Horde\Rpc\Dispatch\ApiCallContext;
use Horde\Rpc\Dispatch\ApiProviderInterface;
use Horde\Rpc\Dispatch\MethodDescriptor;
use Horde\Rpc\Dispatch\MethodInvokerInterface;
use Horde\Rpc\Dispatch\Result;
use Horde\Rpc\JsonRpc\Exception\InternalErrorException;
use Horde\Rpc\JsonRpc\Exception\MethodNotFoundException;

Expand All @@ -34,12 +39,12 @@ public function __construct(
private readonly Horde_Registry $registry,
) {}

public function hasMethod(string $method): bool
public function hasMethod(string $method, ?ApiCallContext $context = null): bool
{
return (bool) $this->registry->hasMethod($this->dotToSlash($method));
}

public function getMethodDescriptor(string $method): ?MethodDescriptor
public function getMethodDescriptor(string $method, ?ApiCallContext $context = null): ?MethodDescriptor
{
if (!$this->hasMethod($method)) {
return null;
Expand All @@ -48,7 +53,7 @@ public function getMethodDescriptor(string $method): ?MethodDescriptor
return new MethodDescriptor($method);
}

public function listMethods(): array
public function listMethods(?ApiCallContext $context = null): array
{
$descriptors = [];
foreach ($this->registry->listMethods() as $slashMethod) {
Expand All @@ -59,7 +64,7 @@ public function listMethods(): array
return $descriptors;
}

public function invoke(string $method, array $params): Result
public function invoke(string $method, array $params, ?ApiCallContext $context = null): Result
{
$slashMethod = $this->dotToSlash($method);

Expand Down
14 changes: 10 additions & 4 deletions src/JsonRpc/Dispatch/MathApiProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@

namespace Horde\Rpc\JsonRpc\Dispatch;

use Horde\Rpc\Dispatch\ApiCallContext;
use Horde\Rpc\Dispatch\ApiProviderInterface;
use Horde\Rpc\Dispatch\CallableMapProvider;
use Horde\Rpc\Dispatch\MethodDescriptor;
use Horde\Rpc\Dispatch\MethodInvokerInterface;
use Horde\Rpc\Dispatch\Result;
use Horde\Rpc\JsonRpc\Exception\InvalidParamsException;

/**
Expand Down Expand Up @@ -121,22 +127,22 @@ public static function create(): self
return new self();
}

public function hasMethod(string $method): bool
public function hasMethod(string $method, ?ApiCallContext $context = null): bool
{
return $this->inner->hasMethod($method);
}

public function getMethodDescriptor(string $method): ?MethodDescriptor
public function getMethodDescriptor(string $method, ?ApiCallContext $context = null): ?MethodDescriptor
{
return $this->inner->getMethodDescriptor($method);
}

public function listMethods(): array
public function listMethods(?ApiCallContext $context = null): array
{
return $this->inner->listMethods();
}

public function invoke(string $method, array $params): Result
public function invoke(string $method, array $params, ?ApiCallContext $context = null): Result
{
return $this->inner->invoke($method, $params);
}
Expand Down
4 changes: 2 additions & 2 deletions src/JsonRpc/JsonRpcHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

namespace Horde\Rpc\JsonRpc;

use Horde\Rpc\JsonRpc\Dispatch\ApiProviderInterface;
use Horde\Rpc\Dispatch\ApiProviderInterface;
use Horde\Rpc\Dispatch\MethodInvokerInterface;
use Horde\Rpc\JsonRpc\Dispatch\Dispatcher;
use Horde\Rpc\JsonRpc\Dispatch\MethodInvokerInterface;
use Horde\Rpc\JsonRpc\Protocol\Codec;
use Horde\Rpc\JsonRpc\Transport\HttpHandler;
use Psr\EventDispatcher\EventDispatcherInterface;
Expand Down
2 changes: 1 addition & 1 deletion src/JsonRpc/Transport/HttpHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

namespace Horde\Rpc\JsonRpc\Transport;

use Horde\Rpc\Dispatch\Result;
use Horde\Rpc\JsonRpc\Dispatch\DispatcherInterface;
use Horde\Rpc\JsonRpc\Dispatch\Result;
use Horde\Rpc\JsonRpc\Event\BatchProcessing;
use Horde\Rpc\JsonRpc\Event\ErrorOccurred;
use Horde\Rpc\JsonRpc\Event\NotificationReceived;
Expand Down
4 changes: 2 additions & 2 deletions src/Mcp/McpRouter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

namespace Horde\Rpc\Mcp;

use Horde\Rpc\JsonRpc\Dispatch\ApiProviderInterface;
use Horde\Rpc\JsonRpc\Dispatch\MethodInvokerInterface;
use Horde\Rpc\Dispatch\ApiProviderInterface;
use Horde\Rpc\Dispatch\MethodInvokerInterface;
use Horde\Rpc\Mcp\Protocol\ServerCapabilities;
use Horde\Rpc\Mcp\Protocol\ServerInfo;
use Horde\Rpc\Mcp\Protocol\ToolDescriptor;
Expand Down
4 changes: 2 additions & 2 deletions src/Mcp/McpServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

namespace Horde\Rpc\Mcp;

use Horde\Rpc\JsonRpc\Dispatch\ApiProviderInterface;
use Horde\Rpc\JsonRpc\Dispatch\MethodInvokerInterface;
use Horde\Rpc\Dispatch\ApiProviderInterface;
use Horde\Rpc\Dispatch\MethodInvokerInterface;
use Horde\Rpc\Mcp\Protocol\ServerCapabilities;
use Horde\Rpc\Mcp\Protocol\ServerInfo;
use Horde\Rpc\Mcp\Transport\HttpHandler;
Expand Down
Loading
Loading