Skip to content

Commit cc6709e

Browse files
authored
Merge pull request #64 from slackhq/ih_handle_hackEnum_refs
Handle hackEnum item refs correctly
2 parents f7d9b72 + e9c6aca commit cc6709e

17 files changed

+347
-96
lines changed

src/Codegen/Codegen.php

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,24 @@ final class Codegen {
108108
),
109109
);
110110

111-
private function __construct(private dict<arraykey, mixed> $schema, private self::TCodegenConfig $config) {}
111+
private ValidatorBuilder $builder;
112+
113+
private function __construct(private dict<arraykey, mixed> $schema, private self::TCodegenConfig $config) {
114+
$config = $this->config['validator'];
115+
116+
$builder = new ValidatorBuilder(
117+
$this->config,
118+
$this->getHackCodegenConfig(),
119+
$this->getJsonSchemaCodegenConfig(),
120+
$this->schema,
121+
);
122+
123+
$this->builder = $builder
124+
->setCreateAbstractClass(false)
125+
->setGeneratedFrom($this->getGeneratedFrom())
126+
->setDiscardChanges($this->config['discardChanges'] ?? false)
127+
->setSanitizeString($config['sanitize_string'] ?? null);
128+
}
112129

113130
/**
114131
* Generate validators for multiple schema paths. This requires using unique
@@ -212,25 +229,10 @@ private function getJsonSchemaCodegenConfig(): IJsonSchemaCodegenConfig {
212229
}
213230

214231
public function build(): CodegenFile {
215-
return $this->buildValidator();
232+
return $this->builder->build();
216233
}
217234

218-
private function buildValidator(): CodegenFile {
219-
$config = $this->config['validator'];
220-
221-
$builder = new ValidatorBuilder(
222-
$this->config,
223-
$this->getHackCodegenConfig(),
224-
$this->getJsonSchemaCodegenConfig(),
225-
$this->schema,
226-
);
227-
228-
return $builder
229-
->setCreateAbstractClass(false)
230-
->setGeneratedFrom($this->getGeneratedFrom())
231-
->setDiscardChanges($this->config['discardChanges'] ?? false)
232-
->setSanitizeString($config['sanitize_string'] ?? null)
233-
->renderToFile($config['file'], $config['namespace'] ?? null, $config['class']);
235+
public function getBuilder(): ValidatorBuilder {
236+
return $this->builder;
234237
}
235-
236238
}

src/Codegen/Constraints/ArrayBuilder.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Slack\Hack\JsonSchema\Codegen;
44

5-
use namespace HH\Lib\C;
65
use type Facebook\HackCodegen\{CodegenMethod, HackBuilder, HackBuilderValues};
76

87
type TArraySchema = shape(
@@ -266,11 +265,12 @@ private function setupSingleItemSchemaBuilder(): void {
266265
*/
267266
private function determineHackArrayType(): void {
268267
$items = $this->typed_schema['items'] ?? null;
269-
if (($this->schema['uniqueItems'] ?? false) && $this->isSchema($items)) {
270-
$schema = type_assert_type($items, TArraySchemaItemsSingleSchema::class);
271-
if (C\contains(keyset[TSchemaType::INTEGER_T, TSchemaType::STRING_T], $schema['type'] ?? null)) {
272-
$this->hackArrayType = HackArrayType::KEYSET;
273-
}
268+
if (
269+
Shapes::idx($this->typed_schema, 'uniqueItems') &&
270+
$this->isSchema($items) &&
271+
$this->singleItemSchemaBuilder?->isArrayKeyType()
272+
) {
273+
$this->hackArrayType = HackArrayType::KEYSET;
274274
}
275275
}
276276
}

