Skip to content

Commit 4ef2199

Browse files
authored
Mark Google media downloads as binary responses (#1096)
1 parent 907502e commit 4ef2199

2 files changed

Lines changed: 93 additions & 11 deletions

File tree

packages/plugins/google/src/sdk/discovery.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,64 @@ it.effect("converts Google Discovery documents into Executor-preserving OpenAPI
274274
}),
275275
);
276276

277+
it.effect("marks Google Discovery media-download methods as binary responses", () =>
278+
Effect.gen(function* () {
279+
const result = yield* convertGoogleDiscoveryToOpenApi({
280+
discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
281+
// @effect-diagnostics-next-line preferSchemaOverJson:off
282+
documentText: JSON.stringify({
283+
name: "drive",
284+
version: "v3",
285+
title: "Drive API",
286+
rootUrl: "https://www.googleapis.com/",
287+
servicePath: "drive/v3/",
288+
resources: {
289+
files: {
290+
methods: {
291+
export: {
292+
id: "drive.files.export",
293+
httpMethod: "GET",
294+
path: "files/{fileId}/export",
295+
supportsMediaDownload: true,
296+
useMediaDownloadService: true,
297+
parameters: {
298+
fileId: { location: "path", required: true, type: "string" },
299+
mimeType: { location: "query", required: true, type: "string" },
300+
},
301+
},
302+
},
303+
},
304+
},
305+
schemas: {},
306+
}),
307+
});
308+
309+
const spec = decodeConvertedSpec(result.specText);
310+
const operation = spec.paths["/files/{fileId}/export"]?.get;
311+
expect(operation?.responses).toMatchObject({
312+
"200": {
313+
content: {
314+
"application/octet-stream": {
315+
schema: { type: "string", format: "binary" },
316+
},
317+
},
318+
},
319+
});
320+
321+
const parsed = yield* parse(result.specText);
322+
const extracted = yield* extract(parsed);
323+
const exportOperation = extracted.operations.find(
324+
(candidate) => candidate.operationId === "files.export",
325+
);
326+
expect(exportOperation?.operationId).toBe("files.export");
327+
const responseFileHint = Option.flatMap(
328+
exportOperation?.responseBody ?? Option.none(),
329+
(body) => body.fileHint,
330+
);
331+
expect(Option.isSome(responseFileHint)).toBe(true);
332+
}),
333+
);
334+
277335
it.effect("bundles Google Discovery documents into one Google OpenAPI source", () =>
278336
Effect.gen(function* () {
279337
const result = yield* convertGoogleDiscoveryBundleToOpenApi({

packages/plugins/google/src/sdk/discovery.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ type OpenApiOperationObject = {
7676
readonly responses: {
7777
readonly "200": {
7878
readonly description: "Successful response";
79-
readonly content: {
80-
readonly "application/json": {
79+
readonly content: Record<
80+
string,
81+
{
8182
readonly schema: OpenApiSchemaObject;
82-
};
83-
};
83+
}
84+
>;
8485
};
8586
};
8687
readonly security?: readonly Record<string, readonly string[]>[];
@@ -174,6 +175,8 @@ const DiscoveryMethod = Schema.Struct({
174175
request: Schema.optional(DiscoveryRef),
175176
response: Schema.optional(DiscoveryRef),
176177
scopes: TextArray,
178+
supportsMediaDownload: Schema.optional(Schema.Boolean),
179+
useMediaDownloadService: Schema.optional(Schema.Boolean),
177180
});
178181
type DiscoveryMethod = typeof DiscoveryMethod.Type;
179182

@@ -570,6 +573,33 @@ const discoveryDocumentInfo = (
570573
};
571574
});
572575

576+
const googleDiscoveryResponseContent = (
577+
method: DiscoveryMethod,
578+
schemaNameForRef: (name: string) => string,
579+
): OpenApiOperationObject["responses"]["200"]["content"] => {
580+
if (
581+
(method.supportsMediaDownload === true || method.useMediaDownloadService === true) &&
582+
method.response === undefined
583+
) {
584+
return {
585+
"application/octet-stream": {
586+
schema: {
587+
type: "string",
588+
format: "binary",
589+
},
590+
},
591+
};
592+
}
593+
594+
return {
595+
"application/json": {
596+
schema: method.response?.$ref
597+
? { $ref: schemaRef(schemaNameForRef(method.response.$ref)) }
598+
: {},
599+
},
600+
};
601+
};
602+
573603
const buildDiscoveryOperation = (input: {
574604
readonly document: DiscoveryDocument;
575605
readonly method: DiscoveryMethod;
@@ -635,13 +665,7 @@ const buildDiscoveryOperation = (input: {
635665
responses: {
636666
"200": {
637667
description: "Successful response",
638-
content: {
639-
"application/json": {
640-
schema: input.method.response?.$ref
641-
? { $ref: schemaRef(schemaNameForRef(input.method.response.$ref)) }
642-
: {},
643-
},
644-
},
668+
content: googleDiscoveryResponseContent(input.method, schemaNameForRef),
645669
},
646670
},
647671
...(methodScopes.length > 0 ? { security: [{ googleOAuth2: methodScopes }] } : {}),

0 commit comments

Comments
 (0)