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\Elicitation;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Arrayable;
18:
19: /**
20: * Schema for a single-line string elicitation field.
21: *
22: * @implements Arrayable<array{
23: * type: 'string',
24: * title?: non-empty-string,
25: * description?: non-empty-string,
26: * minLength?: int<0, max>,
27: * maxLength?: int<0, max>,
28: * format?: 'date'|'date-time'|'email'|'uri',
29: * default?: string,
30: * }>
31: *
32: * @see https://modelcontextprotocol.io/specification/2025-11-25/schema#stringschema
33: */
34: final readonly class StringSchema implements Arrayable, PrimitiveSchemaDefinition
35: {
36: public const string TYPE = 'string';
37: public const string FORMAT_DATE = 'date';
38: public const string FORMAT_DATE_TIME = 'date-time';
39: public const string FORMAT_EMAIL = 'email';
40: public const string FORMAT_URI = 'uri';
41:
42: /**
43: * @var null|non-empty-string
44: */
45: public ?string $title;
46:
47: /**
48: * @var null|non-empty-string
49: */
50: public ?string $description;
51:
52: /**
53: * @var null|'date'|'date-time'|'email'|'uri'
54: */
55: public ?string $format;
56:
57: public function __construct(
58: ?string $title = null,
59: ?string $description = null,
60: public ?int $minLength = null,
61: public ?int $maxLength = null,
62: ?string $format = null,
63: public ?string $default = null,
64: ) {
65: Assert::that($title)->nullOr()->isNonEmptyString('string schema "title" must be a non-empty string or null.');
66: Assert::that($description)->nullOr()->isNonEmptyString('string schema "description" must be a non-empty string or null.');
67: Assert::that($minLength)->nullOr()->isNaturalInt('string schema "minLength" must be a non-negative integer or null.');
68: Assert::that($maxLength)->nullOr()->isNaturalInt('string schema "maxLength" must be a non-negative integer or null.');
69: Assert::that($format)
70: ->nullOr()
71: ->isOneOf(
72: [self::FORMAT_DATE, self::FORMAT_DATE_TIME, self::FORMAT_EMAIL, self::FORMAT_URI],
73: 'string schema "format" must be one of "date", "date-time", "email", "uri".',
74: )
75: ;
76:
77: $this->title = $title;
78: $this->description = $description;
79: $this->format = $format;
80: }
81:
82: /**
83: * @param array<string, mixed> $data
84: */
85: #[\Override]
86: public static function fromArray(array $data): static
87: {
88: Assert::that($data)->hasOffset('type', 'string schema missing the required "type" key.');
89: $type = $data['type'];
90: Assert::that($type)->isIdentical(self::TYPE, 'string schema "type" must be {other}, {value} given.');
91:
92: $title = $data['title'] ?? null;
93: Assert::that($title)->nullOr()->isString('string schema "title" must be a string or null, {type} given.');
94:
95: $description = $data['description'] ?? null;
96: Assert::that($description)->nullOr()->isString('string schema "description" must be a string or null, {type} given.');
97:
98: $minLength = $data['minLength'] ?? null;
99: Assert::that($minLength)->nullOr()->isInt('string schema "minLength" must be an int or null, {type} given.');
100:
101: $maxLength = $data['maxLength'] ?? null;
102: Assert::that($maxLength)->nullOr()->isInt('string schema "maxLength" must be an int or null, {type} given.');
103:
104: $format = $data['format'] ?? null;
105: Assert::that($format)->nullOr()->isString('string schema "format" must be a string or null, {type} given.');
106:
107: $default = $data['default'] ?? null;
108: Assert::that($default)->nullOr()->isString('string schema "default" must be a string or null, {type} given.');
109:
110: return new self($title, $description, $minLength, $maxLength, $format, $default);
111: }
112:
113: #[\Override]
114: public function toArray(): array
115: {
116: $data = ['type' => self::TYPE];
117:
118: if (null !== $this->title) {
119: $data['title'] = $this->title;
120: }
121:
122: if (null !== $this->description) {
123: $data['description'] = $this->description;
124: }
125:
126: if (null !== $this->minLength) {
127: $data['minLength'] = $this->minLength;
128: }
129:
130: if (null !== $this->maxLength) {
131: $data['maxLength'] = $this->maxLength;
132: }
133:
134: if (null !== $this->format) {
135: $data['format'] = $this->format;
136: }
137:
138: if (null !== $this->default) {
139: $data['default'] = $this->default;
140: }
141:
142: return $data;
143: }
144:
145: #[\Override]
146: public function jsonSerialize(): array
147: {
148: return $this->toArray();
149: }
150: }
151: