Skip to content

Commit cfce32f

Browse files
committed
feat: implement serializeUploadOptions function for upload option serialization and add tests
1 parent dd98040 commit cfce32f

File tree

4 files changed

+159
-2
lines changed

4 files changed

+159
-2
lines changed

src/lib/serialization-utils.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Serialize upload options to handle proper formatting for ImageKit backend API.
3+
* Special cases handled:
4+
* - tags: converted to comma-separated string
5+
* - responseFields: converted to comma-separated string
6+
* - extensions: JSON stringified
7+
* - customMetadata: JSON stringified
8+
* - transformation: JSON stringified
9+
*/
10+
export function serializeUploadOptions(uploadOptions: Record<string, any>): Record<string, any> {
11+
const serialized: Record<string, any> = { ...uploadOptions };
12+
13+
for (const key in serialized) {
14+
if (key && serialized[key] !== undefined) {
15+
const value = serialized[key];
16+
17+
if (key === 'tags' && Array.isArray(value)) {
18+
// Tags should be comma-separated string
19+
serialized[key] = value.join(',');
20+
} else if (key === 'responseFields' && Array.isArray(value)) {
21+
// Response fields should be comma-separated string
22+
serialized[key] = value.join(',');
23+
} else if (key === 'extensions' && Array.isArray(value)) {
24+
// Extensions should be JSON stringified
25+
serialized[key] = JSON.stringify(value);
26+
} else if (
27+
key === 'customMetadata' &&
28+
typeof value === 'object' &&
29+
!Array.isArray(value) &&
30+
value !== null
31+
) {
32+
// Custom metadata should be JSON stringified
33+
serialized[key] = JSON.stringify(value);
34+
} else if (key === 'transformation' && typeof value === 'object' && value !== null) {
35+
// Transformation should be JSON stringified
36+
serialized[key] = JSON.stringify(value);
37+
}
38+
}
39+
}
40+
41+
return serialized;
42+
}

src/resources/beta/v2/files.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { APIPromise } from '../../../core/api-promise';
66
import { type Uploadable } from '../../../core/uploads';
77
import { RequestOptions } from '../../../internal/request-options';
88
import { multipartFormRequestOptions } from '../../../internal/uploads';
9+
import { serializeUploadOptions } from '../../../lib/serialization-utils';
910

