Merge pull request #1635 from tatemz/feature/load-file-descriptor-sets

Add functions for loading and parsing binary-encoded or plain object file descriptor sets
This commit is contained in:
Michael Lumish 2020-12-01 11:24:17 -08:00 committed by GitHub
commit 604dd0f206
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 191 additions and 0 deletions

View File

@ -30,6 +30,17 @@ declare module 'protobufjs' {
descriptor.IDescriptorProto;
}
interface RootConstructor {
new (options?: Options): Root;
fromDescriptor(
descriptorSet:
| descriptor.IFileDescriptorSet
| Protobuf.Reader
| Uint8Array
): Root;
fromJSON(json: Protobuf.INamespace, root?: Root): Root;
}
interface Root {
toDescriptor(
protoVersion: string
@ -98,6 +109,9 @@ export type Options = Protobuf.IParseOptions &
includeDirs?: string[];
};
type DecodedDescriptorSet = Protobuf.Message<descriptor.IFileDescriptorSet> &
descriptor.IFileDescriptorSet;
const descriptorOptions: Protobuf.IConversionOptions = {
longs: String,
enums: String,
@ -307,6 +321,19 @@ function addIncludePathResolver(root: Protobuf.Root, includePaths: string[]) {
};
}
function createPackageDefinitionFromDescriptorSet(
decodedDescriptorSet: DecodedDescriptorSet,
options?: Options
) {
options = options || {};
const root = (Protobuf.Root as Protobuf.RootConstructor).fromDescriptor(
decodedDescriptorSet
);
root.resolveAll();
return createPackageDefinition(root, options);
}
/**
* Load a .proto file with the specified options.
* @param filename One or multiple file paths to load. Can be an absolute path
@ -368,6 +395,34 @@ export function loadSync(
return createPackageDefinition(root, options!);
}
export function loadFileDescriptorSetFromBuffer(
descriptorSet: Buffer,
options?: Options
): PackageDefinition {
const decodedDescriptorSet = descriptor.FileDescriptorSet.decode(
descriptorSet
) as DecodedDescriptorSet;
return createPackageDefinitionFromDescriptorSet(
decodedDescriptorSet,
options
);
}
export function loadFileDescriptorSetFromObject(
descriptorSet: Parameters<typeof descriptor.FileDescriptorSet.fromObject>[0],
options?: Options
): PackageDefinition {
const decodedDescriptorSet = descriptor.FileDescriptorSet.fromObject(
descriptorSet
) as DecodedDescriptorSet;
return createPackageDefinitionFromDescriptorSet(
decodedDescriptorSet,
options
);
}
// Load Google's well-known proto files that aren't exposed by Protobuf.js.
// Protobuf.js exposes: any, duration, empty, field_mask, struct, timestamp,

View File

@ -16,6 +16,8 @@
*/
import * as assert from 'assert';
import { rpcFileDescriptorSet } from '../test_protos/rpc.desc';
import { readFileSync } from 'fs';
import * as proto_loader from '../src/index';
@ -99,4 +101,22 @@ describe('Descriptor types', () => {
// This will throw if the well known protos are not available.
proto_loader.loadSync(`${TEST_PROTO_DIR}/well_known.proto`);
});
it('Can load binary-encoded proto file descriptor sets', () => {
const buffer = readFileSync(`${TEST_PROTO_DIR}/rpc.desc.bin`);
// This will throw if the rpc descriptor cannot be decoded
proto_loader.loadFileDescriptorSetFromBuffer(buffer);
});
it('Can load json file descriptor sets', () => {
const buffer = readFileSync(`${TEST_PROTO_DIR}/rpc.desc.json`);
const json = JSON.parse(buffer.toString());
// This will throw if the rpc descriptor JSON cannot be decoded
proto_loader.loadFileDescriptorSetFromObject(json);
});
it('Can parse plain file descriptor set objects', () => {
// This will throw if the file descriptor object cannot be parsed
proto_loader.loadFileDescriptorSetFromObject(rpcFileDescriptorSet);
});
});

View File

@ -0,0 +1,11 @@
˜
test_protos/rpc.proto"
MyRequest
path ( Rpath"$
MyResponse
status (Rstatus20
MyService#
MyMethod
.MyRequest .MyResponsebproto3

View File

@ -0,0 +1,46 @@
{
"file": [
{
"name": "test_protos/rpc.proto",
"messageType": [
{
"name": "MyRequest",
"field": [
{
"name": "path",
"number": 1,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"jsonName": "path"
}
]
},
{
"name": "MyResponse",
"field": [
{
"name": "status",
"number": 2,
"label": "LABEL_OPTIONAL",
"type": "TYPE_INT32",
"jsonName": "status"
}
]
}
],
"service": [
{
"name": "MyService",
"method": [
{
"name": "MyMethod",
"inputType": ".MyRequest",
"outputType": ".MyResponse"
}
]
}
],
"syntax": "proto3"
}
]
}

View File

@ -0,0 +1,46 @@
export const rpcFileDescriptorSet = {
"file": [
{
"name": "test_protos/rpc.proto",
"messageType": [
{
"name": "MyRequest",
"field": [
{
"name": "path",
"number": 1,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"jsonName": "path"
}
]
},
{
"name": "MyResponse",
"field": [
{
"name": "status",
"number": 2,
"label": "LABEL_OPTIONAL",
"type": "TYPE_INT32",
"jsonName": "status"
}
]
}
],
"service": [
{
"name": "MyService",
"method": [
{
"name": "MyMethod",
"inputType": ".MyRequest",
"outputType": ".MyResponse"
}
]
}
],
"syntax": "proto3"
}
]
}

View File

@ -0,0 +1,13 @@
syntax = "proto3";
service MyService {
rpc MyMethod (MyRequest) returns (MyResponse);
}
message MyRequest {
string path = 1;
}
message MyResponse {
int32 status = 2;
}