@outfoxx/sunday 1.0.8 → 1.1.0-alpha.10

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 (151) hide show
  1. package/README.md +35 -2
  2. package/dist/any-type.d.ts +2 -0
  3. package/dist/class-type.d.ts +2 -2
  4. package/dist/date-time-types.d.ts +2 -7
  5. package/dist/date-time-types.js +2 -4
  6. package/dist/date-time-types.js.map +1 -1
  7. package/dist/event-parser.d.ts +17 -0
  8. package/dist/event-parser.js +140 -0
  9. package/dist/event-parser.js.map +1 -0
  10. package/dist/fetch-event-source.d.ts +12 -10
  11. package/dist/fetch-event-source.js +141 -138
  12. package/dist/fetch-event-source.js.map +1 -1
  13. package/dist/fetch-request-factory.d.ts +10 -5
  14. package/dist/fetch-request-factory.js +87 -71
  15. package/dist/fetch-request-factory.js.map +1 -1
  16. package/dist/fetch.d.ts +7 -1
  17. package/dist/fetch.js +61 -8
  18. package/dist/fetch.js.map +1 -1
  19. package/dist/header-parameters.d.ts +3 -0
  20. package/dist/header-parameters.js +40 -0
  21. package/dist/header-parameters.js.map +1 -0
  22. package/dist/index.d.ts +12 -12
  23. package/dist/index.js +12 -12
  24. package/dist/index.js.map +1 -1
  25. package/dist/{binary-decoder.d.ts → media-type-codecs/binary-decoder.d.ts} +2 -2
  26. package/dist/{binary-decoder.js → media-type-codecs/binary-decoder.js} +0 -0
  27. package/dist/media-type-codecs/binary-decoder.js.map +1 -0
  28. package/dist/{binary-encoder.d.ts → media-type-codecs/binary-encoder.d.ts} +0 -0
  29. package/dist/{binary-encoder.js → media-type-codecs/binary-encoder.js} +0 -0
  30. package/dist/media-type-codecs/binary-encoder.js.map +1 -0
  31. package/dist/media-type-codecs/cbor-decoder.d.ts +39 -0
  32. package/dist/media-type-codecs/cbor-decoder.js +413 -0
  33. package/dist/media-type-codecs/cbor-decoder.js.map +1 -0
  34. package/dist/media-type-codecs/cbor-encoder.d.ts +43 -0
  35. package/dist/media-type-codecs/cbor-encoder.js +226 -0
  36. package/dist/media-type-codecs/cbor-encoder.js.map +1 -0
  37. package/dist/media-type-codecs/cbor-tags.d.ts +4 -0
  38. package/dist/media-type-codecs/cbor-tags.js +5 -0
  39. package/dist/media-type-codecs/cbor-tags.js.map +1 -0
  40. package/dist/media-type-codecs/json-decoder.d.ts +39 -0
  41. package/dist/media-type-codecs/json-decoder.js +367 -0
  42. package/dist/media-type-codecs/json-decoder.js.map +1 -0
  43. package/dist/media-type-codecs/json-encoder.d.ts +44 -0
  44. package/dist/media-type-codecs/json-encoder.js +263 -0
  45. package/dist/media-type-codecs/json-encoder.js.map +1 -0
  46. package/dist/media-type-codecs/media-type-decoder.d.ts +11 -0
  47. package/dist/media-type-codecs/media-type-decoder.js +6 -0
  48. package/dist/media-type-codecs/media-type-decoder.js.map +1 -0
  49. package/dist/{media-type-decoders.d.ts → media-type-codecs/media-type-decoders.d.ts} +5 -4
  50. package/dist/{media-type-decoders.js → media-type-codecs/media-type-decoders.js} +6 -6
  51. package/dist/media-type-codecs/media-type-decoders.js.map +1 -0
  52. package/dist/{media-type-encoder.d.ts → media-type-codecs/media-type-encoder.d.ts} +5 -1
  53. package/dist/media-type-codecs/media-type-encoder.js +11 -0
  54. package/dist/media-type-codecs/media-type-encoder.js.map +1 -0
  55. package/dist/{media-type-encoders.d.ts → media-type-codecs/media-type-encoders.d.ts} +5 -4
  56. package/dist/{media-type-encoders.js → media-type-codecs/media-type-encoders.js} +8 -8
  57. package/dist/media-type-codecs/media-type-encoders.js.map +1 -0
  58. package/dist/{url-encoder.d.ts → media-type-codecs/www-form-url-encoder.d.ts} +8 -8
  59. package/dist/{url-encoder.js → media-type-codecs/www-form-url-encoder.js} +38 -37
  60. package/dist/media-type-codecs/www-form-url-encoder.js.map +1 -0
  61. package/dist/media-type.d.ts +91 -13
  62. package/dist/media-type.js +273 -15
  63. package/dist/media-type.js.map +1 -1
  64. package/dist/problem.d.ts +24 -9
  65. package/dist/problem.js +140 -6
  66. package/dist/problem.js.map +1 -1
  67. package/dist/request-factory.d.ts +10 -10
  68. package/dist/sunday-error.d.ts +11 -0
  69. package/dist/sunday-error.js +18 -0
  70. package/dist/sunday-error.js.map +1 -0
  71. package/dist/util/any.d.ts +2 -0
  72. package/dist/util/any.js +11 -0
  73. package/dist/util/any.js.map +1 -0
  74. package/dist/util/error.d.ts +2 -0
  75. package/dist/util/error.js +13 -0
  76. package/dist/util/error.js.map +1 -0
  77. package/dist/util/hex.js +3 -2
  78. package/dist/util/hex.js.map +1 -1
  79. package/dist/util/rxjs.d.ts +3 -0
  80. package/dist/util/rxjs.js +9 -5
  81. package/dist/util/rxjs.js.map +1 -1
  82. package/dist/util/temporal.d.ts +2 -0
  83. package/dist/util/temporal.js +18 -0
  84. package/dist/util/temporal.js.map +1 -0
  85. package/package.json +27 -21
  86. package/src/any-type.ts +4 -0
  87. package/src/class-type.ts +6 -2
  88. package/src/date-time-types.ts +22 -9
  89. package/src/event-parser.ts +190 -0
  90. package/src/fetch-event-source.ts +149 -159
  91. package/src/fetch-request-factory.ts +129 -101
  92. package/src/fetch.ts +65 -14
  93. package/src/header-parameters.ts +52 -0
  94. package/src/index.ts +12 -12
  95. package/src/{binary-decoder.ts → media-type-codecs/binary-decoder.ts} +3 -3
  96. package/src/{binary-encoder.ts → media-type-codecs/binary-encoder.ts} +0 -0
  97. package/src/media-type-codecs/cbor-decoder.ts +515 -0
  98. package/src/media-type-codecs/cbor-encoder.ts +307 -0
  99. package/src/media-type-codecs/cbor-tags.ts +4 -0
  100. package/src/media-type-codecs/json-decoder.ts +470 -0
  101. package/src/media-type-codecs/json-encoder.ts +328 -0
  102. package/src/media-type-codecs/media-type-decoder.ts +20 -0
  103. package/src/{media-type-decoders.ts → media-type-codecs/media-type-decoders.ts} +21 -13
  104. package/src/media-type-codecs/media-type-encoder.ts +31 -0
  105. package/src/{media-type-encoders.ts → media-type-codecs/media-type-encoders.ts} +23 -15
  106. package/src/{url-encoder.ts → media-type-codecs/www-form-url-encoder.ts} +53 -47
  107. package/src/media-type.ts +326 -22
  108. package/src/problem.ts +144 -12
  109. package/src/request-factory.ts +21 -12
  110. package/src/sunday-error.ts +37 -0
  111. package/src/util/any.ts +10 -0
  112. package/src/util/error.ts +14 -0
  113. package/src/util/hex.ts +3 -2
  114. package/src/util/rxjs.ts +16 -5
  115. package/src/util/temporal.ts +18 -0
  116. package/dist/binary-decoder.js.map +0 -1
  117. package/dist/binary-encoder.js.map +0 -1
  118. package/dist/cbor-decoder.d.ts +0 -15
  119. package/dist/cbor-decoder.js +0 -126
  120. package/dist/cbor-decoder.js.map +0 -1
  121. package/dist/cbor-encoder.d.ts +0 -29
  122. package/dist/cbor-encoder.js +0 -81
  123. package/dist/cbor-encoder.js.map +0 -1
  124. package/dist/cbor-tags.d.ts +0 -3
  125. package/dist/cbor-tags.js +0 -4
  126. package/dist/cbor-tags.js.map +0 -1
  127. package/dist/http-error.d.ts +0 -10
  128. package/dist/http-error.js +0 -45
  129. package/dist/http-error.js.map +0 -1
  130. package/dist/json-decoder.d.ts +0 -31
  131. package/dist/json-decoder.js +0 -139
  132. package/dist/json-decoder.js.map +0 -1
  133. package/dist/json-encoder.d.ts +0 -35
  134. package/dist/json-encoder.js +0 -116
  135. package/dist/json-encoder.js.map +0 -1
  136. package/dist/media-type-decoder.d.ts +0 -4
  137. package/dist/media-type-decoder.js +0 -2
  138. package/dist/media-type-decoder.js.map +0 -1
  139. package/dist/media-type-decoders.js.map +0 -1
  140. package/dist/media-type-encoder.js +0 -6
  141. package/dist/media-type-encoder.js.map +0 -1
  142. package/dist/media-type-encoders.js.map +0 -1
  143. package/dist/url-encoder.js.map +0 -1
  144. package/src/cbor-decoder.ts +0 -148
  145. package/src/cbor-encoder.ts +0 -95
  146. package/src/cbor-tags.ts +0 -3
  147. package/src/http-error.ts +0 -55
  148. package/src/json-decoder.ts +0 -164
  149. package/src/json-encoder.ts +0 -144
  150. package/src/media-type-decoder.ts +0 -5
  151. package/src/media-type-encoder.ts +0 -16
