From e71cb5cb5185850a69bb8e5303db2d56b6e73e40 Mon Sep 17 00:00:00 2001 From: Alexander Fenster Date: Tue, 10 Dec 2019 15:36:12 -0800 Subject: [PATCH] feat: support file-level resource annotations (#169) --- typescript/src/schema/api.ts | 130 +++++++++--------- typescript/src/schema/proto.ts | 9 +- .../google/cloud/common_resources.proto | 56 +++----- 3 files changed, 87 insertions(+), 108 deletions(-) diff --git a/typescript/src/schema/api.ts b/typescript/src/schema/api.ts index ddd04632c..3051a299f 100644 --- a/typescript/src/schema/api.ts +++ b/typescript/src/schema/api.ts @@ -112,75 +112,75 @@ export class API { } } +function processOneResource( + option: ResourceDescriptor | undefined, + fileAndMessageNames: string, + resourceMap: ResourceMap +): void { + if (!option) { + return; + } + if (!option.type) { + console.warn( + `Warning: in ${fileAndMessageNames} refers to a resource which does not have a type: ${option}` + ); + return; + } + + const arr = option.type.match(/\/([^.]+)$/); + if (!arr?.[1]) { + console.warn( + `Warning: in ${fileAndMessageNames} refers to a resource which does not have a proper name: ${option}` + ); + return; + } + option.name = arr[1]; + + const pattern = option.pattern; + if (!pattern?.[0]) { + console.warn( + `Warning: in ${fileAndMessageNames} refers to a resource which does not have a proper pattern: ${option}` + ); + return; + } + const params = pattern[0].match(/{[a-zA-Z]+}/g) || []; + for (let i = 0; i < params.length; i++) { + params[i] = params[i].replace('{', '').replace('}', ''); + } + option.params = params; + + resourceMap[option.type!] = option; +} + 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?.options) { - const option = m.options; - if (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?.[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?.[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( - 'Warning: in file ' + - fd.name + - ' message ' + - property + - ' refers to a resource which does not have a proper pattern : ' + - opt - ); - } else { - console.warn( - 'Warning: in file ' + - fd.name + - ' message ' + - property + - ' refers to a resource which does not have a proper name : ' + - opt - ); - } - } - } - } + for (const fd of fileDescriptors.filter(fd => fd)) { + // process file-level options + for (const resource of fd.options?.['.google.api.resourceDefinition'] ?? + []) { + processOneResource( + resource as ResourceDescriptor, + `file ${fd.name} resource_definition option`, + resourceMap + ); + } + + 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]; + processOneResource( + m?.options?.['.google.api.resource'] as ResourceDescriptor | undefined, + `file ${fd.name} message ${property}`, + resourceMap + ); } } return resourceMap; diff --git a/typescript/src/schema/proto.ts b/typescript/src/schema/proto.ts index a380e94b7..230aa9eac 100644 --- a/typescript/src/schema/proto.ts +++ b/typescript/src/schema/proto.ts @@ -16,7 +16,6 @@ import * as plugin from '../../../pbjs-genfiles/plugin'; import { CommentsMap, Comment } from './comments'; import * as objectHash from 'object-hash'; import { milliseconds } from '../util'; -import { FileSystemLoader } from 'nunjucks'; const defaultNonIdempotentRetryCodesName = 'non_idempotent'; const defaultNonIdempotentCodes: plugin.google.rpc.Code[] = []; @@ -540,13 +539,9 @@ function augmentService( const resourceReference = option['.google.api.resourceReference']; const type = resourceReference.type; if (!type || !resourceMap[type.toString()]) { + const resourceJson = JSON.stringify(resourceReference); console.warn( - 'Warning: in service proto ' + - service.name + - ' message ' + - property + - ' refers to an unknown resource: ' + - JSON.stringify(resourceReference) + `Warning: in service proto ${service.name} message ${property} refers to an unknown resource: ${resourceJson}` ); continue; } diff --git a/typescript/test/protos/google/cloud/common_resources.proto b/typescript/test/protos/google/cloud/common_resources.proto index 5d795bff1..56c9f800d 100644 --- a/typescript/test/protos/google/cloud/common_resources.proto +++ b/typescript/test/protos/google/cloud/common_resources.proto @@ -22,47 +22,31 @@ package google.cloud; import "google/api/resource.proto"; -message Project { - option (google.api.resource) = { - type: "cloudresourcemanager.googleapis.com/Project" - pattern: "projects/{project}" - }; +option (google.api.resource_definition) = { + 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}" - }; +option (google.api.resource_definition) = { + 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}" - }; +option (google.api.resource_definition) = { + 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}" - }; +option (google.api.resource_definition) = { + type: "cloudbilling.googleapis.com/BillingAccount" + pattern: "billingAccounts/{billing_account}" +}; - string name = 1; -} +option (google.api.resource_definition) = { + type: "locations.googleapis.com/Location" + pattern: "projects/{project}/locations/{location}" +}; -message Location { - option (google.api.resource) = { - type: "locations.googleapis.com/Location" - pattern: "projects/{project}/locations/{location}" - }; - - string name = 1; -}