@opra/core 0.20.3 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/cjs/adapter/adapter.js +2 -2
  2. package/cjs/adapter/http/express-adapter.js +29 -7
  3. package/cjs/adapter/http/helpers/common.js +66 -0
  4. package/cjs/adapter/http/helpers/convert-to-headers.js +65 -0
  5. package/cjs/adapter/http/helpers/convert-to-raw-headers.js +25 -0
  6. package/cjs/adapter/http/helpers/match-known-fields.js +47 -0
  7. package/cjs/adapter/http/http-adapter.js +76 -438
  8. package/cjs/adapter/http/impl/http-incoming-message-host.js +127 -0
  9. package/cjs/adapter/http/impl/http-outgoing-message-host.js +210 -0
  10. package/cjs/adapter/http/impl/http-server-request.js +125 -0
  11. package/cjs/adapter/http/impl/http-server-response.js +226 -0
  12. package/cjs/adapter/http/request-parsers/batch-request-parser.js +169 -0
  13. package/cjs/adapter/http/request-parsers/parse-collection-request.js +165 -0
  14. package/cjs/adapter/http/request-parsers/parse-request.js +24 -0
  15. package/cjs/adapter/http/request-parsers/parse-singleton-request.js +96 -0
  16. package/cjs/adapter/request-context.host.js +17 -3
  17. package/cjs/adapter/request.host.js +6 -3
  18. package/cjs/adapter/response.host.js +5 -3
  19. package/cjs/index.js +4 -2
  20. package/esm/adapter/adapter.js +2 -2
  21. package/esm/adapter/http/express-adapter.js +6 -6
  22. package/esm/adapter/http/helpers/common.js +60 -0
  23. package/esm/adapter/http/helpers/convert-to-headers.js +60 -0
  24. package/esm/adapter/http/helpers/convert-to-raw-headers.js +21 -0
  25. package/esm/adapter/http/helpers/match-known-fields.js +43 -0
  26. package/esm/adapter/http/http-adapter.js +77 -439
  27. package/esm/adapter/http/impl/http-incoming-message-host.js +122 -0
  28. package/esm/adapter/http/impl/http-outgoing-message-host.js +205 -0
  29. package/esm/adapter/http/impl/http-server-request.js +121 -0
  30. package/esm/adapter/http/impl/http-server-response.js +222 -0
  31. package/esm/adapter/http/request-parsers/batch-request-parser.js +169 -0
  32. package/esm/adapter/http/request-parsers/parse-collection-request.js +161 -0
  33. package/esm/adapter/http/request-parsers/parse-request.js +20 -0
  34. package/esm/adapter/http/request-parsers/parse-singleton-request.js +92 -0
  35. package/esm/adapter/request-context.host.js +17 -3
  36. package/esm/adapter/request.host.js +6 -3
  37. package/esm/adapter/response.host.js +5 -3
  38. package/esm/index.js +4 -2
  39. package/package.json +20 -16
  40. package/types/adapter/adapter.d.ts +1 -1
  41. package/types/adapter/http/helpers/common.d.ts +17 -0
  42. package/types/adapter/http/helpers/convert-to-headers.d.ts +2 -0
  43. package/types/adapter/http/helpers/convert-to-raw-headers.d.ts +3 -0
  44. package/types/adapter/http/helpers/match-known-fields.d.ts +6 -0
  45. package/types/adapter/http/http-adapter.d.ts +7 -12
  46. package/types/adapter/http/impl/http-incoming-message-host.d.ts +58 -0
  47. package/types/adapter/http/impl/http-outgoing-message-host.d.ts +72 -0
  48. package/types/adapter/http/{http-request-message.d.ts → impl/http-server-request.d.ts} +52 -85
  49. package/types/adapter/http/impl/http-server-response.d.ts +137 -0
  50. package/types/adapter/http/request-parsers/batch-request-parser.d.ts +0 -0
  51. package/types/adapter/http/request-parsers/parse-collection-request.d.ts +4 -0
  52. package/types/adapter/http/request-parsers/parse-request.d.ts +4 -0
  53. package/types/adapter/http/request-parsers/parse-singleton-request.d.ts +4 -0
  54. package/types/adapter/interfaces/request-context.interface.d.ts +14 -10
  55. package/types/adapter/interfaces/request.interface.d.ts +3 -2
  56. package/types/adapter/interfaces/response.interface.d.ts +2 -2
  57. package/types/adapter/request-context.host.d.ts +9 -6
  58. package/types/adapter/request.host.d.ts +8 -4
  59. package/types/adapter/response.host.d.ts +6 -4
  60. package/types/index.d.ts +4 -2
  61. package/cjs/adapter/http/http-message.host.js +0 -251
  62. package/cjs/adapter/http/http-request-context.host.js +0 -28
  63. package/cjs/adapter/http/http-request-message.js +0 -152
  64. package/cjs/adapter/http/http-request.host.js +0 -14
  65. package/cjs/adapter/http/http-response-message.js +0 -238
  66. package/cjs/adapter/http/http-response.host.js +0 -14
  67. package/esm/adapter/http/http-message.host.js +0 -246
  68. package/esm/adapter/http/http-request-context.host.js +0 -24
  69. package/esm/adapter/http/http-request-message.js +0 -148
  70. package/esm/adapter/http/http-request.host.js +0 -10
  71. package/esm/adapter/http/http-response-message.js +0 -233
  72. package/esm/adapter/http/http-response.host.js +0 -10
  73. package/types/adapter/http/http-message.host.d.ts +0 -122
  74. package/types/adapter/http/http-request-context.host.d.ts +0 -16
  75. package/types/adapter/http/http-request.host.d.ts +0 -7
  76. package/types/adapter/http/http-response-message.d.ts +0 -321
  77. package/types/adapter/http/http-response.host.d.ts +0 -7
