Skip to content

Commit 99b251b

Browse files
authored
Merge pull request #65 from slackhq/ih_more_refactor
Refactor codegen; support top-level refs
2 parents cc6709e + 6dc30f0 commit 99b251b

File tree

10 files changed

+133
-205
lines changed

10 files changed

+133
-205
lines changed

src/Codegen/Codegen.php

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@
22

33
namespace Slack\Hack\JsonSchema\Codegen;
44

5-
use namespace HH\Lib\Vec;
5+
use namespace HH\Lib\{Str, Vec};
66
use type Facebook\HackCodegen\{
7+
CodegenClass,
78
CodegenFile,
9+
CodegenFileType,
810
CodegenGeneratedFrom,
11+
CodegenMethod,
12+
HackBuilder,
913
HackCodegenConfig,
1014
HackCodegenFactory,
1115
IHackCodegenConfig,
1216
};
1317

14-
final class Codegen {
18+
final class Codegen implements IBuilder {
1519

1620
const type TValidatorRefsUniqueConfig = shape(
1721
/* The root path where all of the schemas live */
@@ -108,23 +112,45 @@ final class Codegen {
108112
),
109113
);
110114

111-
private ValidatorBuilder $builder;
115+
private ?CodegenGeneratedFrom $generatedFrom;
116+
private HackCodegenFactory $cg;
117+
private bool $discardChanges = false;
118+
private bool $createAbstract = false;
119+
private SchemaBuilder $builder;
120+
private CodegenClass $class;
121+
private CodegenFile $file;
122+
private ?Codegen::TSanitizeStringConfig $sanitizeString = null;
112123

113124
private function __construct(private dict<arraykey, mixed> $schema, private self::TCodegenConfig $config) {
114-
$config = $this->config['validator'];
125+
$this->cg = new HackCodegenFactory($this->getHackCodegenConfig());
115126

116-
$builder = new ValidatorBuilder(
127+
$validatorConfig = $config['validator'];
128+
129+
$this->class = $this->cg->codegenClass($validatorConfig['class']);
130+
131+
$this->file = $this->cg
132+
->codegenFile($validatorConfig['file'])
133+
->setFileType(CodegenFileType::HACK_STRICT)
134+
->setGeneratedFrom($this->getGeneratedFrom())
135+
->setDoClobber($config['discardChanges'] ?? false)
136+
->useNamespace('Slack\Hack\JsonSchema');
137+
138+
if (Shapes::keyExists($validatorConfig, 'namespace')) {
139+
$this->file->setNamespace($validatorConfig['namespace']);
140+
}
141+
142+
$context = new Context(
117143
$this->config,
118-
$this->getHackCodegenConfig(),
144+
$this->cg,
119145
$this->getJsonSchemaCodegenConfig(),
120146
$this->schema,
147+
$this->class->getName(),
148+
$this->file,
121149
);
150+
$typed_schema = type_assert_type($this->schema, TSchema::class);
151+
$this->builder = new SchemaBuilder($context, '', $typed_schema, $this->class);
122152

123-
$this->builder = $builder
124-
->setCreateAbstractClass(false)
125-
->setGeneratedFrom($this->getGeneratedFrom())
126-
->setDiscardChanges($this->config['discardChanges'] ?? false)
127-
->setSanitizeString($config['sanitize_string'] ?? null);
153+
$this->sanitizeString = $validatorConfig['sanitize_string'] ?? null;
128154
}
129155

130156
/**
@@ -228,11 +254,64 @@ private function getJsonSchemaCodegenConfig(): IJsonSchemaCodegenConfig {
228254
return $this->config['jsonSchemaCodegenConfig'] ?? new JsonSchemaCodegenConfig();
229255
}
230256

231-
public function build(): CodegenFile {
232-
return $this->builder->build();
257+
public function build(): this {
258+
$this->builder->build();
259+
260+
if ($this->builder->isUniqueRef()) {
261+
// No need to do anything, we're building a top-level ref.
262+
return $this;
263+
}
264+
265+
$this->class = $this->class
266+
->setExtends("JsonSchema\BaseValidator<{$this->builder->getType()}>")
267+
->setIsAbstract(false)
268+
->setIsFinal(true)
269+
->addMethod($this->getCodegenClassProcessMethod());
270+
271+
$this->file->addClass($this->class);
272+
273+
$contents = $this->file->render();
274+
if (Str\contains($contents, 'Constraints\\')) {
275+
$this->file->useNamespace('Slack\Hack\JsonSchema\Constraints');
276+
}
277+
278+
$this->file->save();
279+
280+
return $this;
281+
}
282+
283+
private function getCodegenClassProcessMethod(): CodegenMethod {
284+
$hb = new HackBuilder($this->getHackCodegenConfig());
285+
$hb->addMultilineCall('return self::check', vec['$this->input', '$this->pointer']);
286+
287+
return $this->cg
288+
->codegenMethod('process')
289+
->setReturnType($this->builder->getType())
290+
->addEmptyUserAttribute('__Override')
291+
->setProtected()
292+
->setBody($hb->getCode());
293+
}
294+
295+
public function getClass(): CodegenClass {
296+
return $this->class;
297+
}
298+
299+
public function getFile(): CodegenFile {
300+
return $this->file;
301+
}
302+
303+
public function getClassName(): string {
304+
return $this->builder->getClassName();
305+
}
306+
307+
public function getType(): string {
308+
return $this->builder->getType();
309+
}
310+
311+
public function isArrayKeyType(): bool {
312+
return $this->builder->isArrayKeyType();
233313
}
234314

235-
public function getBuilder(): ValidatorBuilder {
236-
return $this->builder;
315+
public function setSuffix(string $_suffix): void {
237316
}
238317
}

src/Codegen/Constraints/ArrayBuilder.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,7 @@ private function setupSingleItemSchemaBuilder(): void {
264264
* Determine the type of hack array we will generate.
265265
*/
266266
private function determineHackArrayType(): void {
267-
$items = $this->typed_schema['items'] ?? null;
268-
if (
269-
Shapes::idx($this->typed_schema, 'uniqueItems') &&
270-
$this->isSchema($items) &&
271-
$this->singleItemSchemaBuilder?->isArrayKeyType()
272-
) {
267+
if (Shapes::idx($this->typed_schema, 'uniqueItems') && $this->singleItemSchemaBuilder?->isArrayKeyType()) {
273268
$this->hackArrayType = HackArrayType::KEYSET;
274269
}
275270
}

src/Codegen/Constraints/IBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
namespace Slack\Hack\JsonSchema\Codegen;
44

55
interface IBuilder {
6-
public function build(): this;
76
public function getClassName(): string;
87
public function getType(): string;
98
public function isArrayKeyType(): bool;
9+
public function build(): this;
1010
public function setSuffix(string $suffix): void;
1111
}

src/Codegen/Constraints/RootBuilder.php

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

src/Codegen/Constraints/SchemaBuilder.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,8 @@ public function getResolvedContext(): Context {
175175
public function setSuffix(string $suffix): void {
176176
$this->builder->setSuffix($suffix);
177177
}
178+
179+
public function isUniqueRef(): bool {
180+
return $this->getBuilder() is UniqueRefBuilder;
181+
}
178182
}

src/Codegen/Constraints/UniqueRefBuilder.php

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class UniqueRefBuilder implements IBuilder {
1313

1414
private string $classname;
1515
private string $filename;
16-
private ?RootBuilder $builder;
16+
private ?RefCache::TCachedRef $cached_ref;
1717

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

6565
if (RefCache::isRefCached($this->filename)) {
66-
$codegen = RefCache::getCachedCodegen($this->filename);
66+
$this->cached_ref = RefCache::getCachedRef($this->filename);
6767
} else {
6868
$codegen = Codegen::forSchema(Shapes::toDict($this->schema), $codegen_config, $root_directory);
6969
$codegen->build();
70-
RefCache::cacheRef($this->filename, $codegen);
70+
$this->cached_ref = shape(
71+
'type' => $codegen->getType(),
72+
'classname' => $codegen->getClassName(),
73+
'isArrayKeyType' => $codegen->isArrayKeyType(),
74+
);
75+
RefCache::cacheRef($this->filename, $this->cached_ref);
7176
}
7277

73-
$validator_builder = $codegen->getBuilder();
74-
$this->builder = $validator_builder->getBuilder();
7578
return $this;
7679
}
7780

@@ -80,13 +83,13 @@ public function getClassName(): string {
8083
}
8184

8285
public function getType(): string {
83-
invariant($this->builder is nonnull, 'must call `build` method before accessing type');
84-
return $this->builder->getType();
86+
invariant($this->cached_ref is nonnull, 'must call `build` method before accessing type');
87+
return $this->cached_ref['type'];
8588
}
8689

8790
public function isArrayKeyType(): bool {
88-
invariant($this->builder is nonnull, 'must call `build` method before accessing type');
89-
return $this->builder->isArrayKeyType();
91+
invariant($this->cached_ref is nonnull, 'must call `build` method before accessing type');
92+
return $this->cached_ref['isArrayKeyType'];
9093
}
9194

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

src/Codegen/RefCache.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@
55
use namespace HH\Lib\C;
66

77
final class RefCache {
8+
const type TCachedRef = shape(
9+
'type' => string,
10+
'classname' => string,
11+
'isArrayKeyType' => bool,
12+
);
813

9-
static private dict<string, Codegen> $generatedRefs = dict[];
14+
static private dict<string, self::TCachedRef> $generatedRefs = dict[];
1015

1116
//
1217
// Run this resetCache before using any other method
@@ -17,12 +22,12 @@ static public function resetCache(): void {
1722
}
1823

1924
//
20-
// Cache the ref by passing in a CodegenFile object. The $refName should be
25+
// Cache the ref by passing in a Cached Ref. The $refName should be
2126
// the full pathname of the file.
2227
//
2328

24-
static public function cacheRef(string $refName, Codegen $codegen): void {
25-
RefCache::$generatedRefs[$refName] = $codegen;
29+
static public function cacheRef(string $refName, self::TCachedRef $ref): void {
30+
RefCache::$generatedRefs[$refName] = $ref;
2631
}
2732

2833
//
@@ -35,11 +40,11 @@ static public function isRefCached(string $refName): bool {
3540

3641
//
3742
// Do not use this function unless you have already verified that a
38-
// Codegen exists for $refName. This function will not gracefully
43+
// Cached Ref exists for $refName. This function will not gracefully
3944
// handle the case when $refName is not found in the cache.
4045
//
4146

42-
static public function getCachedCodegen(string $refName): Codegen {
47+
static public function getCachedRef(string $refName): this::TCachedRef {
4348
return RefCache::$generatedRefs[$refName];
4449
}
4550
}

0 commit comments

Comments
 (0)