Skip to content

Commit

Permalink
Merge pull request #2711 from n0v1/feat/method-options
Browse files Browse the repository at this point in the history
grpc-loader: Expose method options
  • Loading branch information
murgatroid99 committed Apr 9, 2024
2 parents b2e9d04 + 6c7225f commit 5674498
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/proto-loader/package.json
Expand Up @@ -57,6 +57,7 @@
"@types/node": "^10.17.26",
"@types/yargs": "^17.0.24",
"clang-format": "^1.2.2",
"google-proto-files": "^3.0.2",
"gts": "^3.1.0",
"rimraf": "^3.0.2",
"ts-node": "^10.9.2",
Expand Down
51 changes: 51 additions & 0 deletions packages/proto-loader/src/index.ts
Expand Up @@ -115,6 +115,34 @@ export interface EnumTypeDefinition extends ProtobufTypeDefinition {
format: 'Protocol Buffer 3 EnumDescriptorProto';
}

export enum IdempotencyLevel {
IDEMPOTENCY_UNKNOWN = 'IDEMPOTENCY_UNKNOWN',
NO_SIDE_EFFECTS = 'NO_SIDE_EFFECTS',
IDEMPOTENT = 'IDEMPOTENT'
}

export interface NamePart {
name_part: string;
is_extension: boolean;
}

export interface UninterpretedOption {
name?: NamePart[];
identifier_value?: string;
positive_int_value?: number;
negative_int_value?: number;
double_value?: number;
string_value?: string;
aggregate_value?: string;
}

export interface MethodOptions {
deprecated: boolean;
idempotency_level: IdempotencyLevel;
uninterpreted_option: UninterpretedOption[];
[k: string]: unknown;
}

export interface MethodDefinition<RequestType, ResponseType, OutputRequestType=RequestType, OutputResponseType=ResponseType> {
path: string;
requestStream: boolean;
Expand All @@ -126,6 +154,7 @@ export interface MethodDefinition<RequestType, ResponseType, OutputRequestType=R
originalName?: string;
requestType: MessageTypeDefinition;
responseType: MessageTypeDefinition;
options: MethodOptions;
}

export interface ServiceDefinition {
Expand Down Expand Up @@ -220,6 +249,27 @@ function createSerializer(cls: Protobuf.Type): Serialize<object> {
};
}

function mapMethodOptions(options: Partial<MethodOptions>[] | undefined): MethodOptions {
return (options || []).reduce((obj: MethodOptions, item: Partial<MethodOptions>) => {
for (const [key, value] of Object.entries(item)) {
switch (key) {
case 'uninterpreted_option' :
obj.uninterpreted_option.push(item.uninterpreted_option as UninterpretedOption);
break;
default:
obj[key] = value
}
}
return obj
},
{
deprecated: false,
idempotency_level: IdempotencyLevel.IDEMPOTENCY_UNKNOWN,
uninterpreted_option: [],
}
) as MethodOptions;
}

function createMethodDefinition(
method: Protobuf.Method,
serviceName: string,
Expand All @@ -242,6 +292,7 @@ function createMethodDefinition(
originalName: camelCase(method.name),
requestType: createMessageDefinition(requestType, fileDescriptors),
responseType: createMessageDefinition(responseType, fileDescriptors),
options: mapMethodOptions(method.parsedOptions),
};
}

Expand Down
50 changes: 50 additions & 0 deletions packages/proto-loader/test/descriptor_type_test.ts
Expand Up @@ -20,6 +20,7 @@ import { rpcFileDescriptorSet } from '../test_protos/rpc.desc';
import { readFileSync } from 'fs';

import * as proto_loader from '../src/index';
import { dirname } from 'path';

// Relative path from build output directory to test_protos directory
const TEST_PROTO_DIR = `${__dirname}/../../test_protos/`;
Expand Down Expand Up @@ -128,4 +129,53 @@ describe('Descriptor types', () => {
// This will throw if the file descriptor object cannot be parsed
proto_loader.loadFileDescriptorSetFromObject(rpcFileDescriptorSet);
});

it('Can parse method options into object correctly', () => {
const includeDirs = [
dirname(require.resolve('google-proto-files/package.json'))
];
const packageDefinition = proto_loader.loadSync(`${TEST_PROTO_DIR}/method_options.proto`, { includeDirs });
assert('Hello' in packageDefinition);
const service = packageDefinition.Hello as proto_loader.ServiceDefinition
assert.deepStrictEqual(service.Hello.options, {
deprecated: true,
idempotency_level: 'NO_SIDE_EFFECTS',
uninterpreted_option: [{
name: {
name_part: 'foo',
is_extension: false,
},
identifier_value: 'bar',
positive_int_value: 9007199254740991,
negative_int_value: -9007199254740991,
double_value: 1.2345,
string_value: 'foobar',
aggregate_value: 'foobar'
}],
'(google.api.http)': {
post: '/hello',
body: '*',
response_body: '*',
additional_bindings: {}
},
'(google.api.method_signature)': 'bar'
})
assert.deepStrictEqual(service.HelloWithoutOptions.options, {
deprecated: false,
idempotency_level: 'IDEMPOTENCY_UNKNOWN',
uninterpreted_option: []
})
assert.deepStrictEqual(service.HelloWithSomeOptions.options, {
deprecated: true,
idempotency_level: 'IDEMPOTENCY_UNKNOWN',
uninterpreted_option: [],
'(google.api.http)': {
get: '/hello',
additional_bindings: {
body: '*',
get: '/hello-world'
}
},
})
})
});
48 changes: 48 additions & 0 deletions packages/proto-loader/test_protos/method_options.proto
@@ -0,0 +1,48 @@
syntax = "proto3";

import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/httpbody.proto";

message Empty {}

message MethodSignature {
repeated string method_signature = 1;
}

service Hello {
rpc Hello (Empty) returns (Empty) {
option deprecated = true;
option idempotency_level = NO_SIDE_EFFECTS;
option uninterpreted_option = {
name: {
name_part: 'foo'
is_extension: false
}
identifier_value: 'bar'
positive_int_value: 9007199254740991
negative_int_value: -9007199254740991
double_value: 1.2345
string_value: 'foobar'
aggregate_value: 'foobar'
};
option (google.api.http) = {
post: "/hello"
body: "*"
response_body: "*"
additional_bindings: {}
};
option (google.api.method_signature) = 'bar';
}
rpc HelloWithoutOptions (Empty) returns (Empty) {}
rpc HelloWithSomeOptions (Empty) returns (Empty) {
option deprecated = true;
option (google.api.http) = {
get: "/hello"
additional_bindings: {
get: "/hello-world"
body: "*"
}
};
}
}

0 comments on commit 5674498

Please sign in to comment.