History

Mark Google media downloads as binary responses (#1096)

batumilove committed 4 months ago

Commit 4ef21991 parent 907502e2 files · +93 −11Browse files

Unified · Split

2 files
  1. packages/plugins/google/src/sdk/discovery.test.ts +58 −0
  2. packages/plugins/google/src/sdk/discovery.ts +35 −11
packages/plugins/google/src/sdk/discovery.test.ts+58−0
@@ -274,6 +274,64 @@ it.effect("converts Google Discovery documents into Executor-preserving OpenAPI · 273 unmodified lines
274 }),274 }),
275);275);
276 276
277it.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
277it.effect("bundles Google Discovery documents into one Google OpenAPI source", () =>335it.effect("bundles Google Discovery documents into one Google OpenAPI source", () =>
278 Effect.gen(function* () {336 Effect.gen(function* () {
279 const result = yield* convertGoogleDiscoveryBundleToOpenApi({337 const result = yield* convertGoogleDiscoveryBundleToOpenApi({
packages/plugins/google/src/sdk/discovery.ts+35−11
@@ -76,11 +76,12 @@ type OpenApiOperationObject = { · 75 unmodified lines
76 readonly responses: {76 readonly responses: {
77 readonly "200": {77 readonly "200": {
78 readonly description: "Successful response";78 readonly description: "Successful response";
79 readonly content: {79 readonly content: Record<
80 readonly "application/json": {80 string,
81 {
81 readonly schema: OpenApiSchemaObject;82 readonly schema: OpenApiSchemaObject;
82 };83 }
83 };84 >;
84 };85 };
85 };86 };
86 readonly security?: readonly Record<string, readonly string[]>[];87 readonly security?: readonly Record<string, readonly string[]>[];
@@ -174,6 +175,8 @@ const DiscoveryMethod = Schema.Struct({ · 87 unmodified lines
174 request: Schema.optional(DiscoveryRef),175 request: Schema.optional(DiscoveryRef),
175 response: Schema.optional(DiscoveryRef),176 response: Schema.optional(DiscoveryRef),
176 scopes: TextArray,177 scopes: TextArray,
178 supportsMediaDownload: Schema.optional(Schema.Boolean),
179 useMediaDownloadService: Schema.optional(Schema.Boolean),
177});180});
178type DiscoveryMethod = typeof DiscoveryMethod.Type;181type DiscoveryMethod = typeof DiscoveryMethod.Type;
179 182
@@ -570,6 +573,33 @@ const discoveryDocumentInfo = ( · 390 unmodified lines
570 };573 };
571 });574 });
572 575
576const 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
573const buildDiscoveryOperation = (input: {603const buildDiscoveryOperation = (input: {
574 readonly document: DiscoveryDocument;604 readonly document: DiscoveryDocument;
575 readonly method: DiscoveryMethod;605 readonly method: DiscoveryMethod;
@@ -635,13 +665,7 @@ const buildDiscoveryOperation = (input: { · 59 unmodified lines
635 responses: {665 responses: {
636 "200": {666 "200": {
637 description: "Successful response",667 description: "Successful response",
638 content: {668 content: googleDiscoveryResponseContent(input.method, schemaNameForRef),
639 "application/json": {
640 schema: input.method.response?.$ref
641 ? { $ref: schemaRef(schemaNameForRef(input.method.response.$ref)) }
642 : {},
643 },
644 },
645 },669 },
646 },670 },
647 ...(methodScopes.length > 0 ? { security: [{ googleOAuth2: methodScopes }] } : {}),671 ...(methodScopes.length > 0 ? { security: [{ googleOAuth2: methodScopes }] } : {}),