@@ -1,29 +1,32 @@
1
- import { Observable, of, Subscriber } from 'rxjs';
2
- import { mapTo, switchMap } from 'rxjs/operators';
1
+ import { defer, map, Observable, of, switchMap } from 'rxjs';
3
2
  import { AnyType } from './any-type';
3
+ import { ConstructableClassType } from './class-type';
4
4
  import { validate } from './fetch';
5
5
  import { FetchEventSource } from './fetch-event-source';
6
- import { HttpError } from './http-error';
7
- import { JSONDecoder } from './json-decoder';
6
+ import { HeaderParameters } from './header-parameters';
8
7
  import { Logger } from './logger';
9
- import { mediaType, MediaType } from './media-type';
10
- import { MediaTypeDecoders } from './media-type-decoders';
11
- import { isURLQueryParamsEncoder } from './media-type-encoder';
12
- import { MediaTypeEncoders } from './media-type-encoders';
8
+ import { MediaType } from './media-type';
9
+ import { TextMediaTypeDecoder } from './media-type-codecs/media-type-decoder';
10
+ import { MediaTypeDecoders } from './media-type-codecs/media-type-decoders';
11
+ import { isURLQueryParamsEncoder } from './media-type-codecs/media-type-encoder';
12
+ import { MediaTypeEncoders } from './media-type-codecs/media-type-encoders';
13
+ import { Problem } from './problem';
13
14
  import {
14
- EventTypes,
15
15
  ExtEventSource,
16
16
  RequestAdapter,
17
17
  RequestFactory,
18
18
  RequestSpec,
19
19
  } from './request-factory';
20
+ import { SundayError } from './sunday-error';
20
21
  import { URLTemplate } from './url-template';
22
+ import { errorToMessage } from './util/error';
21
23
 
22
24
  export class FetchRequestFactory implements RequestFactory {
23
25
  public baseUrl: URLTemplate;
24
26
  public adapter?: RequestAdapter;
25
27
  public mediaTypeEncoders: MediaTypeEncoders;
26
28
  public mediaTypeDecoders: MediaTypeDecoders;
29
+ public problemTypes = new Map<string, ConstructableClassType<Problem>>();
27
30
  public logger?: Logger;
28
31
 
29
32
  constructor(
@@ -45,71 +48,92 @@ export class FetchRequestFactory implements RequestFactory {
45
48
  this.logger = options?.logger ?? console;
46
49
  }
47
50
 
51
+ registerProblem(
52
+ type: URL | string,
53
+ problemType: ConstructableClassType<Problem>
54
+ ): void {
55
+ const typeStr = type instanceof URL ? type.toString() : type;
56
+ this.problemTypes.set(typeStr, problemType);
57
+ }
58
+
48
59
  request(
49
60
  requestSpec: RequestSpec<unknown>,
50
61
  requestInit?: RequestInit
51
62
  ): Observable<Request> {
52
63
  //
53
- const url = this.baseUrl.complete(
54
- requestSpec.pathTemplate,
55
- requestSpec.pathParameters ?? {}
56
- );
57
-
58
- if (requestSpec.queryParameters) {
59
- const encoder = this.mediaTypeEncoders.find(
60
- MediaType.WWW_URL_FORM_ENCODED
64
+ return defer(() => {
65
+ const url = this.baseUrl.complete(
66
+ requestSpec.pathTemplate,
67
+ requestSpec.pathParameters ?? {}
61
68
  );
62
- if (!isURLQueryParamsEncoder(encoder)) {
63
- throw Error(
64
- `MediaTypeEncoder for ${MediaType.WWW_URL_FORM_ENCODED} must be an instance of URLEncoder`
69
+
70
+ if (requestSpec.queryParameters) {
71
+ const encoder = this.mediaTypeEncoders.find(
72
+ MediaType.WWWFormUrlEncoded
65
73
  );
74
+ if (!isURLQueryParamsEncoder(encoder)) {
75
+ throw Error(
76
+ `MediaTypeEncoder for ${MediaType.WWWFormUrlEncoded} must be an instance of URLQueryParamsEncoder`
77
+ );
78
+ }
79
+ url.search = `?${encoder.encodeQueryString(
80
+ requestSpec.queryParameters
81
+ )}`;
66
82
  }
67
- url.search = `?${encoder.encodeQueryString(requestSpec.queryParameters)}`;
68
- }
69
83
 
70
- const headers = new Headers(requestSpec.headers);
84
+ const headers = new Headers(HeaderParameters.encode(requestSpec.headers));
71
85
 
72
- // Determine & add accept header
73
- const supportedAcceptTypes = requestSpec.acceptTypes?.filter((type) =>
74
- this.mediaTypeDecoders.supports(type)
75
- );
76
- if (supportedAcceptTypes?.length) {
77
- const accept = supportedAcceptTypes.join(' , ');
86
+ // Determine & add accept header
87
+ if (requestSpec.acceptTypes) {
88
+ const supportedAcceptTypes = requestSpec.acceptTypes.filter((type) =>
89
+ this.mediaTypeDecoders.supports(type)
90
+ );
78
91
 
79
- headers.set('accept', accept);
80
- }
92
+ if (!supportedAcceptTypes.length) {
93
+ throw Error(
94
+ 'None of the provided accept types has a reqistered decoder'
95
+ );
96
+ }
81
97
 
82
- // Determine content type
83
- const contentType = requestSpec.contentTypes?.find((type) =>
84
- this.mediaTypeEncoders.supports(type)
85
- );
98
+ const accept = supportedAcceptTypes.join(' , ');
86
99
 
87
- // If matched, add content type (even if body is nil, to match any expected server requirements)
88
- if (contentType) {
89
- headers.set('content-type', contentType);
90
- }
100
+ headers.set('accept', accept);
101
+ }
102
+
103
+ // Determine content type
104
+ const contentType = requestSpec.contentTypes?.find((type) =>
105
+ this.mediaTypeEncoders.supports(type)
106
+ );
91
107
 
92
- // Encode & add body data
93
- let body: BodyInit | undefined;
94
- if (requestSpec.body) {
95
- if (!contentType) {
96
- throw Error('Unsupported content-type for request body');
108
+ // If matched, add content type (even if body is nil, to match any expected server requirements)
109
+ if (contentType) {
110
+ headers.set('content-type', contentType.toString());
97
111
  }
98
112
 
99
- body = this.mediaTypeEncoders
100
- .find(contentType)
101
- .encode(requestSpec.body, requestSpec.bodyType);
102
- }
113
+ // Encode & add body data
114
+ let body: BodyInit | undefined;
115
+ if (requestSpec.body) {
116
+ if (!contentType) {
117
+ throw Error(
118
+ 'None of the provided content types has a registered encoder'
119
+ );
120
+ }
121
+
122
+ body = this.mediaTypeEncoders
123
+ .find(contentType)
124
+ .encode(requestSpec.body, requestSpec.bodyType);
125
+ }
103
126
 
104
- const init: RequestInit = Object.assign({}, requestInit, {
105
- headers,
106
- body,
107
- method: requestSpec.method,
108
- });
127
+ const init: RequestInit = Object.assign({}, requestInit, {
128
+ headers,
129
+ body,
130
+ method: requestSpec.method,
131
+ });
109
132
 
110
- const request = new Request(url.toString(), init);
133
+ const request = new Request(url.toString(), init);
111
134
 
112
- return this.adapter?.(request) ?? of(request);
135
+ return this.adapter?.(request) ?? of(request);
136
+ });
113
137
  }
114
138
 
115
139
  response(
@@ -120,7 +144,9 @@ export class FetchRequestFactory implements RequestFactory {
120
144
  request instanceof Request ? of(request) : this.request(request);
121
145
  return request$.pipe(
122
146
  switchMap((req) => fetch(req)),
123
- switchMap((response) => validate(response, dataExpected ?? false))
147
+ switchMap((response) =>
148
+ validate(response, dataExpected ?? false, this.problemTypes)
149
+ )
124
150
  );
125
151
  }
126
152
 
@@ -133,20 +159,20 @@ export class FetchRequestFactory implements RequestFactory {
133
159
  const response$ = this.response(request, !!responseType);
134
160
 
135
161
  if (!responseType) {
136
- return response$.pipe(mapTo(undefined));
162
+ return response$.pipe(map(() => undefined));
137
163
  } else {
138
164
  return response$.pipe(
139
165
  switchMap(async (response) => {
140
166
  try {
141
- const contentType = mediaType(
167
+ const contentType = MediaType.from(
142
168
  response.headers.get('content-type'),
143
- MediaType.OCTET_STREAM
169
+ MediaType.OctetStream
144
170
  );
145
171
  const decoder = this.mediaTypeDecoders.find(contentType);
146
172
  return await decoder.decode(response, responseType);
147
173
  } catch (error) {
148
- throw await HttpError.fromResponse(
149
- error.message ?? 'Unknown Error',
174
+ throw await SundayError.fromResponse(
175
+ errorToMessage(error, 'Response Decoding Failed'),
150
176
  response
151
177
  );
152
178
  }
@@ -155,15 +181,7 @@ export class FetchRequestFactory implements RequestFactory {
155
181
  }
156
182
  }
157
183
 
158
- events(requestSpec: RequestSpec<void>): ExtEventSource;
159
- events<E>(
160
- requestSpec: RequestSpec<void>,
161
- eventTypes: EventTypes<E>
162
- ): Observable<E>;
163
- events(
164
- requestSpec: RequestSpec<void>,
165
- eventTypes?: EventTypes<unknown>
166
- ): ExtEventSource | Observable<unknown> {
184
+ eventSource(requestSpec: RequestSpec<void>): ExtEventSource {
167
185
  //
168
186
  const adapter = (
169
187
  url: string,
@@ -175,45 +193,55 @@ export class FetchRequestFactory implements RequestFactory {
175
193
  return this.request(eventSourceSpec, requestInit);
176
194
  };
177
195
 
178
- const eventSource = new FetchEventSource(requestSpec.pathTemplate, {
196
+ return new FetchEventSource(requestSpec.pathTemplate, {
179
197
  logger: this.logger,
180
198
  adapter,
181
199
  });
200
+ }
182
201
 
183
- if (!eventTypes) {
184
- return eventSource;
185
- }
186
-
187
- const generateEventHandler = (
188
- eventType: AnyType,
189
- subscriber: Subscriber<unknown>
190
- ) => {
191
- return async (event: Event) => {
192
- const decoder = this.mediaTypeDecoders.find(
193
- 'application/json'
194
- ) as JSONDecoder;
195
- const msgEvent = event as MessageEvent;
196
- const deserializedEvent = await decoder.decodeText(
197
- msgEvent.data,
198
- eventType
199
- );
200
- subscriber.next(deserializedEvent);
201
- };
202
- };
202
+ eventStream<E>(
203
+ requestSpec: RequestSpec<void>,
204
+ decoder: (
205
+ decoder: TextMediaTypeDecoder,
206
+ event: string | undefined,
207
+ id: string | undefined,
208
+ data: string,
209
+ logger?: Logger
210
+ ) => E | undefined
211
+ ): Observable<E> {
212
+ const eventSource = this.eventSource(requestSpec);
213
+
214
+ const jsonDecoder = this.mediaTypeDecoders.find(
215
+ MediaType.JSON
216
+ ) as TextMediaTypeDecoder;
203
217
 
204
218
  return new Observable((subscriber) => {
205
- for (const eventTypeName of Object.keys(eventTypes)) {
206
- const eventType = eventTypes[eventTypeName];
219
+ eventSource.onmessage = (event: MessageEvent<string>) => {
220
+ if (!event.data) {
221
+ return;
222
+ }
223
+
224
+ try {
225
+ const decodedEvent = decoder(
226
+ jsonDecoder,
227
+ event.type,
228
+ event.lastEventId,
229
+ event.data,
230
+ this.logger
231
+ );
232
+ if (!decodedEvent) {
233
+ return;
234
+ }
207
235
 
208
- eventSource.addEventListener(
209
- eventTypeName,
210
- generateEventHandler(eventType, subscriber)
211
- );
236
+ subscriber.next(decodedEvent);
237
+ } catch (error) {
238
+ subscriber.error(error);
239
+ }
240
+ };
212
241
 
213
- eventSource.onerror = (event) => {
214
- this.logger?.error?.({ event }, 'event source error');
215
- };
216
- }
242
+ eventSource.onerror = (event) => {
243
+ this.logger?.error?.({ event }, 'event source error');
244
+ };
217
245
 
218
246
  eventSource.connect();
219
247
 
package/src/fetch.ts CHANGED
@@ -1,30 +1,81 @@
1
- import { mediaType, MediaType } from './media-type';
2
- import { HttpError } from './http-error';
1
+ import { ConstructableClassType } from './class-type';
2
+ import { MediaType } from './media-type';
3
3
  import { Problem } from './problem';
4
+ import { SundayError } from './sunday-error';
5
+ import { Base64 } from './util/base64';
6
+ import { errorToMessage } from './util/error';
4
7
 
5
8
  export async function validate(
6
9
  response: Response,
7
- dataExpected: boolean
10
+ dataExpected: boolean,
11
+ problemTypes?: Map<string, ConstructableClassType<Problem>>
8
12
  ): Promise<Response> {
9
13
  if (response.status < 200 || response.status >= 300) {
10
- if (
11
- mediaType(response.headers.get('content-type')) !== MediaType.PROBLEM_JSON
12
- ) {
13
- await response.body?.cancel?.();
14
- throw await HttpError.fromResponse(
15
- 'Unacceptable response status code',
16
- response
17
- );
14
+ const mediaType = MediaType.from(
15
+ response.headers.get('content-type'),
16
+ MediaType.OctetStream
17
+ );
18
+ const isProblemJSON = mediaType?.compatible(MediaType.ProblemJSON) ?? false;
19
+ if (!isProblemJSON) {
20
+ throw await Problem.fromResponse(response);
18
21
  }
19
22
 
20
23
  const problemData = await response.json();
21
-
22
- throw new Problem(problemData as Problem);
24
+ const problemType = problemTypes?.get(problemData.type) ?? Problem;
25
+ throw new problemType(problemData as Problem);
23
26
  }
24
27
 
25
28
  if (dataExpected && (response.status === 204 || response.status === 205)) {
26
- throw await HttpError.fromResponse('Unexpected empty response', response);
29
+ throw await SundayError.fromResponse('Unexpected Empty Response', response);
27
30
  }
28
31
 
29
32
  return response;
30
33
  }
34
+
35
+ export namespace ResponseExample {
36
+ export function build(response: Response, bodyExample?: string): string {
37
+ return (
38
+ `HTTP/?.? ${response.status} ${response.statusText}\n` +
39
+ Array.from(response.headers.entries())
40
+ .map(([name, value]) => `${name}: ${value}\n`)
41
+ .join('') +
42
+ '\n' +
43
+ (bodyExample ?? '<none>')
44
+ );
45
+ }
46
+
47
+ export async function bodyExcerpt(
48
+ response: Response,
49
+ maxLength: number
50
+ ): Promise<[string, unknown]> {
51
+ let body: unknown;
52
+ let bodyExcerpt: string;
53
+ try {
54
+ if (response.headers.get('content-type')?.startsWith('text/')) {
55
+ const text = await response.text();
56
+ body = text;
57
+ if (text.length > maxLength) {
58
+ const exampleText = text.slice(0, maxLength);
59
+ const remainingTextLength = text.length - maxLength;
60
+ bodyExcerpt = `${exampleText}<<... ${remainingTextLength} characters>>`;
61
+ } else {
62
+ bodyExcerpt = text;
63
+ }
64
+ } else {
65
+ const blob = await response.blob();
66
+ body = blob;
67
+ if (blob.size > maxLength) {
68
+ bodyExcerpt = `<<binary data: ${blob.size} bytes>>`;
69
+ } else {
70
+ const dataSlice = await blob.slice(0, maxLength).arrayBuffer();
71
+ bodyExcerpt = Base64.encode(dataSlice);
72
+ }
73
+ }
74
+ } catch (error) {
75
+ // ignore errors
76
+ const message = errorToMessage(error);
77
+ body = bodyExcerpt = `<<error displaying response data: ${message}>>`;
78
+ }
79
+ return [bodyExcerpt, body];
80
+ }
81
+ }
@@ -0,0 +1,52 @@
1
+ export namespace HeaderParameters {
2
+ export function encode(
3
+ parameters?: Record<string, unknown>
4
+ ): [string, string][] {
5
+ if (parameters == null) {
6
+ return [];
7
+ }
8
+
9
+ const headers: [string, string][] = [];
10
+
11
+ Object.entries(parameters).forEach(([name, parameter]) => {
12
+ for (const value of encodeParam(name, parameter)) {
13
+ headers.push([name, value]);
14
+ }
15
+ });
16
+
17
+ return headers;
18
+ }
19
+
20
+ function encodeParam(name: string, value: unknown): string[] {
21
+ if (value == null) {
22
+ return [];
23
+ }
24
+
25
+ if (value instanceof Array) {
26
+ const result: string[] = [];
27
+
28
+ for (const item of value) {
29
+ if (item == null) {
30
+ continue;
31
+ }
32
+
33
+ result.push(validate(name, `${item}`));
34
+ }
35
+
36
+ return result;
37
+ }
38
+
39
+ return [validate(name, `${value}`)];
40
+ }
41
+
42
+ const asciiRegex = /^[\x20-\x7F]*$/;
43
+
44
+ function validate(name: string, value: string): string {
45
+ if (!asciiRegex.test(value)) {
46
+ throw new Error(
47
+ `The encoded header value contains one or more invalid characters: header=${name}, value=${value}`
48
+ );
49
+ }
50
+ return value;
51
+ }
52
+ }
package/src/index.ts CHANGED
@@ -5,19 +5,19 @@ export * from './fetch-request-factory';
5
5
  export * from './fetch-event-source';
6
6
  export * from './url-template';
7
7
  export * from './media-type';
8
- export * from './media-type-decoder';
9
- export * from './media-type-decoders';
10
- export * from './media-type-encoder';
11
- export * from './media-type-encoders';
12
- export * from './json-decoder';
13
- export * from './json-encoder';
14
- export * from './cbor-decoder';
15
- export * from './cbor-encoder';
16
- export * from './binary-decoder';
17
- export * from './binary-encoder';
18
- export * from './url-encoder';
8
+ export * from './media-type-codecs/media-type-decoder';
9
+ export * from './media-type-codecs/media-type-decoders';
10
+ export * from './media-type-codecs/media-type-encoder';
11
+ export * from './media-type-codecs/media-type-encoders';
12
+ export * from './media-type-codecs/json-decoder';
13
+ export * from './media-type-codecs/json-encoder';
14
+ export * from './media-type-codecs/cbor-decoder';
15
+ export * from './media-type-codecs/cbor-encoder';
16
+ export * from './media-type-codecs/binary-decoder';
17
+ export * from './media-type-codecs/binary-encoder';
18
+ export * from './media-type-codecs/www-form-url-encoder';
19
19
  export * from './logger';
20
- export * from './http-error';
20
+ export * from './sunday-error';
21
21
  export * from './problem';
22
22
  export * from './date-time-types';
23
23
 
@@ -1,14 +1,14 @@
1
1
  import { MediaTypeDecoder } from './media-type-decoder';
2
- import { AnyType } from './any-type';
2
+ import { AnyConstructableType } from '../any-type';
3
3
 
4
4
  export class BinaryDecoder implements MediaTypeDecoder {
5
5
  static default = new BinaryDecoder();
6
6
 
7
- async decode<T>(response: Response, type: AnyType): Promise<T> {
7
+ async decode<T>(response: Response, type: AnyConstructableType): Promise<T> {
8
8
  const arrayBuffer = await response.arrayBuffer();
9
9
 
10
10
  if (type[0] === ArrayBuffer) {
11
- return (arrayBuffer as unknown) as T;
11
+ return arrayBuffer as unknown as T;
12
12
  } else if (
13
13
  type[0] === Uint8Array ||
14
14
  type[0] === Int8Array ||