@@ -0,0 +1,60 @@
1
+ import { ARRAY_FIELD, COMMA_DELIMITED_FIELD, matchKnownFields, SEMICOLON_DELIMITED_FIELD } from './match-known-fields.js';
2
+ export function convertToHeaders(src, dst, joinDuplicateHeaders) {
3
+ for (let n = 0; n < src.length; n += 2) {
4
+ addHeaderLine(src[n], src[n + 1], dst, joinDuplicateHeaders);
5
+ }
6
+ return dst;
7
+ }
8
+ export function convertToHeadersDistinct(src, dst) {
9
+ const count = src.length % 2;
10
+ for (let n = 0; n < count; n += 2) {
11
+ addHeaderLineDistinct(src[n], src[n + 1], dst);
12
+ }
13
+ return dst;
14
+ }
15
+ function addHeaderLine(field, value, dest, joinDuplicateHeaders) {
16
+ if (value == null)
17
+ return;
18
+ field = field.toLowerCase();
19
+ const [, flag] = matchKnownFields(field);
20
+ // comma(0) or semicolon(2) delimited field
21
+ if (flag === COMMA_DELIMITED_FIELD || flag === SEMICOLON_DELIMITED_FIELD) {
22
+ // Make a delimited list
23
+ if (typeof dest[field] === 'string') {
24
+ dest[field] += (flag === COMMA_DELIMITED_FIELD ? ', ' : '; ') + value;
25
+ }
26
+ else {
27
+ dest[field] = value;
28
+ }
29
+ }
30
+ else if (flag === ARRAY_FIELD) {
31
+ // Array header -- only Set-Cookie at the moment
32
+ if (dest['set-cookie'] !== undefined) {
33
+ dest['set-cookie'].push(value);
34
+ }
35
+ else {
36
+ dest['set-cookie'] = [value];
37
+ }
38
+ }
39
+ else if (joinDuplicateHeaders) {
40
+ if (dest[field] === undefined) {
41
+ dest[field] = value;
42
+ }
43
+ else {
44
+ dest[field] += ', ' + value;
45
+ }
46
+ }
47
+ else if (dest[field] === undefined) {
48
+ // Drop duplicates
49
+ dest[field] = value;
50
+ }
51
+ }
52
+ function addHeaderLineDistinct(field, value, dest) {
53
+ field = field.toLowerCase();
54
+ if (!dest[field]) {
55
+ dest[field] = [value];
56
+ }
57
+ else {
58
+ dest[field].push(value);
59
+ }
60
+ }
@@ -0,0 +1,21 @@
1
+ import { ARRAY_FIELD, COMMA_DELIMITED_FIELD, matchKnownFields, SEMICOLON_DELIMITED_FIELD } from './match-known-fields.js';
2
+ export function convertToRawHeaders(src) {
3
+ return Object.entries(src)
4
+ .reduce((a, [field, v]) => {
5
+ const [name, flag] = matchKnownFields(field);
6
+ if (flag === ARRAY_FIELD) {
7
+ if (Array.isArray(v))
8
+ v.forEach(x => a.push(name, String(x)));
9
+ else
10
+ a.push(name, String(v));
11
+ return a;
12
+ }
13
+ if (flag === COMMA_DELIMITED_FIELD || flag === SEMICOLON_DELIMITED_FIELD) {
14
+ v = Array.isArray(v) ? v.join(flag === COMMA_DELIMITED_FIELD ? ', ' : '; ') : String(v);
15
+ }
16
+ else
17
+ v = Array.isArray(v) ? String(v[0]) : String(v);
18
+ a.push(name, v);
19
+ return a;
20
+ }, []);
21
+ }
@@ -0,0 +1,43 @@
1
+ import { HttpHeaderCodes } from '@opra/common';
2
+ export const NO_DUPLICATES_FIELD = 0;
3
+ export const COMMA_DELIMITED_FIELD = 1;
4
+ export const SEMICOLON_DELIMITED_FIELD = 2;
5
+ export const ARRAY_FIELD = 3;
6
+ const ARRAY_HEADERS = [
7
+ 'set-cookie'
8
+ ];
9
+ const NO_DUPLICATES_HEADERS = [
10
+ 'age',
11
+ 'from',
12
+ 'etag',
13
+ 'server',
14
+ 'referer',
15
+ 'referrer',
16
+ 'expires',
17
+ 'location',
18
+ 'user-agent',
19
+ 'retry-after',
20
+ 'content-type',
21
+ 'content-length',
22
+ 'max-forwards',
23
+ 'last-modified',
24
+ 'authorization',
25
+ 'proxy-authorization',
26
+ 'if-modified-since',
27
+ 'if-unmodified-since',
28
+ ];
29
+ const SEMICOLON_DELIMITED_HEADERS = ['cookie'];
30
+ const KNOWN_FIELDS = Object.values(HttpHeaderCodes)
31
+ .reduce((o, k) => {
32
+ const n = k.toLowerCase();
33
+ o[n] = [k,
34
+ (NO_DUPLICATES_HEADERS.includes(n) ? NO_DUPLICATES_FIELD :
35
+ (ARRAY_HEADERS.includes(n) ? ARRAY_FIELD :
36
+ (SEMICOLON_DELIMITED_HEADERS.includes(n) ? SEMICOLON_DELIMITED_FIELD : COMMA_DELIMITED_FIELD)))
37
+ ];
38
+ return o;
39
+ }, {});
40
+ export function matchKnownFields(field) {
41
+ const x = KNOWN_FIELDS[field.toLowerCase()];
42
+ return x ? x : [field, COMMA_DELIMITED_FIELD];
43
+ }
@@ -1,9 +1,9 @@
1
1
  import { Task } from 'power-tasks';
