Skip to content

Commit 185a005

Browse files
authoredFeb 23, 2024
Merge pull request #82 from slackhq/fix-cyclic-ref
add hackIgnoreRefs option for preventing circular references
2 parents 54f3ad2 + 91bb232 commit 185a005

File tree

4 files changed

+141
-1
lines changed

4 files changed

+141
-1
lines changed
 

‎src/Codegen/Constraints/SchemaBuilder.php

+14-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ enum TSchemaType: string {
2525
?'default' => mixed,
2626
?'enum' => vec<mixed>,
2727
?'hackEnum' => string,
28+
29+
// Generate a mixed type instead of following the ref.
30+
//
31+
// Normally you cannot use circular references in JSON Schema because Hack
32+
// does not support them. However many other languages that use JSON Schema such as
33+
// Typescript and Kotlin do support circular references. This allows us to generate
34+
// circular references in those languages at the cost of generating a mixed type in Hack.
35+
?'hackIgnoreRef' => bool,
2836
...
2937
);
3038

@@ -42,7 +50,12 @@ public function __construct(
4250
protected TSchema $schema,
4351
?CodegenClass $class = null,
4452
) {
45-
$ref = $schema['$ref'] ?? null;
53+
54+
$ref = null;
55+
if (!($schema['hackIgnoreRef'] ?? false)) {
56+
$ref = $schema['$ref'] ?? null;
57+
}
58+
4659
$new_ctx = clone $this->ctx;
4760

4861
// Resolve refs until we get to an actual schema

‎tests/IgnoreRefsValidatorTest.php

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?hh // strict
2+
3+
namespace Slack\Hack\JsonSchema\Tests;
4+
5+
use namespace HH\Lib\C;
6+
use function Facebook\FBExpect\expect;
7+
8+
use type Slack\Hack\JsonSchema\Tests\Generated\{IgnoreRefsValidator};
9+
10+
final class IgnoreRefsValidatorTest extends BaseCodegenTestCase {
11+
12+
<<__Override>>
13+
public static async function beforeFirstTestAsync(): Awaitable<void> {
14+
$ret = self::getBuilder('ignore-refs.json', 'IgnoreRefsValidator');
15+
$ret['codegen']->build();
16+
require_once($ret['path']);
17+
}
18+
19+
public function testInvalidEmptyInput(): void {
20+
$validator = new IgnoreRefsValidator(dict['randomprop' => 'IanIzzy']);
21+
$validator->validate();
22+
expect($validator->isValid())->toBeTrue();
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?hh // strict
2+
/**
3+
* This file is generated. Do not modify it manually!
4+
*
5+
* To re-generate this file run `make test`
6+
*
7+
*
8+
* @generated SignedSource<<f57a9080fda14e4ed995266ea7ae4d40>>
9+
*/
10+
namespace Slack\Hack\JsonSchema\Tests\Generated;
11+
use namespace Slack\Hack\JsonSchema;
12+
use namespace Slack\Hack\JsonSchema\Constraints;
13+
14+
type TIgnoreRefsValidatorPropertiesRandomprop = mixed;
15+
16+
type TIgnoreRefsValidator = shape(
17+
?'randomprop' => TIgnoreRefsValidatorPropertiesRandomprop,
18+
...
19+
);
20+
21+
final class IgnoreRefsValidatorPropertiesRandomprop {
22+
23+
public static function check(
24+
mixed $input,
25+
string $_pointer,
26+
): TIgnoreRefsValidatorPropertiesRandomprop {
27+
return $input;
28+
}
29+
}
30+
31+
final class IgnoreRefsValidator
32+
extends JsonSchema\BaseValidator<TIgnoreRefsValidator> {
33+
34+
private static bool $coerce = false;
35+
36+
public static function check(
37+
mixed $input,
38+
string $pointer,
39+
): TIgnoreRefsValidator {
40+
$typed = Constraints\ObjectConstraint::check($input, $pointer, self::$coerce);
41+
42+
$errors = vec[];
43+
$output = shape();
44+
45+
/*HHAST_IGNORE_ERROR[UnusedVariable] Some loops generated with this statement do not use their $value*/
46+
foreach ($typed as $key => $value) {
47+
/* HH_IGNORE_ERROR[4051] allow dynamic access to preserve input. See comment in the codegen lib for reasoning and alternatives if needed. */
48+
$output[$key] = $value;
49+
}
50+
51+
if (\HH\Lib\C\contains_key($typed, 'randomprop')) {
52+
try {
53+
$output['randomprop'] = IgnoreRefsValidatorPropertiesRandomprop::check(
54+
$typed['randomprop'],
55+
JsonSchema\get_pointer($pointer, 'randomprop'),
56+
);
57+
} catch (JsonSchema\InvalidFieldException $e) {
58+
$errors = \HH\Lib\Vec\concat($errors, $e->errors);
59+
}
60+
}
61+
62+
if (\HH\Lib\C\count($errors)) {
63+
throw new JsonSchema\InvalidFieldException($pointer, $errors);
64+
}
65+
66+
/* HH_IGNORE_ERROR[4163] */
67+
return $output;
68+
}
69+
70+
<<__Override>>
71+
protected function process(): TIgnoreRefsValidator {
72+
return self::check($this->input, $this->pointer);
73+
}
74+
}

‎tests/examples/ignore-refs.json

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"randomprop": {
5+
"$ref": "#/definitions/objectRef",
6+
"hackIgnoreRef": true
7+
}
8+
},
9+
"definitions": {
10+
"otherRef": {
11+
"$ref": "#/definitions/anotherRef"
12+
},
13+
"anotherRef": {
14+
"$ref": "#/definitions/otherRef"
15+
},
16+
"objectRef": {
17+
"type": "object",
18+
"properties": {
19+
"thing": {
20+
"$ref": "#/definitions/objectRef2"
21+
}
22+
23+
}
24+
},
25+
"objectRef2": {
26+
"$ref": "#/definitions/objectRef"
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)
Please sign in to comment.