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