src/Codegen/Constraints/BaseBuilder.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ public function __construct(
2929
*/
3030
abstract public function getType(): string;
3131

32+
public function isArrayKeyType(): bool {
33+
$type = $this->getType();
34+
if ($type === 'string' || $type === 'int') {
35+
return true;
36+
}
37+
38+
$schema = type_assert_type($this->typed_schema, TSchema::class);
39+
return Shapes::keyExists($schema, 'hackEnum');
40+
}
41+
3242
/**
3343
*
3444
* Main method for building the class for the schema and appending it to the

src/Codegen/Constraints/IBuilder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ interface IBuilder {
66
public function build(): this;
77
public function getClassName(): string;
88
public function getType(): string;
9+
public function isArrayKeyType(): bool;
910
public function setSuffix(string $suffix): void;
1011
}

src/Codegen/Constraints/RootBuilder.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,22 @@ public function getClassName(): string {
3131
return $this->root->getClassName();
3232
}
3333

34+
public function getClass(): CodegenClass {
35+
return $this->class;
36+
}
37+
38+
public function getFile(): CodegenFile {
39+
return $this->file;
40+
}
41+
3442
public function getType(): string {
3543
return $this->root->getType();
3644
}
3745

46+
public function isArrayKeyType(): bool {
47+
return $this->root->isArrayKeyType();
48+
}
49+
3850
public function setSuffix(string $_suffix): void {
3951
// noop because we don't use suffixes for the root builder
4052
}

src/Codegen/Constraints/SchemaBuilder.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ public function getType(): string {
152152
return $this->builder->getType();
153153
}
154154

155+
public function isArrayKeyType(): bool {
156+
return $this->builder->isArrayKeyType();
157+
}
158+
155159
public function getClassName(): string {
156160
return $this->builder->getClassName();
157161
}

src/Codegen/Constraints/UniqueRefBuilder.php

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Slack\Hack\JsonSchema\Codegen;
44

5-
use namespace HH\Lib\{Str, Vec};
5+
use namespace HH\Lib\Str;
66

77
use type Facebook\HackCodegen\CodegenMethod;
88

@@ -13,7 +13,7 @@ class UniqueRefBuilder implements IBuilder {
1313

1414
private string $classname;
1515
private string $filename;
16-
private ?string $type;
16+
private ?RootBuilder $builder;
1717

1818
public function __construct(protected Context $ctx, protected string $ref, protected TSchema $schema) {
1919
$this->classname = '';
@@ -62,33 +62,16 @@ public function build(): this {
6262
$codegen_config['validator']['refs'] = $refs_config;
6363
$codegen_config['validator']['context'] = $context_config;
6464

65-
$codegen = Codegen::forSchema(Shapes::toDict($this->schema), $codegen_config, $root_directory);
66-
67-
$codegen_file = null;
6865
if (RefCache::isRefCached($this->filename)) {
69-
$codegen_file = RefCache::getCachedFile($this->filename);
66+
$codegen = RefCache::getCachedCodegen($this->filename);
7067
} else {
71-
$codegen_file = $codegen->build();
68+
$codegen = Codegen::forSchema(Shapes::toDict($this->schema), $codegen_config, $root_directory);
69+
$codegen->build();
70+
RefCache::cacheRef($this->filename, $codegen);
7271
}
7372

74-
RefCache::cacheRef($this->filename, $codegen_file);
75-
76-
$main_class = Vec\filter($codegen_file->getClasses(), $class ==> $class->getName() === $this->classname)[0];
77-
78-
$methods = get_private_property(\get_class($main_class), 'methods', $main_class)
79-
|> type_assert_shape($$, '\Slack\Hack\JsonSchema\Codegen\TCodegenMethods');
80-
81-
$check_method = Vec\filter(
82-
$methods,
83-
$method ==> {
84-
$name = get_private_property(\get_class($method), 'name', $method);
85-
return $name === 'check';
86-
},
87-
)[0];
88-
89-
$return_type = get_private_property(\get_class($check_method), 'returnType', $check_method);
90-
$this->type = $return_type as string;
91-
73+
$validator_builder = $codegen->getBuilder();
74+
$this->builder = $validator_builder->getBuilder();
9275
return $this;
9376
}
9477

@@ -97,8 +80,13 @@ public function getClassName(): string {
9780
}
9881

9982
public function getType(): string {
100-
invariant($this->type is nonnull, 'must call `build` method before accessing type');
101-
return $this->type;
83+
invariant($this->builder is nonnull, 'must call `build` method before accessing type');
84+
return $this->builder->getType();
85+
}
86+
87+
public function isArrayKeyType(): bool {
88+
invariant($this->builder is nonnull, 'must call `build` method before accessing type');
89+
return $this->builder->isArrayKeyType();
10290
}
10391

10492
public function setSuffix(string $_suffix): void {

src/Codegen/RefCache.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
namespace Slack\Hack\JsonSchema\Codegen;
44

55
use namespace HH\Lib\C;
6-
use type Facebook\HackCodegen\CodegenFile;
76

87
final class RefCache {
98

10-
static private dict<string, CodegenFile> $generatedRefs = dict[];
9+
static private dict<string, Codegen> $generatedRefs = dict[];
1110

1211
//
1312
// Run this resetCache before using any other method
@@ -22,8 +21,8 @@ static public function resetCache(): void {
2221
// the full pathname of the file.
2322
//
2423

25-
static public function cacheRef(string $refName, CodegenFile $file): void {
26-
RefCache::$generatedRefs[$refName] = $file;
24+
static public function cacheRef(string $refName, Codegen $codegen): void {
25+
RefCache::$generatedRefs[$refName] = $codegen;
2726
}
2827

2928
//
@@ -36,11 +35,11 @@ static public function isRefCached(string $refName): bool {
3635

3736
//
3837
// Do not use this function unless you have already verified that a
39-
// CodegenFile exists for $refName. This function will not gracefully
38+
// Codegen exists for $refName. This function will not gracefully
4039
// handle the case when $refName is not found in the cache.
4140
//
4241

43-
static public function getCachedFile(string $refName): CodegenFile {
42+
static public function getCachedCodegen(string $refName): Codegen {
4443
return RefCache::$generatedRefs[$refName];
4544
}
4645
}

src/Codegen/ValidatorBuilder.php

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ final class ValidatorBuilder {
1919
private HackCodegenFactory $cg;
2020
private bool $discardChanges = false;
2121
private bool $createAbstract = false;
22+
private RootBuilder $builder;
2223
private ?Codegen::TSanitizeStringConfig $sanitizeString = null;
2324

2425
public function __construct(
@@ -28,6 +29,22 @@ public function __construct(
2829
private dict<arraykey, mixed> $schema,
2930
) {
3031
$this->cg = new HackCodegenFactory($hackCodegenConfig);
32+
33+
$config = $codegenConfig['validator'];
34+
35+
$class = $this->cg->codegenClass($config['class']);
36+
37+
$file = $this->cg
38+
->codegenFile($config['file'])
39+
->setFileType(CodegenFileType::HACK_STRICT)
40+
->useNamespace('Slack\Hack\JsonSchema');
41+
42+
if (Shapes::keyExists($config, 'namespace')) {
43+
$file->setNamespace($config['namespace']);
44+
}
45+
46+
$this->builder =
47+
new RootBuilder($this->codegenConfig, $this->cg, $this->jsonSchemaCodegenConfig, $this->schema, $class, $file);
3148
}
3249

3350
public function setCreateAbstractClass(bool $abstract): this {
@@ -36,12 +53,12 @@ public function setCreateAbstractClass(bool $abstract): this {
3653
}
3754

3855
public function setGeneratedFrom(CodegenGeneratedFrom $generated_from): this {
39-
$this->generatedFrom = $generated_from;
56+
$this->builder->getFile()->setGeneratedFrom($generated_from);
4057
return $this;
4158
}
4259

4360
public function setDiscardChanges(bool $discard): this {
44-
$this->discardChanges = $discard;
61+
$this->builder->getFile()->setDoClobber($discard);
4562
return $this;
4663
}
4764

@@ -50,59 +67,40 @@ public function setSanitizeString(?Codegen::TSanitizeStringConfig $sanitize_stri
5067
return $this;
5168
}
5269

53-
public function renderToFile(string $filename, ?string $namespace, string $classname): CodegenFile {
54-
$file = $this->getCodegenFile($filename, $namespace, $classname);
55-
$file->save();
56-
return $file;
57-
}
58-
59-
private function getCodegenFile(string $filename, ?string $namespace, string $classname): CodegenFile {
60-
$file = $this->cg
61-
->codegenFile($filename)
62-
->setDoClobber($this->discardChanges)
63-
->setFileType(CodegenFileType::HACK_STRICT)
64-
->setGeneratedFrom($this->generatedFrom ?? $this->cg->codegenGeneratedFromScript())
65-
->useNamespace('Slack\Hack\JsonSchema');
66-
67-
$this->buildCodegenClass($classname, $file);
68-
69-
if ($namespace !== null) {
70-
$file->setNamespace($namespace);
71-
}
72-
73-
return $file;
70+
public function getBuilder(): RootBuilder {
71+
return $this->builder;
7472
}
7573

76-
private function buildCodegenClass(string $classname, CodegenFile $file): void {
77-
$class = $this->cg->codegenClass($classname);
78-
79-
$root =
80-
new RootBuilder($this->codegenConfig, $this->cg, $this->jsonSchemaCodegenConfig, $this->schema, $class, $file);
81-
82-
$root->build();
74+
public function build(): CodegenFile {
75+
$this->builder->build();
8376

8477
$abstract = $this->createAbstract;
85-
$class
86-
->setExtends("JsonSchema\BaseValidator<{$root->getType()}>")
78+
$class = $this->builder
79+
->getClass()
80+
->setExtends("JsonSchema\BaseValidator<{$this->builder->getType()}>")
8781
->setIsAbstract($abstract)
8882
->setIsFinal(!$abstract)
89-
->addMethod($this->getCodegenClassProcessMethod($root));
83+
->addMethod($this->getCodegenClassProcessMethod());
9084

85+
$file = $this->builder->getFile();
9186
$file->addClass($class);
9287

9388
$contents = $file->render();
9489
if (Str\contains($contents, 'Constraints\\')) {
9590
$file->useNamespace('Slack\Hack\JsonSchema\Constraints');
9691
}
92+
93+
$file->save();
94+
return $file;
9795
}
9896

99-
private function getCodegenClassProcessMethod(RootBuilder $root): CodegenMethod {
97+
private function getCodegenClassProcessMethod(): CodegenMethod {
10098
$hb = new HackBuilder($this->hackCodegenConfig);
10199
$hb->addMultilineCall('return self::check', vec['$this->input', '$this->pointer']);
102100

103101
return $this->cg
104102
->codegenMethod('process')
105-
->setReturnType($root->getType())
103+
->setReturnType($this->builder->getType())
106104
->addEmptyUserAttribute('__Override')
107105
->setProtected()
108106
->setBody($hb->getCode());

0 commit comments

Comments
 (0)