Skip to content

Commit 2b51b09

Browse files
authored
Merge pull request #295 from superfaceai/feature/map-interpreter-stream
streaming map interpreter
2 parents 7a34907 + 1e88fc7 commit 2b51b09

File tree

14 files changed

+1535
-819
lines changed

14 files changed

+1535
-819
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,4 @@ dist
105105

106106
.yalc
107107
.vscode
108+
workdir/

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
### Added
2121
- `multipart/form-data` supports array values to define duplicate fields
2222

23+
### Changed
24+
- Reworked map interpreter into an asynchronous generator. It should be 1:1 replacement of the original interpreter, but opens up the road to expose streaming perform later.
25+
2326
### Fixed
2427
- Do not set `Accept` and `Content-Type` header when defined in [HTTP request headers](https://spec.superface.dev/latest/map-spec.html#HTTPHeaders) - [#264](https://github.com/superfaceai/one-sdk-js/issues/264)
2528
- Replaced `isomorphic-form-data` with `form-data` package to fix `FormData` serialization - [#291](https://github.com/superfaceai/one-sdk-js/issues/291)
2629
- Create valid `headersInit` shape in `NodeFetch.fetch`
30+
- Added `UnexpectedError` and `SDKExecutionError` to MapInterpreter and BoundProfileProvider return signatures.
2731
- An http call to `/` url returning an error about it being an absolute url
2832

2933
## [2.0.0] - 2022-08-15

src/core/errors/errors.helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export function sourceFileExtensionFoundError(
100100
return new SDKExecutionError(
101101
`${extension} extension found.`,
102102
[],
103-
[`${extension} files needs to be compiled with Superface CLI.`]
103+
[`${extension} files need to be compiled with Superface CLI.`]
104104
);
105105
}
106106

src/core/events/events.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
import { ApiKeyPlacement, HttpScheme, SecurityType } from '@superfaceai/ast';
88
import { getLocal } from 'mockttp';
99

10-
import { err } from '../../lib';
10+
import { err, UnexpectedError } from '../../lib';
1111
import { MockTimers } from '../../mock';
1212
import { NodeCrypto, NodeFetch, NodeFileSystem } from '../../node';
1313
import { Config } from '../config';
@@ -243,6 +243,7 @@ describe('events', () => {
243243
events
244244
);
245245

246+
const error = new UnexpectedError('modified rejection');
246247
events.on(
247248
'post-fetch',
248249
{ priority: 1 },
@@ -254,14 +255,14 @@ describe('events', () => {
254255
} catch (e) {
255256
return {
256257
kind: 'modify',
257-
newResult: Promise.reject('modified rejection'),
258+
newResult: Promise.reject(error),
258259
};
259260
}
260261
}
261262
);
262263

263264
const result = await profile.perform('Test');
264-
expect(result).toStrictEqual(err('modified rejection'));
265+
expect(result).toStrictEqual(err(error));
265266
});
266267

267268
it('passes unhandled http responses to unhandled-http (201)', async () => {
Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
1-
import { createUrl, deleteHeader, getHeader, hasHeader, setHeader } from './utils';
1+
import {
2+
createUrl,
3+
deleteHeader,
4+
getHeader,
5+
hasHeader,
6+
setHeader,
7+
} from './utils';
28

39
describe('interpreter · http · utils', () => {
410
describe('getHeader', () => {
511
it('returns all values', () => {
6-
expect(getHeader({ foo: 'bar', Foo: 'baz', FOO: ['qux', 'quz'] }, 'foo')).toEqual('bar, baz, qux, quz');
12+
expect(
13+
getHeader({ foo: 'bar', Foo: 'baz', FOO: ['qux', 'quz'] }, 'foo')
14+
).toEqual('bar, baz, qux, quz');
715
});
816

917
it('cleans up undefined values', () => {
10-
expect(getHeader({ foo: 'bar', Foo: undefined, FOO: ['qux', 'quz'] }, 'foo')).toEqual('bar, qux, quz');
18+
expect(
19+
getHeader({ foo: 'bar', Foo: undefined, FOO: ['qux', 'quz'] }, 'foo')
20+
).toEqual('bar, qux, quz');
1121
});
1222
});
1323

1424
describe('hasHeader', () => {
1525
it('should return true if header is present', () => {
16-
expect(hasHeader({ 'Foo': 'bar' }, 'foo')).toBe(true);
26+
expect(hasHeader({ Foo: 'bar' }, 'foo')).toBe(true);
1727
});
1828

1929
it('should return false if header is not present', () => {
20-
expect(hasHeader({ 'Foo': 'bar' }, 'baz')).toBe(false);
30+
expect(hasHeader({ Foo: 'bar' }, 'baz')).toBe(false);
2131
});
22-
})
32+
});
2333

2434
describe('setHeader', () => {
2535
it('mutates passed data', () => {
@@ -41,28 +51,28 @@ describe('interpreter · http · utils', () => {
4151
deleteHeader(headers, 'Foo');
4252
expect(headers).toEqual({});
4353
});
44-
})
54+
});
4555

4656
describe('createUrl', () => {
4757
it('correctly creates url for empty string', () => {
4858
const mapUrl = '';
49-
expect(
50-
createUrl(mapUrl, { baseUrl: 'http://example.com' })
51-
).toBe('http://example.com')
59+
expect(createUrl(mapUrl, { baseUrl: 'http://example.com' })).toBe(
60+
'http://example.com'
61+
);
5262
});
5363

5464
it('correctly creates url for single slash', () => {
5565
const mapUrl = '/';
56-
expect(
57-
createUrl(mapUrl, { baseUrl: 'http://example.com' })
58-
).toBe('http://example.com/')
66+
expect(createUrl(mapUrl, { baseUrl: 'http://example.com' })).toBe(
67+
'http://example.com/'
68+
);
5969
});
6070

6171
it('returns an error for absolute url', () => {
6272
const mapUrl = 'something';
63-
expect(
64-
() => createUrl(mapUrl, { baseUrl: 'http://example.com' })
65-
).toThrow('Expected relative url')
73+
expect(() =>
74+
createUrl(mapUrl, { baseUrl: 'http://example.com' })
75+
).toThrow('Expected relative url');
6676
});
6777
});
6878
});

src/core/interpreter/http/utils.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,15 @@ export async function fetchRequest(
145145
* Get header value. For duplicate headers all delimited by `,` are returned
146146
*/
147147
export function getHeader(headers: NonPrimitive, headerName: string): string {
148-
const values = Object.entries(headers).flatMap(([key, value]) => {
149-
if (key.toLowerCase() === headerName.toLowerCase()) {
150-
return value;
151-
}
152-
153-
return undefined;
154-
}).filter(value => value !== undefined);
148+
const values = Object.entries(headers)
149+
.flatMap(([key, value]) => {
150+
if (key.toLowerCase() === headerName.toLowerCase()) {
151+
return value;
152+
}
153+
154+
return undefined;
155+
})
156+
.filter(value => value !== undefined);
155157

156158
return values.join(', ');
157159
}

src/core/interpreter/map-interpreter.errors.test.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { getLocal } from 'mockttp';
99

1010
import { UnexpectedError } from '../../lib';
1111
import { MockTimers } from '../../mock';
12-
import { NodeCrypto, NodeFetch, NodeFileSystem } from '../../node';
12+
import { NodeCrypto, NodeFetch, NodeFileSystem, NodeLogger } from '../../node';
1313
import { Config } from '../config';
1414
import { ServiceSelector } from '../services';
1515
import { MapInterpreter } from './map-interpreter';
@@ -21,11 +21,14 @@ import {
2121
MappedHTTPError,
2222
} from './map-interpreter.errors';
2323

24-
const config = new Config(NodeFileSystem);
2524
const mockServer = getLocal();
2625
const timers = new MockTimers();
27-
const crypto = new NodeCrypto();
28-
const fetchInstance = new NodeFetch(timers);
26+
const interpreterDependencies = {
27+
fetchInstance: new NodeFetch(timers),
28+
config: new Config(NodeFileSystem),
29+
crypto: new NodeCrypto(),
30+
logger: new NodeLogger(),
31+
};
2932
const header: MapHeaderNode = {
3033
kind: 'MapHeader',
3134
profile: {
@@ -231,7 +234,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
231234
security: [],
232235
services: ServiceSelector.empty(),
233236
},
234-
{ fetchInstance, config, crypto }
237+
interpreterDependencies
235238
);
236239
const result = await interpreter.perform({
237240
kind: 'Invalid',
@@ -248,7 +251,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
248251
security: [],
249252
services: ServiceSelector.empty(),
250253
},
251-
{ fetchInstance, config, crypto }
254+
interpreterDependencies
252255
);
253256
const result = await interpreter.perform({
254257
astMetadata,
@@ -266,7 +269,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
266269
security: [],
267270
services: ServiceSelector.empty(),
268271
},
269-
{ fetchInstance, config, crypto }
272+
interpreterDependencies
270273
);
271274
const result = await interpreter.perform(
272275
parseMapFromSource(`
@@ -289,7 +292,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
289292
security: [],
290293
services: ServiceSelector.empty(),
291294
},
292-
{ fetchInstance, config, crypto }
295+
interpreterDependencies
293296
);
294297
const ast = parseMapFromSource(`
295298
map Test {
@@ -319,7 +322,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
319322
security: [],
320323
services: mockServicesSelector,
321324
},
322-
{ fetchInstance, config, crypto }
325+
interpreterDependencies
323326
);
324327

325328
const ast = parseMapFromSource(`
@@ -353,7 +356,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
353356
security: [],
354357
services: mockServicesSelector,
355358
},
356-
{ fetchInstance, config, crypto }
359+
interpreterDependencies
357360
);
358361
const ast = parseMapFromSource(`
359362
map testCase {
@@ -383,7 +386,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
383386
security: [],
384387
services: mockServicesSelector,
385388
},
386-
{ fetchInstance, config, crypto }
389+
interpreterDependencies
387390
);
388391
const ast = parseMapFromSource(`
389392
map Test {
@@ -422,7 +425,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
422425
security: [],
423426
services: mockServicesSelector,
424427
},
425-
{ fetchInstance, config, crypto }
428+
interpreterDependencies
426429
);
427430
const ast = parseMapFromSource(`
428431
map Test {
@@ -453,7 +456,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
453456
security: [],
454457
services: ServiceSelector.empty(),
455458
},
456-
{ fetchInstance, config, crypto }
459+
interpreterDependencies
457460
);
458461
const ast = parseMapFromSource(`
459462
map Test {
@@ -472,7 +475,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
472475
security: [],
473476
services: mockServicesSelector,
474477
},
475-
{ fetchInstance, config, crypto }
478+
interpreterDependencies
476479
);
477480
const ast = parseMapFromSource(`
478481
map Test {
@@ -496,7 +499,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
496499
security: [],
497500
services: mockServicesSelector,
498501
},
499-
{ fetchInstance, config, crypto }
502+
interpreterDependencies
500503
);
501504
const ast = parseMapFromSource(`
502505
map Test {
@@ -538,7 +541,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
538541
`${mockServicesSelector.getUrl()!}/{path}`
539542
),
540543
},
541-
{ fetchInstance, config, crypto }
544+
interpreterDependencies
542545
);
543546
const result = await interpreter.perform(ast);
544547
expect(result.isErr() && result.error.toString()).toMatch(
@@ -573,7 +576,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
573576
security: [],
574577
services: ServiceSelector.empty(),
575578
},
576-
{ fetchInstance, config, crypto }
579+
interpreterDependencies
577580
);
578581

579582
const result = await interpreter.perform(ast);
@@ -609,7 +612,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
609612
security: [],
610613
services: ServiceSelector.empty(),
611614
},
612-
{ fetchInstance, config, crypto }
615+
interpreterDependencies
613616
);
614617

615618
const result = await interpreter.perform(ast);
@@ -645,7 +648,7 @@ AST Path: definitions[0].statements[0].assignments[0].value`
645648
security: [],
646649
services: ServiceSelector.empty(),
647650
},
648-
{ fetchInstance, config, crypto }
651+
interpreterDependencies
649652
);
650653

651654
const result = await interpreter.perform(ast);

0 commit comments

Comments
 (0)