Skip to content

Commit 3ca8237

Browse files
author
Michael Hahn
authored
Merge pull request #45 from ianhoffman/ih-uniqueItems-2
Skip unsupported uniqueItems constraints
2 parents 6b51873 + daf61a4 commit 3ca8237

File tree

5 files changed

+135
-29
lines changed

5 files changed

+135
-29
lines changed

src/Codegen/Constraints/ArrayBuilder.php

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,20 @@
1717
type TArraySchemaItemsSingleSchema = TSchema;
1818
type TArraySchemaItemsMultiSchema = vec<TSchema>;
1919

20+
enum HackArrayType: string {
21+
VEC = 'vec';
22+
KEYSET = 'keyset';
23+
}
24+
2025
class ArrayBuilder extends BaseBuilder<TArraySchema> {
2126
private ?IBuilder $singleItemSchemaBuilder = null;
2227
protected static string $schema_name = 'Slack\Hack\JsonSchema\Codegen\TArraySchema';
28+
private HackArrayType $hackArrayType = HackArrayType::VEC;
2329

2430
<<__Override>>
2531
public function build(): this {
2632
$this->setupSingleItemSchemaBuilder();
27-
$this->validateSchema();
33+
$this->determineHackArrayType();
2834

2935
$class = $this->codegenClass()
3036
->addMethod($this->getCheckMethod());
@@ -65,12 +71,7 @@ public function getType(): string {
6571
// Items is a single type, get the type from the items builder.
6672
$items_builder = $this->singleItemSchemaBuilder;
6773
invariant($items_builder is nonnull, 'must call `build` method before accessing type');
68-
if ($this->schema['uniqueItems'] ?? false) {
69-
$container = 'keyset';
70-
} else {
71-
$container = 'vec';
72-
}
73-
return "{$container}<{$items_builder->getType()}>";
74+
return "{$this->hackArrayType}<{$items_builder->getType()}>";
7475
}
7576

7677
protected function getCheckMethod(): CodegenMethod {
@@ -166,7 +167,7 @@ private function buildItemsSingleSchema(HackBuilder $hb): void {
166167
->endIfBlock()
167168
->ensureEmptyLine();
168169

169-
if ($this->typed_schema['uniqueItems'] ?? false) {
170+
if ($this->hackArrayType === HackArrayType::KEYSET) {
170171
$hb
171172
->addMultilineCall(
172173
'$output = Constraints\ArrayUniqueItemsConstraint::check',
@@ -263,13 +264,13 @@ private function setupSingleItemSchemaBuilder(): void {
263264
}
264265

265266
/**
266-
* Validate that the schema is supported.
267+
* Determine the type of hack array we will generate.
267268
*/
268-
private function validateSchema(): void {
269+
private function determineHackArrayType(): void {
269270
if ($this->schema['uniqueItems'] ?? false) {
270271
$item_type = $this->singleItemSchemaBuilder?->getType();
271-
if ($item_type !== 'string' && $item_type !== 'int') {
272-
throw new \Exception('uniqueItems is only implemented for arrays of strings and integers');
272+
if ($item_type === 'string' || $item_type === 'int') {
273+
$this->hackArrayType = HackArrayType::KEYSET;
273274
}
274275
}
275276
}

tests/ArraySchemaValidatorTest.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,16 @@ public function testUniqueItemsWithCoercion(): void {
206206
expect($validated)->toEqual(shape('unique_numbers_coerce' => keyset($input)));
207207
}
208208

209-
public function testInvalidUniqueItemsConstraint(): void {
210-
$ret = self::getBuilder('array-schema-invalid.json', 'ArraySchemaInvalidValidator');
211-
expect(() ==> $ret['codegen']->build())
212-
->toThrow(\Exception::class, 'uniqueItems is only implemented for arrays of strings and integers');
209+
public function testUnsupportedUniqueItemsConstraint(): void {
210+
$input = vec[dict['foo' => 'a'], dict['foo' => 'a']];
211+
212+
$validator = new ArraySchemaValidator(dict['unsupported_unique_items' => $input]);
213+
$validator->validate();
214+
215+
expect($validator->isValid())->toBeTrue();
216+
$validated = $validator->getValidatedInput();
217+
218+
expect($validated)->toEqual(shape('unsupported_unique_items' => vec[shape('foo' => 'a'), shape('foo' => 'a')]));
213219
}
214220

215221
}

tests/examples/array-schema-invalid.json

Lines changed: 0 additions & 12 deletions
This file was deleted.

tests/examples/array-schema.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@
3939
"items": {
4040
"type": "integer"
4141
}
42+
},
43+
"unsupported_unique_items": {
44+
"type": "array",
45+
"uniqueItems": true,
46+
"items": {
47+
"type": "object",
48+
"properties": {
49+
"foo": {
50+
"type": "string"
51+
}
52+
}
53+
}
4254
}
4355
}
4456
}

tests/examples/codegen/ArraySchemaValidator.php

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,25 @@
55
* To re-generate this file run `make test`
66
*
77
*
8-
* @generated SignedSource<<731edaf71398ea5fb65cfa1e09cab571>>
8+
* @generated SignedSource<<458640baae767319d7594d9b4fcce940>>
99
*/
1010
namespace Slack\Hack\JsonSchema\Tests\Generated;
1111
use namespace Slack\Hack\JsonSchema;
1212
use namespace Slack\Hack\JsonSchema\Constraints;
1313

14+
type TArraySchemaValidatorPropertiesUnsupportedUniqueItemsItems = shape(
15+
?'foo' => string,
16+
...
17+
);
18+
1419
type TArraySchemaValidator = shape(
1520
?'array_of_strings' => vec<string>,
1621
?'untyped_array' => vec<mixed>,
1722
?'coerce_array' => vec<num>,
1823
?'unique_strings' => keyset<string>,
1924
?'unique_numbers' => keyset<int>,
2025
?'unique_numbers_coerce' => keyset<int>,
26+
?'unsupported_unique_items' => vec<TArraySchemaValidatorPropertiesUnsupportedUniqueItemsItems>,
2127
...
2228
);
2329

@@ -252,6 +258,88 @@ public static function check(mixed $input, string $pointer): keyset<int> {
252258
}
253259
}
254260

261+
final class ArraySchemaValidatorPropertiesUnsupportedUniqueItemsItemsPropertiesFoo {
262+
263+
private static bool $coerce = false;
264+
265+
public static function check(mixed $input, string $pointer): string {
266+
$typed = Constraints\StringConstraint::check($input, $pointer, self::$coerce);
267+
268+
return $typed;
269+
}
270+
}
271+
272+
final class ArraySchemaValidatorPropertiesUnsupportedUniqueItemsItems {
273+
274+
private static bool $coerce = false;
275+
276+
public static function check(
277+
mixed $input,
278+
string $pointer,
279+
): TArraySchemaValidatorPropertiesUnsupportedUniqueItemsItems {
280+
$typed = Constraints\ObjectConstraint::check($input, $pointer, self::$coerce);
281+
282+
$errors = vec[];
283+
$output = shape();
284+
285+
/*HHAST_IGNORE_ERROR[UnusedVariable] Some loops generated with this statement do not use their $value*/
286+
foreach ($typed as $key => $value) {
287+
/* HH_IGNORE_ERROR[4051] allow dynamic access to preserve input. See comment in the codegen lib for reasoning and alternatives if needed. */
288+
$output[$key] = $value;
289+
}
290+
291+
if (\HH\Lib\C\contains_key($typed, 'foo')) {
292+
try {
293+
$output['foo'] = ArraySchemaValidatorPropertiesUnsupportedUniqueItemsItemsPropertiesFoo::check(
294+
$typed['foo'],
295+
JsonSchema\get_pointer($pointer, 'foo'),
296+
);
297+
} catch (JsonSchema\InvalidFieldException $e) {
298+
$errors = \HH\Lib\Vec\concat($errors, $e->errors);
299+
}
300+
}
301+
302+
if (\HH\Lib\C\count($errors)) {
303+
throw new JsonSchema\InvalidFieldException($pointer, $errors);
304+
}
305+
306+
/* HH_IGNORE_ERROR[4163] */
307+
return $output;
308+
}
309+
}
310+
311+
final class ArraySchemaValidatorPropertiesUnsupportedUniqueItems {
312+
313+
private static bool $coerce = false;
314+
315+
public static function check(
316+
mixed $input,
317+
string $pointer,
318+
): vec<TArraySchemaValidatorPropertiesUnsupportedUniqueItemsItems> {
319+
$typed = Constraints\ArrayConstraint::check($input, $pointer, self::$coerce);
320+
321+
$output = vec[];
322+
$errors = vec[];
323+
324+
foreach ($typed as $index => $value) {
325+
try {
326+
$output[] = ArraySchemaValidatorPropertiesUnsupportedUniqueItemsItems::check(
327+
$value,
328+
JsonSchema\get_pointer($pointer, (string) $index),
329+
);
330+
} catch (JsonSchema\InvalidFieldException $e) {
331+
$errors = \HH\Lib\Vec\concat($errors, $e->errors);
332+
}
333+
}
334+
335+
if (\HH\Lib\C\count($errors)) {
336+
throw new JsonSchema\InvalidFieldException($pointer, $errors);
337+
}
338+
339+
return $output;
340+
}
341+
}
342+
255343
final class ArraySchemaValidator
256344
extends JsonSchema\BaseValidator<TArraySchemaValidator> {
257345

@@ -338,6 +426,17 @@ public static function check(
338426
}
339427
}
340428

429+
if (\HH\Lib\C\contains_key($typed, 'unsupported_unique_items')) {
430+
try {
431+
$output['unsupported_unique_items'] = ArraySchemaValidatorPropertiesUnsupportedUniqueItems::check(
432+
$typed['unsupported_unique_items'],
433+
JsonSchema\get_pointer($pointer, 'unsupported_unique_items'),
434+
);
435+
} catch (JsonSchema\InvalidFieldException $e) {
436+
$errors = \HH\Lib\Vec\concat($errors, $e->errors);
437+
}
438+
}
439+
341440
if (\HH\Lib\C\count($errors)) {
342441
throw new JsonSchema\InvalidFieldException($pointer, $errors);
343442
}

0 commit comments

Comments
 (0)