diff --git a/src/main/ts/index.ts b/src/main/ts/index.ts index 5358ddd..eff4373 100644 --- a/src/main/ts/index.ts +++ b/src/main/ts/index.ts @@ -1,4 +1,5 @@ export { createHttpPipe, IHttpPipeOpts, IHttpHeaders } from './pipes/http' +export { createHttpPipeFallback } from './pipes/httpFallback' export { createMaskerPipe, panMaskerPipe } from './pipes/masker' export { createFlpPipeline, eventifyPipe } from './pipes/flp' export { createDeviceInfoPipe, getDeviceInfo } from './pipes/deviceInfo' diff --git a/src/main/ts/pipes/httpFallback.ts b/src/main/ts/pipes/httpFallback.ts new file mode 100644 index 0000000..212e95d --- /dev/null +++ b/src/main/ts/pipes/httpFallback.ts @@ -0,0 +1,20 @@ +import { IPipe, IPipeOutput, ITransmittable } from '../interfaces' +import { IPromise } from '@qiwi/substrate' +import { createHttpPipe, IHttpPipeOpts } from './http' +import { executeFailproof } from '../utils' + +export const type = 'http-fallback' + +export const createHttpPipeFallback = (opts: IHttpPipeOpts[]): IPipe => { + if (opts.length === 0) { + throw new Error('createHttpPipeFallback opts must not be empty') + } + + const httpPipes = opts.map(createHttpPipe) + return { + type, + execute (transmittable : ITransmittable): IPromise { + return executeFailproof(transmittable, httpPipes) + }, + } +} diff --git a/src/main/ts/utils/executeFailproof.ts b/src/main/ts/utils/executeFailproof.ts new file mode 100644 index 0000000..c5c8f7d --- /dev/null +++ b/src/main/ts/utils/executeFailproof.ts @@ -0,0 +1,15 @@ +import { IPipe, IPipeOutput, ITransmittable } from '../interfaces' +import { identity } from '.' + +export async function executeFailproof (transmittable: ITransmittable, pipeline: IPipe[]): Promise { + const pipe = pipeline.shift() as IPipe + const [err, succ] = await pipe.execute(transmittable, identity) + + if (pipeline.length === 0) { + return succ + ? [null, succ] + : [err, null] + } + + return executeFailproof(transmittable, pipeline) +} diff --git a/src/main/ts/utils/index.ts b/src/main/ts/utils/index.ts index 1cbfeee..3b82ab1 100644 --- a/src/main/ts/utils/index.ts +++ b/src/main/ts/utils/index.ts @@ -1,5 +1,6 @@ import set from 'lodash.set' export { deepMap } from './deepmap' +export { executeFailproof } from './executeFailproof' export { clone } from './clone' export { set } diff --git a/src/test/ts/pipes/http.ts b/src/test/ts/pipes/http.ts index 748737e..fd87e9f 100644 --- a/src/test/ts/pipes/http.ts +++ b/src/test/ts/pipes/http.ts @@ -13,7 +13,7 @@ describe('httpPipe', () => { expect(httpPipe.execute).toEqual(expect.any(Function)) }) - it('returns remote data if succeeds', () => { + it('returns remote data if succeeds', async () => { const httpPipe = createHttpPipe({ url: 'https://reqres.in/api/users/2', method: HttpMethod.GET }) const transmittable: ITransmittable = { data: null, meta: { history: [] } } diff --git a/src/test/ts/pipes/httpFallback.ts b/src/test/ts/pipes/httpFallback.ts new file mode 100644 index 0000000..83db8fa --- /dev/null +++ b/src/test/ts/pipes/httpFallback.ts @@ -0,0 +1,67 @@ +import { HttpMethod } from '@qiwi/substrate-types' +import { createHttpPipeFallback, ITransmittable } from '../../../main/ts' + +import 'cross-fetch/polyfill' + +const noop = () => { /* noop */ } + +describe('httpPipe', () => { + it('factory returns IPipe', () => { + const httpPipeFallback = createHttpPipeFallback([{ url: 'https://reqres.in/api/users/2', method: HttpMethod.GET }]) + expect(httpPipeFallback.type).toBe('http-fallback') + expect(httpPipeFallback.execute).toEqual(expect.any(Function)) + }) + + it('returns remote data if succeeds', async () => { + const httpPipe = createHttpPipeFallback([{ url: 'https://reqres.in/api/users/2', method: HttpMethod.GET }]) + const transmittable: ITransmittable = { data: null, meta: { history: [] } } + + return expect(httpPipe.execute(transmittable, noop)) + .resolves.toMatchObject([null, { + data: { + id: 2, + email: 'janet.weaver@reqres.in', + first_name: 'Janet', + last_name: 'Weaver', + avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg', + }, + }]) + }) + + it('uses fallback urls', () => { + const httpPipe = createHttpPipeFallback([ + { url: 'https://reqres.in/api/users/23', method: HttpMethod.GET }, + { url: 'https://reqres.in/api/unknown/23', method: HttpMethod.GET }, + { url: 'https://reqres.in/api/users/2', method: HttpMethod.GET }, + ]) + const transmittable: ITransmittable = { data: null, meta: { history: [] } } + + return expect(httpPipe.execute(transmittable, noop)) + .resolves.toMatchObject([null, { + data: { + id: 2, + email: 'janet.weaver@reqres.in', + first_name: 'Janet', + last_name: 'Weaver', + avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg', + }, + }]) + }) + + it('handle errors', () => { + const httpPipe = createHttpPipeFallback([ + { url: 'https://reqres.in/api/users/23', method: HttpMethod.GET }, + { url: 'https://reqres.in/api/unknown/23', method: HttpMethod.GET }, + { url: 'https://reqres.in/api/users/23', method: HttpMethod.GET }, + ]) + const transmittable: ITransmittable = { data: null, meta: { history: [] } } + + return expect(httpPipe.execute(transmittable, noop)) + .resolves.toEqual([new Error('Not Found'), null]) + }) + + it('throw error when opts is empty', () => { + return expect(() => createHttpPipeFallback([])) + .toThrow(new Error('createHttpPipeFallback opts must not be empty')) + }) +}) diff --git a/src/test/ts/pipes/index.ts b/src/test/ts/pipes/index.ts index 1b8ce7c..3119827 100644 --- a/src/test/ts/pipes/index.ts +++ b/src/test/ts/pipes/index.ts @@ -28,13 +28,13 @@ describe('pipes', () => { describe('pipes', () => { describe('execute', () => { - it('execute works correctly', async (done) => { + it('execute works correctly', async () => { const upper: IPipe = { type: 'upper', // eslint-disable-next-line @typescript-eslint/no-unused-vars async execute (data, next: ICallable) { data.data.message = data.data.message.toUpperCase() - await next([null, data]) + await next([null, data.data]) data.data.message = data.data.message.toUpperCase() return Promise.resolve([null, data]) }, @@ -51,13 +51,24 @@ describe('pipes', () => { const pipeline2: TPipeline = [upper, foo] - const [err, data] = await execute(createTransmittable({ + const res = await execute(createTransmittable({ message: 'baz', }), pipeline2) - console.log(err, JSON.stringify(data)) - - done() + expect(res).toMatchObject([ + null, + { + data: { message: 'FOO' }, + err: null, + meta: + { + history: + [ + { pipelineId: '{0-upper}{1-foo}', pipeId: '1', pipeType: 'foo', err: null, res: false }, + { pipelineId: '{0-upper}{1-foo}', pipeId: '0', pipeType: 'upper', err: null, res: true }], + }, + }, + ]) }) }) })