|
2 | 2 |
|
3 | 3 | namespace Slack\Hack\JsonSchema\Codegen; |
4 | 4 |
|
5 | | -use namespace HH\Lib\Vec; |
| 5 | +use namespace HH\Lib\{Str, Vec}; |
6 | 6 | use type Facebook\HackCodegen\{ |
| 7 | + CodegenClass, |
7 | 8 | CodegenFile, |
| 9 | + CodegenFileType, |
8 | 10 | CodegenGeneratedFrom, |
| 11 | + CodegenMethod, |
| 12 | + HackBuilder, |
9 | 13 | HackCodegenConfig, |
10 | 14 | HackCodegenFactory, |
11 | 15 | IHackCodegenConfig, |
12 | 16 | }; |
13 | 17 |
|
14 | | -final class Codegen { |
| 18 | +final class Codegen implements IBuilder { |
15 | 19 |
|
16 | 20 | const type TValidatorRefsUniqueConfig = shape( |
17 | 21 | /* The root path where all of the schemas live */ |
@@ -108,23 +112,45 @@ final class Codegen { |
108 | 112 | ), |
109 | 113 | ); |
110 | 114 |
|
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; |
112 | 123 |
|
113 | 124 | private function __construct(private dict<arraykey, mixed> $schema, private self::TCodegenConfig $config) { |
114 | | - $config = $this->config['validator']; |
| 125 | + $this->cg = new HackCodegenFactory($this->getHackCodegenConfig()); |
115 | 126 |
|
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( |
117 | 143 | $this->config, |
118 | | - $this->getHackCodegenConfig(), |
| 144 | + $this->cg, |
119 | 145 | $this->getJsonSchemaCodegenConfig(), |
120 | 146 | $this->schema, |
| 147 | + $this->class->getName(), |
| 148 | + $this->file, |
121 | 149 | ); |
| 150 | + $typed_schema = type_assert_type($this->schema, TSchema::class); |
| 151 | + $this->builder = new SchemaBuilder($context, '', $typed_schema, $this->class); |
122 | 152 |
|
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; |
128 | 154 | } |
129 | 155 |
|
130 | 156 | /** |
@@ -228,11 +254,64 @@ private function getJsonSchemaCodegenConfig(): IJsonSchemaCodegenConfig { |
228 | 254 | return $this->config['jsonSchemaCodegenConfig'] ?? new JsonSchemaCodegenConfig(); |
229 | 255 | } |
230 | 256 |
|
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(); |
233 | 313 | } |
234 | 314 |
|
235 | | - public function getBuilder(): ValidatorBuilder { |
236 | | - return $this->builder; |
| 315 | + public function setSuffix(string $_suffix): void { |
237 | 316 | } |
238 | 317 | } |
0 commit comments