@trayio/commons 3.12.0-unstable → 3.12.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.
@@ -3,6 +3,7 @@ import * as O from 'fp-ts/lib/Option';
3
3
  import { Readable } from 'stream';
4
4
  import { File } from '../file/File';
5
5
  import { SerializationFormat } from '../serialization/Serialization';
6
+ import { DynamicObject } from '../dynamictype/DynamicType';
6
7
  export type HttpHeaderValue = string | string[];
7
8
  export type HttpHeaders = Record<string, HttpHeaderValue | HttpContentType>;
8
9
  export type HttpQueryStringSingleValue = number | string | boolean;
@@ -51,6 +52,8 @@ export declare enum HttpHeader {
51
52
  export interface HttpHeadersInterface {
52
53
  get: (headers: HttpHeaders, name: string) => O.Option<HttpHeaderValue>;
53
54
  extractBearerToken: (headers: HttpHeaders) => O.Option<string>;
55
+ fromDynamicObject: (headers: DynamicObject) => HttpHeaders;
56
+ toDynamicObject: (headers: HttpHeaders) => DynamicObject;
54
57
  }
55
58
  export declare const HttpHeaders: HttpHeadersInterface;
56
59
  export declare const serializationFormatToHttpContentType: Record<SerializationFormat, HttpContentType>;
@@ -1 +1 @@
1
- {"version":3,"file":"Http.d.ts","sourceRoot":"","sources":["../../src/http/Http.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,CAAC,MAAM,kBAAkB,CAAC;AAEtC,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAOrE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC;AAEhD,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAAC,CAAC;AAE5E,MAAM,MAAM,0BAA0B,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEnE,MAAM,MAAM,oBAAoB,GAC7B,0BAA0B,GAC1B,0BAA0B,EAAE,CAAC;AAEhC,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;AAEnE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;AAE9C,MAAM,MAAM,wBAAwB,GAAG;IACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;AAEhF,MAAM,MAAM,WAAW,GAAG;IACzB,OAAO,EAAE,WAAW,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,WAAW,EAAE,eAAe,CAAC;IAC7B,IAAI,EAAE,eAAe,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,EAAE,QAAQ,CAAC;CACf,CAAC;AAEF,oBAAY,UAAU;IACrB,MAAM,WAAW;IACjB,GAAG,QAAQ;IACX,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,KAAK,UAAU;IACf,IAAI,SAAS;IACb,GAAG,QAAQ;CACX;AAED,oBAAY,eAAe;IAC1B,IAAI,qBAAqB;IACzB,WAAW,6BAA6B;IACxC,cAAc,sCAAsC;IACpD,oBAAoB,wBAAwB;IAC5C,IAAI,eAAe;CACnB;AAED,oBAAY,UAAU;IACrB,aAAa,kBAAkB;IAC/B,WAAW,iBAAiB;IAC5B,aAAa,6BAA6B;IAC1C,SAAS,eAAe;IACxB,aAAa,mBAAmB;CAChC;AAED,MAAM,WAAW,oBAAoB;IACpC,GAAG,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACvE,kBAAkB,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;CAC/D;AAED,eAAO,MAAM,WAAW,EAAE,oBAYzB,CAAC;AAEF,eAAO,MAAM,oCAAoC,EAAE,MAAM,CACxD,mBAAmB,EACnB,eAAe,CAGf,CAAC;AAEF,eAAO,MAAM,eAAe,SACrB,eAAe,qCAEkC,CAAC"}
1
+ {"version":3,"file":"Http.d.ts","sourceRoot":"","sources":["../../src/http/Http.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,CAAC,MAAM,kBAAkB,CAAC;AAEtC,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAO3D,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC;AAEhD,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAAC,CAAC;AAE5E,MAAM,MAAM,0BAA0B,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEnE,MAAM,MAAM,oBAAoB,GAC7B,0BAA0B,GAC1B,0BAA0B,EAAE,CAAC;AAEhC,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;AAEnE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;AAE9C,MAAM,MAAM,wBAAwB,GAAG;IACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;AAEhF,MAAM,MAAM,WAAW,GAAG;IACzB,OAAO,EAAE,WAAW,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,WAAW,EAAE,eAAe,CAAC;IAC7B,IAAI,EAAE,eAAe,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,EAAE,QAAQ,CAAC;CACf,CAAC;AAEF,oBAAY,UAAU;IACrB,MAAM,WAAW;IACjB,GAAG,QAAQ;IACX,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,KAAK,UAAU;IACf,IAAI,SAAS;IACb,GAAG,QAAQ;CACX;AAED,oBAAY,eAAe;IAC1B,IAAI,qBAAqB;IACzB,WAAW,6BAA6B;IACxC,cAAc,sCAAsC;IACpD,oBAAoB,wBAAwB;IAC5C,IAAI,eAAe;CACnB;AAED,oBAAY,UAAU;IACrB,aAAa,kBAAkB;IAC/B,WAAW,iBAAiB;IAC5B,aAAa,6BAA6B;IAC1C,SAAS,eAAe;IACxB,aAAa,mBAAmB;CAChC;AAED,MAAM,WAAW,oBAAoB;IACpC,GAAG,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACvE,kBAAkB,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/D,iBAAiB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,WAAW,CAAC;IAC3D,eAAe,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,aAAa,CAAC;CACzD;AAED,eAAO,MAAM,WAAW,EAAE,oBAyIzB,CAAC;AAEF,eAAO,MAAM,oCAAoC,EAAE,MAAM,CACxD,mBAAmB,EACnB,eAAe,CAGf,CAAC;AAEF,eAAO,MAAM,eAAe,SACrB,eAAe,qCAEkC,CAAC"}
package/dist/http/Http.js CHANGED
@@ -59,6 +59,115 @@ exports.HttpHeaders = {
59
59
  extractBearerToken: (headers) => (0, function_1.pipe)(getHeader(headers, HttpHeader.Authorization), O.chain((header) => header.toLowerCase().startsWith('bearer ')
60
60
  ? O.fromNullable(header.substring(7))
61
61
  : O.fromNullable(null))),
62
+ fromDynamicObject(headers) {
63
+ const flattenObject = (obj, prefix = '') => Object.entries(obj).reduce((acc, [key, value]) => {
64
+ const newKey = prefix ? `${prefix}.${key}` : key;
65
+ if (typeof value === 'object' &&
66
+ !Array.isArray(value) &&
67
+ value !== null) {
68
+ return { ...acc, ...flattenObject(value, newKey) };
69
+ }
70
+ acc[newKey] = Array.isArray(value) ? value.join(',') : value;
71
+ return acc;
72
+ }, {});
73
+ const transformHeaders = (obj) => Object.entries(obj).reduce((acc, [key, value]) => {
74
+ const keyParts = key.split('.');
75
+ const mainKey = keyParts.shift();
76
+ if (mainKey === undefined) {
77
+ return acc;
78
+ }
79
+ if (typeof value === 'string' && keyParts.length === 0) {
80
+ if (Array.isArray(headers[mainKey])) {
81
+ const topLevelArr = headers[mainKey];
82
+ if (topLevelArr.every((item) => !item.includes('='))) {
83
+ /*
84
+ Single element arrays or empty arrays should be treated as a single value
85
+ [] -> ''
86
+ ['value'] -> 'value'
87
+ */
88
+ if (topLevelArr.length === 1) {
89
+ // eslint-disable-next-line prefer-destructuring
90
+ acc[mainKey] = topLevelArr[0];
91
+ }
92
+ else if (topLevelArr.length === 0) {
93
+ acc[mainKey] = '';
94
+ }
95
+ else {
96
+ acc[mainKey] = topLevelArr;
97
+ }
98
+ }
99
+ }
100
+ else {
101
+ acc[mainKey] = value;
102
+ }
103
+ }
104
+ else {
105
+ acc[mainKey] = acc[mainKey] || [];
106
+ acc[mainKey].push(`${keyParts.join('.')}=${value}`);
107
+ }
108
+ return acc;
109
+ }, {});
110
+ const flattened = flattenObject(headers);
111
+ return transformHeaders(flattened);
112
+ },
113
+ toDynamicObject: (headers) => {
114
+ /*
115
+ Headers will conform to OpenAPI 3.0 standards with 'style: simple', 'explode: true'
116
+ See: https://swagger.io/docs/specification/serialization/
117
+ EG:
118
+ Primitive - X-MyHeader: [1]
119
+ Array - X-MyHeader: [3,4,5]
120
+ Object - X-MyHeader: [role=admin, name.first=Alex, name.last=Smith, someArray=2,3,4]
121
+ */
122
+ const parseNestedProperties = (item, obj) => {
123
+ const [key, value] = item.split('=');
124
+ const modifiedVal = value.includes(',') ? value.split(',') : undefined;
125
+ if (key.includes('.')) {
126
+ assignNestedProperty(key, value, obj);
127
+ }
128
+ else {
129
+ obj[key] = modifiedVal || value;
130
+ }
131
+ return obj;
132
+ };
133
+ const assignNestedProperty = (key, value, obj) => {
134
+ const keys = key.split('.');
135
+ obj[keys[0]] = obj[keys[0]] || {};
136
+ if (keys.length > 1) {
137
+ parseNestedProperties(`${keys.slice(1).join('.')}=${value}`, obj[keys[0]]);
138
+ }
139
+ else {
140
+ obj[keys[0]] = value;
141
+ }
142
+ };
143
+ return Object.keys(headers).reduce((acc, key) => {
144
+ const value = headers[key];
145
+ if (Array.isArray(value)) {
146
+ if (value.length === 0) {
147
+ acc[key] = '';
148
+ }
149
+ else if (value.length === 1) {
150
+ if (value[0].includes('=')) {
151
+ acc[key] = parseNestedProperties(value[0], {});
152
+ }
153
+ else {
154
+ // eslint-disable-next-line prefer-destructuring
155
+ acc[key] = value[0];
156
+ }
157
+ }
158
+ else if (value.every((item) => !item.includes('='))) {
159
+ acc[key] = value;
160
+ }
161
+ else {
162
+ acc[key] = value.reduce((obj, item) => parseNestedProperties(item, obj), {});
163
+ }
164
+ }
165
+ else {
166
+ acc[key] = value;
167
+ }
168
+ return acc;
169
+ }, {});
170
+ },
62
171
  };
63
172
  exports.serializationFormatToHttpContentType = {
64
173
  [Serialization_1.SerializationFormat.Json]: HttpContentType.Json,
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Http.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Http.unit.test.d.ts","sourceRoot":"","sources":["../../src/http/Http.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const Http_1 = require("./Http");
4
+ describe('HttpHeaders', () => {
5
+ it('should convert headers to DynamicObject', () => {
6
+ const headers = {
7
+ 'content-type': 'application/json',
8
+ 'content-length': '100',
9
+ 'x-custom-header': [
10
+ 'role=admin',
11
+ 'name.first=Alex',
12
+ 'name.last=Smith',
13
+ 'description.detail.age=30',
14
+ 'array.values=2,3,4',
15
+ ],
16
+ 'x-custom-header-2': ['prop=val'],
17
+ 'x-custom-header-3': ['val'],
18
+ 'x-custom-header-4': ['1', '2', '3'],
19
+ };
20
+ const result = Http_1.HttpHeaders.toDynamicObject(headers);
21
+ expect(result).toEqual({
22
+ 'content-type': 'application/json',
23
+ 'content-length': '100',
24
+ 'x-custom-header': {
25
+ role: 'admin',
26
+ name: {
27
+ first: 'Alex',
28
+ last: 'Smith',
29
+ },
30
+ description: {
31
+ detail: {
32
+ age: '30',
33
+ },
34
+ },
35
+ array: {
36
+ values: ['2', '3', '4'],
37
+ },
38
+ },
39
+ 'x-custom-header-2': {
40
+ prop: 'val',
41
+ },
42
+ 'x-custom-header-3': 'val',
43
+ 'x-custom-header-4': ['1', '2', '3'],
44
+ });
45
+ });
46
+ it('should convert headers to DynamicObject with empty array', () => {
47
+ const headers = {
48
+ 'content-type': 'application/json',
49
+ 'content-length': '100',
50
+ 'x-custom-header': [],
51
+ };
52
+ const result = Http_1.HttpHeaders.toDynamicObject(headers);
53
+ expect(result).toEqual({
54
+ 'content-type': 'application/json',
55
+ 'content-length': '100',
56
+ 'x-custom-header': '',
57
+ });
58
+ });
59
+ it('should convert DynamicObject to headers', () => {
60
+ const headers = {
61
+ 'content-type': 'application/json',
62
+ 'content-length': '100',
63
+ 'x-empty-header': [],
64
+ 'x-single-element-header': ['element'],
65
+ 'x-custom-header': {
66
+ role: 'admin',
67
+ name: {
68
+ first: 'Alex',
69
+ last: 'Smith',
70
+ },
71
+ description: {
72
+ detail: {
73
+ age: '30',
74
+ },
75
+ },
76
+ array: {
77
+ values: ['2', '3', '4'],
78
+ },
79
+ },
80
+ 'x-custom-header-4': ['1', '2', '3'],
81
+ };
82
+ const result = Http_1.HttpHeaders.fromDynamicObject(headers);
83
+ expect(result).toEqual({
84
+ 'content-type': 'application/json',
85
+ 'content-length': '100',
86
+ 'x-empty-header': '',
87
+ 'x-single-element-header': 'element',
88
+ 'x-custom-header': [
89
+ 'role=admin',
90
+ 'name.first=Alex',
91
+ 'name.last=Smith',
92
+ 'description.detail.age=30',
93
+ 'array.values=2,3,4',
94
+ ],
95
+ 'x-custom-header-4': ['1', '2', '3'],
96
+ });
97
+ });
98
+ it('should serialize headers then deserialize and get the same result', () => {
99
+ const headers = {
100
+ 'content-type': 'application/json',
101
+ 'content-length': '100',
102
+ 'x-custom-header': {
103
+ role: 'admin',
104
+ name: {
105
+ first: 'Alex',
106
+ last: 'Smith',
107
+ },
108
+ description: {
109
+ detail: {
110
+ age: '30',
111
+ },
112
+ },
113
+ array: {
114
+ values: ['2', '3', '4'],
115
+ },
116
+ },
117
+ 'x-custom-header-4': ['1', '2', '3'],
118
+ 'x-custom-header-2': '',
119
+ };
120
+ const serialized = Http_1.HttpHeaders.fromDynamicObject(headers);
121
+ const deserialized = Http_1.HttpHeaders.toDynamicObject(serialized);
122
+ expect(deserialized).toEqual(headers);
123
+ });
124
+ it('should serialize headers with empty array then deserialize and get an empty string', () => {
125
+ const headers = {
126
+ 'x-custom-header-2': [],
127
+ };
128
+ const serialized = Http_1.HttpHeaders.fromDynamicObject(headers);
129
+ const deserialized = Http_1.HttpHeaders.toDynamicObject(serialized);
130
+ expect(deserialized).toEqual({
131
+ 'x-custom-header-2': '',
132
+ });
133
+ });
134
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trayio/commons",
3
- "version": "3.12.0-unstable",
3
+ "version": "3.12.0",
4
4
  "description": "Extensions to the standard/core libraries and basic features",
5
5
  "exports": {
6
6
  "./*": "./dist/*.js"