Skip to content

Commit

Permalink
feat(code-gen): combine files & body handling when body is multipart
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
- The open api importer is not compatible anymore with 'legacy' code-gen, please migrate to experimental code-gen.
- Rewrite of FormData handling in the 'experimental' api clients, check the new generate output and retest your usages of features using multipart bodies / files.
- In the near future, we will fully combine `files` & `body` handling for the backends as well.
  • Loading branch information
dirkdev98 committed Apr 29, 2023
1 parent a67b383 commit 1af5f7d
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 105 deletions.
50 changes: 28 additions & 22 deletions packages/code-gen/src/experimental/api-client/js-axios.js
Expand Up @@ -208,35 +208,41 @@ export function jsAxiosGenerateFunction(
)}(${args.join(", ")})`,
);

if (route.files) {
if (route.files || route.metadata?.requestBodyType === "form-data") {
const parameter = route.body ? "body" : "files";

fileWrite(
file,
`
const data = new FormData();
for (const key of Object.keys(files)) {
const keyFiles = Array.isArray(files[key]) ? files[key] : [files[key]];
for (const file of keyFiles) {`,
`const data = ${parameter} instanceof FormData ? ${parameter} : new FormData();`,
);
fileContextSetIndent(file, 2);

fileWrite(file, `data.append(key, file.data, file.name);`);
fileBlockStart(file, `if (!(${parameter} instanceof FormData))`);

fileContextSetIndent(file, -2);
fileWrite(file, `}\n}`);
}
/** @type {import("../generated/common/types.js").ExperimentalObjectDefinition} */
// @ts-expect-error
const type = structureResolveReference(
generateContext.structure,

if (route.metadata?.requestBodyType === "form-data") {
fileWrite(
file,
`const data = body instanceof FormData ? body : new FormData();`,
);
fileBlockStart(file, `if (!(body instanceof FormData))`);
fileWrite(
file,
`for (const key of Object.keys(body)) { data.append(key, body[key]); }`,
// @ts-expect-error
route.body ?? route.files,
);

for (const key of Object.keys(type.keys)) {
const fieldType =
type.keys[key].type === "reference"
? structureResolveReference(generateContext.structure, type.keys[key])
: type.keys[key];

if (fieldType.type === "file") {
fileWrite(
file,
`data.append("${key}", ${parameter}["${key}"].data, ${parameter}["${key}"].name);`,
);
} else {
fileWrite(file, `data.append("${key}", ${parameter}["${key}"]);`);
}
}

fileBlockEnd(file);
}

Expand Down
49 changes: 27 additions & 22 deletions packages/code-gen/src/experimental/api-client/js-fetch.js
Expand Up @@ -234,35 +234,40 @@ export function jsFetchGenerateFunction(
)}(${args.join(", ")})`,
);

