@wp-typia/block-runtime 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -10
- package/dist/blocks.d.ts +180 -0
- package/dist/blocks.js +108 -0
- package/dist/defaults.d.ts +25 -1
- package/dist/defaults.js +73 -1
- package/dist/editor.d.ts +33 -1
- package/dist/editor.js +165 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inspector-runtime.d.ts +129 -0
- package/dist/inspector-runtime.js +283 -0
- package/dist/inspector.d.ts +1 -1
- package/dist/inspector.js +1 -1
- package/dist/json-utils.d.ts +10 -0
- package/dist/json-utils.js +12 -0
- package/dist/metadata-analysis.d.ts +11 -0
- package/dist/metadata-analysis.js +285 -0
- package/dist/metadata-core.d.ts +286 -0
- package/dist/metadata-core.js +810 -0
- package/dist/metadata-model.d.ts +84 -0
- package/dist/metadata-model.js +59 -0
- package/dist/metadata-parser.d.ts +53 -0
- package/dist/metadata-parser.js +794 -0
- package/dist/metadata-php-render.d.ts +29 -0
- package/dist/metadata-php-render.js +549 -0
- package/dist/metadata-projection.d.ts +7 -0
- package/dist/metadata-projection.js +233 -0
- package/dist/migration-types.d.ts +53 -0
- package/dist/migration-types.js +1 -0
- package/dist/object-utils.d.ts +2 -0
- package/dist/object-utils.js +7 -0
- package/dist/schema-core.d.ts +267 -0
- package/dist/schema-core.js +597 -0
- package/dist/typia-tags.d.ts +11 -0
- package/dist/typia-tags.js +1 -0
- package/dist/validation.d.ts +53 -1
- package/dist/validation.js +206 -1
- package/package.json +16 -5
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ManifestAttribute, ManifestDocument } from "./metadata-model.js";
|
|
2
|
+
/**
|
|
3
|
+
* Render a PHP validator class from one manifest document.
|
|
4
|
+
*
|
|
5
|
+
* @param manifest Manifest document describing the block attribute schema and
|
|
6
|
+
* Typia metadata to enforce in PHP.
|
|
7
|
+
* @returns Generated PHP source plus any warn-only coverage gaps discovered
|
|
8
|
+
* while traversing the manifest.
|
|
9
|
+
*/
|
|
10
|
+
export declare function renderPhpValidator(manifest: ManifestDocument): {
|
|
11
|
+
source: string;
|
|
12
|
+
warnings: string[];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Collect warn-only PHP validator generation gaps for one manifest branch.
|
|
16
|
+
*
|
|
17
|
+
* @param attribute Manifest attribute metadata to inspect.
|
|
18
|
+
* @param pathLabel Human-readable path used in emitted warning messages.
|
|
19
|
+
* @param warnings Mutable accumulator that receives any discovered warnings.
|
|
20
|
+
*/
|
|
21
|
+
export declare function collectPhpGenerationWarnings(attribute: ManifestAttribute, pathLabel: string, warnings: string[]): void;
|
|
22
|
+
/**
|
|
23
|
+
* Render one JavaScript value into a PHP literal string.
|
|
24
|
+
*
|
|
25
|
+
* @param value JSON-like value to encode for the generated validator manifest.
|
|
26
|
+
* @param indentLevel Current indentation depth, expressed in tab levels.
|
|
27
|
+
* @returns PHP source code representing the provided value.
|
|
28
|
+
*/
|
|
29
|
+
export declare function renderPhpValue(value: unknown, indentLevel: number): string;
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
const SUPPORTED_PHP_FORMATS = new Set([
|
|
2
|
+
"uuid",
|
|
3
|
+
"email",
|
|
4
|
+
"url",
|
|
5
|
+
"uri",
|
|
6
|
+
"ipv4",
|
|
7
|
+
"ipv6",
|
|
8
|
+
"date-time",
|
|
9
|
+
]);
|
|
10
|
+
const SUPPORTED_PHP_TYPE_TAGS = new Set([
|
|
11
|
+
"uint32",
|
|
12
|
+
"int32",
|
|
13
|
+
"uint64",
|
|
14
|
+
"float",
|
|
15
|
+
"double",
|
|
16
|
+
]);
|
|
17
|
+
/**
|
|
18
|
+
* Render a PHP validator class from one manifest document.
|
|
19
|
+
*
|
|
20
|
+
* @param manifest Manifest document describing the block attribute schema and
|
|
21
|
+
* Typia metadata to enforce in PHP.
|
|
22
|
+
* @returns Generated PHP source plus any warn-only coverage gaps discovered
|
|
23
|
+
* while traversing the manifest.
|
|
24
|
+
*/
|
|
25
|
+
export function renderPhpValidator(manifest) {
|
|
26
|
+
const warnings = [];
|
|
27
|
+
for (const [key, attribute] of Object.entries(manifest.attributes)) {
|
|
28
|
+
collectPhpGenerationWarnings(attribute, key, warnings);
|
|
29
|
+
}
|
|
30
|
+
const phpManifest = renderPhpValue(manifest, 2);
|
|
31
|
+
return {
|
|
32
|
+
source: `<?php
|
|
33
|
+
declare(strict_types=1);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generated from typia.manifest.json. Do not edit manually.
|
|
37
|
+
*/
|
|
38
|
+
return new class {
|
|
39
|
+
\tprivate array $manifest = ${phpManifest};
|
|
40
|
+
|
|
41
|
+
\tpublic function apply_defaults(array $attributes): array
|
|
42
|
+
\t{
|
|
43
|
+
\t\treturn $this->applyDefaultsForObject($attributes, $this->manifest['attributes'] ?? []);
|
|
44
|
+
\t}
|
|
45
|
+
|
|
46
|
+
\tpublic function validate(array $attributes): array
|
|
47
|
+
\t{
|
|
48
|
+
\t\t$normalized = $this->apply_defaults($attributes);
|
|
49
|
+
\t\t$errors = [];
|
|
50
|
+
|
|
51
|
+
\t\tforeach (($this->manifest['attributes'] ?? []) as $name => $attribute) {
|
|
52
|
+
\t\t\t$this->validateAttribute(
|
|
53
|
+
\t\t\t\tarray_key_exists($name, $normalized),
|
|
54
|
+
\t\t\t\t$normalized[$name] ?? null,
|
|
55
|
+
\t\t\t\t$attribute,
|
|
56
|
+
\t\t\t\t(string) $name,
|
|
57
|
+
\t\t\t\t$errors,
|
|
58
|
+
\t\t\t);
|
|
59
|
+
\t\t}
|
|
60
|
+
|
|
61
|
+
\t\treturn [
|
|
62
|
+
\t\t\t'errors' => $errors,
|
|
63
|
+
\t\t\t'valid' => count($errors) === 0,
|
|
64
|
+
\t\t];
|
|
65
|
+
\t}
|
|
66
|
+
|
|
67
|
+
\tpublic function is_valid(array $attributes): bool
|
|
68
|
+
\t{
|
|
69
|
+
\t\treturn $this->validate($attributes)['valid'];
|
|
70
|
+
\t}
|
|
71
|
+
|
|
72
|
+
\tprivate function applyDefaultsForObject(array $attributes, array $schema): array
|
|
73
|
+
\t{
|
|
74
|
+
\t\t$result = $attributes;
|
|
75
|
+
|
|
76
|
+
\t\tforeach ($schema as $name => $attribute) {
|
|
77
|
+
\t\t\tif (!array_key_exists($name, $result)) {
|
|
78
|
+
\t\t\t\t$derivedDefault = $this->deriveDefaultValue($attribute);
|
|
79
|
+
\t\t\t\tif ($derivedDefault !== null) {
|
|
80
|
+
\t\t\t\t\t$result[$name] = $derivedDefault;
|
|
81
|
+
\t\t\t\t}
|
|
82
|
+
\t\t\t\tcontinue;
|
|
83
|
+
\t\t\t}
|
|
84
|
+
|
|
85
|
+
\t\t\t$result[$name] = $this->applyDefaultsForNode($result[$name], $attribute);
|
|
86
|
+
\t\t}
|
|
87
|
+
|
|
88
|
+
\t\treturn $result;
|
|
89
|
+
\t}
|
|
90
|
+
|
|
91
|
+
\tprivate function applyDefaultsForNode($value, array $attribute)
|
|
92
|
+
\t{
|
|
93
|
+
\t\tif ($value === null) {
|
|
94
|
+
\t\t\treturn null;
|
|
95
|
+
\t\t}
|
|
96
|
+
|
|
97
|
+
\t\t$kind = $attribute['ts']['kind'] ?? $attribute['wp']['type'] ?? null;
|
|
98
|
+
\t\tif ($kind === 'union') {
|
|
99
|
+
\t\t\treturn $this->applyDefaultsForUnion($value, $attribute);
|
|
100
|
+
\t\t}
|
|
101
|
+
\t\tif ($kind === 'object' && is_array($value) && !$this->isListArray($value)) {
|
|
102
|
+
\t\t\treturn $this->applyDefaultsForObject($value, $attribute['ts']['properties'] ?? []);
|
|
103
|
+
\t\t}
|
|
104
|
+
\t\tif (
|
|
105
|
+
\t\t\t$kind === 'array' &&
|
|
106
|
+
\t\t\tis_array($value) &&
|
|
107
|
+
\t\t\t$this->isListArray($value) &&
|
|
108
|
+
\t\t\tisset($attribute['ts']['items']) &&
|
|
109
|
+
\t\t\tis_array($attribute['ts']['items'])
|
|
110
|
+
\t\t) {
|
|
111
|
+
\t\t\t$result = [];
|
|
112
|
+
\t\t\tforeach ($value as $index => $item) {
|
|
113
|
+
\t\t\t\t$result[$index] = $this->applyDefaultsForNode($item, $attribute['ts']['items']);
|
|
114
|
+
\t\t\t}
|
|
115
|
+
\t\t\treturn $result;
|
|
116
|
+
\t\t}
|
|
117
|
+
|
|
118
|
+
\t\treturn $value;
|
|
119
|
+
\t}
|
|
120
|
+
|
|
121
|
+
\tprivate function deriveDefaultValue(array $attribute)
|
|
122
|
+
\t{
|
|
123
|
+
\t\tif ($this->hasDefault($attribute)) {
|
|
124
|
+
\t\t\treturn $attribute['typia']['defaultValue'];
|
|
125
|
+
\t\t}
|
|
126
|
+
|
|
127
|
+
\t\t$kind = $attribute['ts']['kind'] ?? $attribute['wp']['type'] ?? null;
|
|
128
|
+
\t\tif ($kind !== 'object') {
|
|
129
|
+
\t\t\treturn null;
|
|
130
|
+
\t\t}
|
|
131
|
+
|
|
132
|
+
\t\t$properties = $attribute['ts']['properties'] ?? null;
|
|
133
|
+
\t\tif (!is_array($properties)) {
|
|
134
|
+
\t\t\treturn null;
|
|
135
|
+
\t\t}
|
|
136
|
+
|
|
137
|
+
\t\t$derived = [];
|
|
138
|
+
\t\tforeach ($properties as $name => $child) {
|
|
139
|
+
\t\t\tif (!is_array($child)) {
|
|
140
|
+
\t\t\t\tcontinue;
|
|
141
|
+
\t\t\t}
|
|
142
|
+
\t\t\t$childDefault = $this->deriveDefaultValue($child);
|
|
143
|
+
\t\t\tif ($childDefault !== null) {
|
|
144
|
+
\t\t\t\t$derived[$name] = $childDefault;
|
|
145
|
+
\t\t\t}
|
|
146
|
+
\t\t}
|
|
147
|
+
|
|
148
|
+
\t\treturn count($derived) > 0 ? $derived : null;
|
|
149
|
+
\t}
|
|
150
|
+
|
|
151
|
+
\tprivate function applyDefaultsForUnion($value, array $attribute)
|
|
152
|
+
\t{
|
|
153
|
+
\t\tif (!is_array($value) || $this->isListArray($value)) {
|
|
154
|
+
\t\t\treturn $value;
|
|
155
|
+
\t\t}
|
|
156
|
+
|
|
157
|
+
\t\t$union = $attribute['ts']['union'] ?? null;
|
|
158
|
+
\t\tif (!is_array($union)) {
|
|
159
|
+
\t\t\treturn $value;
|
|
160
|
+
\t\t}
|
|
161
|
+
|
|
162
|
+
\t\t$discriminator = $union['discriminator'] ?? null;
|
|
163
|
+
\t\tif (!is_string($discriminator) || !array_key_exists($discriminator, $value)) {
|
|
164
|
+
\t\t\treturn $value;
|
|
165
|
+
\t\t}
|
|
166
|
+
|
|
167
|
+
\t\t$branchKey = $value[$discriminator];
|
|
168
|
+
\t\tif (!is_string($branchKey) || !isset($union['branches'][$branchKey]) || !is_array($union['branches'][$branchKey])) {
|
|
169
|
+
\t\t\treturn $value;
|
|
170
|
+
\t\t}
|
|
171
|
+
|
|
172
|
+
\t\treturn $this->applyDefaultsForNode($value, $union['branches'][$branchKey]);
|
|
173
|
+
\t}
|
|
174
|
+
|
|
175
|
+
\tprivate function validateAttribute(bool $exists, $value, array $attribute, string $path, array &$errors): void
|
|
176
|
+
\t{
|
|
177
|
+
\t\tif (!$exists) {
|
|
178
|
+
\t\t\tif (($attribute['ts']['required'] ?? false) && !$this->hasDefault($attribute)) {
|
|
179
|
+
\t\t\t\t$errors[] = sprintf('%s is required', $path);
|
|
180
|
+
\t\t\t}
|
|
181
|
+
\t\t\treturn;
|
|
182
|
+
\t\t}
|
|
183
|
+
|
|
184
|
+
\t\t$kind = $attribute['ts']['kind'] ?? $attribute['wp']['type'] ?? null;
|
|
185
|
+
\t\tif (!is_string($kind) || $kind === '') {
|
|
186
|
+
\t\t\t$errors[] = sprintf('%s has an invalid schema kind', $path);
|
|
187
|
+
\t\t\treturn;
|
|
188
|
+
\t\t}
|
|
189
|
+
\t\tif ($value === null) {
|
|
190
|
+
\t\t\t$errors[] = sprintf('%s must be %s', $path, $this->expectedKindLabel($attribute));
|
|
191
|
+
\t\t\treturn;
|
|
192
|
+
\t\t}
|
|
193
|
+
|
|
194
|
+
\t\tif (($attribute['wp']['enum'] ?? null) !== null && !$this->valueInEnum($value, $attribute['wp']['enum'])) {
|
|
195
|
+
\t\t\t$errors[] = sprintf('%s must be one of %s', $path, implode(', ', $attribute['wp']['enum']));
|
|
196
|
+
\t\t}
|
|
197
|
+
|
|
198
|
+
\t\tswitch ($kind) {
|
|
199
|
+
\t\t\tcase 'string':
|
|
200
|
+
\t\t\t\tif (!is_string($value)) {
|
|
201
|
+
\t\t\t\t\t$errors[] = sprintf('%s must be string', $path);
|
|
202
|
+
\t\t\t\t\treturn;
|
|
203
|
+
\t\t\t\t}
|
|
204
|
+
\t\t\t\t$this->validateString($value, $attribute, $path, $errors);
|
|
205
|
+
\t\t\t\treturn;
|
|
206
|
+
\t\t\tcase 'number':
|
|
207
|
+
\t\t\t\t$allowsUint64String =
|
|
208
|
+
\t\t\t\t\t($attribute['typia']['constraints']['typeTag'] ?? null) === 'uint64' &&
|
|
209
|
+
\t\t\t\t\t$this->matchesUint64($value);
|
|
210
|
+
\t\t\t\tif (!$this->isNumber($value) && !$allowsUint64String) {
|
|
211
|
+
\t\t\t\t\t$errors[] = sprintf('%s must be number', $path);
|
|
212
|
+
\t\t\t\t\treturn;
|
|
213
|
+
\t\t\t\t}
|
|
214
|
+
\t\t\t\t$this->validateNumber($value, $attribute, $path, $errors);
|
|
215
|
+
\t\t\t\treturn;
|
|
216
|
+
\t\t\tcase 'boolean':
|
|
217
|
+
\t\t\t\tif (!is_bool($value)) {
|
|
218
|
+
\t\t\t\t\t$errors[] = sprintf('%s must be boolean', $path);
|
|
219
|
+
\t\t\t\t}
|
|
220
|
+
\t\t\t\treturn;
|
|
221
|
+
\t\t\tcase 'array':
|
|
222
|
+
\t\t\t\tif (!is_array($value) || !$this->isListArray($value)) {
|
|
223
|
+
\t\t\t\t\t$errors[] = sprintf('%s must be array', $path);
|
|
224
|
+
\t\t\t\t\treturn;
|
|
225
|
+
\t\t\t\t}
|
|
226
|
+
\t\t\t\t$this->validateArray($value, $attribute, $path, $errors);
|
|
227
|
+
\t\t\t\tif (isset($attribute['ts']['items']) && is_array($attribute['ts']['items'])) {
|
|
228
|
+
\t\t\t\t\tforeach ($value as $index => $item) {
|
|
229
|
+
\t\t\t\t\t\t$this->validateAttribute(true, $item, $attribute['ts']['items'], sprintf('%s[%s]', $path, (string) $index), $errors);
|
|
230
|
+
\t\t\t\t\t}
|
|
231
|
+
\t\t\t\t}
|
|
232
|
+
\t\t\t\treturn;
|
|
233
|
+
\t\t\tcase 'object':
|
|
234
|
+
\t\t\t\tif (!is_array($value) || $this->isListArray($value)) {
|
|
235
|
+
\t\t\t\t\t$errors[] = sprintf('%s must be object', $path);
|
|
236
|
+
\t\t\t\t\treturn;
|
|
237
|
+
\t\t\t\t}
|
|
238
|
+
\t\t\t\tforeach (($attribute['ts']['properties'] ?? []) as $name => $child) {
|
|
239
|
+
\t\t\t\t\t$this->validateAttribute(
|
|
240
|
+
\t\t\t\t\t\tarray_key_exists($name, $value),
|
|
241
|
+
\t\t\t\t\t\t$value[$name] ?? null,
|
|
242
|
+
\t\t\t\t\t\t$child,
|
|
243
|
+
\t\t\t\t\t\tsprintf('%s.%s', $path, (string) $name),
|
|
244
|
+
\t\t\t\t\t\t$errors,
|
|
245
|
+
\t\t\t\t\t);
|
|
246
|
+
\t\t\t\t}
|
|
247
|
+
\t\t\t\treturn;
|
|
248
|
+
\t\t\tcase 'union':
|
|
249
|
+
\t\t\t\t$this->validateUnion($value, $attribute, $path, $errors);
|
|
250
|
+
\t\t\t\treturn;
|
|
251
|
+
\t\t\tdefault:
|
|
252
|
+
\t\t\t\t$errors[] = sprintf('%s has unsupported schema kind %s', $path, $kind);
|
|
253
|
+
\t\t}
|
|
254
|
+
\t}
|
|
255
|
+
|
|
256
|
+
\tprivate function validateUnion($value, array $attribute, string $path, array &$errors): void
|
|
257
|
+
\t{
|
|
258
|
+
\t\tif (!is_array($value) || $this->isListArray($value)) {
|
|
259
|
+
\t\t\t$errors[] = sprintf('%s must be object', $path);
|
|
260
|
+
\t\t\treturn;
|
|
261
|
+
\t\t}
|
|
262
|
+
|
|
263
|
+
\t\t$union = $attribute['ts']['union'] ?? null;
|
|
264
|
+
\t\tif (!is_array($union)) {
|
|
265
|
+
\t\t\t$errors[] = sprintf('%s has invalid union schema metadata', $path);
|
|
266
|
+
\t\t\treturn;
|
|
267
|
+
\t\t}
|
|
268
|
+
|
|
269
|
+
\t\t$discriminator = $union['discriminator'] ?? null;
|
|
270
|
+
\t\tif (!is_string($discriminator) || $discriminator === '') {
|
|
271
|
+
\t\t\t$errors[] = sprintf('%s has invalid union discriminator metadata', $path);
|
|
272
|
+
\t\t\treturn;
|
|
273
|
+
\t\t}
|
|
274
|
+
\t\tif (!array_key_exists($discriminator, $value)) {
|
|
275
|
+
\t\t\t$errors[] = sprintf('%s.%s is required', $path, $discriminator);
|
|
276
|
+
\t\t\treturn;
|
|
277
|
+
\t\t}
|
|
278
|
+
|
|
279
|
+
\t\t$branchKey = $value[$discriminator];
|
|
280
|
+
\t\tif (!is_string($branchKey)) {
|
|
281
|
+
\t\t\t$errors[] = sprintf('%s.%s must be string', $path, $discriminator);
|
|
282
|
+
\t\t\treturn;
|
|
283
|
+
\t\t}
|
|
284
|
+
\t\tif (!isset($union['branches'][$branchKey]) || !is_array($union['branches'][$branchKey])) {
|
|
285
|
+
\t\t\t$errors[] = sprintf('%s.%s must be one of %s', $path, $discriminator, implode(', ', array_keys($union['branches'] ?? [])));
|
|
286
|
+
\t\t\treturn;
|
|
287
|
+
\t\t}
|
|
288
|
+
|
|
289
|
+
\t\t$this->validateAttribute(true, $value, $union['branches'][$branchKey], $path, $errors);
|
|
290
|
+
\t}
|
|
291
|
+
|
|
292
|
+
\tprivate function validateString(string $value, array $attribute, string $path, array &$errors): void
|
|
293
|
+
\t{
|
|
294
|
+
\t\t$constraints = $attribute['typia']['constraints'] ?? [];
|
|
295
|
+
|
|
296
|
+
\t\tif (isset($constraints['minLength']) && is_int($constraints['minLength']) && strlen($value) < $constraints['minLength']) {
|
|
297
|
+
\t\t\t$errors[] = sprintf('%s must be at least %d characters', $path, $constraints['minLength']);
|
|
298
|
+
\t\t}
|
|
299
|
+
\t\tif (isset($constraints['maxLength']) && is_int($constraints['maxLength']) && strlen($value) > $constraints['maxLength']) {
|
|
300
|
+
\t\t\t$errors[] = sprintf('%s must be at most %d characters', $path, $constraints['maxLength']);
|
|
301
|
+
\t\t}
|
|
302
|
+
\t\tif (
|
|
303
|
+
\t\t\tisset($constraints['pattern']) &&
|
|
304
|
+
\t\t\tis_string($constraints['pattern']) &&
|
|
305
|
+
\t\t\t$constraints['pattern'] !== '' &&
|
|
306
|
+
\t\t\t!$this->matchesPattern($constraints['pattern'], $value)
|
|
307
|
+
\t\t) {
|
|
308
|
+
\t\t\t$errors[] = sprintf('%s does not match %s', $path, $constraints['pattern']);
|
|
309
|
+
\t\t}
|
|
310
|
+
\t\tif (
|
|
311
|
+
\t\t\tisset($constraints['format']) &&
|
|
312
|
+
\t\t\tis_string($constraints['format']) &&
|
|
313
|
+
\t\t\t!$this->matchesFormat($constraints['format'], $value)
|
|
314
|
+
\t\t) {
|
|
315
|
+
\t\t\t$errors[] = sprintf('%s must match format %s', $path, $constraints['format']);
|
|
316
|
+
\t\t}
|
|
317
|
+
\t}
|
|
318
|
+
|
|
319
|
+
\tprivate function validateArray(array $value, array $attribute, string $path, array &$errors): void
|
|
320
|
+
\t{
|
|
321
|
+
\t\t$constraints = $attribute['typia']['constraints'] ?? [];
|
|
322
|
+
|
|
323
|
+
\t\tif (isset($constraints['minItems']) && is_int($constraints['minItems']) && count($value) < $constraints['minItems']) {
|
|
324
|
+
\t\t\t$errors[] = sprintf('%s must have at least %d items', $path, $constraints['minItems']);
|
|
325
|
+
\t\t}
|
|
326
|
+
\t\tif (isset($constraints['maxItems']) && is_int($constraints['maxItems']) && count($value) > $constraints['maxItems']) {
|
|
327
|
+
\t\t\t$errors[] = sprintf('%s must have at most %d items', $path, $constraints['maxItems']);
|
|
328
|
+
\t\t}
|
|
329
|
+
\t}
|
|
330
|
+
|
|
331
|
+
\tprivate function validateNumber($value, array $attribute, string $path, array &$errors): void
|
|
332
|
+
\t{
|
|
333
|
+
\t\t$constraints = $attribute['typia']['constraints'] ?? [];
|
|
334
|
+
|
|
335
|
+
\t\tif (isset($constraints['minimum']) && $this->isNumber($constraints['minimum']) && $value < $constraints['minimum']) {
|
|
336
|
+
\t\t\t$errors[] = sprintf('%s must be >= %s', $path, (string) $constraints['minimum']);
|
|
337
|
+
\t\t}
|
|
338
|
+
\t\tif (isset($constraints['maximum']) && $this->isNumber($constraints['maximum']) && $value > $constraints['maximum']) {
|
|
339
|
+
\t\t\t$errors[] = sprintf('%s must be <= %s', $path, (string) $constraints['maximum']);
|
|
340
|
+
\t\t}
|
|
341
|
+
\t\tif (
|
|
342
|
+
\t\t\tisset($constraints['exclusiveMinimum']) &&
|
|
343
|
+
\t\t\t$this->isNumber($constraints['exclusiveMinimum']) &&
|
|
344
|
+
\t\t\t$value <= $constraints['exclusiveMinimum']
|
|
345
|
+
\t\t) {
|
|
346
|
+
\t\t\t$errors[] = sprintf('%s must be > %s', $path, (string) $constraints['exclusiveMinimum']);
|
|
347
|
+
\t\t}
|
|
348
|
+
\t\tif (
|
|
349
|
+
\t\t\tisset($constraints['exclusiveMaximum']) &&
|
|
350
|
+
\t\t\t$this->isNumber($constraints['exclusiveMaximum']) &&
|
|
351
|
+
\t\t\t$value >= $constraints['exclusiveMaximum']
|
|
352
|
+
\t\t) {
|
|
353
|
+
\t\t\t$errors[] = sprintf('%s must be < %s', $path, (string) $constraints['exclusiveMaximum']);
|
|
354
|
+
\t\t}
|
|
355
|
+
\t\tif (
|
|
356
|
+
\t\t\tisset($constraints['multipleOf']) &&
|
|
357
|
+
\t\t\t$this->isNumber($constraints['multipleOf']) &&
|
|
358
|
+
\t\t\t!$this->matchesMultipleOf($value, $constraints['multipleOf'])
|
|
359
|
+
\t\t) {
|
|
360
|
+
\t\t\t$errors[] = sprintf('%s must be a multiple of %s', $path, (string) $constraints['multipleOf']);
|
|
361
|
+
\t\t}
|
|
362
|
+
\t\tif (
|
|
363
|
+
\t\t\tisset($constraints['typeTag']) &&
|
|
364
|
+
\t\t\tis_string($constraints['typeTag']) &&
|
|
365
|
+
\t\t\t!$this->matchesTypeTag($value, $constraints['typeTag'])
|
|
366
|
+
\t\t) {
|
|
367
|
+
\t\t\t$errors[] = sprintf('%s must be a %s', $path, $constraints['typeTag']);
|
|
368
|
+
\t\t}
|
|
369
|
+
\t}
|
|
370
|
+
|
|
371
|
+
\tprivate function hasDefault(array $attribute): bool
|
|
372
|
+
\t{
|
|
373
|
+
\t\treturn ($attribute['typia']['hasDefault'] ?? false) === true;
|
|
374
|
+
\t}
|
|
375
|
+
|
|
376
|
+
\tprivate function valueInEnum($value, array $enum): bool
|
|
377
|
+
\t{
|
|
378
|
+
\t\tforeach ($enum as $candidate) {
|
|
379
|
+
\t\t\tif ($candidate === $value) {
|
|
380
|
+
\t\t\t\treturn true;
|
|
381
|
+
\t\t\t}
|
|
382
|
+
\t\t}
|
|
383
|
+
\t\treturn false;
|
|
384
|
+
\t}
|
|
385
|
+
|
|
386
|
+
\tprivate function matchesPattern(string $pattern, string $value): bool
|
|
387
|
+
\t{
|
|
388
|
+
\t\t$escapedPattern = str_replace('~', '\\\\~', $pattern);
|
|
389
|
+
\t\t$result = @preg_match('~' . $escapedPattern . '~u', $value);
|
|
390
|
+
\t\treturn $result === 1;
|
|
391
|
+
\t}
|
|
392
|
+
|
|
393
|
+
\tprivate function matchesFormat(string $format, string $value): bool
|
|
394
|
+
\t{
|
|
395
|
+
\t\tswitch ($format) {
|
|
396
|
+
\t\t\tcase 'uuid':
|
|
397
|
+
\t\t\t\treturn preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value) === 1;
|
|
398
|
+
\t\t\tcase 'email':
|
|
399
|
+
\t\t\t\treturn filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
|
|
400
|
+
\t\t\tcase 'url':
|
|
401
|
+
\t\t\tcase 'uri':
|
|
402
|
+
\t\t\t\treturn filter_var($value, FILTER_VALIDATE_URL) !== false;
|
|
403
|
+
\t\t\tcase 'ipv4':
|
|
404
|
+
\t\t\t\treturn filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
|
|
405
|
+
\t\t\tcase 'ipv6':
|
|
406
|
+
\t\t\t\treturn filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
|
|
407
|
+
\t\t\tcase 'date-time':
|
|
408
|
+
\t\t\t\treturn preg_match('/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$/', $value) === 1;
|
|
409
|
+
\t\t\tdefault:
|
|
410
|
+
\t\t\t\treturn true;
|
|
411
|
+
\t\t}
|
|
412
|
+
\t}
|
|
413
|
+
|
|
414
|
+
\tprivate function matchesTypeTag($value, string $typeTag): bool
|
|
415
|
+
\t{
|
|
416
|
+
\t\tswitch ($typeTag) {
|
|
417
|
+
\t\t\tcase 'uint32':
|
|
418
|
+
\t\t\t\treturn is_int($value) && $value >= 0 && $value <= 4294967295;
|
|
419
|
+
\t\t\tcase 'int32':
|
|
420
|
+
\t\t\t\treturn is_int($value) && $value >= -2147483648 && $value <= 2147483647;
|
|
421
|
+
\t\t\tcase 'uint64':
|
|
422
|
+
\t\t\t\treturn $this->matchesUint64($value);
|
|
423
|
+
\t\t\tcase 'float':
|
|
424
|
+
\t\t\tcase 'double':
|
|
425
|
+
\t\t\t\treturn is_int($value) || is_float($value);
|
|
426
|
+
\t\t\tdefault:
|
|
427
|
+
\t\t\t\treturn true;
|
|
428
|
+
\t\t}
|
|
429
|
+
\t}
|
|
430
|
+
|
|
431
|
+
\tprivate function matchesUint64($value): bool
|
|
432
|
+
\t{
|
|
433
|
+
\t\tif (is_int($value)) {
|
|
434
|
+
\t\t\treturn $value >= 0;
|
|
435
|
+
\t\t}
|
|
436
|
+
\t\tif (!is_string($value) || $value === '' || !ctype_digit($value)) {
|
|
437
|
+
\t\t\treturn false;
|
|
438
|
+
\t\t}
|
|
439
|
+
\t\tif (strlen($value) < 20) {
|
|
440
|
+
\t\t\treturn true;
|
|
441
|
+
\t\t}
|
|
442
|
+
\t\tif (strlen($value) > 20) {
|
|
443
|
+
\t\t\treturn false;
|
|
444
|
+
\t\t}
|
|
445
|
+
\t\treturn strcmp($value, '18446744073709551615') <= 0;
|
|
446
|
+
\t}
|
|
447
|
+
|
|
448
|
+
\tprivate function matchesMultipleOf($value, $multipleOf): bool
|
|
449
|
+
\t{
|
|
450
|
+
\t\tif ($multipleOf === 0) {
|
|
451
|
+
\t\t\treturn true;
|
|
452
|
+
\t\t}
|
|
453
|
+
\t\tif (is_int($value) && is_int($multipleOf)) {
|
|
454
|
+
\t\t\treturn $value % $multipleOf === 0;
|
|
455
|
+
\t\t}
|
|
456
|
+
|
|
457
|
+
\t\t$remainder = fmod((float) $value, (float) $multipleOf);
|
|
458
|
+
\t\t$epsilon = 0.000000001;
|
|
459
|
+
\t\treturn abs($remainder) < $epsilon || abs(abs((float) $multipleOf) - abs($remainder)) < $epsilon;
|
|
460
|
+
\t}
|
|
461
|
+
|
|
462
|
+
\tprivate function isNumber($value): bool
|
|
463
|
+
\t{
|
|
464
|
+
\t\treturn is_int($value) || is_float($value);
|
|
465
|
+
\t}
|
|
466
|
+
|
|
467
|
+
\tprivate function isListArray(array $value): bool
|
|
468
|
+
\t{
|
|
469
|
+
\t\t$expectedKey = 0;
|
|
470
|
+
\t\tforeach ($value as $key => $_item) {
|
|
471
|
+
\t\t\tif ($key !== $expectedKey) {
|
|
472
|
+
\t\t\t\treturn false;
|
|
473
|
+
\t\t\t}
|
|
474
|
+
\t\t\t$expectedKey += 1;
|
|
475
|
+
\t\t}
|
|
476
|
+
\t\treturn true;
|
|
477
|
+
\t}
|
|
478
|
+
|
|
479
|
+
\tprivate function expectedKindLabel(array $attribute): string
|
|
480
|
+
\t{
|
|
481
|
+
\t\t$kind = $attribute['ts']['kind'] ?? $attribute['wp']['type'] ?? 'value';
|
|
482
|
+
\t\treturn $kind === 'union' ? 'object' : (string) $kind;
|
|
483
|
+
\t}
|
|
484
|
+
};
|
|
485
|
+
`,
|
|
486
|
+
warnings,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Collect warn-only PHP validator generation gaps for one manifest branch.
|
|
491
|
+
*
|
|
492
|
+
* @param attribute Manifest attribute metadata to inspect.
|
|
493
|
+
* @param pathLabel Human-readable path used in emitted warning messages.
|
|
494
|
+
* @param warnings Mutable accumulator that receives any discovered warnings.
|
|
495
|
+
*/
|
|
496
|
+
export function collectPhpGenerationWarnings(attribute, pathLabel, warnings) {
|
|
497
|
+
const { format, typeTag } = attribute.typia.constraints;
|
|
498
|
+
if (format !== null && !SUPPORTED_PHP_FORMATS.has(format)) {
|
|
499
|
+
warnings.push(`${pathLabel}: unsupported PHP validator format "${format}"`);
|
|
500
|
+
}
|
|
501
|
+
if (typeTag !== null && !SUPPORTED_PHP_TYPE_TAGS.has(typeTag)) {
|
|
502
|
+
warnings.push(`${pathLabel}: unsupported PHP validator type tag "${typeTag}"`);
|
|
503
|
+
}
|
|
504
|
+
if (attribute.ts.items) {
|
|
505
|
+
collectPhpGenerationWarnings(attribute.ts.items, `${pathLabel}[]`, warnings);
|
|
506
|
+
}
|
|
507
|
+
for (const [key, property] of Object.entries(attribute.ts.properties ?? {})) {
|
|
508
|
+
collectPhpGenerationWarnings(property, `${pathLabel}.${key}`, warnings);
|
|
509
|
+
}
|
|
510
|
+
for (const [branchKey, branch] of Object.entries(attribute.ts.union?.branches ?? {})) {
|
|
511
|
+
collectPhpGenerationWarnings(branch, `${pathLabel}<${branchKey}>`, warnings);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Render one JavaScript value into a PHP literal string.
|
|
516
|
+
*
|
|
517
|
+
* @param value JSON-like value to encode for the generated validator manifest.
|
|
518
|
+
* @param indentLevel Current indentation depth, expressed in tab levels.
|
|
519
|
+
* @returns PHP source code representing the provided value.
|
|
520
|
+
*/
|
|
521
|
+
export function renderPhpValue(value, indentLevel) {
|
|
522
|
+
const indent = "\t".repeat(indentLevel);
|
|
523
|
+
const nestedIndent = "\t".repeat(indentLevel + 1);
|
|
524
|
+
if (value === null) {
|
|
525
|
+
return "null";
|
|
526
|
+
}
|
|
527
|
+
if (typeof value === "string") {
|
|
528
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
529
|
+
}
|
|
530
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
531
|
+
return String(value);
|
|
532
|
+
}
|
|
533
|
+
if (Array.isArray(value)) {
|
|
534
|
+
if (value.length === 0) {
|
|
535
|
+
return "[]";
|
|
536
|
+
}
|
|
537
|
+
const items = value.map((item) => `${nestedIndent}${renderPhpValue(item, indentLevel + 1)}`);
|
|
538
|
+
return `[\n${items.join(",\n")}\n${indent}]`;
|
|
539
|
+
}
|
|
540
|
+
if (typeof value === "object") {
|
|
541
|
+
const entries = Object.entries(value);
|
|
542
|
+
if (entries.length === 0) {
|
|
543
|
+
return "[]";
|
|
544
|
+
}
|
|
545
|
+
const items = entries.map(([key, item]) => `${nestedIndent}'${key.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}' => ${renderPhpValue(item, indentLevel + 1)}`);
|
|
546
|
+
return `[\n${items.join(",\n")}\n${indent}]`;
|
|
547
|
+
}
|
|
548
|
+
throw new Error(`Unable to encode PHP value for manifest node: ${String(value)}`);
|
|
549
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type AttributeNode, type BlockJsonAttribute, type JsonValue, type ManifestAttribute, type ManifestDocument } from "./metadata-model.js";
|
|
2
|
+
export declare function createBlockJsonAttribute(node: AttributeNode, warnings: string[]): BlockJsonAttribute;
|
|
3
|
+
export declare function createManifestAttribute(node: AttributeNode): ManifestAttribute;
|
|
4
|
+
export declare function createManifestDocument(sourceTypeName: string, attributes: Record<string, AttributeNode>): ManifestDocument;
|
|
5
|
+
export declare function validateWordPressExtractionAttributes(attributes: Record<string, AttributeNode>): void;
|
|
6
|
+
export declare function validateWordPressExtractionAttribute(node: AttributeNode, isTopLevel?: boolean): void;
|
|
7
|
+
export declare function createExampleValue(node: AttributeNode, key: string): JsonValue;
|