-
-
Notifications
You must be signed in to change notification settings - Fork 487
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for File Upload #1214
Comments
Currently, openapi-typescript ignores In this example, I believe That’s why the transform API exists—to give openapi-typescript additional insight into how your APIs may be transformed as they’re consumed—whether or not you parse That said, if others prefer |
Thanks for pointing that out, I was able to use the transform API to switch the property type to File. I think what might help is mentioning this approach as a tip in the introduction section of the documentation; I had avoided the Node JS section because I thought it didn't apply for my use case. Also perhaps an Thanks again for the quick response. I really like your library, will definitely recommend it to others, great job! |
I agree that there should be a prominent line in the docs noting that |
I spent the last 3 hours trying to figure this out. Part of it was the I'm going with the I'd love it if |
Is it true tho? This is what the docs say about content type for a part in multipart body: For
or
I think because they insist on using JSON Schema for validating things that aren't JSON, like form-data, and JSON Schema doesn't have type "binary", they reused the string type (stupid) and made this magical option which must be handled specially. const formData = new FormData();
// prop with primitive value (strings, numbers, booleans, I guess?)
formData.append("prop1", String(value)); // same as sending a text/plain blob
// prop with object
formData.append(
"prop2",
new Blob([JSON.stringify(value)], { type: "application/json" }),
);
// for 3.0: prop with type "string", but format "binary"
// for 3.1: everything except object
formData.append(
"prop3",
new Blob([value], { type: "application/octet-stream" }),
);
// (and for arrays just call append many times for single property)
// send
fetch("https://example.com", { method: "POST", body: formData }); I think this lib should do similar thing.
|
For those that, like me, got stuck trying to make this work. Here's my complete solution for file uploads and downloads.
openapi-typescript types
import openapiTS from 'openapi-typescript';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
const baseDir = path.dirname(fileURLToPath(import.meta.url));
const localPath = new URL(path.resolve(baseDir, '../apiSpec.json'), import.meta.url);
const output = await openapiTS(localPath, {
transform(schemaObject, metadata) {
if (schemaObject.format === 'binary') {
if (metadata.path.endsWith('multipart/form-data')) {
return schemaObject.nullable ? 'File | null' : 'File';
}
if (metadata.path.endsWith('application/octet-stream')) {
return schemaObject.nullable ? 'Blob | null' : 'Blob';
}
}
return undefined;
}
});
await fs.promises.writeFile(path.resolve(baseDir, '../src/schema.d.ts'), output); This sets the type to THIS WILL NOT WORK: "multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/SomeFormSchema"
}
} THIS WORKS: "multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"file": {
"type": "string",
"format": "binary"
}
}
}
} openapi-fetch clientUploading a fileasync function upload(file: File) {
await client.POST('/path/to/endpoint', {
body: {
file
},
bodySerializer: (body) => {
const formData = new FormData();
formData.set('file', body.file);
return formData;
}
});
} Downloading a fileasync function download(): Blob | undefined {
const { data } = client.GET('/path/to/endpoint', {
parseAs: 'blob'
});
return data;
} |
Also stumbled up on this. I think it would be great to change the default options or at least give a CLI option to apply less conservative parsing? |
Also keep in mind that NextJS server actions cannot take objects with Blob data in them yet. If you're using server actions and for some reason don't use Just in case you're struggling with this, it might be fixed in an upcoming release based on this PR. |
Not to add another, "me too" for the requested change, but I just spent an unhealthy amount of time trying to remedy this on my end. When trying to use the transform API, I kept getting errors because the schema didn't match the expected input for transformation, despite it being a valid schema otherwise. I started changing several parts of the schema, including adding fields that are not required according to the OpenAPI spec (e.g. I understand the resistance to imposing opinion. I think an optional flag when generating the schema viaa CLI could work just as well. It appears that a vast majority of use-cases for |
Thank you so much 🥇 |
Based on what @fredsensibill did, I've implemented his solution using How the given example works: it grabs, for example, To be able to use top-level await make sure to use "compilerOptions": {
"target": "ES2017",
"module": "Preserve"
}, Here's import fs from 'fs';
import openapiTS, { OpenAPITSOptions } from 'openapi-typescript';
const inputFolder = './specs';
const outputFolder = './schemas/specs';
const files = await fs.promises.readdir(inputFolder);
const schemaFilenames = files.filter(f => f.endsWith('.yaml')).map(f => f.split('.yaml')[0]);
// common options object for the AST
const options: OpenAPITSOptions = {
transform(schemaObject, metadata) {
if (schemaObject.format === 'binary') {
if (metadata.path.endsWith('multipart/form-data')) {
return schemaObject.nullable ? 'File | null' : 'File';
}
if (metadata.path.endsWith('application/octet-stream')) {
return schemaObject.nullable ? 'Blob | null' : 'Blob';
}
}
return undefined;
}
}
// grab all the outputs
const promises: Promise<string>[] = schemaFilenames.map((schemaFilename) =>
openapiTS(new URL(`${inputFolder}/${schemaFilename}.yaml`, import.meta.url), options)
);
const resolvedPromises = await Promise.all(promises);
// write to the filesystem
await fs.promises.mkdir(outputFolder, { recursive: true });
const fsPromises = schemaFilenames.map((schemaFilename, idx) =>
fs.promises.writeFile(`${outputFolder}/${schemaFilename}.ts`, resolvedPromises[idx], { flag: 'w+' })
);
await Promise.all(fsPromises); |
This issue is stale because it has been open for 90 days with no activity. If there is no activity in the next 7 days, the issue will be closed. |
This issue was closed because it has been inactive for 7 days since being marked as stale. Please open a new issue if you believe you are encountering a related problem. |
Version 7+ of openapi-typescript requires This is working for me, but I do not work with the TypeScript AST very often and parts of it look dodgy. Please consider I am just some dude in a comment section before using this for real. import type { OpenAPITSOptions } from 'openapi-typescript';
import { factory } from 'typescript';
const options: OpenAPITSOptions = {
transform({ format, nullable }, { path }) {
if (format !== 'binary' || !path) {
return;
}
const typeName = path.includes('multipart~1form-data')
? 'File'
: path.includes('application~1octet-stream')
? 'Blob'
: null;
if (!typeName) {
return;
}
const node = factory.createTypeReferenceNode(typeName);
return nullable
? factory.createUnionTypeNode([
node,
factory.createTypeReferenceNode('null')
])
: node;
}
}; |
FWIW, also sharing my solution, same disclaimer as above (just a random comment guy). To be called directly with API spec as argument: import openapiTS, { astToString } from 'openapi-typescript';
import fs from "node:fs";
import ts from "typescript";
if (process.argv.length === 2) {
console.error('Expected at least one argument!');
process.exit(1);
}
const BLOB = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Blob"));
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());
const ast = await openapiTS(new URL(process.argv[2]), {
transform(schemaObject, metadata) {
if (schemaObject.format === 'binary') {
return schemaObject.nullable ? ts.factory.createUnionTypeNode([BLOB, NULL]) : BLOB;
}
}
});
const output = astToString(ast);
fs.writeFileSync('./src/lib/api/v1.d.ts', output); |
The docs actually have an up-to-date example for the new AST-based API: https://openapi-ts.dev/node#example-blob-types |
I have been fighting with this exact problem now for almost a day with no success, so I will try to describe my situation to see if someone can help me out.
until now I have managed to add the
now in my tests when sending the request using generated client like this:
on the server I'm getting a header however making the following call from exactly same test works perfectly:
can someone tell me what is wrong here? |
also wanted to mention that I have tried a bunch of other variations with no success including this one:
which I read somewhere shall force "browser" set the correct header + boundary, but I wonder what/if that will happen in my tests. |
Description
In OpenAPI 3.0 we can describe the schema for files uploaded directly or as multipart:
The binary format indicates a
File
type object. However, the typescript interface generated by the library sets this property tostring
:Proposal
The type of the property should be set to
File
:NOTE: For reference the openapi-generator for typescript correctly sets the type to
File
but doesn't support OpenAPI 3.0Checklist
Similar to #1123
The text was updated successfully, but these errors were encountered: