Skip to content

Commit ea855df

Browse files
authored
Fixes for new metadata structure (#379)
1 parent d46663f commit ea855df

File tree

12 files changed

+94
-51
lines changed

12 files changed

+94
-51
lines changed

packages/file-store/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe('FileStore', function () {
7171
// @ts-expect-error todo
7272
size: this.testFileSize,
7373
offset: 0,
74-
metadata: 'filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg==,is_confidential',
74+
metadata: {filename: 'world_domination_plan.pdf', is_confidential: null},
7575
})
7676

7777
it("created file's size should match 'upload_length'", async function () {

packages/gcs-store/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@ export class GCSStore extends DataStore {
4343
}
4444

4545
const gcs_file = this.bucket.file(file.id)
46+
4647
const options = {
4748
metadata: {
4849
metadata: {
4950
tus_version: TUS_RESUMABLE,
5051
size: file.size,
5152
sizeIsDeferred: `${file.sizeIsDeferred}`,
5253
offset: file.offset,
53-
metadata: file.metadata,
54+
metadata: JSON.stringify(file.metadata),
5455
},
5556
},
5657
}
@@ -160,7 +161,7 @@ export class GCSStore extends DataStore {
160161
id,
161162
size: size ? Number.parseInt(size, 10) : size,
162163
offset: Number.parseInt(metadata.size, 10), // `size` is set by GCS
163-
metadata: meta,
164+
metadata: meta ? JSON.parse(meta) : undefined,
164165
})
165166
)
166167
})

