Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -5105,6 +5105,7 @@
]
}
],
"resumableUploads": [],
"longRunning": [],
"diregapicLRO": [],
"streaming": [],
Expand Down Expand Up @@ -10252,6 +10253,7 @@
]
}
],
"resumableUploads": [],
"longRunning": [],
"diregapicLRO": [],
"streaming": [],
Expand Down Expand Up @@ -16698,6 +16700,7 @@
]
}
],
"resumableUploads": [],
"longRunning": [],
"diregapicLRO": [],
"streaming": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5105,6 +5105,7 @@
]
}
],
"resumableUploads": [],
"longRunning": [],
"diregapicLRO": [],
"streaming": [],
Expand Down Expand Up @@ -10252,6 +10253,7 @@
]
}
],
"resumableUploads": [],
"longRunning": [],
"diregapicLRO": [],
"streaming": [],
Expand Down Expand Up @@ -16698,6 +16700,7 @@
]
}
],
"resumableUploads": [],
"longRunning": [],
"diregapicLRO": [],
"streaming": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ async function main(processArgv: string[]) {
'Override the list of mixins to use. Semicolon-separated list of API names to mixin, e.g. google.longrunning.Operations. Use "none" to disable all mixins.',
)
.string('mixins')
.describe(
'resumable_upload_methods',
'Semicolon-separated list of ServiceName.MethodName pairs that should be treated as resumable upload methods, e.g. ResumableUploadService.CreateResumableUpload.',
)
.string('resumable_upload_methods')
.alias('resumable_upload_methods', 'resumable-upload-methods')
.describe(
'enable_telemetry_tracing',
'Set to true to generate a library instrumented with telemetry tracing.',
Expand Down Expand Up @@ -158,6 +164,8 @@ async function main(processArgv: string[]) {
const legacyProtoLoad = argv.legacyProtoLoad as boolean | undefined;
const restNumericEnums = argv.restNumericEnums as boolean | undefined;
const mixins = argv.mixins as string | undefined;
const resumableUploadMethods = argv.resumableUploadMethods as
string | undefined;
const enableTelemetryTracing = argv.enableTelemetryTracing as
boolean | undefined;

Expand Down Expand Up @@ -252,6 +260,11 @@ async function main(processArgv: string[]) {
if (mixins) {
protocCommand.push(`--typescript_gapic_opt="mixins=${mixins}"`);
}
if (resumableUploadMethods) {
protocCommand.push(
`--typescript_gapic_opt="resumable-upload-methods=${resumableUploadMethods}"`,
);
}
protocCommand.push(...protoDirsArg);
protocCommand.push(...protoFiles);
protocCommand.push(`-I${commonProtoPath}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export class Generator {
legacyProtoLoad?: boolean;
restNumericEnums?: boolean;
mixinsOverride?: string[];
resumableUploadMethods?: string[];
format?: string | string[];
enableTelemetryTracing?: boolean;

Expand Down Expand Up @@ -267,6 +268,15 @@ export class Generator {
}
}

private readResumableUploadMethods() {
if (typeof this.paramMap['resumable-upload-methods'] === 'string') {
this.resumableUploadMethods = this.paramMap['resumable-upload-methods']
.split(';')
.map(name => name.trim())
.filter(name => name.length > 0);
}
}
Comment thread
feywind marked this conversation as resolved.

async initializeFromStdin() {
const inputBuffer = await getStdin();
const CodeGeneratorRequest = this.root.lookupType('CodeGeneratorRequest');
Expand Down Expand Up @@ -297,6 +307,7 @@ export class Generator {
this.readRestNumericEnums();
this.readFormat();
this.readEnableTelemetryTracing();
this.readResumableUploadMethods();
}
}

Expand Down Expand Up @@ -358,6 +369,7 @@ export class Generator {
restNumericEnums: this.restNumericEnums,
mixinsOverridden: this.mixinsOverride !== undefined,
enableTelemetryTracing: this.enableTelemetryTracing,
resumableUploadMethods: this.resumableUploadMethods,
});
return api;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
handwrittenLayer?: boolean;
legacyProtoLoad: boolean;
restNumericEnums: boolean;
resumableUploadMethods: string[];
documentationUri: any;

Check warning on line 47 in core/generator/gapic-generator-typescript/typescript/src/schema/api.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
newIssueUri: string;
enableTelemetryTracing?: boolean;
title?: string;
Expand Down Expand Up @@ -110,6 +111,7 @@
this.diregapic = options.diregapic ?? false;
this.legacyProtoLoad = options.legacyProtoLoad ?? false;
this.restNumericEnums = options.restNumericEnums ?? false;
this.resumableUploadMethods = options.resumableUploadMethods ?? [];
this.documentationUri =
options.serviceYaml?.publishing?.documentation_uri ?? '';
this.newIssueUri = options.serviceYaml?.publishing?.new_issue_uri ?? '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface Options {
restNumericEnums?: boolean;
mixinsOverridden?: boolean;
enableTelemetryTracing?: boolean;
resumableUploadMethods?: string[];
}

export class Naming {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@ export interface MethodDescriptorProto
longRunningResponseType?: string;
longRunningMetadataType?: string;
streaming:
| 'CLIENT_STREAMING'
| 'SERVER_STREAMING'
| 'BIDI_STREAMING'
| undefined;
'CLIENT_STREAMING' | 'SERVER_STREAMING' | 'BIDI_STREAMING' | undefined;
pagingFieldName: string | undefined;
pagingResponseType?: string;
pagingMapResponseType?: string;
Expand Down Expand Up @@ -79,6 +76,9 @@ export interface MethodDescriptorProto
bundleConfig?: BundleConfig;
toJSON: Function | undefined;
isDiregapicLRO?: boolean;
// If set, the method opts into the resumable upload protocol
// via the resumable_upload_methods generator parameter.
resumableUpload?: {uploadPrefix: string} | undefined;
// if wrappers are allowed and there is a maxResultsParamter, return true
maxResultsParameter?: boolean;
}
Expand Down Expand Up @@ -138,6 +138,7 @@ export interface ServiceDescriptorProto
longRunningOperationsMixinFlags?: OperationsMixinConfig;
protoFile: string;
diregapicLRO?: MethodDescriptorProto[];
resumableUploads: MethodDescriptorProto[];
httpRules?: protos.google.api.IHttpRule[];
selectiveGapic: SelectiveGapicConfig;
}
Expand Down Expand Up @@ -303,6 +304,27 @@ function streaming(method: MethodDescriptorProto) {
return undefined;
}

// Methods selected with the resumable_upload_methods generator parameter use
// the default upload prefix, since the parameter only identifies methods.
const DEFAULT_RESUMABLE_UPLOAD_PREFIX = '/resumable/upload';

function resumableUploadMethodNames(
serviceName: string | null | undefined,
resumableUploadMethods: string[] | undefined,
): Set<string> {
const methodNames = new Set<string>();
if (!serviceName) {
return methodNames;
}
const servicePrefix = `${serviceName}.`;
for (const methodName of resumableUploadMethods ?? []) {
if (methodName.startsWith(servicePrefix)) {
methodNames.add(methodName.substring(servicePrefix.length));
}
}
return methodNames;
}

// returns true if the method has wrappers for UInt32Value enabled
// and is a paginated call with a maxResults parameter instead of pageSize
// as of its creation, this should only be true for BigQuery
Expand Down Expand Up @@ -1031,18 +1053,31 @@ export function augmentService(parameters: AugmentServiceParameters) {
augmentedService.bundleConfigs = parameters.options.bundleConfigs?.filter(
bc => bc.serviceName === parameters.service.name,
);
const resumableUploadMethods = resumableUploadMethodNames(
parameters.service.name,
parameters.options.resumableUploadMethods,
);
augmentedService.method =
augmentedService.method?.map(method =>
augmentMethod(
augmentedService.method?.map(method => {
const augmentedMethod = augmentMethod(
{
allMessages: parameters.allMessages,
localMessages: parameters.localMessages,
service: augmentedService,
diregapic: parameters.options.diregapic,
},
method,
),
) ?? [];
);
if (
augmentedMethod.name &&
resumableUploadMethods.has(augmentedMethod.name)
) {
augmentedMethod.resumableUpload = {
uploadPrefix: DEFAULT_RESUMABLE_UPLOAD_PREFIX,
};
}
return augmentedMethod;
}) ?? [];

/* Selective GAPIC method handling. */
augmentedService.method = augmentedService.method.filter(
Expand Down Expand Up @@ -1076,7 +1111,13 @@ export function augmentService(parameters: AugmentServiceParameters) {
);
augmentedService.simpleMethods = augmentedService.method.filter(
method =>
!method.longRunning && !method.streaming && !method.pagingFieldName,
!method.longRunning &&
!method.streaming &&
!method.pagingFieldName &&
!method.resumableUpload,
);
augmentedService.resumableUploads = augmentedService.method.filter(
method => method.resumableUpload,
);
augmentedService.longRunning = augmentedService.method.filter(
method => method.longRunning,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,40 @@ describe('src/schema/api.ts', () => {
]);
});

it('should expose resumable upload methods passed as options', () => {
const fd = {} as protos.google.protobuf.FileDescriptorProto;
fd.name = 'google/cloud/test/v1/test.proto';
fd.package = 'google.cloud.test.v1';
fd.service = [{} as protos.google.protobuf.ServiceDescriptorProto];
fd.service[0].name = 'ZService';
fd.service[0].options = {
'.google.api.defaultHost': 'hostname.example.com:443',
};
const api = new API([fd], 'google.cloud.test.v1', {
grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig,
resumableUploadMethods: ['ZService.Upload', 'ZService.Resume'],
});
assert.deepStrictEqual(api.resumableUploadMethods, [
'ZService.Upload',
'ZService.Resume',
]);
});

it('should default resumable upload methods to an empty array', () => {
const fd = {} as protos.google.protobuf.FileDescriptorProto;
fd.name = 'google/cloud/test/v1/test.proto';
fd.package = 'google.cloud.test.v1';
fd.service = [{} as protos.google.protobuf.ServiceDescriptorProto];
fd.service[0].name = 'ZService';
fd.service[0].options = {
'.google.api.defaultHost': 'hostname.example.com:443',
};
const api = new API([fd], 'google.cloud.test.v1', {
grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig,
});
assert.deepStrictEqual(api.resumableUploadMethods, []);
});

it('should correctly derive a valid logging name', () => {
const fd = {} as protos.google.protobuf.FileDescriptorProto;
fd.name = 'google/cloud/test/v1/test.proto';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,9 @@
] as protos.google.protobuf.MethodDescriptorProto[];

// Simulating mixins being enabled (non-zero value)
(fd.service[0] as any).IAMPolicyMixin = 1;

Check warning on line 434 in core/generator/gapic-generator-typescript/typescript/test/unit/proto.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
(fd.service[0] as any).LocationMixin = 1;

Check warning on line 435 in core/generator/gapic-generator-typescript/typescript/test/unit/proto.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
(fd.service[0] as any).LongRunningOperationsMixin = 1;

Check warning on line 436 in core/generator/gapic-generator-typescript/typescript/test/unit/proto.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

const options: Options = {
grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig,
Expand Down Expand Up @@ -1794,3 +1794,96 @@
});
});
});

describe('src/schema/proto.ts - resumable upload methods', () => {
function augmentResumableTestService(resumableUploadMethods?: string[]) {
const fd = {} as protos.google.protobuf.FileDescriptorProto;
fd.package = 'google.samples.resumable.v1';
fd.service = [{} as protos.google.protobuf.ServiceDescriptorProto];
fd.service[0].name = 'ResumableUploadService';
fd.service[0].method = [
{
name: 'CreateResumableUpload',
inputType: '.google.samples.resumable.v1.CreateResumableUploadRequest',
outputType:
'.google.samples.resumable.v1.CreateResumableUploadResponse',
},
{
name: 'GetUploadStatus',
inputType: '.google.samples.resumable.v1.GetUploadStatusRequest',
outputType: '.google.samples.resumable.v1.GetUploadStatusResponse',
},
] as protos.google.protobuf.MethodDescriptorProto[];

const options: Options = {
grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig,
resumableUploadMethods,
};
return augmentService({
allMessages: {},
localMessages: {},
packageName: 'google.samples.resumable.v1',
service: fd.service[0],
commentsMap: new CommentsMap([fd]),
allResourceDatabase: new ResourceDatabase(),
resourceDatabase: new ResourceDatabase(),
options,
protoFile: 'fd',
});
}

it('marks methods listed in resumableUploadMethods', () => {
const augmentedService = augmentResumableTestService([
'OtherService.DoNotEnable',
'ResumableUploadService.CreateResumableUpload',
]);

assert.strictEqual(augmentedService.resumableUploads.length, 1);
assert.deepStrictEqual(
augmentedService.resumableUploads[0].resumableUpload,
{uploadPrefix: '/resumable/upload'},
);
assert.strictEqual(
augmentedService.method.find(m => m.name === 'GetUploadStatus')
?.resumableUpload,
undefined,
);
assert.strictEqual(
augmentedService.simpleMethods.some(
m => m.name === 'CreateResumableUpload',
),
false,
);
assert.strictEqual(
augmentedService.simpleMethods.some(m => m.name === 'GetUploadStatus'),
true,
);
});

it('does not enable methods that are not listed for the service', () => {
const augmentedService = augmentResumableTestService([
'ResumableUploadService.CreateResumableUpload',
]);

assert.strictEqual(
augmentedService.method.find(m => m.name === 'GetUploadStatus')
?.resumableUpload,
undefined,
);
assert.strictEqual(
augmentedService.simpleMethods.some(m => m.name === 'GetUploadStatus'),
true,
);
});

it('does not enable resumable uploads when the option is omitted', () => {
const augmentedService = augmentResumableTestService();

assert.strictEqual(augmentedService.resumableUploads.length, 0);
assert.strictEqual(
augmentedService.method.find(m => m.name === 'CreateResumableUpload')
?.resumableUpload,
undefined,
);
});
});
Loading