if (route.files) {
if (route.files || route.metadata?.requestBodyType === "form-data") {
const parameter = route.body ? "body" : "files";
fileWrite(
file,
`
const data = new FormData();
for (const key of Object.keys(files)) {
const keyFiles = Array.isArray(files[key]) ? files[key] : [files[key]];
for (const file of keyFiles) {`,
`const data = ${parameter} instanceof FormData ? ${parameter} : new FormData();`,
);
fileContextSetIndent(file, 2);

fileWrite(file, `data.append(key, file.data, file.name);`);
fileBlockStart(file, `if (!(${parameter} instanceof FormData))`);

fileContextSetIndent(file, -2);
fileWrite(file, `}\n}`);
}
/** @type {import("../generated/common/types.js").ExperimentalObjectDefinition} */
// @ts-expect-error
const type = structureResolveReference(
generateContext.structure,

if (route.metadata?.requestBodyType === "form-data") {
fileWrite(
file,
`const data = body instanceof FormData ? body : new FormData();`,
);
fileBlockStart(file, `if (!(body instanceof FormData))`);
fileWrite(
file,
`for (const key of Object.keys(body)) { data.append(key, body[key]); }`,
// @ts-expect-error
route.body ?? route.files,
);

for (const key of Object.keys(type.keys)) {
const fieldType =
type.keys[key].type === "reference"
? structureResolveReference(generateContext.structure, type.keys[key])
: type.keys[key];

if (fieldType.type === "file") {
fileWrite(
file,
`data.append("${key}", ${parameter}["${key}"].data, ${parameter}["${key}"].name);`,
);
} else {
fileWrite(file, `data.append("${key}", ${parameter}["${key}"]);`);
}
}

fileBlockEnd(file);
}

Expand Down
66 changes: 36 additions & 30 deletions packages/code-gen/src/experimental/api-client/ts-axios.js
Expand Up @@ -166,39 +166,45 @@ export function tsAxiosGenerateFunction(
)}(${args.join(", ")}): Promise<${contextNames.responseTypeName}>`,
);

if (route.files) {
if (route.files || route.metadata?.requestBodyType === "form-data") {
const parameter = route.body ? "body" : "files";

fileWrite(
file,
`
const data = new FormData();
for (const key of Object.keys(files)) {
const keyFiles = Array.isArray((files as any)[key]) ? (files as any)[key] : [(files as any)[key]];
for (const file of keyFiles) {`,
`const data = ${parameter} instanceof FormData ? ${parameter} : new FormData();`,
);
fileContextSetIndent(file, 2);

if (distilledTargetInfo.isReactNative) {
fileWrite(file, `data.append(key, file);`);
} else {
fileWrite(file, `data.append(key, file.data, file.name);`);
}
fileBlockStart(file, `if (!(${parameter} instanceof FormData))`);

fileContextSetIndent(file, -2);
fileWrite(file, `}\n}`);
}
/** @type {import("../generated/common/types.js").ExperimentalObjectDefinition} */
// @ts-expect-error
const type = structureResolveReference(
generateContext.structure,

if (route.metadata?.requestBodyType === "form-data") {
fileWrite(
file,
`const data = body instanceof FormData ? body : new FormData();`,
);
fileBlockStart(file, `if (!(body instanceof FormData))`);
fileWrite(
file,
`for (const key of Object.keys(body)) { data.append(key, body[key]); }`,
// @ts-expect-error
route.body ?? route.files,
);

for (const key of Object.keys(type.keys)) {
const fieldType =
type.keys[key].type === "reference"
? structureResolveReference(generateContext.structure, type.keys[key])
: type.keys[key];

if (fieldType.type === "file") {
if (distilledTargetInfo.isReactNative) {
fileWrite(file, `data.append("${key}", ${parameter}["${key}"]);`);
} else {
fileWrite(
file,
`data.append("${key}", ${parameter}["${key}"].data, ${parameter}["${key}"].name);`,
);
}
} else {
fileWrite(file, `data.append("${key}", ${parameter}["${key}"]);`);
}
}

fileBlockEnd(file);
}

Expand All @@ -221,16 +227,16 @@ for (const key of Object.keys(files)) {

if (route.files || route.metadata?.requestBodyType === "form-data") {
fileWrite(file, `data,`);

if (distilledTargetInfo.isReactNative) {
fileWrite(file, `headers: { "Content-Type": "multipart/form-data" },`);
}
}

if (route.body && route.metadata?.requestBodyType !== "form-data") {
fileWrite(file, `data: body,`);
}

if (distilledTargetInfo.isReactNative) {
fileWrite(file, `headers: { "Content-Type": "multipart/form-data" },`);
}

if (
route.response &&
structureResolveReference(generateContext.structure, route.response)
Expand Down
58 changes: 32 additions & 26 deletions packages/code-gen/src/experimental/api-client/ts-fetch.js
Expand Up @@ -226,39 +226,45 @@ export function tsFetchGenerateFunction(
}>`,
);

if (route.files) {
if (route.files || route.metadata?.requestBodyType === "form-data") {
const parameter = route.body ? "body" : "files";

fileWrite(
file,
`
const data = new FormData();
for (const key of Object.keys(files)) {
const keyFiles = Array.isArray((files as any)[key]) ? (files as any)[key] : [(files as any)[key]];
for (const file of keyFiles) {`,
`const data = ${parameter} instanceof FormData ? ${parameter} : new FormData();`,
);
fileContextSetIndent(file, 2);

if (distilledTargetInfo.isReactNative) {
fileWrite(file, `data.append(key, file);`);
} else {
fileWrite(file, `data.append(key, file.data, file.name);`);
}
fileBlockStart(file, `if (!(${parameter} instanceof FormData))`);

fileContextSetIndent(file, -2);
fileWrite(file, `}\n}`);
}
/** @type {import("../generated/common/types.js").ExperimentalObjectDefinition} */
// @ts-expect-error
const type = structureResolveReference(
generateContext.structure,

if (route.metadata?.requestBodyType === "form-data") {
fileWrite(
file,
`const data = body instanceof FormData ? body : new FormData();`,
);
fileBlockStart(file, `if (!(body instanceof FormData))`);
fileWrite(
file,
`for (const key of Object.keys(body)) { data.append(key, body[key]); }`,
// @ts-expect-error
route.body ?? route.files,
);

for (const key of Object.keys(type.keys)) {
const fieldType =
type.keys[key].type === "reference"
? structureResolveReference(generateContext.structure, type.keys[key])
: type.keys[key];

if (fieldType.type === "file") {
if (distilledTargetInfo.isReactNative) {
fileWrite(file, `data.append("${key}", ${parameter}["${key}"]);`);
} else {
fileWrite(
file,
`data.append("${key}", ${parameter}["${key}"].data, ${parameter}["${key}"].name);`,
);
}
} else {
fileWrite(file, `data.append("${key}", ${parameter}["${key}"]);`);
}
}

fileBlockEnd(file);
}

Expand Down
6 changes: 1 addition & 5 deletions packages/code-gen/src/open-api-importer.js
Expand Up @@ -144,11 +144,7 @@ function extractRoute(context, path, method) {
compasStruct,
);

if (body && JSON.stringify(body).includes(`type":"file"`)) {
compasStruct.files = body;
} else {
compasStruct.body = body;
}
compasStruct.body = body;

compasStruct.response = transformResponse(
context,
Expand Down

0 comments on commit 1af5f7d

Please sign in to comment.