2
- import { BadRequestError, Collection, HttpHeaderCodes, HttpStatusCodes, InternalServerError, isReadable, IssueSeverity, MethodNotAllowedError, OpraException, OpraSchema, OpraURL, Singleton, wrapException } from '@opra/common';
2
+ import { BadRequestError, Collection, HttpHeaderCodes, HttpStatusCodes, InternalServerError, isReadable, IssueSeverity, OpraException, OpraSchema, Singleton, wrapException } from '@opra/common';
3
3
  import { OpraAdapter } from '../adapter.js';
4
- import { HttpRequestHost } from './http-request.host.js';
5
- import { HttpRequestContextHost } from './http-request-context.host.js';
6
- import { HttpResponseHost } from './http-response.host.js';
4
+ import { RequestContextHost } from '../request-context.host.js';
5
+ import { ResponseHost } from '../response.host.js';
6
+ import { parseRequest } from './request-parsers/parse-request.js';
7
7
  /**
8
8
  *
9
9
  * @class OpraHttpAdapter
@@ -17,43 +17,79 @@ export class OpraHttpAdapter extends OpraAdapter {
17
17
  */
18
18
  async handler(incoming, outgoing) {
19
19
  try {
20
- // Batch
21
- if (incoming.is('multipart/mixed')) {
22
- throw new BadRequestError({ message: 'Not implemented yet' });
20
+ try {
21
+ const request = await parseRequest(this._apiRoot, incoming);
22
+ const task = this.createRequestTask(request, outgoing);
23
+ await task.toPromise();
23
24
  }
24
- if (!(incoming.method === 'POST' || incoming.method === 'PATCH') || incoming.is('json')) {
25
- const request = await this.parseRequest(incoming);
26
- const response = new HttpResponseHost({}, outgoing);
27
- const context = new HttpRequestContextHost(this.platform, this.api, request, response);
28
- const task = new Task(async () => {
29
- try {
30
- await this.executeRequest(context);
31
- if (request.operation === 'findMany' && request.args.count && response.count != null) {
32
- response.switchToHttp().header(HttpHeaderCodes.X_Opra_Total_Matches, String(response.count));
33
- }
34
- }
35
- catch (error) {
36
- return this.errorHandler(incoming, outgoing, [error]);
37
- }
38
- await this.sendResponse(context);
39
- }, {
40
- id: incoming.get('content-id'),
41
- exclusive: request.crud !== 'read'
42
- });
43
- await task.toPromise().catch(e => {
44
- this.logger?.error?.(e);
45
- outgoing.sendStatus(500);
46
- });
47
- await this.emitAsync('request-finish', context);
48
- return;
25
+ catch (e) {
26
+ if (e instanceof OpraException)
27
+ throw e;
28
+ throw new BadRequestError(e);
49
29
  }
50
- throw new BadRequestError({ message: 'Unsupported Content-Type' });
51
30
  }
52
31
  catch (error) {
53
- await this.errorHandler(incoming, outgoing, [error]);
32
+ await this.handleError(incoming, outgoing, [error]);
54
33
  }
55
34
  }
56
- async errorHandler(incoming, outgoing, errors) {
35
+ createRequestTask(request, outgoing) {
36
+ return new Task(async () => {
37
+ const response = new ResponseHost({ http: outgoing });
38
+ const context = new RequestContextHost('http', this.platform, this.api, request, response);
39
+ return this.executeRequest(context);
40
+ }, {
41
+ id: request.contentId,
42
+ exclusive: request.crud !== 'read'
43
+ });
44
+ }
45
+ async executeRequest(context) {
46
+ const { request, response } = context;
47
+ await super.executeRequest(context)
48
+ .catch(e => {
49
+ response.errors = response.errors || [];
50
+ response.errors.push(e);
51
+ });
52
+ const outgoing = response.switchToHttp();
53
+ if (response.errors?.length) {
54
+ const errors = response.errors.map(e => wrapException(e));
55
+ await this.handleError(request.switchToHttp(), outgoing, errors);
56
+ return;
57
+ }
58
+ if (request.resource instanceof Singleton || request.resource instanceof Collection) {
59
+ outgoing.setHeader(HttpHeaderCodes.X_Opra_Data_Type, String(request.resource.type.name));
60
+ outgoing.setHeader(HttpHeaderCodes.X_Opra_Operation, request.operation);
61
+ }
62
+ if (request.crud === 'create') {
63
+ if (!response.value)
64
+ throw new InternalServerError();
65
+ // todo validate
66
+ outgoing.statusCode = 201;
67
+ }
68
+ if (request.resource instanceof Collection &&
69
+ request.crud === 'read' && request.many && request.args.count >= 0) {
70
+ outgoing.setHeader(HttpHeaderCodes.X_Opra_Total_Matches, String(response.count));
71
+ }
72
+ outgoing.statusCode = outgoing.statusCode || HttpStatusCodes.OK;
73
+ outgoing.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
74
+ outgoing.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
75
+ outgoing.setHeader(HttpHeaderCodes.Expires, '-1');
76
+ outgoing.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
77
+ if (response.value) {
78
+ if (typeof response.value === 'object') {
79
+ if (isReadable(response.value) || Buffer.isBuffer(response.value))
80
+ outgoing.send(response.value);
81
+ else {
82
+ const body = this.i18n.deep(response.value);
83
+ outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
84
+ outgoing.send(JSON.stringify(body));
85
+ }
86
+ }
87
+ else
88
+ outgoing.send(JSON.stringify(response.value));
89
+ }
90
+ outgoing.end();
91
+ }
92
+ async handleError(incoming, outgoing, errors) {
57
93
  errors.forEach(e => {
58
94
  this.log((e instanceof OpraException) ? 'error' : 'fatal', incoming, e); // todo. implement a better logger
59
95
  });
@@ -74,12 +110,12 @@ export class OpraHttpAdapter extends OpraAdapter {
74
110
  const body = this.i18n.deep({
75
111
  errors: errors.map(e => e.issue)
76
112
  });
77
- outgoing.set(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
78
- outgoing.set(HttpHeaderCodes.Cache_Control, 'no-cache');
79
- outgoing.set(HttpHeaderCodes.Pragma, 'no-cache');
80
- outgoing.set(HttpHeaderCodes.Expires, '-1');
81
- outgoing.set(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
82
- outgoing.status(status);
113
+ outgoing.statusCode = status;
114
+ outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
115
+ outgoing.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
116
+ outgoing.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
117
+ outgoing.setHeader(HttpHeaderCodes.Expires, '-1');
118
+ outgoing.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
83
119
  outgoing.send(JSON.stringify(body));
84
120
  outgoing.end();
85
121
  }
@@ -91,402 +127,4 @@ export class OpraHttpAdapter extends OpraAdapter {
91
127
  return;
92
128
  logFn.apply(this.logger, [String(message), ...optionalParams]);
93
129
  }
94
- async executeRequest(context) {
95
- await super.executeRequest(context);
96
- const { request } = context;
97
- const response = context.response;
98
- const { crud } = request;
99
- const httpResponse = response.switchToHttp();
100
- if (request.resource instanceof Singleton || request.resource instanceof Collection) {
101
- httpResponse.set(HttpHeaderCodes.X_Opra_Data_Type, request.resource.type.name);
102
- httpResponse.set(HttpHeaderCodes.X_Opra_Operation, request.operation);
103
- }
104
- if (crud === 'create') {
105
- if (!response.value)
106
- throw new InternalServerError();
107
- // todo validate
108
- httpResponse.status(201);
109
- }
110
- }
111
- /**
112
- *
113
- * @param incoming
114
- * @protected
115
- */
116
- async parseRequest(incoming) {
117
- try {
118
- const url = new OpraURL();
119
- url.searchParams.define({
120
- '$search': { codec: 'string' },
121
- '$pick': { codec: 'string', array: 'strict' },
122
- '$omit': { codec: 'string', array: 'strict' },
123
- '$include': { codec: 'string', array: 'strict' },
124
- '$sort': { codec: 'string', array: 'strict' },
125
- '$filter': { codec: 'filter' },
126
- '$limit': { codec: 'number' },
127
- '$skip': { codec: 'number' },
128
- '$distinct': { codec: 'boolean' },
129
- '$count': { codec: 'boolean' },
130
- });
131
- url.parse(incoming.url);
132
- // const {context, url, method, headers, body, contentId} = args;
133
- if (!url.path.size)
134
- throw new BadRequestError();
135
- const method = incoming.method;
136
- if (method !== 'GET' && url.path.size > 1)
137
- throw new BadRequestError();
138
- // const pathLen = url.path.size;
139
- // let pathIndex = 0;
140
- const params = url.searchParams;
141
- const p = url.path.get(0);
142
- const resource = this._internalDoc.getResource(p.resource);
143
- // let container: IResourceContainer | undefined;
144
- // while (resource && resource instanceof ContainerResourceInfo) {
145
- // container = resource;
146
- // p = url.path.get(++pathIndex);
147
- // resource = container.getResource(p.resource);
148
- // }
149
- // const headers = incoming.headers;
150
- /*
151
- * Collection
152
- */
153
- if (resource instanceof Collection) {
154
- switch (method) {
155
- case 'POST': {
156
- if (!p.key) {
157
- const pick = params.get('$pick');
158
- const omit = params.get('$omit');
159
- const include = params.get('$include');
160
- return new HttpRequestHost({
161
- kind: 'CollectionCreateRequest',
162
- resource,
163
- operation: 'create',
164
- crud: 'create',
165
- many: false,
166
- args: {
167
- data: incoming.body,
168
- pick: pick && resource.normalizeFieldPath(pick),
169
- omit: omit && resource.normalizeFieldPath(omit),
170
- include: include && resource.normalizeFieldPath(include)
171
- }
172
- }, incoming);
173
- }
174
- break;
175
- }
176
- case 'DELETE': {
177
- if (p.key) {
178
- return new HttpRequestHost({
179
- kind: 'CollectionDeleteRequest',
180
- resource,
181
- operation: 'delete',
182
- crud: 'delete',
183
- many: false,
184
- args: {
185
- key: resource.parseKeyValue(p.key)
186
- }
187
- }, incoming);
188
- }
189
- const filter = resource.normalizeFilter(params.get('$filter'));
190
- return new HttpRequestHost({
191
- kind: 'CollectionDeleteManyRequest',
192
- resource,
193
- operation: 'deleteMany',
194
- crud: 'delete',
195
- many: true,
196
- args: {
197
- filter
198
- }
199
- }, incoming);
200
- }
201
- case 'GET': {
202
- const pick = params.get('$pick');
203
- const omit = params.get('$omit');
204
- const include = params.get('$include');
205
- if (p.key) {
206
- return new HttpRequestHost({
207
- kind: 'CollectionGetRequest',
208
- resource,
209
- operation: 'get',
210
- crud: 'read',
211
- many: false,
212
- args: {
213
- key: resource.parseKeyValue(p.key),
214
- pick: pick && resource.normalizeFieldPath(pick),
215
- omit: omit && resource.normalizeFieldPath(omit),
216
- include: include && resource.normalizeFieldPath(include)
217
- }
218
- }, incoming);
219
- }
220
- const filter = resource.normalizeFilter(params.get('$filter'));
221
- const sort = params.get('$sort');
222
- return new HttpRequestHost({
223
- kind: 'CollectionFindManyRequest',
224
- resource,
225
- operation: 'findMany',
226
- crud: 'read',
227
- many: true,
228
- args: {
229
- pick: pick && resource.normalizeFieldPath(pick),
230
- omit: omit && resource.normalizeFieldPath(omit),
231
- include: include && resource.normalizeFieldPath(include),
232
- sort: sort && resource.normalizeSortFields(sort),
233
- filter,
234
- limit: params.get('$limit'),
235
- skip: params.get('$skip'),
236
- distinct: params.get('$distinct'),
237
- count: params.get('$count'),
238
- }
239
- }, incoming);
240
- }
241
- case 'PATCH': {
242
- if (p.key) {
243
- const pick = params.get('$pick');
244
- const omit = params.get('$omit');
245
- const include = params.get('$include');
246
- return new HttpRequestHost({
247
- kind: 'CollectionUpdateRequest',
248
- resource,
249
- operation: 'update',
250
- crud: 'update',
251
- many: false,
252
- args: {
253
- key: resource.parseKeyValue(p.key),
254
- data: incoming.body,
255
- pick: pick && resource.normalizeFieldPath(pick),
256
- omit: omit && resource.normalizeFieldPath(omit),
257
- include: include && resource.normalizeFieldPath(include),
258
- }
259
- }, incoming);
260
- }
261
- const filter = resource.normalizeFilter(params.get('$filter'));
262
- return new HttpRequestHost({
263
- kind: 'CollectionUpdateManyRequest',
264
- resource,
265
- operation: 'updateMany',
266
- crud: 'update',
267
- many: true,
268
- args: {
269
- data: incoming.body,
270
- filter,
271
- }
272
- }, incoming);
273
- }
274
- default:
275
- throw new BadRequestError();
276
- }
277
- }
278
- else
279
- /*
280
- * Singleton
281
- */
282
- if (resource instanceof Singleton) {
283
- if (p.key)
284
- throw new BadRequestError();
285
- switch (method) {
286
- case 'POST': {
287
- const pick = params.get('$pick');
288
- const omit = params.get('$omit');
289
- const include = params.get('$include');
290
- return new HttpRequestHost({
291
- kind: 'SingletonCreateRequest',
292
- resource,
293
- operation: 'create',
294
- crud: 'create',
295
- many: false,
296
- args: {
297
- data: incoming.body,
298
- pick: pick && resource.normalizeFieldPath(pick),
299
- omit: omit && resource.normalizeFieldPath(omit),
300
- include: include && resource.normalizeFieldPath(include),
301
- }
302
- }, incoming);
303
- }
304
- case 'DELETE': {
305
- return new HttpRequestHost({
306
- kind: 'SingletonDeleteRequest',
307
- resource,
308
- operation: 'delete',
309
- crud: 'delete',
310
- many: false,
311
- args: {}
312
- }, incoming);
313
- }
314
- case 'GET': {
315
- const pick = params.get('$pick');
316
- const omit = params.get('$omit');
317
- const include = params.get('$include');
318
- return new HttpRequestHost({
319
- kind: 'SingletonGetRequest',
320
- resource,
321
- operation: 'get',
322
- crud: 'read',
323
- many: false,
324
- args: {
325
- pick: pick && resource.normalizeFieldPath(pick),
326
- omit: omit && resource.normalizeFieldPath(omit),
327
- include: include && resource.normalizeFieldPath(include),
328
- }
329
- }, incoming);
330
- }
331
- case 'PATCH': {
332
- const pick = params.get('$pick');
333
- const omit = params.get('$omit');
334
- const include = params.get('$include');
335
- return new HttpRequestHost({
336
- kind: 'SingletonUpdateRequest',
337
- resource,
338
- operation: 'update',
339
- crud: 'update',
340
- many: false,
341
- args: {
342
- data: incoming.body,
343
- pick: pick && resource.normalizeFieldPath(pick),
344
- omit: omit && resource.normalizeFieldPath(omit),
345
- include: include && resource.normalizeFieldPath(include),
346
- }
347
- }, incoming);
348
- }
349
- default:
350
- throw new BadRequestError();
351
- }
352
- }
353
- else
354
- throw new InternalServerError();
355
- // if (query instanceof SingletonGetQuery || query instanceof CollectionGetQuery || query instanceof ElementReadQuery) {
356
- // // Move through properties
357
- // let parentType: DataType;
358
- // const curPath: string[] = [];
359
- // let parent: SingletonGetQuery | CollectionGetQuery | ElementReadQuery = query;
360
- // while (++pathIndex < pathLen) {
361
- // p = url.path.get(pathIndex);
362
- // parentType = parent.type;
363
- // if (parent.type instanceof UnionType) {
364
- // if (parent.type.name === 'any')
365
- // parentType = this.document.getComplexType('object');
366
- // else
367
- // throw new TypeError(`"${resource.name}.${curPath.join()}" is a UnionType and needs type casting.`);
368
- // }
369
- // if (!(parentType instanceof ComplexType))
370
- // throw new TypeError(`"${resource.name}.${curPath.join()}" is not a ComplexType and has no fields.`);
371
- // curPath.push(p.resource);
372
- // parent.child = new ElementReadQuery(parent, p.resource, {castingType: parentType});
373
- // parent = parent.child;
374
- // }
375
- // }
376
- throw new MethodNotAllowedError({
377
- message: `Method "${method}" is not allowed by target endpoint`
378
- });
379
- }
380
- catch (e) {
381
- if (e instanceof OpraException)
382
- throw e;
383
- throw new BadRequestError(e);
384
- }
385
- }
386
- // async parseMultiPart(
387
- // context: TExecutionContext,
388
- // url: OpraURL,
389
- // headers: IncomingHttpHeaders,
390
- // input: Readable,
391
- // boundary: string
392
- // ): Promise<BatchRequestContext> {
393
- // return await new Promise((resolve, reject) => {
394
- // let _resolved = false;
395
- // const dicer = new Dicer({boundary});
396
- // const doReject = (e) => {
397
- // if (_resolved) return;
398
- // _resolved = true;
399
- // reject(e);
400
- // taskQueue.clearQueue();
401
- // dicer.destroy();
402
- // }
403
- // const taskQueue = new TaskQueue({concurrency: 1});
404
- // taskQueue.on('error', doReject);
405
- //
406
- // const queries: SingleRequestContext[] = [];
407
- // let partCounter = 0;
408
- // dicer.on('error', doReject);
409
- // dicer.on('part', part => {
410
- // const partIndex = partCounter++;
411
- // let header: any;
412
- // const chunks: Buffer[] = [];
413
- // part.on('error', doReject);
414
- // part.on('header', (_header) => header = normalizeHeaders(_header));
415
- // part.on('data', (chunk: Buffer) => chunks.push(chunk));
416
- // part.on('end', () => {
417
- // if (_resolved || !(header || chunks.length))
418
- // return;
419
- // const ct = header['content-type'];
420
- // if (ct === 'application/http') {
421
- // taskQueue.enqueue(async () => {
422
- // const data = Buffer.concat(chunks);
423
- // if (!(data && data.length))
424
- // return;
425
- // const r = HttpRequest.parse(data);
426
- // await callMiddlewares(r, [jsonBodyParser]);
427
- // const subUrl = new OpraURL(r.url);
428
- // const contentId = header && header['content-id'];
429
- // queries.push(this.parseSingleQuery({
430
- // context,
431
- // url: subUrl,
432
- // method: r.method,
433
- // headers: r.headers,
434
- // body: r.body,
435
- // contentId
436
- // }));
437
- // });
438
- // } else doReject(new BadRequestError({
439
- // message: 'Unaccepted "content-type" header in multipart data',
440
- // details: {
441
- // position: `${boundary}[${partIndex}]`
442
- // }
443
- // }))
444
- // });
445
- // });
446
- // dicer.on('finish', () => {
447
- // taskQueue.enqueue(() => {
448
- // if (_resolved) return;
449
- // _resolved = true;
450
- // const batch = new BatchRequestContext({
451
- // service: this.document,
452
- // context,
453
- // headers,
454
- // queries,
455
- // params: url.searchParams,
456
- // continueOnError: false
457
- // });
458
- // resolve(batch);
459
- // });
460
- // });
461
- // input.pipe(dicer);
462
- // });
463
- // }
464
- async sendResponse(context) {
465
- const { request, response } = context;
466
- const outgoing = response.switchToHttp();
467
- const errors = response.errors?.map(e => wrapException(e));
468
- if (errors && errors.length) {
469
- await this.errorHandler(request.switchToHttp(), outgoing, errors);
470
- return;
471
- }
472
- outgoing.set(HttpHeaderCodes.Cache_Control, 'no-cache');
473
- outgoing.set(HttpHeaderCodes.Pragma, 'no-cache');
474
- outgoing.set(HttpHeaderCodes.Expires, '-1');
475
- outgoing.set(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
476
- outgoing.status(outgoing.statusCode || HttpStatusCodes.OK);
477
- if (response.value) {
478
- if (typeof response.value === 'object') {
479
- if (isReadable(response.value) || Buffer.isBuffer(response.value))
480
- outgoing.send(response.value);
481
- else {
482
- const body = this.i18n.deep(response.value);
483
- outgoing.set(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
484
- outgoing.send(JSON.stringify(body));
485
- }
486
- }
487
- else
488
- outgoing.send(JSON.stringify(response.value));
489
- }
490
- outgoing.end();
491
- }
492
130
  }