@lokalise/file-storage-service-sdk
Advanced tools
Comparing version
@@ -0,4 +1,5 @@ | ||
/// <reference types="node" /> | ||
import type { Either } from '@lokalise/node-core/dist/src/errors/either'; | ||
import type { FileStorageClientConfig } from './configModels'; | ||
import type { FileData, FileMetadata, FileUploadData, HttpRequestContext } from './model'; | ||
import type { DownloadError, FileData, FileMetadata, FileUploadData, HttpRequestContext } from './model'; | ||
import type { ContentHeaders, ErrorResponse, UseCaseEnum } from './schema/storageSchemas'; | ||
@@ -13,2 +14,4 @@ export declare class FileStorageClient { | ||
getFileDownloadUrl(ownerId: string, fileId: string, useCase: UseCaseEnum, requestContext: HttpRequestContext): Promise<Either<ErrorResponse, string>>; | ||
downloadFile(projectId: string, fileId: string, useCase: UseCaseEnum, requestContext: HttpRequestContext): Promise<Either<DownloadError, Blob>>; | ||
private isTemporaryErrorResponseCode; | ||
deleteFile(ownerId: string, fileId: string, requestContext: HttpRequestContext): Promise<Either<ErrorResponse, boolean>>; | ||
@@ -15,0 +18,0 @@ private getOwnerEndpoint; |
@@ -6,2 +6,3 @@ "use strict"; | ||
const configModels_1 = require("./configModels"); | ||
const restUtils_1 = require("./utils/restUtils"); | ||
class FileStorageClient { | ||
@@ -90,2 +91,30 @@ retryConfig; | ||
} | ||
async downloadFile(projectId, fileId, useCase, requestContext) { | ||
const downloadUrlResult = await this.getFileDownloadUrl(projectId, fileId, useCase, requestContext); | ||
if (downloadUrlResult.error) { | ||
if (downloadUrlResult.error.statusCode === 404) { | ||
return { error: 'FILE_NOT_FOUND' }; | ||
} | ||
if (this.isTemporaryErrorResponseCode(downloadUrlResult.error.statusCode)) { | ||
return { error: 'TEMPORARY_ERROR' }; | ||
} | ||
return { error: 'UNKNOWN_ERROR' }; | ||
} | ||
const url = new URL(downloadUrlResult.result); | ||
const result = await (0, node_core_1.sendGet)((0, node_core_1.buildClient)(url.origin), url.pathname, { | ||
query: (0, restUtils_1.getUrlQuery)(url), | ||
retryConfig: { | ||
...this.retryConfig, | ||
blobBody: true, | ||
}, | ||
throwOnError: false, | ||
}); | ||
if (result.error) { | ||
return { error: 'UNKNOWN_ERROR' }; | ||
} | ||
return { result: result.result.body }; | ||
} | ||
isTemporaryErrorResponseCode(statusCode) { | ||
return (statusCode >= 500 && statusCode < 600) || statusCode === 429; | ||
} | ||
async deleteFile(ownerId, fileId, requestContext) { | ||
@@ -92,0 +121,0 @@ const endpoint = this.getOwnerEndpoint(ownerId, fileId); |
@@ -8,2 +8,3 @@ "use strict"; | ||
const mockServer = (0, mockttp_1.getLocal)(); | ||
const mockStorageServer = (0, mockttp_1.getLocal)(); | ||
describe('fileStorageClient', () => { | ||
@@ -19,5 +20,7 @@ let fileStorageClient; | ||
await mockServer.start(8085); | ||
await mockStorageServer.start(8086); | ||
}); | ||
afterEach(async () => { | ||
await mockServer.stop(); | ||
await mockStorageServer.stop(); | ||
}); | ||
@@ -51,4 +54,2 @@ it('The download URL is returned on request', async () => { | ||
it('Attempts to upload a file correctly', async () => { | ||
const mockStorageServer = (0, mockttp_1.getLocal)(); | ||
await mockStorageServer.start(8086); | ||
const uploadHost = 'http://localhost:8086'; | ||
@@ -69,7 +70,4 @@ const uploadPath = '/upload-files-here'; | ||
await expect(fileStorageClient.uploadFile(testFs_1.FS_TEST_DATA.PROJECT_ID, 'import', testFs_1.FS_TEST_DATA.FILE_DESCRIPTION, {}, testFs_1.FS_TEST_DATA.REQUEST_CONTEXT)).resolves.toEqual({ result: fileId }); | ||
await mockStorageServer.stop(); | ||
}); | ||
it('Attempts to upload a file correctly with query params', async () => { | ||
const mockStorageServer = (0, mockttp_1.getLocal)(); | ||
await mockStorageServer.start(8086); | ||
const uploadHost = 'http://localhost:8086'; | ||
@@ -91,3 +89,2 @@ const uploadPath = '/upload-files-here'; | ||
await expect(fileStorageClient.uploadFile(testFs_1.FS_TEST_DATA.PROJECT_ID, 'import', testFs_1.FS_TEST_DATA.FILE_DESCRIPTION, {}, testFs_1.FS_TEST_DATA.REQUEST_CONTEXT)).resolves.toEqual({ result: fileId }); | ||
await mockStorageServer.stop(); | ||
}); | ||
@@ -107,4 +104,2 @@ it('returns error on file upload', async () => { | ||
it('returns error on uploading file by generated upload url', async () => { | ||
const mockStorageServer = (0, mockttp_1.getLocal)(); | ||
await mockStorageServer.start(8086); | ||
const uploadHost = 'http://localhost:8086'; | ||
@@ -135,3 +130,2 @@ const uploadPath = '/upload-files-here'; | ||
await expect(fileStorageClient.uploadFile(testFs_1.FS_TEST_DATA.PROJECT_ID, 'import', testFs_1.FS_TEST_DATA.FILE_DESCRIPTION, {}, testFs_1.FS_TEST_DATA.REQUEST_CONTEXT)).resolves.toEqual({ error: testFs_1.FS_TEST_DATA.ERROR_REPLY }); | ||
await mockStorageServer.stop(); | ||
}); | ||
@@ -185,3 +179,64 @@ it('get file metadata successful', async () => { | ||
}); | ||
it('downloads file from FSS', async () => { | ||
const downloadUrl = 'http://localhost:8086/path-to-file'; | ||
const reply = { | ||
data: { | ||
downloadUrl, | ||
filename: 'someFile.zip', | ||
mimeType: 'application/zip', | ||
fileSize: 22, | ||
}, | ||
}; | ||
await mockServer.forGet(testStorageClient_1.downloadInterceptData.path).thenReply(200, JSON.stringify(reply), { | ||
'content-type': 'application/json', | ||
}); | ||
await mockStorageServer.forGet(downloadUrl).thenReply(200, 'file-contents'); | ||
const contentsResult = await fileStorageClient.downloadFile(testFs_1.FS_TEST_DATA.PROJECT_ID, testFs_1.FS_TEST_DATA.FILE_ID, 'import', testFs_1.FS_TEST_DATA.REQUEST_CONTEXT); | ||
const contentsString = await contentsResult.result?.text(); | ||
expect(contentsString).toBe('file-contents'); | ||
}); | ||
it('file on FSS not found', async () => { | ||
await mockServer | ||
.forGet(testStorageClient_1.downloadInterceptData.path) | ||
.thenReply(404, JSON.stringify({ statusCode: 404, message: 'Not found' }), { | ||
'content-type': 'application/json', | ||
}); | ||
const result = await fileStorageClient.downloadFile(testFs_1.FS_TEST_DATA.PROJECT_ID, testFs_1.FS_TEST_DATA.FILE_ID, 'import', testFs_1.FS_TEST_DATA.REQUEST_CONTEXT); | ||
expect(result.error).toBe('FILE_NOT_FOUND'); | ||
}); | ||
it('unknown error on downloadUrl call', async () => { | ||
await mockServer.forGet(testStorageClient_1.downloadInterceptData.path).thenReply(500, JSON.stringify({}), { | ||
'content-type': 'application/json', | ||
}); | ||
const result = await fileStorageClient.downloadFile(testFs_1.FS_TEST_DATA.PROJECT_ID, testFs_1.FS_TEST_DATA.FILE_ID, 'import', testFs_1.FS_TEST_DATA.REQUEST_CONTEXT); | ||
expect(result.error).toBe('UNKNOWN_ERROR'); | ||
}); | ||
it('unknown error on file download', async () => { | ||
const downloadUrl = 'http://localhost:8086/path-to-file'; | ||
const reply = { | ||
data: { | ||
downloadUrl, | ||
filename: 'someFile.zip', | ||
mimeType: 'application/zip', | ||
fileSize: 22, | ||
}, | ||
}; | ||
await mockServer.forGet(testStorageClient_1.downloadInterceptData.path).thenReply(200, JSON.stringify(reply), { | ||
'content-type': 'application/json', | ||
}); | ||
await mockStorageServer.forGet(downloadUrl).thenReply(404, 'Not found'); | ||
const result = await fileStorageClient.downloadFile(testFs_1.FS_TEST_DATA.PROJECT_ID, testFs_1.FS_TEST_DATA.FILE_ID, 'import', testFs_1.FS_TEST_DATA.REQUEST_CONTEXT); | ||
expect(result.error).toBe('UNKNOWN_ERROR'); | ||
}); | ||
it('temporary error on download', async () => { | ||
await mockServer.forGet(testStorageClient_1.downloadInterceptData.path).thenReply(503, JSON.stringify({ | ||
statusCode: 503, | ||
message: 'Something went wrong', | ||
}), { | ||
'content-type': 'application/json', | ||
}); | ||
const result = await fileStorageClient.downloadFile(testFs_1.FS_TEST_DATA.PROJECT_ID, testFs_1.FS_TEST_DATA.FILE_ID, 'import', testFs_1.FS_TEST_DATA.REQUEST_CONTEXT); | ||
expect(result.error).toBe('TEMPORARY_ERROR'); | ||
}); | ||
}); | ||
//# sourceMappingURL=fileStorageClient.spec.js.map |
@@ -5,2 +5,3 @@ /// <reference types="node" /> | ||
import type { StatusEnum } from './schema/storageSchemas'; | ||
export type DownloadError = 'FILE_NOT_FOUND' | 'TEMPORARY_ERROR' | 'UNKNOWN_ERROR'; | ||
export interface HttpRequestContext { | ||
@@ -7,0 +8,0 @@ reqId: string; |
{ | ||
"name": "@lokalise/file-storage-service-sdk", | ||
"version": "4.0.1", | ||
"version": "4.1.0", | ||
"description": "SDK for file-storage-service", | ||
@@ -49,4 +49,4 @@ "author": { | ||
"@types/node": "^18.13.0", | ||
"@typescript-eslint/eslint-plugin": "^5.52.0", | ||
"@typescript-eslint/parser": "^5.52.0", | ||
"@typescript-eslint/eslint-plugin": "^6.6.0", | ||
"@typescript-eslint/parser": "^6.6.0", | ||
"auto-changelog": "^2.4.0", | ||
@@ -53,0 +53,0 @@ "eslint": "^8.34.0", |
@@ -14,2 +14,3 @@ # file-storage-service-sdk | ||
#### Authorization | ||
Authorization will require a valid `jwtToken`. | ||
@@ -22,4 +23,5 @@ | ||
#### Error handling | ||
All function returning monad `Either<L, R>` where `L` - Left is a potential error and | ||
`R` - Right is a potential result, one of these will always present in response. | ||
Consumer of the api is responsible to handle result/error. | ||
All function returning monad `Either<L, R>` where `L` - Left is a potential error and | ||
`R` - Right is a potential result, one of these will always present in response. | ||
Consumer of the api is responsible to handle result/error. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
124427
8.84%51
13.33%1538
8.01%26
13.04%