Skip to content

Commit

Permalink
feat: respect common resources (#81)
Browse files Browse the repository at this point in the history
* respect common resource

* gts fix

* template

* name & type

* format

* feedback

* proper warn messages

* take second element in array to match resource name

* add translate baseline test

* update baseline for merge

* update baselines
  • Loading branch information
xiaozhenliu-gg5 authored and alexander-fenster committed Oct 29, 2019
1 parent 193a899 commit ef6036c
Show file tree
Hide file tree
Showing 17 changed files with 2,498 additions and 38 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pbjs-genfiles/
.idea
.test-out-keymanager
.test-out-showcase
.test-out-translate
.client_library
*test-out*
docker/package.tgz
Expand Down
20 changes: 10 additions & 10 deletions templates/typescript_gapic/src/$version/$service_client.ts.njk
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class {{ service.name }}Client {
*/
private _descriptors: Descriptors = {page: {}, stream: {}, longrunning: {}};
private _innerApiCalls: {[name: string]: Function};
{%- if (service.pathTemplate.length > 0) %}
{%- if (service.pathTemplates.length > 0) %}
private _pathTemplates: {[name: string]: gax.PathTemplate};
{%- endif %}
auth: gax.GoogleAuth;
Expand Down Expand Up @@ -144,10 +144,10 @@ export class {{ service.name }}Client {
require("../../protos/protos.json") :
nodejsProtoPath
);
{%- if (service.pathTemplate.length > 0) %}
{%- if (service.pathTemplates.length > 0) %}
this._pathTemplates = {
{%- for template in service.pathTemplate %}
{{ template.type.toCamelCase() }}PathTemplate: new gaxModule.PathTemplate(
{%- for template in service.pathTemplates %}
{{ template.name.toCamelCase() }}PathTemplate: new gaxModule.PathTemplate(
'{{ template.pattern }}'
),
{%- endfor %}
Expand Down Expand Up @@ -495,26 +495,26 @@ export class {{ service.name }}Client {
return this._innerApiCalls.{{ method.name.toCamelCase() }}(request, options, callback);
}
{%- endfor %}
{%- if (service.pathTemplate.length > 0) %}
{%- if (service.pathTemplates.length > 0) %}
// --------------------
// -- Path templates --
// --------------------
{%- for template in service.pathTemplate %}
{{ template.type.toLowerCase() }}Path(
{%- for template in service.pathTemplates %}
{{ template.name.toLowerCase() }}Path(
{%- set paramJoiner = joiner() %}
{%- for param in template.params %}
{{-paramJoiner()-}}{{ param }}:string
{%- endfor -%}
){
return this._pathTemplates.{{ template.type.toLowerCase() }}PathTemplate.render({
return this._pathTemplates.{{ template.name.toLowerCase() }}PathTemplate.render({
{%- for param in template.params %}
{{ param }}: {{ param }},
{%- endfor %}
});
}
{%- for param in template.params %}
match{{ param.capitalize() }}From{{ template.type }}Name({{ template.type.toLowerCase() }}Name: string){
return this._pathTemplates.{{ template.type.toLowerCase() }}PathTemplate.match({{ template.type.toLowerCase() }}Name).{{ param }};
match{{ param.capitalize() }}From{{ template.name }}Name({{ template.name.toLowerCase() }}Name: string){
return this._pathTemplates.{{ template.name.toLowerCase() }}PathTemplate.match({{ template.name.toLowerCase() }}Name).{{ param }};
}
{%- endfor %}
{%- endfor %}
Expand Down
89 changes: 87 additions & 2 deletions typescript/src/schema/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as plugin from '../../../pbjs-genfiles/plugin';

import { Naming } from './naming';
import { Proto } from './proto';
import { Proto, MessagesMap, ResourceDescriptor, ResourceMap } from './proto';
import { fstat } from 'fs-extra';

export interface ProtosMap {
Expand All @@ -26,11 +26,19 @@ export class API {
fd => fd.package && fd.package.startsWith(packageName)
)
);
// construct resource map
const resourceMap = getResourceMap(fileDescriptors);
// parse resource map to Proto constructor
this.protos = fileDescriptors
.filter(fd => fd.name)
.reduce(
(map, fd) => {
map[fd.name!] = new Proto(fd, packageName, grpcServiceConfig);
map[fd.name!] = new Proto(
fd,
packageName,
grpcServiceConfig,
resourceMap
);
return map;
},
{} as ProtosMap
Expand Down Expand Up @@ -83,3 +91,80 @@ export class API {
);
}
}

function getResourceMap(
fileDescriptors: plugin.google.protobuf.IFileDescriptorProto[]
): ResourceMap {
const resourceMap: ResourceMap = {};
for (const fd of fileDescriptors) {
if (fd && fd.messageType) {
const messages = fd.messageType
.filter(message => message.name)
.reduce(
(map, message) => {
map['.' + fd.package! + '.' + message.name!] = message;
return map;
},
{} as MessagesMap
);
for (const property of Object.keys(messages)) {
const m = messages[property];
if (m && m.options) {
const option = m.options;
if (option && option['.google.api.resource']) {
const opt = option['.google.api.resource'];
const oneResource = option[
'.google.api.resource'
] as ResourceDescriptor;
if (opt.type) {
const arr = opt.type.match(/\/([^.]+)$/);
if (arr && arr[1]) {
oneResource.name = arr[1];
}
} else {
console.warn(
'In file ' +
fd.name +
' message ' +
property +
' refers to a resource which does not have a type: ' +
opt
);
continue;
}
const pattern = opt.pattern;
if (pattern && pattern[0]) {
const params = pattern[0].match(/{[a-zA-Z]+}/g) || [];
for (let i = 0; i < params.length; i++) {
params[i] = params[i].replace('{', '').replace('}', '');
}
oneResource.params = params;
}
if (oneResource.name && oneResource.params) {
resourceMap[opt.type!] = oneResource;
} else if (oneResource.name) {
console.warn(
'In file ' +
fd.name +
' message ' +
property +
' refers to a resource which does not have a proper pattern : ' +
opt
);
} else {
console.warn(
'In file ' +
fd.name +
' message ' +
property +
' refers to a resource which does not have a proper name : ' +
opt
);
}
}
}
}
}
}
return resourceMap;
}
61 changes: 35 additions & 26 deletions typescript/src/schema/proto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,22 @@ interface ServiceDescriptorProto
port: number;
oauthScopes: string[];
comments: string;
pathTemplate: ResourceDescriptor[];
pathTemplates: ResourceDescriptor[];
commentsMap: CommentsMap;
retryableCodeMap: RetryableCodeMap;
grpcServiceConfig: plugin.grpc.service_config.ServiceConfig;
}

export interface ResourceDescriptor
extends plugin.google.api.IResourceDescriptor {
name: string;
params: string[];
}

export interface ResourceMap {
[name: string]: ResourceDescriptor;
}

export interface ServicesMap {
[name: string]: ServiceDescriptorProto;
}
Expand Down Expand Up @@ -385,7 +390,8 @@ function augmentService(
packageName: string,
service: plugin.google.protobuf.IServiceDescriptorProto,
commentsMap: CommentsMap,
grpcServiceConfig: plugin.grpc.service_config.ServiceConfig
grpcServiceConfig: plugin.grpc.service_config.ServiceConfig,
resourceMap: ResourceMap
) {
const augmentedService = service as ServiceDescriptorProto;
augmentedService.packageName = packageName;
Expand Down Expand Up @@ -442,32 +448,33 @@ function augmentService(
'.google.api.oauthScopes'
].split(',');
}
augmentedService.pathTemplate = [];
augmentedService.pathTemplates = [];
for (const property of Object.keys(messages)) {
const m = messages[property];
if (m && m.options) {
const option = m.options;
if (option && option['.google.api.resource']) {
const opt = option['.google.api.resource'];
const onePathTemplate = option[
'.google.api.resource'
] as ResourceDescriptor;
if (opt.type) {
const arr = opt.type.match(/\/([^.]+)$/);
if (arr) {
opt.type = arr[arr.length - 1];
}
}
const pattern = opt.pattern;
//TODO: SUPPORT MULTIPLE PATTERNS
if (pattern && pattern[0]) {
const params = pattern[0].match(/{[a-zA-Z]+}/g) || [];
for (let i = 0; i < params.length; i++) {
params[i] = params[i].replace('{', '').replace('}', '');
if (m && m.field) {
const fields = m.field;
for (const fieldDescriptor of fields) {
if (fieldDescriptor && fieldDescriptor.options) {
const option = fieldDescriptor.options;
if (option && option['.google.api.resourceReference']) {
const resourceReference = option['.google.api.resourceReference'];
const type = resourceReference.type;
if (!type || !resourceMap[type.toString()]) {
console.warn(
'In service proto ' +
service.name +
' message ' +
property +
' refers to an unkown resource: ' +
resourceReference
);
continue;
}
const resource = resourceMap[resourceReference.type!.toString()];
if (augmentedService.pathTemplates.includes(resource)) continue;
augmentedService.pathTemplates.push(resource);
}
onePathTemplate.params = params;
}
augmentedService.pathTemplate.push(onePathTemplate);
}
}
}
Expand All @@ -485,7 +492,8 @@ export class Proto {
constructor(
fd: plugin.google.protobuf.IFileDescriptorProto,
packageName: string,
grpcServiceConfig: plugin.grpc.service_config.ServiceConfig
grpcServiceConfig: plugin.grpc.service_config.ServiceConfig,
resourceMap: ResourceMap
) {
fd.enumType = fd.enumType || [];
fd.messageType = fd.messageType || [];
Expand Down Expand Up @@ -525,7 +533,8 @@ export class Proto {
packageName,
service,
commentsMap,
grpcServiceConfig
grpcServiceConfig,
resourceMap
)
)
.reduce(
Expand Down
68 changes: 68 additions & 0 deletions typescript/test/protos/google/cloud/common_resources.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2019 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
//
// http://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.

// This file contains stub messages for common resources in GCP.
// It is not intended to be directly generated, and is instead used by
// other tooling to be able to match common resource patterns.
syntax = "proto3";

package google.cloud;

import "google/api/resource.proto";


message Project {
option (google.api.resource) = {
type: "cloudresourcemanager.googleapis.com/Project"
pattern: "projects/{project}"
};

string name = 1;
}

message Organization {
option (google.api.resource) = {
type: "cloudresourcemanager.googleapis.com/Organization"
pattern: "organizations/{organization}"
};

string name = 1;
}

message Folder {
option (google.api.resource) = {
type: "cloudresourcemanager.googleapis.com/Folder"
pattern: "folders/{folder}"
};

string name = 1;
}

message BillingAccount {
option (google.api.resource) = {
type: "cloudbilling.googleapis.com/BillingAccount"
pattern: "billingAccounts/{billing_account}"
};

string name = 1;
}

message Location {
option (google.api.resource) = {
type: "locations.googleapis.com/Location"
pattern: "projects/{project}/locations/{location}"
};

string name = 1;
}
Loading

0 comments on commit ef6036c

Please sign in to comment.