From 7ed70ef925aa0c2e2a79f715a8e2767b3cc626f9 Mon Sep 17 00:00:00 2001 From: kafkiansky Date: Sun, 15 Feb 2026 15:53:05 +0300 Subject: [PATCH 1/2] Implement `Descriptor Pool` generation --- bin/compiler.php | 9 +- src/Entrypoint.php | 16 +- src/Plugin/AutoloadFunctionGenerator.php | 40 ++ src/Plugin/ClassLikeGenerator.php | 85 ++++ src/Plugin/Compiler.php | 67 +++- src/Plugin/Generator.php | 96 ----- .../DescriptorMetadataRegistryGenerator.php | 82 ++++ src/Plugin/Generator/FileFactory.php | 59 +++ src/Plugin/Generator/ProtoGenerator.php | 11 +- src/Plugin/NameIndex.php | 38 ++ src/Plugin/Naming.php | 10 + src/Plugin/Parser.php | 7 +- src/Plugin/Parser/EnumDescriptor.php | 1 + src/Plugin/Parser/MessageDescriptor.php | 1 + src/Plugin/PathTable.php | 75 ++++ src/ProtobufEncoder.php | 50 +++ tests/CompilerTest.php | 363 +++++++++++++++++- tests/Plugin/PathTableTest.php | 53 +++ 18 files changed, 931 insertions(+), 132 deletions(-) create mode 100644 src/Plugin/AutoloadFunctionGenerator.php create mode 100644 src/Plugin/ClassLikeGenerator.php delete mode 100644 src/Plugin/Generator.php create mode 100644 src/Plugin/Generator/DescriptorMetadataRegistryGenerator.php create mode 100644 src/Plugin/Generator/FileFactory.php create mode 100644 src/Plugin/NameIndex.php create mode 100644 src/Plugin/PathTable.php create mode 100644 src/ProtobufEncoder.php create mode 100644 tests/Plugin/PathTableTest.php diff --git a/bin/compiler.php b/bin/compiler.php index 5d2d19e..04b71c3 100755 --- a/bin/compiler.php +++ b/bin/compiler.php @@ -5,16 +5,15 @@ require_once __DIR__ . '/../vendor/autoload.php'; -use Thesis\Protobuf; -use Thesis\Protobuf\Reflection; use Thesis\Protoc; use Thesis\Protoc\Io; use Thesis\Protoc\Plugin; +$protobuf = Protoc\ProtobufEncoder::default(); + $entrypoint = new Protoc\Entrypoint( - new Plugin\Compiler(), - new Protobuf\Serializer(), - Reflection\Reflector::build(), + new Plugin\Compiler($protobuf), + $protobuf, ); $entrypoint->run( diff --git a/src/Entrypoint.php b/src/Entrypoint.php index 174eb33..7054f5b 100644 --- a/src/Entrypoint.php +++ b/src/Entrypoint.php @@ -6,8 +6,6 @@ use Google\Protobuf\Compiler\CodeGeneratorRequest; use Google\Protobuf\Compiler\CodeGeneratorResponse; -use Thesis\Protobuf; -use Thesis\Protobuf\Reflection; /** * @api @@ -16,8 +14,7 @@ { public function __construct( private Plugin\Compiler $compiler, - private Protobuf\Serializer $serializer, - private Reflection\Reflector $reflector, + private ProtobufEncoder $protobuf, ) {} public function run( @@ -25,11 +22,8 @@ public function run( WriteOutput $output, ): void { try { - $request = $this->reflector->map( - $this->serializer->deserialize( - $this->reflector->type(CodeGeneratorRequest::class), - $input->read(), - ), + $request = $this->protobuf->decode( + $input->read(), CodeGeneratorRequest::class, ); @@ -44,9 +38,7 @@ public function run( ); } - $buffer = $this->serializer->serialize( - $this->reflector->message($response), - ); + $buffer = $this->protobuf->encode($response); if ($buffer !== '') { $output->write($buffer); diff --git a/src/Plugin/AutoloadFunctionGenerator.php b/src/Plugin/AutoloadFunctionGenerator.php new file mode 100644 index 0000000..d0c7880 --- /dev/null +++ b/src/Plugin/AutoloadFunctionGenerator.php @@ -0,0 +1,40 @@ + $descriptors + */ + public function generateAutoloadFile(array $descriptors): CodeGeneratorResponse\File + { + return $this->files->create( + code: \sprintf( + <<<'PHP' +\Thesis\Protobuf\Pool\Registry::get()->register( +%s, +); + +PHP, + implode(",\n", array_map( + static fn(string $descriptor) => " new \\Thesis\\Protobuf\\Pool\\OnceRegistrar(new \\{$descriptor}())", + $descriptors, + )), + ), + path: 'autoload.metadata', + ); + } +} diff --git a/src/Plugin/ClassLikeGenerator.php b/src/Plugin/ClassLikeGenerator.php new file mode 100644 index 0000000..d3b26dc --- /dev/null +++ b/src/Plugin/ClassLikeGenerator.php @@ -0,0 +1,85 @@ +grpc = new Generator\GrpcGenerator( + $namespacer, + $graph, + $package, + ); + $this->proto = new Generator\ProtoGenerator( + $graph, + $index, + $namespacer, + $syntax, + ); + $this->metadata = new Generator\DescriptorMetadataRegistryGenerator( + $namespacer, + ); + } + + /** + * @return iterable + */ + public function generateMessages(Parser\MessageDescriptor $message): iterable + { + foreach ($this->proto->generateMessages($message) as $path => $namespace) { + yield $this->files->create($namespace, $path); + } + } + + public function generateEnum(Parser\EnumDescriptor $enum): CodeGeneratorResponse\File + { + return $this->files->create($this->proto->generateEnum($enum), $enum->path); + } + + public function generateGrpcClient(Parser\ServiceDescriptor $service): CodeGeneratorResponse\File + { + return $this->files->create($this->grpc->generateClient($service), "{$service->path}Client"); + } + + /** + * @return iterable + */ + public function generateGrpcServer(Parser\ServiceDescriptor $service): iterable + { + yield $this->files->create($this->grpc->generateServer($service), "{$service->path}Server"); + + if ($service->methods !== []) { + yield $this->files->create($this->grpc->generateServerRegistry($service), "{$service->path}ServerRegistry"); + } + } + + public function generateDescriptorMetadataRegistry( + NameIndex $index, + string $descriptorName, + string $buffer, + ): CodeGeneratorResponse\File { + return $this->files->create($this->metadata->generate($index, $descriptorName, $buffer), $descriptorName); + } +} diff --git a/src/Plugin/Compiler.php b/src/Plugin/Compiler.php index 5f91193..875bd70 100644 --- a/src/Plugin/Compiler.php +++ b/src/Plugin/Compiler.php @@ -9,8 +9,10 @@ use Google\Protobuf\Compiler\CodeGeneratorResponse; use Thesis\Package; use Thesis\Protoc\Exception\CodeCannotBeGenerated; +use Thesis\Protoc\Plugin\Generator\FileFactory; use Thesis\Protoc\Plugin\Parser\FileDescriptor; use Thesis\Protoc\Plugin\Parser\MessageDescriptor; +use Thesis\Protoc\ProtobufEncoder; use Thesis\Protoc\ProtocException; /** @@ -24,8 +26,9 @@ private Parser $parser; - public function __construct() - { + public function __construct( + private ProtobufEncoder $protobuf, + ) { $this->parser = new Parser(); } @@ -58,14 +61,21 @@ private function doGenerate( $registry = new Dependency\Registry($request); + $descriptorPaths = new PathTable(); + foreach ($request as $source => $proto) { $phpNamespace = self::determinePhpNamespace($proto, $options); - $generator = new Generator( + $index = new NameIndex(); + + $generator = new ClassLikeGenerator( namespace: $phpNamespace, - path: $options->srcPath ?? str_replace('\\', '/', $phpNamespace), - generatedDoc: self::createGeneratedDoc($request, $source), + files: new FileFactory( + self::createClassLikeGeneratedDoc($request, $source), + $path = $options->srcPath ?? str_replace('\\', '/', $phpNamespace), + ), graph: $registry->graph($source), + index: $index, package: $proto->package, syntax: $proto->syntax, ); @@ -85,13 +95,36 @@ private function doGenerate( foreach ($proto->messages as $descriptor) { yield from $this->doGenerateMessages($generator, $descriptor); } + + if (!$index->empty()) { + $descriptorName = Naming::descriptorName($source); + + yield $generator->generateDescriptorMetadataRegistry( + $index, + $descriptorName, + $this->protobuf->encode($proto->file), + ); + + $descriptorPaths->addRelation("{$phpNamespace}\\{$descriptorName}", $path); + } + } + + foreach ($descriptorPaths->groupByNamespace() as $ns => $descriptors) { + $generator = new AutoloadFunctionGenerator( + new FileFactory( + self::createAutoloadFileGeneratedDoc($request), + $options->srcPath ?? str_replace('\\', '/', $ns), + ), + ); + + yield $generator->generateAutoloadFile($descriptors); } } /** * @return iterable */ - private function doGenerateMessages(Generator $generator, MessageDescriptor $descriptor): iterable + private function doGenerateMessages(ClassLikeGenerator $generator, MessageDescriptor $descriptor): iterable { if (!($descriptor->options?->mapEntry === true)) { yield from $generator->generateMessages($descriptor); @@ -135,7 +168,7 @@ private static function determinePhpNamespace( /** * @return non-empty-string */ - private static function createGeneratedDoc( + private static function createClassLikeGeneratedDoc( Parser\Request $request, string $source, ): string { @@ -153,6 +186,26 @@ private static function createGeneratedDoc( ); } + /** + * @return non-empty-string + */ + private static function createAutoloadFileGeneratedDoc( + Parser\Request $request, + ): string { + return \sprintf( + <<<'DOC' +/** + * Code generated by thesis/protoc-plugin. DO NOT EDIT. + * Versions: + * thesis/protoc-plugin — v%s + * protoc — v%s + */ +DOC, + Package\version(self::PLUGIN_NAME), + self::createCompilerVersion($request), + ); + } + private static function createCompilerVersion(Parser\Request $request): string { $compilerVersion = $request->request->compilerVersion; diff --git a/src/Plugin/Generator.php b/src/Plugin/Generator.php deleted file mode 100644 index bfba3f1..0000000 --- a/src/Plugin/Generator.php +++ /dev/null @@ -1,96 +0,0 @@ -printer = new Printer()->setTypeResolving(false); - $this->namespacer = new Generator\PhpNamespacer($namespace); - $this->grpc = new Generator\GrpcGenerator( - $this->namespacer, - $graph, - $package, - ); - $this->proto = new Generator\ProtoGenerator( - $graph, - $this->namespacer, - $syntax, - ); - } - - /** - * @return iterable - */ - public function generateMessages(Parser\MessageDescriptor $message): iterable - { - foreach ($this->proto->generateMessages($message) as $path => $namespace) { - yield $this->createFile($namespace, $path); - } - } - - public function generateEnum(Parser\EnumDescriptor $enum): CodeGeneratorResponse\File - { - return $this->createFile($this->proto->generateEnum($enum), $enum->path); - } - - public function generateGrpcClient(Parser\ServiceDescriptor $service): CodeGeneratorResponse\File - { - return $this->createFile($this->grpc->generateClient($service), "{$service->path}Client"); - } - - /** - * @return iterable - */ - public function generateGrpcServer(Parser\ServiceDescriptor $service): iterable - { - yield $this->createFile($this->grpc->generateServer($service), "{$service->path}Server"); - - if ($service->methods !== []) { - yield $this->createFile($this->grpc->generateServerRegistry($service), "{$service->path}ServerRegistry"); - } - } - - private function createFile( - PhpNamespace $namespace, - string $path, - ): CodeGeneratorResponse\File { - $file = new PhpFile() - ->setStrictTypes() - ->setComment($this->generatedDoc) - ->add($namespace); - - return new CodeGeneratorResponse\File( - name: \sprintf('%s/%s.php', $this->path, Naming::path($path)), - content: $this->printer->printFile($file), - ); - } -} diff --git a/src/Plugin/Generator/DescriptorMetadataRegistryGenerator.php b/src/Plugin/Generator/DescriptorMetadataRegistryGenerator.php new file mode 100644 index 0000000..57867b9 --- /dev/null +++ b/src/Plugin/Generator/DescriptorMetadataRegistryGenerator.php @@ -0,0 +1,82 @@ +namespacer->create($className); + + $classType = new ClassType($className) + ->setFinal() + ->setReadOnly() + ->addComment('@api') + ->setImplements([ + 'Pool\Registrar', + ]) + ->addMember( + new Constant('DESCRIPTOR_BUFFER') + ->setPrivate() + ->setType('string') + ->setValue(base64_encode($buffer)), + ); + + $namespace->add($classType); + + $namespace->addUse('Thesis\Protobuf\Pool'); + $namespace->addUse(\Override::class); + + $classType + ->addMethod('register') + ->setPublic() + ->setParameters([ + new Parameter('pool')->setType('Pool\Registry'), + ]) + ->setReturnType('void') + ->addAttribute(\Override::class) + ->setBody( + \sprintf( + <<<'PHP' +$pool->add(Pool\Descriptor::base64(self::DESCRIPTOR_BUFFER), [ + %s, +]); +PHP, + implode(",\n\t", array_fill(0, \count($index), '?')), + ), + array_merge( + array_map( + static fn(string $type, string $fqcn) => new Literal(\sprintf("'%s' => new Pool\\MessageMetadata(\\%s::class)", $type, $fqcn)), + array_keys($index->messageTypes), + array_values($index->messageTypes), + ), + array_map( + static fn(string $type, string $fqcn) => new Literal(\sprintf("'%s' => new Pool\\EnumMetadata(\\%s::class)", $type, $fqcn)), + array_keys($index->enumTypes), + array_values($index->enumTypes), + ), + ), + ); + + return $namespace; + } +} diff --git a/src/Plugin/Generator/FileFactory.php b/src/Plugin/Generator/FileFactory.php new file mode 100644 index 0000000..37daa0c --- /dev/null +++ b/src/Plugin/Generator/FileFactory.php @@ -0,0 +1,59 @@ +printer = new Printer()->setTypeResolving(false); + } + + public function create( + PhpNamespace|string $code, + string $path, + ): CodeGeneratorResponse\File { + $content = match (true) { + $code instanceof PhpNamespace => $this->printer->printFile( + new PhpFile() + ->setStrictTypes() + ->setComment($this->generatedDoc) + ->add($code), + ), + default => \sprintf( + <<<'PHP' +generatedDoc, + $code, + ), + }; + + return new CodeGeneratorResponse\File( + name: \sprintf('%s/%s.php', $this->path, $code instanceof PhpNamespace ? Naming::path($path) : $path), + content: $content, + ); + } +} diff --git a/src/Plugin/Generator/ProtoGenerator.php b/src/Plugin/Generator/ProtoGenerator.php index 61ccb09..4ea8e88 100644 --- a/src/Plugin/Generator/ProtoGenerator.php +++ b/src/Plugin/Generator/ProtoGenerator.php @@ -12,6 +12,7 @@ use Nette\PhpGenerator\Literal; use Nette\PhpGenerator\PhpNamespace; use Thesis\Protoc\Plugin\Dependency; +use Thesis\Protoc\Plugin\NameIndex; use Thesis\Protoc\Plugin\Naming; use Thesis\Protoc\Plugin\Parser; @@ -24,6 +25,7 @@ public function __construct( Dependency\Graph $graph, + private NameIndex $index, private PhpNamespacer $namespacer, private ?string $syntax = null, ) { @@ -34,7 +36,11 @@ public function generateEnum(Parser\EnumDescriptor $enum): PhpNamespace { $namespace = $this->namespacer->create($enum->path); - $enumType = new EnumType(Naming::pascalCase($enum->name)) + $enumName = Naming::pascalCase($enum->name); + + $this->index->addEnumType($enum->fqcn, "{$namespace->getName()}\\{$enumName}"); + + $enumType = new EnumType($enumName) ->addComment('@api') ->addComment($enum->comment !== null ? "\n{$enum->comment}" : '') ->setType('int') @@ -93,6 +99,8 @@ private function generateMessage(Parser\MessageDescriptor $message, array $imple $className = Naming::pascalCase($message->name); + $this->index->addMessageType($message->fqcn, "{$namespace->getName()}\\{$className}"); + $classType = new ClassType($className) ->setFinal() ->setReadOnly() @@ -278,6 +286,7 @@ private function generateOneofVariant( $descriptor = new Parser\MessageDescriptor( name: $className, + fqcn: $message->fqcn, path: $path = "{$message->path}.{$className}", fields: [ // Remove oneof index. diff --git a/src/Plugin/NameIndex.php b/src/Plugin/NameIndex.php new file mode 100644 index 0000000..3516563 --- /dev/null +++ b/src/Plugin/NameIndex.php @@ -0,0 +1,38 @@ + */ + public private(set) array $messageTypes = []; + + /** @var array */ + public private(set) array $enumTypes = []; + + public function addMessageType(string $type, string $fqcn): void + { + $this->messageTypes[$type] = $fqcn; + } + + public function addEnumType(string $type, string $fqcn): void + { + $this->enumTypes[$type] = $fqcn; + } + + public function empty(): bool + { + return $this->messageTypes === [] && $this->enumTypes === []; + } + + #[\Override] + public function count(): int + { + return \count($this->messageTypes) + \count($this->enumTypes); + } +} diff --git a/src/Plugin/Naming.php b/src/Plugin/Naming.php index ee1dd70..eefbe64 100644 --- a/src/Plugin/Naming.php +++ b/src/Plugin/Naming.php @@ -102,6 +102,16 @@ enum Naming 'class' => 1, ]; + public static function descriptorName(string $file): string + { + $info = pathinfo($file); + $dir = $info['dirname'] ?? '.'; + $filename = $info['filename']; + $name = Naming::pascalCase(($dir !== '.' ? (str_replace('/', ' ', $dir) . ' ') : '') . $filename); + + return "{$name}DescriptorRegistry"; + } + public static function camelCase(string $name): string { return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $name)))); diff --git a/src/Plugin/Parser.php b/src/Plugin/Parser.php index 2bfe3a7..e93280e 100644 --- a/src/Plugin/Parser.php +++ b/src/Plugin/Parser.php @@ -45,7 +45,7 @@ private static function parseFileDescriptor(FileDescriptorProto $descriptor, str name: $name, file: $descriptor, messages: self::parseMessages($descriptor, $descriptor->messageType, $comments), - enums: self::parseEnums($descriptor->enumType, $comments), + enums: self::parseEnums($descriptor, $descriptor->enumType, $comments), services: self::parseServices($descriptor->service, $comments), dependencies: $descriptor->dependency, package: $descriptor->package, @@ -95,9 +95,10 @@ public static function parseMessages( $messages[] = new Parser\MessageDescriptor( name: $descriptor->name, + fqcn: $file->package !== null ? "{$file->package}.{$path}" : $path, path: $path, fields: self::parseMessageFields($descriptor, $file, $descriptor->field, $messageComments, $path), - enums: self::parseEnums($descriptor->enumType, $messageComments, $path), + enums: self::parseEnums($file, $descriptor->enumType, $messageComments, $path), messages: self::parseMessages($file, $descriptor->nestedType, $messageComments, $path), oneofs: $oneofs, comment: $messageComments->extract(), @@ -164,6 +165,7 @@ private static function parseMessageFields( * @return list */ private static function parseEnums( + FileDescriptorProto $file, array $descriptors, CommentExtractor $comments, ?string $parent = null, @@ -185,6 +187,7 @@ private static function parseEnums( $enums[] = new Parser\EnumDescriptor( $descriptor->name, + $file->package !== null ? "{$file->package}.{$path}" : $path, $path, self::parseEnumCases( $descriptor->value, diff --git a/src/Plugin/Parser/EnumDescriptor.php b/src/Plugin/Parser/EnumDescriptor.php index 5162db4..4e3832d 100644 --- a/src/Plugin/Parser/EnumDescriptor.php +++ b/src/Plugin/Parser/EnumDescriptor.php @@ -17,6 +17,7 @@ */ public function __construct( public string $name, + public string $fqcn, public string $path, public array $cases, public ?Comment $comment = null, diff --git a/src/Plugin/Parser/MessageDescriptor.php b/src/Plugin/Parser/MessageDescriptor.php index 8e62344..9525c68 100644 --- a/src/Plugin/Parser/MessageDescriptor.php +++ b/src/Plugin/Parser/MessageDescriptor.php @@ -20,6 +20,7 @@ */ public function __construct( public string $name, + public string $fqcn, public string $path, public array $fields = [], public array $enums = [], diff --git a/src/Plugin/PathTable.php b/src/Plugin/PathTable.php new file mode 100644 index 0000000..30eee4e --- /dev/null +++ b/src/Plugin/PathTable.php @@ -0,0 +1,75 @@ + */ + private array $paths = []; + + public function addRelation(string $className, string $path): void + { + $this->paths[$className] = $path; + } + + /** + * @return array> + */ + public function groupByNamespace(): array + { + $groups = []; + + foreach ($this->paths as $fqcn => $namespace) { + $ns = explode('/', $namespace)[0]; + + $groups[$ns]['types'][] = $fqcn; + $groups[$ns]['ns'][] = $namespace; + } + + $dict = []; + + foreach ($groups as $group) { + $ns = $this->lookupNamespace($group['ns']); + $dict[$ns] = $group['types']; + } + + return $dict; + } + + /** + * @param list $namespaces + */ + private function lookupNamespace(array $namespaces): string + { + if ($namespaces === []) { + return ''; + } + + $parts = array_map( + static fn(string $ns) => explode('/', $ns), + $namespaces, + ); + $min = min(array_map(\count(...), $parts)); + + $common = []; + + for ($i = 0; $i < $min; ++$i) { + $current = $parts[0][$i] ?? ''; + + foreach ($parts as $ns) { + if (($ns[$i] ?? '') !== $current) { + return implode('\\', $common); + } + } + + $common[] = $current; + } + + return implode('\\', $common); + } +} diff --git a/src/ProtobufEncoder.php b/src/ProtobufEncoder.php new file mode 100644 index 0000000..8dcc9f7 --- /dev/null +++ b/src/ProtobufEncoder.php @@ -0,0 +1,50 @@ +serializer->serialize( + $this->reflector->message($message), + ); + } + + /** + * @template T of object + * @param class-string $classType + * @return T + */ + public function decode(string $buffer, string $classType): object + { + return $this->reflector->map( + $this->serializer->deserialize( + $this->reflector->type($classType), + $buffer, + ), + $classType, + ); + } +} diff --git a/tests/CompilerTest.php b/tests/CompilerTest.php index 46e6b0b..8f95b48 100644 --- a/tests/CompilerTest.php +++ b/tests/CompilerTest.php @@ -11,8 +11,6 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Thesis\Package; -use Thesis\Protobuf\Reflection\Reflector; -use Thesis\Protobuf\Serializer; use Thesis\Protoc\Plugin\Compiler; #[CoversClass(Compiler::class)] @@ -27,15 +25,11 @@ public function testCompile(string $file, CodeGeneratorResponse $response): void $bytes = hex2bin($hex); self::assertIsString($bytes); - $serializer = new Serializer(); - $reflector = Reflector::build(); + $protobuf = ProtobufEncoder::default(); - $request = $reflector->map( - $serializer->deserialize($reflector->type(CodeGeneratorRequest::class), $bytes), - CodeGeneratorRequest::class, - ); + $request = $protobuf->decode($bytes, CodeGeneratorRequest::class); - self::assertEquals($response, new Compiler()->compile($request)); + self::assertEquals($response, new Compiler($protobuf)->compile($request)); } /** @@ -661,6 +655,61 @@ public function __construct( PHP, ), ), + new CodeGeneratorResponse\File( + name: 'Proto/Api/V1/Proto2TestDescriptorRegistry.php', + content: self::phpContent( + 'proto2/test.proto', + <<<'PHP' +namespace Proto\Api\V1; + +use Override; +use Thesis\Protobuf\Pool; + +/** + * @api + */ +final readonly class Proto2TestDescriptorRegistry implements Pool\Registrar +{ + private const string DESCRIPTOR_BUFFER = 'ChFwcm90bzIvdGVzdC5wcm90bxIMcHJvdG8uYXBpLnYxGh9nb29nbGUvcHJvdG9idWYvdGltZXN0YW1wLnByb3RvGh5nb29nbGUvcHJvdG9idWYvZHVyYXRpb24ucHJvdG8aHGdvb2dsZS9wcm90b2J1Zi9zdHJ1Y3QucHJvdG8aG2dvb2dsZS9wcm90b2J1Zi9lbXB0eS5wcm90bxoZZ29vZ2xlL3Byb3RvYnVmL2FueS5wcm90byLHJAoLVGVzdFJlcXVlc3QSMgoEa2luZBgBIAIoDjIeLnByb3RvLmFwaS52MS5UZXN0UmVxdWVzdC5LaW5kUgRraW5kEiMKDWJvb2xfcmVxdWlyZWQYCiACKAhSDGJvb2xSZXF1aXJlZBIlCg5pbnQzMl9yZXF1aXJlZBgLIAIoBVINaW50MzJSZXF1aXJlZBIlCg5pbnQ2NF9yZXF1aXJlZBgMIAIoA1INaW50NjRSZXF1aXJlZBIpChBmaXhlZDMyX3JlcXVpcmVkGA0gAigHUg9maXhlZDMyUmVxdWlyZWQSKQoQZml4ZWQ2NF9yZXF1aXJlZBgOIAIoBlIPZml4ZWQ2NFJlcXVpcmVkEicKD3VpbnQzMl9yZXF1aXJlZBgPIAIoDVIOdWludDMyUmVxdWlyZWQSJwoPdWludDY0X3JlcXVpcmVkGBAgAigEUg51aW50NjRSZXF1aXJlZBIlCg5nbG9hdF9yZXF1aXJlZBgRIAIoAlINZ2xvYXRSZXF1aXJlZBInCg9kb3VibGVfcmVxdWlyZWQYEiACKAFSDmRvdWJsZVJlcXVpcmVkEicKD3N0cmluZ19yZXF1aXJlZBgTIAIoCVIOc3RyaW5nUmVxdWlyZWQSJQoOYnl0ZXNfcmVxdWlyZWQYZSACKAxSDWJ5dGVzUmVxdWlyZWQSJwoPc2ludDMyX3JlcXVpcmVkGGYgAigRUg5zaW50MzJSZXF1aXJlZBInCg9zaW50NjRfcmVxdWlyZWQYZyACKBJSDnNpbnQ2NFJlcXVpcmVkEisKEXNmaXhlZDMyX3JlcXVpcmVkGGggAigPUhBzZml4ZWQzMlJlcXVpcmVkEisKEXNmaXhlZDY0X3JlcXVpcmVkGGkgAigQUhBzZml4ZWQ2NFJlcXVpcmVkEiMKDWJvb2xfb3B0aW9uYWwYHiABKAhSDGJvb2xPcHRpb25hbBIlCg5pbnQzMl9vcHRpb25hbBgfIAEoBVINaW50MzJPcHRpb25hbBIlCg5pbnQ2NF9vcHRpb25hbBggIAEoA1INaW50NjRPcHRpb25hbBIpChBmaXhlZDMyX29wdGlvbmFsGCEgASgHUg9maXhlZDMyT3B0aW9uYWwSKQoQZml4ZWQ2NF9vcHRpb25hbBgiIAEoBlIPZml4ZWQ2NE9wdGlvbmFsEicKD3VpbnQzMl9vcHRpb25hbBgjIAEoDVIOdWludDMyT3B0aW9uYWwSJwoPdWludDY0X29wdGlvbmFsGCQgASgEUg51aW50NjRPcHRpb25hbBIlCg5mbG9hdF9vcHRpb25hbBglIAEoAlINZmxvYXRPcHRpb25hbBInCg9kb3VibGVfb3B0aW9uYWwYJiABKAFSDmRvdWJsZU9wdGlvbmFsEicKD3N0cmluZ19vcHRpb25hbBgnIAEoCVIOc3RyaW5nT3B0aW9uYWwSJgoOYnl0ZXNfb3B0aW9uYWwYrQIgASgMUg1ieXRlc09wdGlvbmFsEigKD3NpbnQzMl9vcHRpb25hbBiuAiABKBFSDnNpbnQzMk9wdGlvbmFsEigKD3NpbnQ2NF9vcHRpb25hbBivAiABKBJSDnNpbnQ2NE9wdGlvbmFsEiwKEXNmaXhlZDMyX29wdGlvbmFsGLACIAEoD1IQc2ZpeGVkMzJPcHRpb25hbBIsChFzZml4ZWQ2NF9vcHRpb25hbBixAiABKBBSEHNmaXhlZDY0T3B0aW9uYWwSIwoNYm9vbF9yZXBlYXRlZBgUIAMoCFIMYm9vbFJlcGVhdGVkEiUKDmludDMyX3JlcGVhdGVkGBUgAygFUg1pbnQzMlJlcGVhdGVkEiUKDmludDY0X3JlcGVhdGVkGBYgAygDUg1pbnQ2NFJlcGVhdGVkEikKEGZpeGVkMzJfcmVwZWF0ZWQYFyADKAdSD2ZpeGVkMzJSZXBlYXRlZBIpChBmaXhlZDY0X3JlcGVhdGVkGBggAygGUg9maXhlZDY0UmVwZWF0ZWQSJwoPdWludDMyX3JlcGVhdGVkGBkgAygNUg51aW50MzJSZXBlYXRlZBInCg91aW50NjRfcmVwZWF0ZWQYGiADKARSDnVpbnQ2NFJlcGVhdGVkEiUKDmZsb2F0X3JlcGVhdGVkGBsgAygCUg1mbG9hdFJlcGVhdGVkEicKD2RvdWJsZV9yZXBlYXRlZBgcIAMoAVIOZG91YmxlUmVwZWF0ZWQSJwoPc3RyaW5nX3JlcGVhdGVkGB0gAygJUg5zdHJpbmdSZXBlYXRlZBImCg5ieXRlc19yZXBlYXRlZBjJASADKAxSDWJ5dGVzUmVwZWF0ZWQSKAoPc2ludDMyX3JlcGVhdGVkGMoBIAMoEVIOc2ludDMyUmVwZWF0ZWQSKAoPc2ludDY0X3JlcGVhdGVkGMsBIAMoElIOc2ludDY0UmVwZWF0ZWQSLAoRc2ZpeGVkMzJfcmVwZWF0ZWQYzAEgAygPUhBzZml4ZWQzMlJlcGVhdGVkEiwKEXNmaXhlZDY0X3JlcGVhdGVkGM0BIAMoEFIQc2ZpeGVkNjRSZXBlYXRlZBI0ChRib29sX3JlcGVhdGVkX3BhY2tlZBgyIAMoCFISYm9vbFJlcGVhdGVkUGFja2VkQgIQARI2ChVpbnQzMl9yZXBlYXRlZF9wYWNrZWQYMyADKAVSE2ludDMyUmVwZWF0ZWRQYWNrZWRCAhABEjYKFWludDY0X3JlcGVhdGVkX3BhY2tlZBg0IAMoA1ITaW50NjRSZXBlYXRlZFBhY2tlZEICEAESOgoXZml4ZWQzMl9yZXBlYXRlZF9wYWNrZWQYNSADKAdSFWZpeGVkMzJSZXBlYXRlZFBhY2tlZEICEAESOgoXZml4ZWQ2NF9yZXBlYXRlZF9wYWNrZWQYNiADKAZSFWZpeGVkNjRSZXBlYXRlZFBhY2tlZEICEAESOAoWdWludDMyX3JlcGVhdGVkX3BhY2tlZBg3IAMoDVIUdWludDMyUmVwZWF0ZWRQYWNrZWRCAhABEjgKFnVpbnQ2NF9yZXBlYXRlZF9wYWNrZWQYOCADKARSFHVpbnQ2NFJlcGVhdGVkUGFja2VkQgIQARI2ChVmbG9hdF9yZXBlYXRlZF9wYWNrZWQYOSADKAJSE2Zsb2F0UmVwZWF0ZWRQYWNrZWRCAhABEjgKFmRvdWJsZV9yZXBlYXRlZF9wYWNrZWQYOiADKAFSFGRvdWJsZVJlcGVhdGVkUGFja2VkQgIQARI5ChZzaW50MzJfcmVwZWF0ZWRfcGFja2VkGPYDIAMoEVIUc2ludDMyUmVwZWF0ZWRQYWNrZWRCAhABEjkKFnNpbnQ2NF9yZXBlYXRlZF9wYWNrZWQY9wMgAygSUhRzaW50NjRSZXBlYXRlZFBhY2tlZEICEAESPQoYc2ZpeGVkMzJfcmVwZWF0ZWRfcGFja2VkGPgDIAMoD1IWc2ZpeGVkMzJSZXBlYXRlZFBhY2tlZEICEAESPQoYc2ZpeGVkNjRfcmVwZWF0ZWRfcGFja2VkGPkDIAMoEFIWc2ZpeGVkNjRSZXBlYXRlZFBhY2tlZEICEAESKwoOYm9vbF9kZWZhdWx0ZWQYKCABKAg6BHRydWVSDWJvb2xEZWZhdWx0ZWQSKwoPaW50MzJfZGVmYXVsdGVkGCkgASgFOgIzMlIOaW50MzJEZWZhdWx0ZWQSKwoPaW50NjRfZGVmYXVsdGVkGCogASgDOgI2NFIOaW50NjREZWZhdWx0ZWQSMAoRZml4ZWQzMl9kZWZhdWx0ZWQYKyABKAc6AzMyMFIQZml4ZWQzMkRlZmF1bHRlZBIwChFmaXhlZDY0X2RlZmF1bHRlZBgsIAEoBjoDNjQwUhBmaXhlZDY0RGVmYXVsdGVkEi8KEHVpbnQzMl9kZWZhdWx0ZWQYLSABKA06BDMyMDBSD3VpbnQzMkRlZmF1bHRlZBIvChB1aW50NjRfZGVmYXVsdGVkGC4gASgEOgQ2NDAwUg91aW50NjREZWZhdWx0ZWQSLwoPZmxvYXRfZGVmYXVsdGVkGC8gASgCOgYzMTQxNTlSDmZsb2F0RGVmYXVsdGVkEjEKEGRvdWJsZV9kZWZhdWx0ZWQYMCABKAE6BjI3MTgyOFIPZG91YmxlRGVmYXVsdGVkEjsKEHN0cmluZ19kZWZhdWx0ZWQYMSABKAk6EGhlbGxvLCAid29ybGQhIgpSD3N0cmluZ0RlZmF1bHRlZBIxCg9ieXRlc19kZWZhdWx0ZWQYkQMgASgMOgdCaWdub3NlUg5ieXRlc0RlZmF1bHRlZBIvChBzaW50MzJfZGVmYXVsdGVkGJIDIAEoEToDLTMyUg9zaW50MzJEZWZhdWx0ZWQSLwoQc2ludDY0X2RlZmF1bHRlZBiTAyABKBI6Ay02NFIPc2ludDY0RGVmYXVsdGVkEjMKEnNmaXhlZDMyX2RlZmF1bHRlZBiUAyABKA86Ay0zMlIRc2ZpeGVkMzJEZWZhdWx0ZWQSMwoSc2ZpeGVkNjRfZGVmYXVsdGVkGJUDIAEoEDoDLTY0UhFzZml4ZWQ2NERlZmF1bHRlZBJbChFtYXBfc3RyaW5nX3N0cmluZxiWAyADKAsyLi5wcm90by5hcGkudjEuVGVzdFJlcXVlc3QuTWFwU3RyaW5nU3RyaW5nRW50cnlSD21hcFN0cmluZ1N0cmluZxJECg9rbm93bl90aW1lc3RhbXAYlwMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcFIOa25vd25UaW1lc3RhbXASQQoOa25vd25fZHVyYXRpb24YmAMgASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uUg1rbm93bkR1cmF0aW9uEjsKDGtub3duX3N0cnVjdBiZAyABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0Ugtrbm93blN0cnVjdBI4Cgtrbm93bl9lbXB0eRiaAyABKAsyFi5nb29nbGUucHJvdG9idWYuRW1wdHlSCmtub3duRW1wdHkSMgoJa25vd25fYW55GJsDIAEoCzIULmdvb2dsZS5wcm90b2J1Zi5BbnlSCGtub3duQW55EjkKBm5lc3RlZBicAyABKAsyIC5wcm90by5hcGkudjEuVGVzdFJlcXVlc3QuTmVzdGVkUgZuZXN0ZWQSFgoGbnVtYmVyGAUgASgFUgZudW1iZXISEgoEbmFtZRgGIAEoCVIEbmFtZRISCgRkYXRhGAcgASgMUgRkYXRhEhUKBnRlbXBfYxgIIAEoAVIFdGVtcEMSMgoDY29sGAkgASgLMiAucHJvdG8uYXBpLnYxLlRlc3RSZXF1ZXN0Lk5lc3RlZFIDY29sEksKCWRlZXBfZW51bRgEIAEoDjIuLnByb3RvLmFwaS52MS5UZXN0UmVxdWVzdC5OZXN0ZWQuRGVlcC5EZWVwRW51bVIIZGVlcEVudW0SRwoLbmVzdGVkX2RlZXAYnQMgASgLMiUucHJvdG8uYXBpLnYxLlRlc3RSZXF1ZXN0Lk5lc3RlZC5EZWVwUgpuZXN0ZWREZWVwEiEKCmxhc3RfZmllbGQY/////wEgASgJUglsYXN0RmllbGQaQgoUTWFwU3RyaW5nU3RyaW5nRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKAlSBXZhbHVlOgI4ARrEAgoGTmVzdGVkEhIKBG5hbWUYASABKAlSBG5hbWUSFAoFZ3JvdXAYAiABKAlSBWdyb3VwEjkKBGRlZXAYAyABKAsyJS5wcm90by5hcGkudjEuVGVzdFJlcXVlc3QuTmVzdGVkLkRlZXBSBGRlZXAa1AEKBERlZXASEgoEbmFtZRgBIAIoCVIEbmFtZRIUCgVwaG9uZRgCIAEoCVIFcGhvbmUSFAoFZW1haWwYAyABKAlSBWVtYWlsEksKCWRlZXBfZW51bRgEIAEoDjIuLnByb3RvLmFwaS52MS5UZXN0UmVxdWVzdC5OZXN0ZWQuRGVlcC5EZWVwRW51bVIIZGVlcEVudW0iNgoIRGVlcEVudW0SFwoVREVFUF9FTlVNX1VOU1BFQ0lGSUVEEhEKDURFRVBfRU5VTV9GT08QAUIHCgV1bmlvbiI6CgRLaW5kEhIKEEtJTkRfVU5TUEVDSUZJRUQSCAoEVk9JRBABEgoKBlNUUklORxACEggKBEJPT0wQA0IJCgd4X2ZpZWxkIoYCCg5Bbm90aGVyUmVxdWVzdBI4CgZuZXN0ZWQYASABKAsyIC5wcm90by5hcGkudjEuVGVzdFJlcXVlc3QuTmVzdGVkUgZuZXN0ZWQSOQoEZGVlcBgCIAEoCzIlLnByb3RvLmFwaS52MS5UZXN0UmVxdWVzdC5OZXN0ZWQuRGVlcFIEZGVlcBIyCgRraW5kGAMgASgOMh4ucHJvdG8uYXBpLnYxLlRlc3RSZXF1ZXN0LktpbmRSBGtpbmQSSwoJZGVlcF9lbnVtGAQgASgOMi4ucHJvdG8uYXBpLnYxLlRlc3RSZXF1ZXN0Lk5lc3RlZC5EZWVwLkRlZXBFbnVtUghkZWVwRW51bSoyCgNGb28SEQoPRk9PX1VOU1BFQ0lGSUVEEgsKB0ZPT19CQVIQARILCgdGT09fQkFaEAJK00QKBxIFAACVAQEKCAoBDBIDAAASCggKAQISAwIAFQoJCgIDABIDBAApCgkKAgMBEgMFACgKCQoCAwISAwYAJgoJCgIDAxIDBwAlCgkKAgMEEgMIACMKGwoCBQASBAsAEAEaDyBFbnVtIGNvbW1lbnQuCgoKCgMFAAESAwsFCAocCgQFAAIAEgMNBBgaDyBDYXNlIGNvbW1lbnQuCgoMCgUFAAIAARIDDQQTCgwKBQUAAgACEgMNFhcKCwoEBQACARIDDgQQCgwKBQUAAgEBEgMOBAsKDAoF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'; + + #[Override] + public function register(Pool\Registry $pool): void + { + $pool->add(Pool\Descriptor::base64(self::DESCRIPTOR_BUFFER), [ + 'proto.api.v1.TestRequest' => new Pool\MessageMetadata(\Proto\Api\V1\TestRequest\XFieldDeepEnum::class), + 'proto.api.v1.TestRequest.Nested' => new Pool\MessageMetadata(\Proto\Api\V1\TestRequest\Nested::class), + 'proto.api.v1.TestRequest.Nested.Deep' => new Pool\MessageMetadata(\Proto\Api\V1\TestRequest\Nested\Deep\UnionEmail::class), + 'proto.api.v1.AnotherRequest' => new Pool\MessageMetadata(\Proto\Api\V1\AnotherRequest::class), + 'proto.api.v1.Foo' => new Pool\EnumMetadata(\Proto\Api\V1\Foo::class), + 'proto.api.v1.TestRequest.Kind' => new Pool\EnumMetadata(\Proto\Api\V1\TestRequest\Kind::class), + 'proto.api.v1.TestRequest.Nested.Deep.DeepEnum' => new Pool\EnumMetadata(\Proto\Api\V1\TestRequest\Nested\Deep\DeepEnum::class), + ]); + } +} + +PHP, + ), + ), + new CodeGeneratorResponse\File( + name: 'Proto/Api/V1/autoload.metadata.php', + content: <<<'PHP' +register( + new \Thesis\Protobuf\Pool\OnceRegistrar(new \Proto\Api\V1\Proto2TestDescriptorRegistry()), +); + +PHP, + ), ], ), ]; @@ -685,6 +734,55 @@ public function __construct( PHP, ), ), + new CodeGeneratorResponse\File( + name: 'Thesis/Api/V1/PhpNamespacePhpNamespaceDescriptorRegistry.php', + content: self::phpContent( + source: 'php_namespace/php_namespace.proto', + content: <<<'PHP' +namespace Thesis\Api\V1; + +use Override; +use Thesis\Protobuf\Pool; + +/** + * @api + */ +final readonly class PhpNamespacePhpNamespaceDescriptorRegistry implements Pool\Registrar +{ + private const string DESCRIPTOR_BUFFER = 'CiFwaHBfbmFtZXNwYWNlL3BocF9uYW1lc3BhY2UucHJvdG8SC3Rlc3QuYXBpLnYxIg0KC1Rlc3RSZXF1ZXN0Qhn4AQHCAgNLZWvKAg1UaGVzaXNcQXBpXFYxSl0KBhIEAAAHFgoICgEMEgMAABIKCAoBCBIDAgApCgkKAggpEgMCACkKCAoBCBIDAwAgCgkKAggoEgMDACAKCAoBAhIDBQAUCgkKAgQAEgMHABYKCgoDBAABEgMHCBNiBnByb3RvMw=='; + + #[Override] + public function register(Pool\Registry $pool): void + { + $pool->add(Pool\Descriptor::base64(self::DESCRIPTOR_BUFFER), [ + 'test.api.v1.TestRequest' => new Pool\MessageMetadata(\Thesis\Api\V1\TestRequest::class), + ]); + } +} + +PHP, + ), + ), + new CodeGeneratorResponse\File( + name: 'Thesis/Api/V1/autoload.metadata.php', + content: <<<'PHP' +register( + new \Thesis\Protobuf\Pool\OnceRegistrar(new \Thesis\Api\V1\PhpNamespacePhpNamespaceDescriptorRegistry()), +); + +PHP, + ), ], ), ]; @@ -991,6 +1089,60 @@ public function __construct( PHP, ), ), + new CodeGeneratorResponse\File( + name: 'Proto/Api/V1/SnakeCaseSnakeCaseDescriptorRegistry.php', + content: self::phpContent( + 'snake_case/snake_case.proto', + <<<'PHP' +namespace Proto\Api\V1; + +use Override; +use Thesis\Protobuf\Pool; + +/** + * @api + */ +final readonly class SnakeCaseSnakeCaseDescriptorRegistry implements Pool\Registrar +{ + private const string DESCRIPTOR_BUFFER = 'ChtzbmFrZV9jYXNlL3NuYWtlX2Nhc2UucHJvdG8SDHByb3RvLmFwaS52MSLrBAoMVGVzdF9SZXF1ZXN0EkEKBm5lc3RlZBgBIAEoCzIpLnByb3RvLmFwaS52MS5UZXN0X1JlcXVlc3QuTmVzdGVkX01lc3NhZ2VSBm5lc3RlZBIWCgZudW1iZXIYAiABKAVSBm51bWJlchI7CgNjb2wYAyABKAsyKS5wcm90by5hcGkudjEuVGVzdF9SZXF1ZXN0Lk5lc3RlZF9NZXNzYWdlUgNjb2wSVQoJZGVlcF9lbnVtGAQgASgOMjgucHJvdG8uYXBpLnYxLlRlc3RfUmVxdWVzdC5OZXN0ZWRfTWVzc2FnZS5EZWVwLkRlZXBfRW51bVIIZGVlcEVudW0STwoLbmVzdGVkX2RlZXAYBSABKAsyLi5wcm90by5hcGkudjEuVGVzdF9SZXF1ZXN0Lk5lc3RlZF9NZXNzYWdlLkRlZXBSCm5lc3RlZERlZXAajwIKDk5lc3RlZF9NZXNzYWdlEkIKBGRlZXAYASABKAsyLi5wcm90by5hcGkudjEuVGVzdF9SZXF1ZXN0Lk5lc3RlZF9NZXNzYWdlLkRlZXBSBGRlZXAauAEKBERlZXASFAoFcGhvbmUYASABKAlSBXBob25lEhQKBWVtYWlsGAIgASgJUgVlbWFpbBJVCglkZWVwX2VudW0YAyABKA4yOC5wcm90by5hcGkudjEuVGVzdF9SZXF1ZXN0Lk5lc3RlZF9NZXNzYWdlLkRlZXAuRGVlcF9FbnVtUghkZWVwRW51bSIkCglEZWVwX0VudW0SFwoVREVFUF9FTlVNX1VOU1BFQ0lGSUVEQgcKBXVuaW9uQgkKB3hfZmllbGQiqwEKD0Fub3RoZXJfUmVxdWVzdBJBCgZuZXN0ZWQYASABKAsyKS5wcm90by5hcGkudjEuVGVzdF9SZXF1ZXN0Lk5lc3RlZF9NZXNzYWdlUgZuZXN0ZWQSVQoJZGVlcF9lbnVtGAIgASgOMjgucHJvdG8uYXBpLnYxLlRlc3RfUmVxdWVzdC5OZXN0ZWRfTWVzc2FnZS5EZWVwLkRlZXBfRW51bVIIZGVlcEVudW0qGAoDRm9vEhEKD0ZPT19VTlNQRUNJRklFREr9BwoGEgQAACMBCggKAQwSAwAAEgoICgECEgMCABUKCgoCBQASBAQABgEKCgoDBQABEgMEBQgKCwoEBQACABIDBQQYCgwKBQUAAgABEgMFBBMKDAoFBQACAAISAwUWFwoKCgIEABIECAAeAQoKCgMEAAESAwgIFAoMCgQEAAMAEgQJBBYFCgwKBQQAAwABEgMJDBoKDgoGBAADAAMAEgQKCBQJCg4KBwQAAwADAAESAwoQFAoQCggEAAMAAwAEABIECwwNDQoQCgkEAAMAAwAEAAESAwsRGgoRCgoEAAMAAwAEAAIAEgMMECoKEgoLBAADAAMABAACAAESAwwQJQoSCgsEAAMAAwAEAAIAAhIDDCgpChAKCAQAAwADAAgAEgQPDBINChAKCQQAAwADAAgAARIDDxIXCg8KCAQAAwADAAIAEgMQECEKEAoJBAADAAMAAgAFEgMQEBYKEAoJBAADAAMAAgABEgMQFxwKEAoJBAADAAMAAgADEgMQHyAKDwoIBAADAAMAAgESAxEQIQoQCgkEAAMAAwACAQUSAxEQFgoQCgkEAAMAAwACAQESAxEXHAoQCgkEAAMAAwACAQMSAxEfIAoPCggEAAMAAwACAhIDEwwkChAKCQQAAwADAAICBhIDEwwVChAKCQQAAwADAAICARIDExYfChAKCQQAAwADAAICAxIDEyIjCg0KBgQAAwACABIDFQgWCg4KBwQAAwACAAYSAxUIDAoOCgcEAAMAAgABEgMVDREKDgoHBAADAAIAAxIDFRQVCgsKBAQAAgASAxcEHgoMCgUEAAIABhIDFwQSCgwKBQQAAgABEgMXExkKDAoFBAACAAMSAxccHQoMCgQEAAgAEgQYBBwFCgwKBQQACAABEgMYChEKCwoEBAACARIDGQgZCgwKBQQAAgEFEgMZCA0KDAoFBAACAQESAxkOFAoMCgUEAAIBAxIDGRcYCgsKBAQAAgISAxoIHwoMCgUEAAICBhIDGggWCgwKBQQAAgIBEgMaFxoKDAoFBAACAgMSAxodHgoLCgQEAAIDEgMbCDQKDAoFBAACAwYSAxsIJQoMCgUEAAIDARIDGyYvCgwKBQQAAgMDEgMbMjMKCwoEBAACBBIDHQQoCgwKBQQAAgQGEgMdBBcKDAoFBAACBAESAx0YIwoMCgUEAAIEAxIDHSYnCgoKAgQBEgQgACMBCgoKAwQBARIDIAgXCgsKBAQBAgASAyEEKwoMCgUEAQIABhIDIQQfCgwKBQQBAgABEgMhICYKDAoFBAECAAMSAyEpKgoLCgQEAQIBEgMiBD0KDAoFBAECAQYSAyIELgoMCgUEAQIBARIDIi84CgwKBQQBAgEDEgMiOzxiBnByb3RvMw=='; + + #[Override] + public function register(Pool\Registry $pool): void + { + $pool->add(Pool\Descriptor::base64(self::DESCRIPTOR_BUFFER), [ + 'proto.api.v1.Test_Request' => new Pool\MessageMetadata(\Proto\Api\V1\TestRequest\XFieldDeepEnum::class), + 'proto.api.v1.Test_Request.Nested_Message' => new Pool\MessageMetadata(\Proto\Api\V1\TestRequest\NestedMessage::class), + 'proto.api.v1.Test_Request.Nested_Message.Deep' => new Pool\MessageMetadata(\Proto\Api\V1\TestRequest\NestedMessage\Deep\UnionEmail::class), + 'proto.api.v1.Another_Request' => new Pool\MessageMetadata(\Proto\Api\V1\AnotherRequest::class), + 'proto.api.v1.Foo' => new Pool\EnumMetadata(\Proto\Api\V1\Foo::class), + 'proto.api.v1.Test_Request.Nested_Message.Deep.Deep_Enum' => new Pool\EnumMetadata(\Proto\Api\V1\TestRequest\NestedMessage\Deep\DeepEnum::class), + ]); + } +} + +PHP, + ), + ), + new CodeGeneratorResponse\File( + name: 'Proto/Api/V1/autoload.metadata.php', + content: <<<'PHP' +register( + new \Thesis\Protobuf\Pool\OnceRegistrar(new \Proto\Api\V1\SnakeCaseSnakeCaseDescriptorRegistry()), +); + +PHP, + ), ], ), ]; @@ -1157,6 +1309,62 @@ public function __construct( PHP, ), ), + new CodeGeneratorResponse\File( + name: 'ReservedTypes/ReservedNamesReservedNamesDescriptorRegistry.php', + content: self::phpContent( + 'reserved_names/reserved_names.proto', + <<<'PHP' +namespace ReservedTypes; + +use Override; +use Thesis\Protobuf\Pool; + +/** + * @api + */ +final readonly class ReservedNamesReservedNamesDescriptorRegistry implements Pool\Registrar +{ + private const string DESCRIPTOR_BUFFER = 'CiNyZXNlcnZlZF9uYW1lcy9yZXNlcnZlZF9uYW1lcy5wcm90bxIOcmVzZXJ2ZWRfdHlwZXMiBgoEZXhpdCJECgpJTlNUQU5DRU9GEjYKBWJyZWFrGAEgASgLMiAucmVzZXJ2ZWRfdHlwZXMuQ2xhc3MuQ2FzZS5CcmVha1IFYnJlYWsiZAoFQ2xhc3MSNgoFYnJlYWsYASABKAsyIC5yZXNlcnZlZF90eXBlcy5DbGFzcy5DYXNlLkJyZWFrUgVicmVhaxojCgRDYXNlGhsKBUJyZWFrEhIKBG5hbWUYASABKAlSBG5hbWUqQgoKTm90QWxsb3dlZBIKCghhYnN0cmFjdBIHCgNhbmQQARIJCgVhcnJheRACEgkKBWVtcHR5EAMSCQoFY2xhc3MQBCoRCgZzdHJpbmcSBwoFWkVSTzEqEAoFVHJhaXQSBwoFWkVSTzJKuQUKBhIEAAAeAQoICgEMEgMAABIKCAoBAhIDAgAXCgoKAgUAEgQEAAoBCgoKAwUAARIDBAUPCgsKBAUAAgASAwUEEQoMCgUFAAIAARIDBQQMCgwKBQUAAgACEgMFDxAKCwoEBQACARIDBgQMCgwKBQUAAgEBEgMGBAcKDAoFBQACAQISAwYKCwoLCgQFAAICEgMHBA4KDAoFBQACAgESAwcECQoMCgUFAAICAhIDBwwNCgsKBAUAAgMSAwgEDgoMCgUFAAIDARIDCAQJCgwKBQUAAgMCEgMIDA0KCwoEBQACBBIDCQQOCgwKBQUAAgQBEgMJBAkKDAoFBQACBAISAwkMDQoJCgIFARIDDAAaCgoKAwUBARIDDAULCgsKBAUBAgASAwwOGAoMCgUFAQIAARIDDA4TCgwKBQUBAgACEgMMFhcKCQoCBQISAw4AGQoKCgMFAgESAw4FCgoLCgQFAgIAEgMODRcKDAoFBQICAAESAw4NEgoMCgUFAgIAAhIDDhUWCgkKAgQAEgMQAA8KCgoDBAABEgMQCAwKCgoCBAESBBIAFAEKCgoDBAEBEgMSCBIKCwoEBAECABIDEwQfCgwKBQQBAgAGEgMTBBQKDAoFBAECAAESAxMVGgoMCgUEAQIAAxIDEx0eCgoKAgQCEgQWAB4BCgoKAwQCARIDFggNCgwKBAQCAwASBBcEGwUKDAoFBAIDAAESAxcMEAoOCgYEAgMAAwASBBgIGgkKDgoHBAIDAAMAARIDGBAVCg8KCAQCAwADAAIAEgMZDBwKEAoJBAIDAAMAAgAFEgMZDBIKEAoJBAIDAAMAAgABEgMZExcKEAoJBAIDAAMAAgADEgMZGhsKCwoEBAICABIDHQQZCgwKBQQCAgAGEgMdBA4KDAoFBAICAAESAx0PFAoMCgUEAgIAAxIDHRcYYgZwcm90bzM='; + + #[Override] + public function register(Pool\Registry $pool): void + { + $pool->add(Pool\Descriptor::base64(self::DESCRIPTOR_BUFFER), [ + 'reserved_types.exit' => new Pool\MessageMetadata(\ReservedTypes\Exit_::class), + 'reserved_types.INSTANCEOF' => new Pool\MessageMetadata(\ReservedTypes\INSTANCEOF_::class), + 'reserved_types.Class' => new Pool\MessageMetadata(\ReservedTypes\Class_::class), + 'reserved_types.Class.Case' => new Pool\MessageMetadata(\ReservedTypes\Class_\Case_::class), + 'reserved_types.Class.Case.Break' => new Pool\MessageMetadata(\ReservedTypes\Class_\Case_\Break_::class), + 'reserved_types.NotAllowed' => new Pool\EnumMetadata(\ReservedTypes\NotAllowed::class), + 'reserved_types.string' => new Pool\EnumMetadata(\ReservedTypes\String_::class), + 'reserved_types.Trait' => new Pool\EnumMetadata(\ReservedTypes\Trait_::class), + ]); + } +} + +PHP, + ), + ), + new CodeGeneratorResponse\File( + name: 'ReservedTypes/autoload.metadata.php', + content: <<<'PHP' +register( + new \Thesis\Protobuf\Pool\OnceRegistrar(new \ReservedTypes\ReservedNamesReservedNamesDescriptorRegistry()), +); + +PHP, + ), ], ), ]; @@ -1258,6 +1466,55 @@ interface Contact {} PHP, ), ), + new CodeGeneratorResponse\File( + name: 'Proto/Api/V1/Proto3TestDescriptorRegistry.php', + content: self::phpContent( + 'proto3/test.proto', + <<<'PHP' +namespace Proto\Api\V1; + +use Override; +use Thesis\Protobuf\Pool; + +/** + * @api + */ +final readonly class Proto3TestDescriptorRegistry implements Pool\Registrar +{ + private const string DESCRIPTOR_BUFFER = 'ChFwcm90bzMvdGVzdC5wcm90bxIMcHJvdG8uYXBpLnYxIroBCgtUZXN0UmVxdWVzdBIhCgxzdHJpbmdfdmFsdWUYASABKAlSC3N0cmluZ1ZhbHVlEjcKFW9wdGlvbmFsX3N0cmluZ192YWx1ZRgCIAEoCUgBUhNvcHRpb25hbFN0cmluZ1ZhbHVliAEBEhQKBXBob25lGAMgASgJUgVwaG9uZRIUCgVlbWFpbBgEIAEoCVIFZW1haWxCCQoHY29udGFjdEIYChZfb3B0aW9uYWxfc3RyaW5nX3ZhbHVlSroCCgYSBAAACwEKCAoBDBIDAAASCggKAQISAwIAFQoKCgIEABIEBAALAQoKCgMEAAESAwQIEwoLCgQEAAIAEgMFBBwKDAoFBAACAAUSAwUECgoMCgUEAAIAARIDBQsXCgwKBQQAAgADEgMFGhsKCwoEBAACARIDBgQuCgwKBQQAAgEEEgMGBAwKDAoFBAACAQUSAwYNEwoMCgUEAAIBARIDBhQpCgwKBQQAAgEDEgMGLC0KDAoEBAAIABIEBwQKBQoMCgUEAAgAARIDBwoRCgsKBAQAAgISAwgIGQoMCgUEAAICBRIDCAgOCgwKBQQAAgIBEgMIDxQKDAoFBAACAgMSAwgXGAoLCgQEAAIDEgMJCBkKDAoFBAACAwUSAwkIDgoMCgUEAAIDARIDCQ8UCgwKBQQAAgMDEgMJFxhiBnByb3RvMw=='; + + #[Override] + public function register(Pool\Registry $pool): void + { + $pool->add(Pool\Descriptor::base64(self::DESCRIPTOR_BUFFER), [ + 'proto.api.v1.TestRequest' => new Pool\MessageMetadata(\Proto\Api\V1\TestRequest\ContactEmail::class), + ]); + } +} + +PHP, + ), + ), + new CodeGeneratorResponse\File( + name: 'Proto/Api/V1/autoload.metadata.php', + content: <<<'PHP' +register( + new \Thesis\Protobuf\Pool\OnceRegistrar(new \Proto\Api\V1\Proto3TestDescriptorRegistry()), +); + +PHP, + ), ], ), ]; @@ -1596,6 +1853,36 @@ public function __construct( ) {} } +PHP, + ), + ), + new CodeGeneratorResponse\File( + name: 'Thesis/Auth/ProtosAuthDescriptorRegistry.php', + content: self::phpContent( + 'protos/auth.proto', + <<<'PHP' +namespace Thesis\Auth; + +use Override; +use Thesis\Protobuf\Pool; + +/** + * @api + */ +final readonly class ProtosAuthDescriptorRegistry implements Pool\Registrar +{ + private const string DESCRIPTOR_BUFFER = 'ChFwcm90b3MvYXV0aC5wcm90bxILVGhlc2lzLkF1dGgiPgoMTG9naW5SZXF1ZXN0EhIKBHVzZXIYAiABKAlSBHVzZXISGgoIcGFzc3dvcmQYAyABKAlSCHBhc3N3b3JkIiUKDUxvZ2luUmVzcG9uc2USFAoFdG9rZW4YASABKAlSBXRva2VuSvEBCgYSBAAACwEKCAoBDBIDAAASCggKAQISAwIAFAoKCgIEABIEBAAHAQoKCgMEAAESAwQIFAoLCgQEAAIAEgMFBBQKDAoFBAACAAUSAwUECgoMCgUEAAIAARIDBQsPCgwKBQQAAgADEgMFEhMKCwoEBAACARIDBgQYCgwKBQQAAgEFEgMGBAoKDAoFBAACAQESAwYLEwoMCgUEAAIBAxIDBhYXCgoKAgQBEgQJAAsBCgoKAwQBARIDCQgVCgsKBAQBAgASAwoEFQoMCgUEAQIABRIDCgQKCgwKBQQBAgABEgMKCxAKDAoFBAECAAMSAwoTFGIGcHJvdG8z'; + + #[Override] + public function register(Pool\Registry $pool): void + { + $pool->add(Pool\Descriptor::base64(self::DESCRIPTOR_BUFFER), [ + 'Thesis.Auth.LoginRequest' => new Pool\MessageMetadata(\Thesis\Auth\LoginRequest::class), + 'Thesis.Auth.LoginResponse' => new Pool\MessageMetadata(\Thesis\Auth\LoginResponse::class), + ]); + } +} + PHP, ), ), @@ -1760,6 +2047,64 @@ public function __construct( PHP, ), ), + new CodeGeneratorResponse\File( + name: 'Thesis/Queue/ProtosQueueDescriptorRegistry.php', + content: self::phpContent( + 'protos/queue.proto', + <<<'PHP' +namespace Thesis\Queue; + +use Override; +use Thesis\Protobuf\Pool; + +/** + * @api + */ +final readonly class ProtosQueueDescriptorRegistry implements Pool\Registrar +{ + private const string DESCRIPTOR_BUFFER = 'ChJwcm90b3MvcXVldWUucHJvdG8SDFRoZXNpcy5RdWV1ZSIyCgtQdXNoUmVxdWVzdBojCgdNZXNzYWdlEhgKB2NvbnRlbnQYASABKAlSB2NvbnRlbnQiVAoLUHVsbFJlcXVlc3QSEAoDcW9zGAEgASgFUgNxb3MaMwoHTWVzc2FnZRIYCgdjb250ZW50GAEgASgJUgdjb250ZW50Eg4KAmlkGAIgASgJUgJpZCI3CglIZWFydGJlYXQaFAoKRnJvbUNsaWVudBoGCgRQaW5nGhQKCkZyb21TZXJ2ZXIaBgoEUGluZ0qGBAoGEgQAABsBCggKAQwSAwAAEgoICgECEgMCABUKCgoCBAASBAQACAEKCgoDBAABEgMECBMKDAoEBAADABIEBQQHBQoMCgUEAAMAARIDBQwTCg0KBgQAAwACABIDBggbCg4KBwQAAwACAAUSAwYIDgoOCgcEAAMAAgABEgMGDxYKDgoHBAADAAIAAxIDBhkaCgoKAgQBEgQKABEBCgoKAwQBARIDCggTCgwKBAQBAwASBAsEDgUKDAoFBAEDAAESAwsMEwoNCgYEAQMAAgASAwwIGwoOCgcEAQMAAgAFEgMMCA4KDgoHBAEDAAIAARIDDA8WCg4KBwQBAwACAAMSAwwZGgoNCgYEAQMAAgESAw0IFgoOCgcEAQMAAgEFEgMNCA4KDgoHBAEDAAIBARIDDQ8RCg4KBwQBAwACAQMSAw0UFQoLCgQEAQIAEgMQBBIKDAoFBAECAAUSAxAECQoMCgUEAQIAARIDEAoNCgwKBQQBAgADEgMQEBEKCgoCBAISBBMAGwEKCgoDBAIBEgMTCBEKDAoEBAIDABIEFAQWBQoMCgUEAgMAARIDFAwWCg0KBgQCAwADABIDFQgXCg4KBwQCAwADAAESAxUQFAoMCgQEAgMBEgQYBBoFCgwKBQQCAwEBEgMYDBYKDQoGBAIDAQMAEgMZCBcKDgoHBAIDAQMAARIDGRAUYgZwcm90bzM='; + + #[Override] + public function register(Pool\Registry $pool): void + { + $pool->add(Pool\Descriptor::base64(self::DESCRIPTOR_BUFFER), [ + 'Thesis.Queue.PushRequest' => new Pool\MessageMetadata(\Thesis\Queue\PushRequest::class), + 'Thesis.Queue.PushRequest.Message' => new Pool\MessageMetadata(\Thesis\Queue\PushRequest\Message::class), + 'Thesis.Queue.PullRequest' => new Pool\MessageMetadata(\Thesis\Queue\PullRequest::class), + 'Thesis.Queue.PullRequest.Message' => new Pool\MessageMetadata(\Thesis\Queue\PullRequest\Message::class), + 'Thesis.Queue.Heartbeat' => new Pool\MessageMetadata(\Thesis\Queue\Heartbeat::class), + 'Thesis.Queue.Heartbeat.FromClient' => new Pool\MessageMetadata(\Thesis\Queue\Heartbeat\FromClient::class), + 'Thesis.Queue.Heartbeat.FromClient.Ping' => new Pool\MessageMetadata(\Thesis\Queue\Heartbeat\FromClient\Ping::class), + 'Thesis.Queue.Heartbeat.FromServer' => new Pool\MessageMetadata(\Thesis\Queue\Heartbeat\FromServer::class), + 'Thesis.Queue.Heartbeat.FromServer.Ping' => new Pool\MessageMetadata(\Thesis\Queue\Heartbeat\FromServer\Ping::class), + ]); + } +} + +PHP, + ), + ), + new CodeGeneratorResponse\File( + name: 'Thesis/autoload.metadata.php', + content: <<<'PHP' +register( + new \Thesis\Protobuf\Pool\OnceRegistrar(new \Thesis\Auth\ProtosAuthDescriptorRegistry()), + new \Thesis\Protobuf\Pool\OnceRegistrar(new \Thesis\Queue\ProtosQueueDescriptorRegistry()), +); + +PHP, + ), ], ), ]; diff --git a/tests/Plugin/PathTableTest.php b/tests/Plugin/PathTableTest.php new file mode 100644 index 0000000..3984b9c --- /dev/null +++ b/tests/Plugin/PathTableTest.php @@ -0,0 +1,53 @@ + $paths + * @param array> $groups + */ + #[TestWith( + [ + [ + 'A' => 'Thesis/Api', + 'B' => 'Thesis/Api/V1', + 'C' => 'Thesis/Api/V2', + 'D' => 'Thesis/Api/Common', + 'X' => 'Typhoon/Analyzer', + 'Y' => 'Typhoon/Analyzer/Ir', + 'L' => 'Ydb/Auth', + 'R' => 'Ydb/Topic', + ], + [ + 'Thesis\Api' => ['A', 'B', 'C', 'D'], + 'Typhoon\Analyzer' => ['X', 'Y'], + 'Ydb' => ['L', 'R'], + ], + ], + )] + #[TestWith( + [ + [], + [], + ], + )] + public function testGroupByNamespace(array $paths, array $groups): void + { + $table = new PathTable(); + + foreach ($paths as $fqcn => $path) { + $table->addRelation($fqcn, $path); + } + + self::assertSame($groups, $table->groupByNamespace()); + } +} From 38b7596e61482db18c6e1d1d6c8813b03a8f53be Mon Sep 17 00:00:00 2001 From: kafkiansky Date: Sun, 15 Feb 2026 15:59:45 +0300 Subject: [PATCH 2/2] chore: fix tests --- tests/CompilerTest.php | 112 +++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 72 deletions(-) diff --git a/tests/CompilerTest.php b/tests/CompilerTest.php index 8f95b48..bcfc0e3 100644 --- a/tests/CompilerTest.php +++ b/tests/CompilerTest.php @@ -692,23 +692,14 @@ public function register(Pool\Registry $pool): void ), new CodeGeneratorResponse\File( name: 'Proto/Api/V1/autoload.metadata.php', - content: <<<'PHP' -register( new \Thesis\Protobuf\Pool\OnceRegistrar(new \Proto\Api\V1\Proto2TestDescriptorRegistry()), ); PHP, + ), ), ], ), @@ -765,23 +756,14 @@ public function register(Pool\Registry $pool): void ), new CodeGeneratorResponse\File( name: 'Thesis/Api/V1/autoload.metadata.php', - content: <<<'PHP' -register( new \Thesis\Protobuf\Pool\OnceRegistrar(new \Thesis\Api\V1\PhpNamespacePhpNamespaceDescriptorRegistry()), ); PHP, + ), ), ], ), @@ -1125,23 +1107,14 @@ public function register(Pool\Registry $pool): void ), new CodeGeneratorResponse\File( name: 'Proto/Api/V1/autoload.metadata.php', - content: <<<'PHP' -register( new \Thesis\Protobuf\Pool\OnceRegistrar(new \Proto\Api\V1\SnakeCaseSnakeCaseDescriptorRegistry()), ); PHP, + ), ), ], ), @@ -1347,23 +1320,14 @@ public function register(Pool\Registry $pool): void ), new CodeGeneratorResponse\File( name: 'ReservedTypes/autoload.metadata.php', - content: <<<'PHP' -register( new \Thesis\Protobuf\Pool\OnceRegistrar(new \ReservedTypes\ReservedNamesReservedNamesDescriptorRegistry()), ); PHP, + ), ), ], ), @@ -1497,23 +1461,14 @@ public function register(Pool\Registry $pool): void ), new CodeGeneratorResponse\File( name: 'Proto/Api/V1/autoload.metadata.php', - content: <<<'PHP' -register( new \Thesis\Protobuf\Pool\OnceRegistrar(new \Proto\Api\V1\Proto3TestDescriptorRegistry()), ); PHP, + ), ), ], ), @@ -2086,24 +2041,15 @@ public function register(Pool\Registry $pool): void ), new CodeGeneratorResponse\File( name: 'Thesis/autoload.metadata.php', - content: <<<'PHP' -register( new \Thesis\Protobuf\Pool\OnceRegistrar(new \Thesis\Auth\ProtosAuthDescriptorRegistry()), new \Thesis\Protobuf\Pool\OnceRegistrar(new \Thesis\Queue\ProtosQueueDescriptorRegistry()), ); PHP, + ), ), ], ), @@ -2133,4 +2079,26 @@ private static function phpContent(string $source, string $content): string $content, ); } + + private static function autoloadContent(string $content): string + { + return \sprintf( + <<<'PHP' +