Skip to content

Commit

Permalink
feat: set main service name (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-fenster authored Dec 19, 2019
1 parent 599e23c commit be27d86
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 135 deletions.
29 changes: 16 additions & 13 deletions typescript/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export class Generator {
paramMap: OptionsMap;
// This field is for users passing proper publish package name like @google-cloud/text-to-speech.
publishName?: string;
// For historical reasons, Webpack library name matches "the main" service of the client library.
// Sometimes it's hard to figure out automatically, so making this an option.
mainServiceName?: string;

constructor() {
this.request = plugin.google.protobuf.compiler.CodeGeneratorRequest.create();
Expand Down Expand Up @@ -99,10 +102,12 @@ export class Generator {
}
}

private async readPublishPackageName(map: OptionsMap) {
if (map?.['package-name']) {
this.publishName = map['package-name'];
}
private readPublishPackageName(map: OptionsMap) {
this.publishName = map['package-name'];
}

private readMainServiceName(map: OptionsMap) {
this.mainServiceName = map['main-service'];
}

async initializeFromStdin() {
Expand All @@ -113,7 +118,8 @@ export class Generator {
if (this.request.parameter) {
this.getParamMap(this.request.parameter);
await this.readGrpcServiceConfig(this.paramMap);
await this.readPublishPackageName(this.paramMap);
this.readPublishPackageName(this.paramMap);
this.readMainServiceName(this.paramMap);
}
}

Expand Down Expand Up @@ -146,12 +152,11 @@ export class Generator {
if (packageName === '') {
throw new Error('Cannot get package name to generate.');
}
const api = new API(
this.request.protoFile,
packageName,
this.grpcServiceConfig,
this.publishName
);
const api = new API(this.request.protoFile, packageName, {
grpcServiceConfig: this.grpcServiceConfig,
publishName: this.publishName,
mainServiceName: this.mainServiceName,
});
return api;
}

Expand All @@ -161,8 +166,6 @@ export class Generator {
}

async generate() {
const fileToGenerate = this.request.fileToGenerate;

this.response = plugin.google.protobuf.compiler.CodeGeneratorResponse.create();

this.addProtosToResponse();
Expand Down
64 changes: 40 additions & 24 deletions typescript/src/schema/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,29 @@ export class API {
protos: ProtosMap;
hostName?: string;
port?: string;
mainServiceName?: string;
// This field is for users passing proper publish package name like @google-cloud/text-to-speech.
publishName: string;
// oauth_scopes: plugin.google.protobuf.IServiceOptions.prototype[".google.api.oauthScopes"];
// TODO: subpackages
// For historical reasons, Webpack library name matches "the main" service of the client library.
// Sometimes it's hard to figure out automatically, so making this an option.
mainServiceName: string;

constructor(
fileDescriptors: plugin.google.protobuf.IFileDescriptorProto[],
packageName: string,
grpcServiceConfig: plugin.grpc.service_config.ServiceConfig,
publishName?: string
options: {
grpcServiceConfig: plugin.grpc.service_config.ServiceConfig;
publishName?: string;
mainServiceName?: string;
}
) {
this.naming = new Naming(
fileDescriptors.filter(
fd => fd.package && fd.package.startsWith(packageName)
)
);
// users specify the actual package name, if not, set it to product name.
this.publishName = publishName || this.naming.productName.toKebabCase();
this.publishName =
options.publishName || this.naming.productName.toKebabCase();
// construct resource map
const resourceMap = getResourceDatabase(fileDescriptors);
// parse resource map to Proto constructor
Expand All @@ -61,27 +65,36 @@ export class API {
map[fd.name!] = new Proto(
fd,
packageName,
grpcServiceConfig,
options.grpcServiceConfig,
resourceMap
);
return map;
}, {} as ProtosMap);
fileDescriptors.forEach(fd => {
if (fd.service) {
fd.service.forEach(service => {
if (service.options) {
const serviceOption = service.options;
if (serviceOption['.google.api.defaultHost']) {
const defaultHost = serviceOption['.google.api.defaultHost'];
const arr = defaultHost.split(':');
this.hostName = arr[0] || 'localhost';
this.port = arr.length > 1 ? arr[1] : '443';
this.mainServiceName = service.name || this.naming.name;
}
}
});
}
});

const serviceNamesList: string[] = [];
fileDescriptors
.filter(fd => fd.service)
.reduce((servicesList, fd) => {
servicesList.push(...fd.service!);
return servicesList;
}, [] as plugin.google.protobuf.IServiceDescriptorProto[])
.filter(service => service?.options?.['.google.api.defaultHost'])
.sort((service1, service2) =>
service1.name!.localeCompare(service2.name!)
)
.forEach(service => {
const defaultHost = service!.options!['.google.api.defaultHost']!;
const [hostname, port] = defaultHost.split(':');
if (hostname && this.hostName && hostname !== this.hostName) {
console.warn(
`Warning: different hostnames ${hostname} and ${this.hostName} within the same client are not supported.`
);
}
this.hostName = hostname || this.hostName || 'localhost';
this.port = port ?? this.port ?? '443';
serviceNamesList.push(service.name || this.naming.name);
});
this.mainServiceName = options.mainServiceName || serviceNamesList[0];
}

get services() {
Expand All @@ -93,7 +106,10 @@ export class API {
...Object.keys(proto.services).map(name => proto.services[name])
);
return retval;
}, [] as plugin.google.protobuf.IServiceDescriptorProto[]);
}, [] as plugin.google.protobuf.IServiceDescriptorProto[])
.sort((service1, service2) =>
service1.name!.localeCompare(service2.name!)
);
}

get filesToGenerate() {
Expand Down
12 changes: 11 additions & 1 deletion typescript/src/start_script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ const argv = yargs
.describe('grpc-service-config', 'Path to gRPC service config JSON')
.alias('package-name', 'package_name')
.describe('package-name', 'Publish package name')
.alias('main-service', 'main_service')
.describe(
'main_service',
'Main service name (if the package has multiple services, this one will be used for Webpack bundle name)'
)
.alias('common-proto-path', 'common_protos_path')
.describe(
'common_proto_path',
Expand All @@ -46,7 +51,7 @@ const argv = yargs
const outputDir = argv.outputDir as string;
const grpcServiceConfig = argv.grpcServiceConfig as string | undefined;
const packageName = argv.packageName as string | undefined;

const mainServiceName = argv.mainService as string | undefined;
const protoDirs: string[] = [];
if (argv.I) {
protoDirs.push(...(argv.I as string[]));
Expand Down Expand Up @@ -77,6 +82,11 @@ if (grpcServiceConfig) {
if (packageName) {
protocCommand.push(`--typescript_gapic_opt="package-name=${packageName}"`);
}
if (mainServiceName) {
protocCommand.push(
`--typescript_gapic_opt="main-service=${mainServiceName}"`
);
}
protocCommand.push(...protoDirsArg);
protocCommand.push(...protoFiles);
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/longrunning/operations.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
import "google/rpc/status.proto";

package google.showcase.v1beta1;

option go_package = "github.com/googleapis/gapic-showcase/server/genproto";
option java_package = "com.google.showcase.v1beta1";
option java_multiple_files = true;

// This service is used showcase the four main types of rpcs - unary, server
// side streaming, client side streaming, and bidirectional streaming. This
// service also exposes methods that explicitly implement server delay, and
// paginated calls.
service Echo {
// This service is meant to only run locally on the port 7469 (keypad digits
// for "show").
option (google.api.default_host) = "localhost:7469";

// This method simply echos the request. This method is showcases unary rpcs.
rpc Echo(EchoRequest) returns (EchoResponse) {
option (google.api.http) = {
post: "/v1beta1/echo:echo"
body: "*"
};
}

// This method split the given content into words and will pass each word back
// through the stream. This method showcases server-side streaming rpcs.
rpc Expand(ExpandRequest) returns (stream EchoResponse) {
option (google.api.http) = {
post: "/v1beta1/echo:expand"
body: "*"
};
// TODO(landrito): change this to be `fields: ["content", "error"]` once
// github.com/dcodeIO/protobuf.js/issues/1094 has been resolved.
option (google.api.method_signature) = "content,error";
}

// This method will collect the words given to it. When the stream is closed
// by the client, this method will return the a concatenation of the strings
// passed to it. This method showcases client-side streaming rpcs.
rpc Collect(stream EchoRequest) returns (EchoResponse) {
option (google.api.http) = {
post: "/v1beta1/echo:collect"
body: "*"
};
}

// This method, upon receiving a request on the stream, the same content will
// be passed back on the stream. This method showcases bidirectional
// streaming rpcs.
rpc Chat(stream EchoRequest) returns (stream EchoResponse);

// This is similar to the Expand method but instead of returning a stream of
// expanded words, this method returns a paged list of expanded words.
rpc PagedExpand(PagedExpandRequest) returns (PagedExpandResponse) {
option (google.api.http) = {
post: "/v1beta1/echo:pagedExpand"
body: "*"
};
}

// This method will wait the requested amount of and then return.
// This method showcases how a client handles a request timing out.
rpc Wait(WaitRequest) returns (google.longrunning.Operation) {
option (google.api.http) = {
post: "/v1beta1/echo:wait"
body: "*"
};
option (google.longrunning.operation_info) = {
response_type: "WaitResponse"
metadata_type: "WaitMetadata"
};
}
}

// The request message used for the Echo, Collect and Chat methods. If content
// is set in this message then the request will succeed. If status is set in
// this message then the status will be returned as an error.
message EchoRequest {
oneof response {
// The content to be echoed by the server.
string content = 1;

// The error to be thrown by the server.
google.rpc.Status error = 2;
}
}

// The response message for the Echo methods.
message EchoResponse {
// The content specified in the request.
string content = 1;
}

// The request message for the Expand method.
message ExpandRequest {
// The content that will be split into words and returned on the stream.
string content = 1;

// The error that is thrown after all words are sent on the stream.
google.rpc.Status error = 2;
}

// The request for the PagedExpand method.
message PagedExpandRequest {
// The string to expand.
string content = 1 [(google.api.field_behavior) = REQUIRED];

// The amount of words to returned in each page.
int32 page_size = 2;

// The position of the page to be returned.
string page_token = 3;
}

// The response for the PagedExpand method.
message PagedExpandResponse {
// The words that were expanded.
repeated EchoResponse responses = 1;

// The next page token.
string next_page_token = 2;
}

// The request for Wait method.
message WaitRequest {
oneof end {
// The time that this operation will complete.
google.protobuf.Timestamp end_time = 1;

// The duration of this operation.
google.protobuf.Duration ttl = 4;
}

oneof response {
// The error that will be returned by the server. If this code is specified
// to be the OK rpc code, an empty response will be returned.
google.rpc.Status error = 2;

// The response to be returned on operation completion.
WaitResponse success = 3;
}
}

// The result of the Wait operation.
message WaitResponse {
// This content of the result.
string content = 1;
}

// The metadata for Wait operation.
message WaitMetadata {
// The time that this operation will complete.
google.protobuf.Timestamp end_time =1;
}
4 changes: 2 additions & 2 deletions typescript/test/testdata/showcase/webpack.config.js.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const path = require('path');
module.exports = {
entry: './src/index.ts',
output: {
library: 'Echo',
filename: './echo.js',
library: 'ShowcaseService',
filename: './showcase-service.js',
},
node: {
child_process: 'empty',
Expand Down
Loading

0 comments on commit be27d86

Please sign in to comment.