Skip to content

Commit

Permalink
add hackIgnoreRefs option for preventing circular references
Browse files Browse the repository at this point in the history
  • Loading branch information
Isara Ritthaworn committed Feb 23, 2024
1 parent 54f3ad2 commit 1e151da
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 1 deletion.
15 changes: 14 additions & 1 deletion src/Codegen/Constraints/SchemaBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ enum TSchemaType: string {
?'default' => mixed,
?'enum' => vec<mixed>,
?'hackEnum' => string,
// Generate a mixed type instead of following the ref.
//
// Normally you cannot use circular references in JSON Schema because Hack
// does not support them. However many other languages that use JSON Schema such as
// Typescript and Kotlin do support circular references. This allows us to generate
// circular references in those languages at the cost of generating a mixed type in Hack.
?'hackIgnoreRef' => bool,
...
);

Expand All @@ -42,7 +50,12 @@ public function __construct(
protected TSchema $schema,
?CodegenClass $class = null,
) {
$ref = $schema['$ref'] ?? null;

$ref = null;
if (!($schema['hackIgnoreRef'] ?? false)) {
$ref = $schema['$ref'] ?? null;
}

$new_ctx = clone $this->ctx;

// Resolve refs until we get to an actual schema
Expand Down
24 changes: 24 additions & 0 deletions tests/IgnoreRefsValidatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?hh // strict

namespace Slack\Hack\JsonSchema\Tests;

use namespace HH\Lib\C;
use function Facebook\FBExpect\expect;

use type Slack\Hack\JsonSchema\Tests\Generated\{IgnoreRefsValidator};

final class IgnoreRefsValidatorTest extends BaseCodegenTestCase {

<<__Override>>
public static async function beforeFirstTestAsync(): Awaitable<void> {
$ret = self::getBuilder('ignore-refs.json', 'IgnoreRefsValidator');
$ret['codegen']->build();
require_once($ret['path']);
}

public function testInvalidEmptyInput(): void {
$validator = new IgnoreRefsValidator(dict['randomprop' => 'IanIzzy']);
$validator->validate();
expect($validator->isValid())->toBeTrue();
}
}
74 changes: 74 additions & 0 deletions tests/examples/codegen/IgnoreRefsValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?hh // strict
/**
* This file is generated. Do not modify it manually!
*
* To re-generate this file run `make test`
*
*
* @generated SignedSource<<f57a9080fda14e4ed995266ea7ae4d40>>
*/
namespace Slack\Hack\JsonSchema\Tests\Generated;
use namespace Slack\Hack\JsonSchema;
use namespace Slack\Hack\JsonSchema\Constraints;

type TIgnoreRefsValidatorPropertiesRandomprop = mixed;

type TIgnoreRefsValidator = shape(
?'randomprop' => TIgnoreRefsValidatorPropertiesRandomprop,
...
);

final class IgnoreRefsValidatorPropertiesRandomprop {

public static function check(
mixed $input,
string $_pointer,
): TIgnoreRefsValidatorPropertiesRandomprop {
return $input;
}
}

final class IgnoreRefsValidator
extends JsonSchema\BaseValidator<TIgnoreRefsValidator> {

private static bool $coerce = false;

public static function check(
mixed $input,
string $pointer,
): TIgnoreRefsValidator {
$typed = Constraints\ObjectConstraint::check($input, $pointer, self::$coerce);

$errors = vec[];
$output = shape();

/*HHAST_IGNORE_ERROR[UnusedVariable] Some loops generated with this statement do not use their $value*/
foreach ($typed as $key => $value) {
/* HH_IGNORE_ERROR[4051] allow dynamic access to preserve input. See comment in the codegen lib for reasoning and alternatives if needed. */
$output[$key] = $value;
}

if (\HH\Lib\C\contains_key($typed, 'randomprop')) {
try {
$output['randomprop'] = IgnoreRefsValidatorPropertiesRandomprop::check(
$typed['randomprop'],
JsonSchema\get_pointer($pointer, 'randomprop'),
);
} catch (JsonSchema\InvalidFieldException $e) {
$errors = \HH\Lib\Vec\concat($errors, $e->errors);
}
}

if (\HH\Lib\C\count($errors)) {
throw new JsonSchema\InvalidFieldException($pointer, $errors);
}

/* HH_IGNORE_ERROR[4163] */
return $output;
}

<<__Override>>
protected function process(): TIgnoreRefsValidator {
return self::check($this->input, $this->pointer);
}
}
29 changes: 29 additions & 0 deletions tests/examples/ignore-refs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"type": "object",
"properties": {
"randomprop": {
"$ref": "#/definitions/objectRef",
"hackIgnoreRef": true
}
},
"definitions": {
"otherRef": {
"$ref": "#/definitions/anotherRef"
},
"anotherRef": {
"$ref": "#/definitions/otherRef"
},
"objectRef": {
"type": "object",
"properties": {
"thing": {
"$ref": "#/definitions/objectRef2"
}

}
},
"objectRef2": {
"$ref": "#/definitions/objectRef"
}
}
}

0 comments on commit 1e151da

Please sign in to comment.