1011
export class Files extends APIResource {
1112
/**
@@ -46,10 +47,12 @@ export class Files extends APIResource {
4647
* ```
4748
*/
4849
upload(body: FileUploadParams, options?: RequestOptions): APIPromise<FileUploadResponse> {
50+
const serializedBody = serializeUploadOptions(body);
51+
4952
return this._client.post(
5053
'/api/v2/files/upload',
5154
multipartFormRequestOptions(
52-
{ body, defaultBaseURL: 'https://upload.imagekit.io', ...options },
55+
{ body: serializedBody, defaultBaseURL: 'https://upload.imagekit.io', ...options },
5356
this._client,
5457
),
5558
);

src/resources/files/files.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { buildHeaders } from '../../internal/headers';
3030
import { RequestOptions } from '../../internal/request-options';
3131
import { multipartFormRequestOptions } from '../../internal/uploads';
3232
import { path } from '../../internal/utils/path';
33+
import { serializeUploadOptions } from '../../lib/serialization-utils';
3334

3435
export class Files extends APIResource {
3536
bulk: BulkAPI.Bulk = new BulkAPI.Bulk(this._client);
@@ -181,10 +182,12 @@ export class Files extends APIResource {
181182
* ```
182183
*/
183184
upload(body: FileUploadParams, options?: RequestOptions): APIPromise<FileUploadResponse> {
185+
const serializedBody = serializeUploadOptions(body);
186+
184187
return this._client.post(
185188
'/api/v1/files/upload',
186189
multipartFormRequestOptions(
187-
{ body, defaultBaseURL: 'https://upload.imagekit.io', ...options },
190+
{ body: serializedBody, defaultBaseURL: 'https://upload.imagekit.io', ...options },
188191
this._client,
189192
),
190193
);

tests/serialization-utils.test.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { serializeUploadOptions } from '../src/lib/serialization-utils';
2+
3+
describe('serializeUploadOptions', () => {
4+
it('should serialize all special fields correctly while preserving other fields', () => {
5+
const extensions = [
6+
{ name: 'google-auto-tagging', maxTags: 10, minConfidence: 80 },
7+
{ name: 'remove-bg', options: { bg_color: 'white' } },
8+
];
9+
10+
const customMetadata = {
11+
photographer: 'John Doe',
12+
category: 'nature',
13+
rating: 5,
14+
};
15+
16+
const transformation = {
17+
pre: 'w-500,h-300',
18+
post: [
19+
{ type: 'transformation', value: 'w-200,h-150' },
20+
{ type: 'gif-to-video', value: 'q-80' },
21+
],
22+
};
23+
24+
const input = {
25+
// Special fields that should be serialized
26+
tags: ['nature', 'landscape', 'photography'],
27+
responseFields: ['tags', 'customMetadata', 'isPrivateFile'],
28+
extensions,
29+
customMetadata,
30+
transformation,
31+
32+
// Regular fields that should remain unchanged
33+
fileName: 'test-image.jpg',
34+
folder: '/photos/2024',
35+
isPrivateFile: false,
36+
useUniqueFileName: true,
37+
description: 'A beautiful landscape photo',
38+
webhookUrl: 'https://example.com/webhook',
39+
};
40+
41+
const result = serializeUploadOptions(input);
42+
43+
// Assert special fields are properly serialized
44+
expect(result['tags']).toBe('nature,landscape,photography');
45+
expect(result['responseFields']).toBe('tags,customMetadata,isPrivateFile');
46+
expect(result['extensions']).toBe(JSON.stringify(extensions));
47+
expect(result['customMetadata']).toBe(JSON.stringify(customMetadata));
48+
expect(result['transformation']).toBe(JSON.stringify(transformation));
49+
50+
// Assert regular fields remain unchanged
51+
expect(result['fileName']).toBe('test-image.jpg');
52+
expect(result['folder']).toBe('/photos/2024');
53+
expect(result['isPrivateFile']).toBe(false);
54+
expect(result['useUniqueFileName']).toBe(true);
55+
expect(result['description']).toBe('A beautiful landscape photo');
56+
expect(result['webhookUrl']).toBe('https://example.com/webhook');
57+
58+
// Ensure original object is not modified
59+
expect(input.tags).toEqual(['nature', 'landscape', 'photography']);
60+
expect(input.extensions).toBe(extensions);
61+
expect(input.customMetadata).toBe(customMetadata);
62+
expect(input.transformation).toBe(transformation);
63+
});
64+
65+
it('should handle edge cases with null, undefined, and empty values', () => {
66+
const input = {
67+
fileName: 'test.jpg',
68+
69+
// undefined values
70+
tags: undefined,
71+
transformation: undefined,
72+
73+
// null values
74+
responseFields: null,
75+
customMetadata: null,
76+
77+
// empty arrays and objects
78+
extensions: [],
79+
emptyObject: {},
80+
emptyArray: [],
81+
82+
// non-special arrays and objects should remain unchanged
83+
regularArray: ['item1', 'item2'],
84+
regularObject: { key: 'value' },
85+
};
86+
87+
const result = serializeUploadOptions(input);
88+
89+
// undefined values should remain undefined
90+
expect(result['tags']).toBeUndefined();
91+
expect(result['transformation']).toBeUndefined();
92+
93+
// null values should remain null
94+
expect(result['responseFields']).toBeNull();
95+
expect(result['customMetadata']).toBeNull();
96+
97+
// empty arrays for special fields should be serialized
98+
expect(result['extensions']).toBe('[]');
99+
100+
// empty arrays/objects for non-special fields should remain unchanged
101+
expect(result['emptyObject']).toEqual({});
102+
expect(result['emptyArray']).toEqual([]);
103+
expect(result['regularArray']).toEqual(['item1', 'item2']);
104+
expect(result['regularObject']).toEqual({ key: 'value' });
105+
106+
// regular field should remain unchanged
107+
expect(result['fileName']).toBe('test.jpg');
108+
});
109+
});

0 commit comments

Comments
 (0)