packages/s3-store/index.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,22 +84,21 @@ export class S3Store extends DataStore {
8484
* on the S3 object's `Metadata` field, so that only a `headObject`
8585
* is necessary to retrieve the data.
8686
*/
87-
private async saveMetadata(file: Upload, upload_id: string) {
88-
log(`[${file.id}] saving metadata`)
87+
private async saveMetadata(upload: Upload, upload_id: string) {
88+
log(`[${upload.id}] saving metadata`)
8989
await this.client
9090
.putObject({
9191
Bucket: this.bucket,
92-
Key: `${file.id}.info`,
92+
Key: `${upload.id}.info`,
9393
Body: '',
9494
Metadata: {
95-
file: JSON.stringify(file),
95+
file: JSON.stringify(upload),
9696
upload_id,
9797
tus_version: TUS_RESUMABLE,
9898
},
9999
})
100100
.promise()
101-
log(`[${file.id}] metadata file saved`)
102-
return {file, upload_id}
101+
log(`[${upload.id}] metadata file saved`)
103102
}
104103

105104
/**
@@ -399,7 +398,11 @@ export class S3Store extends DataStore {
399398
Key: upload.id,
400399
Metadata: {tus_version: TUS_RESUMABLE},
401400
}
402-
const file: Record<string, string | number> = {id: upload.id, offset: upload.offset}
401+
const file: Record<string, string | number | Upload['metadata']> = {
402+
id: upload.id,
403+
offset: upload.offset,
404+
metadata: upload.metadata,
405+
}
403406

404407
if (upload.size) {
405408
file.size = upload.size.toString()
@@ -486,6 +489,7 @@ export class S3Store extends DataStore {
486489
...this.cache.get(id)?.file,
487490
offset: metadata.file.size as number,
488491
size: metadata.file.size,
492+
metadata: metadata.file.metadata,
489493
})
490494
}
491495

packages/server/src/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ export const ERRORS = {
5353
status_code: 400,
5454
body: 'Upload-Length or Upload-Defer-Length header required\n',
5555
},
56+
INVALID_METADATA: {
57+
status_code: 400,
58+
body: 'Upload-Metadata is invalid. It MUST consist of one or more comma-separated key-value pairs. The key and value MUST be separated by a space. The key MUST NOT contain spaces and commas and MUST NOT be empty. The key SHOULD be ASCII encoded and the value MUST be Base64 encoded. All keys MUST be unique',
59+
},
5660
UNKNOWN_ERROR: {
5761
status_code: 500,
5862
body: 'Something went wrong with that request\n',

packages/server/src/handlers/HeadHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class HeadHandler extends BaseHandler {
4949
if (file.metadata !== undefined) {
5050
// If the size of the upload is known, the Server MUST include
5151
// the Upload-Length header in the response.
52-
res.setHeader('Upload-Metadata', Metadata.stringify(file.metadata))
52+
res.setHeader('Upload-Metadata', Metadata.stringify(file.metadata) as string)
5353
}
5454

5555
return res.end()

packages/server/src/handlers/PostHandler.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,25 @@ export class PostHandler extends BaseHandler {
5252
}
5353

5454
let id
55-
5655
try {
5756
id = this.options.namingFunction(req)
5857
} catch (error) {
5958
log('create: check your `namingFunction`. Error', error)
6059
throw ERRORS.FILE_WRITE_ERROR
6160
}
6261

62+
let metadata
63+
try {
64+
metadata = Metadata.parse(upload_metadata)
65+
} catch (error) {
66+
throw ERRORS.INVALID_METADATA
67+
}
68+
6369
const upload = new Upload({
6470
id,
6571
size: upload_length ? Number.parseInt(upload_length, 10) : undefined,
6672
offset: 0,
67-
metadata: Metadata.parse(upload_metadata),
73+
metadata,
6874
})
6975

7076
if (this.options.onUploadCreate) {

packages/server/src/models/Metadata.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ export function validateValue(value: string) {
3232
}
3333

3434
export function parse(str?: string) {
35-
const meta: Record<string, string | undefined> = {}
35+
const meta: Record<string, string | null> = {}
3636

3737
if (!str) {
38-
return {}
38+
return undefined
3939
}
4040

4141
for (const pair of str.split(',')) {
@@ -46,9 +46,7 @@ export function parse(str?: string) {
4646
(tokens.length === 2 && validateKey(key) && validateValue(value))) &&
4747
!(key in meta)
4848
) {
49-
const decodedValue = value
50-
? Buffer.from(value, 'base64').toString('utf8')
51-
: undefined
49+
const decodedValue = value ? Buffer.from(value, 'base64').toString('utf8') : null
5250
meta[key] = decodedValue
5351
} else {
5452
throw new Error('Metadata string is not valid')
@@ -58,10 +56,10 @@ export function parse(str?: string) {
5856
return meta
5957
}
6058

61-
export function stringify(metadata: NonNullable<Upload['metadata']>) {
59+
export function stringify(metadata: NonNullable<Upload['metadata']>): string {
6260
return Object.entries(metadata)
6361
.map(([key, value]) => {
64-
if (value === undefined) {
62+
if (value === null) {
6563
return key
6664
}
6765

packages/server/src/models/Upload.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ type TUpload = {
22
id: string
33
size?: number
44
offset: number
5-
metadata?: Record<string, string | undefined | never>
5+
metadata?: Record<string, string | null>
66
creation_date?: string
77
}
88

packages/server/test/HeadHandler.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe('HeadHandler', () => {
6565
const file = new Upload({
6666
id: '1234',
6767
offset: 0,
68-
metadata: {is_confidential: undefined, foo: 'bar'},
68+
metadata: {is_confidential: null, foo: 'bar'},
6969
})
7070
fake_store.getUpload.resolves(file)
7171
await handler.send(req, res)

packages/server/test/Metadata.test.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ describe('Metadata', () => {
99
'file/name': 'test.mp4',
1010
size: '960244',
1111
'type!': 'video/mp4',
12-
video: undefined,
13-
withWhitespace: undefined,
12+
video: null,
13+
withWhitespace: null,
1414
}
1515
const decoded = parse(str)
1616
assert.deepStrictEqual(decoded, obj)
@@ -21,8 +21,8 @@ describe('Metadata', () => {
2121
filename: 'test.mp4',
2222
size: '960244',
2323
type: 'video/mp4',
24-
video: undefined,
25-
withWhitespace: undefined,
24+
video: null,
25+
withWhitespace: null,
2626
}
2727
const encoded = stringify(obj)
2828

@@ -34,11 +34,9 @@ describe('Metadata', () => {
3434
assert.strictEqual(stringify({size: '960244'}), 'size OTYwMjQ0')
3535
assert.strictEqual(stringify({type: 'video/mp4'}), 'type dmlkZW8vbXA0')
3636
// Multiple valid options
37-
assert.notStrictEqual(['video', 'video '].indexOf(stringify({video: undefined})), -1)
37+
assert.notStrictEqual(['video', 'video '].indexOf(stringify({video: null})), -1)
3838
assert.notStrictEqual(
39-
['withWhitespace', 'withWhitespace '].indexOf(
40-
stringify({withWhitespace: undefined})
41-
),
39+
['withWhitespace', 'withWhitespace '].indexOf(stringify({withWhitespace: null})),
4240
-1
4341
)
4442
})
@@ -51,20 +49,20 @@ describe('Metadata', () => {
5149
assert.deepStrictEqual(parse('type dmlkZW8vbXA0'), {
5250
type: 'video/mp4',
5351
})
54-
assert.deepStrictEqual(parse('video'), {video: undefined})
55-
assert.deepStrictEqual(parse('video '), {video: undefined})
52+
assert.deepStrictEqual(parse('video'), {video: null})
53+
assert.deepStrictEqual(parse('video '), {video: null})
5654
assert.deepStrictEqual(parse('withWhitespace'), {
57-
withWhitespace: undefined,
55+
withWhitespace: null,
5856
})
5957
assert.deepStrictEqual(parse('withWhitespace '), {
60-
withWhitespace: undefined,
58+
withWhitespace: null,
6159
})
6260
})
6361

6462
it('cyclic test', () => {
6563
const obj = {
6664
filename: 'world_domination_plan.pdf',
67-
is_confidential: undefined,
65+
is_confidential: null,
6866
}
6967
// Object -> string -> object
7068
assert.deepStrictEqual(parse(stringify(obj)), obj)

0 commit comments

Comments
 (0)