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