@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.
- package/README.md +35 -2
- package/dist/any-type.d.ts +2 -0
- package/dist/class-type.d.ts +2 -2
- package/dist/date-time-types.d.ts +2 -7
- package/dist/date-time-types.js +2 -4
- package/dist/date-time-types.js.map +1 -1
- package/dist/event-parser.d.ts +17 -0
- package/dist/event-parser.js +140 -0
- package/dist/event-parser.js.map +1 -0
- package/dist/fetch-event-source.d.ts +12 -10
- package/dist/fetch-event-source.js +141 -138
- package/dist/fetch-event-source.js.map +1 -1
- package/dist/fetch-request-factory.d.ts +10 -5
- package/dist/fetch-request-factory.js +87 -71
- package/dist/fetch-request-factory.js.map +1 -1
- package/dist/fetch.d.ts +7 -1
- package/dist/fetch.js +61 -8
- package/dist/fetch.js.map +1 -1
- package/dist/header-parameters.d.ts +3 -0
- package/dist/header-parameters.js +40 -0
- package/dist/header-parameters.js.map +1 -0
- package/dist/index.d.ts +12 -12
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/{binary-decoder.d.ts → media-type-codecs/binary-decoder.d.ts} +2 -2
- package/dist/{binary-decoder.js → media-type-codecs/binary-decoder.js} +0 -0
- package/dist/media-type-codecs/binary-decoder.js.map +1 -0
- package/dist/{binary-encoder.d.ts → media-type-codecs/binary-encoder.d.ts} +0 -0
- package/dist/{binary-encoder.js → media-type-codecs/binary-encoder.js} +0 -0
- package/dist/media-type-codecs/binary-encoder.js.map +1 -0
- package/dist/media-type-codecs/cbor-decoder.d.ts +39 -0
- package/dist/media-type-codecs/cbor-decoder.js +413 -0
- package/dist/media-type-codecs/cbor-decoder.js.map +1 -0
- package/dist/media-type-codecs/cbor-encoder.d.ts +43 -0
- package/dist/media-type-codecs/cbor-encoder.js +226 -0
- package/dist/media-type-codecs/cbor-encoder.js.map +1 -0
- package/dist/media-type-codecs/cbor-tags.d.ts +4 -0
- package/dist/media-type-codecs/cbor-tags.js +5 -0
- package/dist/media-type-codecs/cbor-tags.js.map +1 -0
- package/dist/media-type-codecs/json-decoder.d.ts +39 -0
- package/dist/media-type-codecs/json-decoder.js +367 -0
- package/dist/media-type-codecs/json-decoder.js.map +1 -0
- package/dist/media-type-codecs/json-encoder.d.ts +44 -0
- package/dist/media-type-codecs/json-encoder.js +263 -0
- package/dist/media-type-codecs/json-encoder.js.map +1 -0
- package/dist/media-type-codecs/media-type-decoder.d.ts +11 -0
- package/dist/media-type-codecs/media-type-decoder.js +6 -0
- package/dist/media-type-codecs/media-type-decoder.js.map +1 -0
- package/dist/{media-type-decoders.d.ts → media-type-codecs/media-type-decoders.d.ts} +5 -4
- package/dist/{media-type-decoders.js → media-type-codecs/media-type-decoders.js} +6 -6
- package/dist/media-type-codecs/media-type-decoders.js.map +1 -0
- package/dist/{media-type-encoder.d.ts → media-type-codecs/media-type-encoder.d.ts} +5 -1
- package/dist/media-type-codecs/media-type-encoder.js +11 -0
- package/dist/media-type-codecs/media-type-encoder.js.map +1 -0
- package/dist/{media-type-encoders.d.ts → media-type-codecs/media-type-encoders.d.ts} +5 -4
- package/dist/{media-type-encoders.js → media-type-codecs/media-type-encoders.js} +8 -8
- package/dist/media-type-codecs/media-type-encoders.js.map +1 -0
- package/dist/{url-encoder.d.ts → media-type-codecs/www-form-url-encoder.d.ts} +8 -8
- package/dist/{url-encoder.js → media-type-codecs/www-form-url-encoder.js} +38 -37
- package/dist/media-type-codecs/www-form-url-encoder.js.map +1 -0
- package/dist/media-type.d.ts +91 -13
- package/dist/media-type.js +273 -15
- package/dist/media-type.js.map +1 -1
- package/dist/problem.d.ts +24 -9
- package/dist/problem.js +140 -6
- package/dist/problem.js.map +1 -1
- package/dist/request-factory.d.ts +10 -10
- package/dist/sunday-error.d.ts +11 -0
- package/dist/sunday-error.js +18 -0
- package/dist/sunday-error.js.map +1 -0
- package/dist/util/any.d.ts +2 -0
- package/dist/util/any.js +11 -0
- package/dist/util/any.js.map +1 -0
- package/dist/util/error.d.ts +2 -0
- package/dist/util/error.js +13 -0
- package/dist/util/error.js.map +1 -0
- package/dist/util/hex.js +3 -2
- package/dist/util/hex.js.map +1 -1
- package/dist/util/rxjs.d.ts +3 -0
- package/dist/util/rxjs.js +9 -5
- package/dist/util/rxjs.js.map +1 -1
- package/dist/util/temporal.d.ts +2 -0
- package/dist/util/temporal.js +18 -0
- package/dist/util/temporal.js.map +1 -0
- package/package.json +27 -21
- package/src/any-type.ts +4 -0
- package/src/class-type.ts +6 -2
- package/src/date-time-types.ts +22 -9
- package/src/event-parser.ts +190 -0
- package/src/fetch-event-source.ts +149 -159
- package/src/fetch-request-factory.ts +129 -101
- package/src/fetch.ts +65 -14
- package/src/header-parameters.ts +52 -0
- package/src/index.ts +12 -12
- package/src/{binary-decoder.ts → media-type-codecs/binary-decoder.ts} +3 -3
- package/src/{binary-encoder.ts → media-type-codecs/binary-encoder.ts} +0 -0
- package/src/media-type-codecs/cbor-decoder.ts +515 -0
- package/src/media-type-codecs/cbor-encoder.ts +307 -0
- package/src/media-type-codecs/cbor-tags.ts +4 -0
- package/src/media-type-codecs/json-decoder.ts +470 -0
- package/src/media-type-codecs/json-encoder.ts +328 -0
- package/src/media-type-codecs/media-type-decoder.ts +20 -0
- package/src/{media-type-decoders.ts → media-type-codecs/media-type-decoders.ts} +21 -13
- package/src/media-type-codecs/media-type-encoder.ts +31 -0
- package/src/{media-type-encoders.ts → media-type-codecs/media-type-encoders.ts} +23 -15
- package/src/{url-encoder.ts → media-type-codecs/www-form-url-encoder.ts} +53 -47
- package/src/media-type.ts +326 -22
- package/src/problem.ts +144 -12
- package/src/request-factory.ts +21 -12
- package/src/sunday-error.ts +37 -0
- package/src/util/any.ts +10 -0
- package/src/util/error.ts +14 -0
- package/src/util/hex.ts +3 -2
- package/src/util/rxjs.ts +16 -5
- package/src/util/temporal.ts +18 -0
- package/dist/binary-decoder.js.map +0 -1
- package/dist/binary-encoder.js.map +0 -1
- package/dist/cbor-decoder.d.ts +0 -15
- package/dist/cbor-decoder.js +0 -126
- package/dist/cbor-decoder.js.map +0 -1
- package/dist/cbor-encoder.d.ts +0 -29
- package/dist/cbor-encoder.js +0 -81
- package/dist/cbor-encoder.js.map +0 -1
- package/dist/cbor-tags.d.ts +0 -3
- package/dist/cbor-tags.js +0 -4
- package/dist/cbor-tags.js.map +0 -1
- package/dist/http-error.d.ts +0 -10
- package/dist/http-error.js +0 -45
- package/dist/http-error.js.map +0 -1
- package/dist/json-decoder.d.ts +0 -31
- package/dist/json-decoder.js +0 -139
- package/dist/json-decoder.js.map +0 -1
- package/dist/json-encoder.d.ts +0 -35
- package/dist/json-encoder.js +0 -116
- package/dist/json-encoder.js.map +0 -1
- package/dist/media-type-decoder.d.ts +0 -4
- package/dist/media-type-decoder.js +0 -2
- package/dist/media-type-decoder.js.map +0 -1
- package/dist/media-type-decoders.js.map +0 -1
- package/dist/media-type-encoder.js +0 -6
- package/dist/media-type-encoder.js.map +0 -1
- package/dist/media-type-encoders.js.map +0 -1
- package/dist/url-encoder.js.map +0 -1
- package/src/cbor-decoder.ts +0 -148
- package/src/cbor-encoder.ts +0 -95
- package/src/cbor-tags.ts +0 -3
- package/src/http-error.ts +0 -55
- package/src/json-decoder.ts +0 -164
- package/src/json-encoder.ts +0 -144
- package/src/media-type-decoder.ts +0 -5
- package/src/media-type-encoder.ts +0 -16
|
@@ -1,29 +1,32 @@
|
|
|
1
|
-
import { Observable, of,
|
|
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 {
|
|
7
|
-
import { JSONDecoder } from './json-decoder';
|
|
6
|
+
import { HeaderParameters } from './header-parameters';
|
|
8
7
|
import { Logger } from './logger';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
84
|
+
const headers = new Headers(HeaderParameters.encode(requestSpec.headers));
|
|
71
85
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
80
|
-
|
|
92
|
+
if (!supportedAcceptTypes.length) {
|
|
93
|
+
throw Error(
|
|
94
|
+
'None of the provided accept types has a reqistered decoder'
|
|
95
|
+
);
|
|
96
|
+
}
|
|
81
97
|
|
|
82
|
-
|
|
83
|
-
const contentType = requestSpec.contentTypes?.find((type) =>
|
|
84
|
-
this.mediaTypeEncoders.supports(type)
|
|
85
|
-
);
|
|
98
|
+
const accept = supportedAcceptTypes.join(' , ');
|
|
86
99
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
127
|
+
const init: RequestInit = Object.assign({}, requestInit, {
|
|
128
|
+
headers,
|
|
129
|
+
body,
|
|
130
|
+
method: requestSpec.method,
|
|
131
|
+
});
|
|
109
132
|
|
|
110
|
-
|
|
133
|
+
const request = new Request(url.toString(), init);
|
|
111
134
|
|
|
112
|
-
|
|
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) =>
|
|
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(
|
|
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 =
|
|
167
|
+
const contentType = MediaType.from(
|
|
142
168
|
response.headers.get('content-type'),
|
|
143
|
-
MediaType.
|
|
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
|
|
149
|
-
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
|
-
|
|
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
|
-
|
|
196
|
+
return new FetchEventSource(requestSpec.pathTemplate, {
|
|
179
197
|
logger: this.logger,
|
|
180
198
|
adapter,
|
|
181
199
|
});
|
|
200
|
+
}
|
|
182
201
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
236
|
+
subscriber.next(decodedEvent);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
subscriber.error(error);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
212
241
|
|
|
213
|
-
|
|
214
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
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
|
|
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 './
|
|
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 {
|
|
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:
|
|
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
|
|
11
|
+
return arrayBuffer as unknown as T;
|
|
12
12
|
} else if (
|
|
13
13
|
type[0] === Uint8Array ||
|
|
14
14
|
type[0] === Int8Array ||
|
|
File without changes
|