1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * This file is part of the Nexus MCP SDK package.
7: *
8: * (c) 2026 John Paul E. Balandan, CPA <paulbalandan@gmail.com>
9: *
10: * For the full copyright and license information, please view
11: * the LICENSE file that was distributed with this source code.
12: */
13:
14: namespace Nexus\Mcp\Core\Schema\Result;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Arrayable;
18: use Nexus\Mcp\Core\Schema\Cursor;
19: use Nexus\Mcp\Core\Schema\Enum\CacheScope;
20: use Nexus\Mcp\Core\Schema\Enum\ResultType;
21: use Nexus\Mcp\Core\Schema\MetaObject;
22: use Nexus\Mcp\Core\Schema\MetaObject\GenericResultMetaObject;
23: use Nexus\Mcp\Core\Schema\MetaObject\ResultMetaObject;
24: use Nexus\Mcp\Core\Schema\Resource\ResourceTemplate;
25: use Nexus\Mcp\Core\Validation\EnumValueValidator;
26:
27: /**
28: * The result returned by the server for a `resources/templates/list` request.
29: *
30: * @extends PaginatedResult<array{
31: * _meta?: template-type<ResultMetaObject, MetaObject, 'T'>,
32: * resultType: non-empty-string,
33: * resourceTemplates: list<template-type<ResourceTemplate, Arrayable, 'T'>>,
34: * nextCursor?: non-empty-string,
35: * ttlMs: int,
36: * cacheScope: value-of<CacheScope>,
37: * }>
38: *
39: * @see https://modelcontextprotocol.io/specification/2026-07-28/schema#listresourcetemplatesresult
40: */
41: final readonly class ListResourceTemplatesResult extends PaginatedResult implements ServerResult
42: {
43: /**
44: * @var list<ResourceTemplate>
45: */
46: public array $resourceTemplates;
47:
48: /**
49: * @param list<ResourceTemplate> $resourceTemplates
50: */
51: public function __construct(
52: array $resourceTemplates,
53: int $ttlMs,
54: CacheScope $cacheScope,
55: ?Cursor $nextCursor = null,
56: ResultMetaObject $meta = new GenericResultMetaObject(),
57: ) {
58: Assert::that($resourceTemplates)
59: ->isList('"result.resourceTemplates" must be a list, non-list array given.')
60: ->values()->isInstanceOf(ResourceTemplate::class)
61: ;
62:
63: $this->resourceTemplates = $resourceTemplates;
64:
65: parent::__construct(ttlMs: $ttlMs, cacheScope: $cacheScope, nextCursor: $nextCursor, meta: $meta);
66: }
67:
68: #[\Override]
69: public static function fromArray(array $data): static
70: {
71: Assert::that($data)->hasOffset('resourceTemplates', '"result" is missing the required "resourceTemplates" key.');
72: Assert::that($data['resourceTemplates'])
73: ->isList('"result.resourceTemplates" must be a list, {type} given.')
74: ->values()
75: ->isArray('each "result.resourceTemplate" must be an object, {type} given.')
76: ->isMap('each "result.resourceTemplate" must be a string-keyed object.')
77: ;
78: $resourceTemplates = array_map(ResourceTemplate::fromArray(...), $data['resourceTemplates']);
79:
80: Assert::that($data)->hasOffset('ttlMs', '"result" is missing the required "ttlMs" key.');
81: $ttlMs = $data['ttlMs'];
82: Assert::that($ttlMs)->isInt('"result.ttlMs" must be an integer, {type} given.');
83:
84: Assert::that($data)->hasOffset('cacheScope', '"result" is missing the required "cacheScope" key.');
85: $cacheScope = EnumValueValidator::parse(CacheScope::class, $data['cacheScope'], '"result.cacheScope"');
86:
87: $nextCursor = null;
88:
89: if (\array_key_exists('nextCursor', $data)) {
90: $raw = $data['nextCursor'];
91: Assert::that($raw)->isString('"result.nextCursor" must be a string, {type} given.');
92: $nextCursor = new Cursor(cursor: $raw);
93: }
94:
95: $meta = new GenericResultMetaObject();
96:
97: if (\array_key_exists('_meta', $data)) {
98: Assert::that($data['_meta'])
99: ->isArray('"result._meta" must be an object, {type} given.')
100: ->isMap('"result._meta" must be a string-keyed object.')
101: ;
102: $meta = GenericResultMetaObject::fromArray($data['_meta']);
103: }
104:
105: return new self(
106: resourceTemplates: $resourceTemplates,
107: ttlMs: $ttlMs,
108: cacheScope: $cacheScope,
109: nextCursor: $nextCursor,
110: meta: $meta,
111: );
112: }
113:
114: #[\Override]
115: public function toArray(): array
116: {
117: $data = [];
118: $meta = $this->meta->toArray();
119:
120: if ([] !== $meta) {
121: $data['_meta'] = $meta;
122: }
123:
124: $data['resultType'] = self::getResultType();
125: $data['resourceTemplates'] = array_map(
126: static fn(ResourceTemplate $template): array => $template->toArray(),
127: $this->resourceTemplates,
128: );
129:
130: if (null !== $this->nextCursor) {
131: $data['nextCursor'] = $this->nextCursor->cursor;
132: }
133:
134: $data['ttlMs'] = $this->ttlMs;
135: $data['cacheScope'] = $this->cacheScope->value;
136:
137: return $data;
138: }
139:
140: #[\Override]
141: public function rebuildWithMeta(ResultMetaObject $meta): static
142: {
143: return new self(
144: resourceTemplates: $this->resourceTemplates,
145: ttlMs: $this->ttlMs,
146: cacheScope: $this->cacheScope,
147: nextCursor: $this->nextCursor,
148: meta: $meta,
149: );
150: }
151:
152: #[\Override]
153: protected function getResultType(): string
154: {
155: return ResultType::Complete->value;
156: }
157: }
158: