@outfoxx/sunday 1.0.9 → 1.1.0-alpha.11
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/LICENSE.txt +203 -0
- package/README.md +35 -2
- package/dist/any-type.d.ts +2 -0
- package/dist/any-type.js +13 -0
- package/dist/any-type.js.map +1 -1
- package/dist/class-type.d.ts +2 -2
- package/dist/class-type.js +13 -0
- package/dist/class-type.js.map +1 -1
- package/dist/date-time-types.d.ts +2 -7
- package/dist/date-time-types.js +15 -4
- package/dist/date-time-types.js.map +1 -1
- package/dist/event-parser.d.ts +17 -0
- package/dist/event-parser.js +153 -0
- package/dist/event-parser.js.map +1 -0
- package/dist/fetch-event-source.d.ts +12 -10
- package/dist/fetch-event-source.js +154 -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 +100 -71
- package/dist/fetch-request-factory.js.map +1 -1
- package/dist/fetch.d.ts +7 -1
- package/dist/fetch.js +74 -8
- package/dist/fetch.js.map +1 -1
- package/dist/header-parameters.d.ts +3 -0
- package/dist/header-parameters.js +53 -0
- package/dist/header-parameters.js.map +1 -0
- package/dist/index.d.ts +12 -12
- package/dist/index.js +25 -12
- package/dist/index.js.map +1 -1
- package/dist/logger.js +13 -0
- package/dist/logger.js.map +1 -1
- package/dist/{binary-decoder.d.ts → media-type-codecs/binary-decoder.d.ts} +2 -2
- package/dist/media-type-codecs/binary-decoder.js +29 -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/media-type-codecs/binary-encoder.js +26 -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 +426 -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 +239 -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 +18 -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 +380 -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 +276 -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 +19 -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-codecs/media-type-decoders.js +53 -0
- 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 +24 -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-codecs/media-type-encoders.js +55 -0
- 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} +51 -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 +286 -15
- package/dist/media-type.js.map +1 -1
- package/dist/problem.d.ts +24 -9
- package/dist/problem.js +153 -6
- package/dist/problem.js.map +1 -1
- package/dist/request-factory.d.ts +10 -10
- package/dist/request-factory.js +13 -0
- package/dist/request-factory.js.map +1 -1
- package/dist/sunday-error.d.ts +11 -0
- package/dist/sunday-error.js +31 -0
- package/dist/sunday-error.js.map +1 -0
- package/dist/url-template.js +13 -0
- package/dist/url-template.js.map +1 -1
- package/dist/util/any.d.ts +2 -0
- package/dist/util/any.js +24 -0
- package/dist/util/any.js.map +1 -0
- package/dist/util/base64.js +13 -0
- package/dist/util/base64.js.map +1 -1
- package/dist/util/error.d.ts +2 -0
- package/dist/util/error.js +26 -0
- package/dist/util/error.js.map +1 -0
- package/dist/util/hex.js +16 -2
- package/dist/util/hex.js.map +1 -1
- package/dist/util/rxjs.d.ts +3 -0
- package/dist/util/rxjs.js +22 -5
- package/dist/util/rxjs.js.map +1 -1
- package/dist/util/stream-rxjs.js +13 -0
- package/dist/util/stream-rxjs.js.map +1 -1
- package/dist/util/temporal.d.ts +2 -0
- package/dist/util/temporal.js +31 -0
- package/dist/util/temporal.js.map +1 -0
- package/package.json +29 -22
- package/src/any-type.ts +18 -0
- package/src/class-type.ts +20 -2
- package/src/date-time-types.ts +35 -8
- package/src/event-parser.ts +204 -0
- package/src/fetch-event-source.ts +163 -159
- package/src/fetch-request-factory.ts +142 -100
- package/src/fetch.ts +79 -14
- package/src/header-parameters.ts +66 -0
- package/src/index.ts +26 -12
- package/src/logger.ts +14 -0
- package/src/media-type-codecs/binary-decoder.ts +38 -0
- package/src/media-type-codecs/binary-encoder.ts +33 -0
- package/src/media-type-codecs/cbor-decoder.ts +529 -0
- package/src/media-type-codecs/cbor-encoder.ts +321 -0
- package/src/media-type-codecs/cbor-tags.ts +18 -0
- package/src/media-type-codecs/json-decoder.ts +484 -0
- package/src/media-type-codecs/json-encoder.ts +342 -0
- package/src/media-type-codecs/media-type-decoder.ts +34 -0
- package/src/media-type-codecs/media-type-decoders.ts +81 -0
- package/src/media-type-codecs/media-type-encoder.ts +45 -0
- package/src/media-type-codecs/media-type-encoders.ts +83 -0
- package/src/media-type-codecs/www-form-url-encoder.ts +193 -0
- package/src/media-type.ts +340 -22
- package/src/problem.ts +158 -12
- package/src/request-factory.ts +35 -12
- package/src/sunday-error.ts +51 -0
- package/src/url-template.ts +14 -0
- package/src/util/any.ts +24 -0
- package/src/util/base64.ts +14 -0
- package/src/util/error.ts +28 -0
- package/src/util/hex.ts +17 -2
- package/src/util/rxjs.ts +30 -5
- package/src/util/stream-rxjs.ts +14 -0
- package/src/util/temporal.ts +32 -0
- package/dist/binary-decoder.js +0 -16
- package/dist/binary-decoder.js.map +0 -1
- package/dist/binary-encoder.js +0 -13
- 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 -134
- 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 +0 -40
- 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 +0 -42
- package/dist/media-type-encoders.js.map +0 -1
- package/dist/url-encoder.js.map +0 -1
- package/src/binary-decoder.ts +0 -24
- package/src/binary-encoder.ts +0 -19
- 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 -162
- package/src/media-type-decoder.ts +0 -5
- package/src/media-type-decoders.ts +0 -59
- package/src/media-type-encoder.ts +0 -16
- package/src/media-type-encoders.ts +0 -61
- package/src/url-encoder.ts +0 -173
|
@@ -1,29 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Copyright 2020 Outfox, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import { defer, map, Observable, of, switchMap } from 'rxjs';
|
|
3
16
|
import { AnyType } from './any-type';
|
|
17
|
+
import { ConstructableClassType } from './class-type';
|
|
4
18
|
import { validate } from './fetch';
|
|
5
19
|
import { FetchEventSource } from './fetch-event-source';
|
|
6
|
-
import {
|
|
7
|
-
import { JSONDecoder } from './json-decoder';
|
|
20
|
+
import { HeaderParameters } from './header-parameters';
|
|
8
21
|
import { Logger } from './logger';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
22
|
+
import { MediaType } from './media-type';
|
|
23
|
+
import { TextMediaTypeDecoder } from './media-type-codecs/media-type-decoder';
|
|
24
|
+
import { MediaTypeDecoders } from './media-type-codecs/media-type-decoders';
|
|
25
|
+
import { isURLQueryParamsEncoder } from './media-type-codecs/media-type-encoder';
|
|
26
|
+
import { MediaTypeEncoders } from './media-type-codecs/media-type-encoders';
|
|
27
|
+
import { Problem } from './problem';
|
|
13
28
|
import {
|
|
14
|
-
EventTypes,
|
|
15
29
|
ExtEventSource,
|
|
16
30
|
RequestAdapter,
|
|
17
31
|
RequestFactory,
|
|
18
32
|
RequestSpec,
|
|
19
33
|
} from './request-factory';
|
|
34
|
+
import { SundayError } from './sunday-error';
|
|
20
35
|
import { URLTemplate } from './url-template';
|
|
36
|
+
import { errorToMessage } from './util/error';
|
|
21
37
|
|
|
22
38
|
export class FetchRequestFactory implements RequestFactory {
|
|
23
39
|
public baseUrl: URLTemplate;
|
|
24
40
|
public adapter?: RequestAdapter;
|
|
25
41
|
public mediaTypeEncoders: MediaTypeEncoders;
|
|
26
42
|
public mediaTypeDecoders: MediaTypeDecoders;
|
|
43
|
+
public problemTypes = new Map<string, ConstructableClassType<Problem>>();
|
|
27
44
|
public logger?: Logger;
|
|
28
45
|
|
|
29
46
|
constructor(
|
|
@@ -45,71 +62,92 @@ export class FetchRequestFactory implements RequestFactory {
|
|
|
45
62
|
this.logger = options?.logger ?? console;
|
|
46
63
|
}
|
|
47
64
|
|
|
65
|
+
registerProblem(
|
|
66
|
+
type: URL | string,
|
|
67
|
+
problemType: ConstructableClassType<Problem>
|
|
68
|
+
): void {
|
|
69
|
+
const typeStr = type instanceof URL ? type.toString() : type;
|
|
70
|
+
this.problemTypes.set(typeStr, problemType);
|
|
71
|
+
}
|
|
72
|
+
|
|
48
73
|
request(
|
|
49
74
|
requestSpec: RequestSpec<unknown>,
|
|
50
75
|
requestInit?: RequestInit
|
|
51
76
|
): Observable<Request> {
|
|
52
77
|
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (requestSpec.queryParameters) {
|
|
59
|
-
const encoder = this.mediaTypeEncoders.find(
|
|
60
|
-
MediaType.WWW_URL_FORM_ENCODED
|
|
78
|
+
return defer(() => {
|
|
79
|
+
const url = this.baseUrl.complete(
|
|
80
|
+
requestSpec.pathTemplate,
|
|
81
|
+
requestSpec.pathParameters ?? {}
|
|
61
82
|
);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
83
|
+
|
|
84
|
+
if (requestSpec.queryParameters) {
|
|
85
|
+
const encoder = this.mediaTypeEncoders.find(
|
|
86
|
+
MediaType.WWWFormUrlEncoded
|
|
65
87
|
);
|
|
88
|
+
if (!isURLQueryParamsEncoder(encoder)) {
|
|
89
|
+
throw Error(
|
|
90
|
+
`MediaTypeEncoder for ${MediaType.WWWFormUrlEncoded} must be an instance of URLQueryParamsEncoder`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
url.search = `?${encoder.encodeQueryString(
|
|
94
|
+
requestSpec.queryParameters
|
|
95
|
+
)}`;
|
|
66
96
|
}
|
|
67
|
-
url.search = `?${encoder.encodeQueryString(requestSpec.queryParameters)}`;
|
|
68
|
-
}
|
|
69
97
|
|
|
70
|
-
|
|
98
|
+
const headers = new Headers(HeaderParameters.encode(requestSpec.headers));
|
|
71
99
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const accept = supportedAcceptTypes.join(' , ');
|
|
100
|
+
// Determine & add accept header
|
|
101
|
+
if (requestSpec.acceptTypes) {
|
|
102
|
+
const supportedAcceptTypes = requestSpec.acceptTypes.filter((type) =>
|
|
103
|
+
this.mediaTypeDecoders.supports(type)
|
|
104
|
+
);
|
|
78
105
|
|
|
79
|
-
|
|
80
|
-
|
|
106
|
+
if (!supportedAcceptTypes.length) {
|
|
107
|
+
throw Error(
|
|
108
|
+
'None of the provided accept types has a reqistered decoder'
|
|
109
|
+
);
|
|
110
|
+
}
|
|
81
111
|
|
|
82
|
-
|
|
83
|
-
const contentType = requestSpec.contentTypes?.find((type) =>
|
|
84
|
-
this.mediaTypeEncoders.supports(type)
|
|
85
|
-
);
|
|
112
|
+
const accept = supportedAcceptTypes.join(' , ');
|
|
86
113
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
headers.set('content-type', contentType);
|
|
90
|
-
}
|
|
114
|
+
headers.set('accept', accept);
|
|
115
|
+
}
|
|
91
116
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
117
|
+
// Determine content type
|
|
118
|
+
const contentType = requestSpec.contentTypes?.find((type) =>
|
|
119
|
+
this.mediaTypeEncoders.supports(type)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// If matched, add content type (even if body is nil, to match any expected server requirements)
|
|
123
|
+
if (contentType) {
|
|
124
|
+
headers.set('content-type', contentType.toString());
|
|
97
125
|
}
|
|
98
126
|
|
|
99
|
-
body
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
127
|
+
// Encode & add body data
|
|
128
|
+
let body: BodyInit | undefined;
|
|
129
|
+
if (requestSpec.body) {
|
|
130
|
+
if (!contentType) {
|
|
131
|
+
throw Error(
|
|
132
|
+
'None of the provided content types has a registered encoder'
|
|
133
|
+
);
|
|
134
|
+
}
|
|
103
135
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
});
|
|
136
|
+
body = this.mediaTypeEncoders
|
|
137
|
+
.find(contentType)
|
|
138
|
+
.encode(requestSpec.body, requestSpec.bodyType);
|
|
139
|
+
}
|
|
109
140
|
|
|
110
|
-
|
|
141
|
+
const init: RequestInit = Object.assign({}, requestInit, {
|
|
142
|
+
headers,
|
|
143
|
+
body,
|
|
144
|
+
method: requestSpec.method,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const request = new Request(url.toString(), init);
|
|
111
148
|
|
|
112
|
-
|
|
149
|
+
return this.adapter?.(request) ?? of(request);
|
|
150
|
+
});
|
|
113
151
|
}
|
|
114
152
|
|
|
115
153
|
response(
|
|
@@ -120,7 +158,9 @@ export class FetchRequestFactory implements RequestFactory {
|
|
|
120
158
|
request instanceof Request ? of(request) : this.request(request);
|
|
121
159
|
return request$.pipe(
|
|
122
160
|
switchMap((req) => fetch(req)),
|
|
123
|
-
switchMap((response) =>
|
|
161
|
+
switchMap((response) =>
|
|
162
|
+
validate(response, dataExpected ?? false, this.problemTypes)
|
|
163
|
+
)
|
|
124
164
|
);
|
|
125
165
|
}
|
|
126
166
|
|
|
@@ -133,20 +173,20 @@ export class FetchRequestFactory implements RequestFactory {
|
|
|
133
173
|
const response$ = this.response(request, !!responseType);
|
|
134
174
|
|
|
135
175
|
if (!responseType) {
|
|
136
|
-
return response$.pipe(
|
|
176
|
+
return response$.pipe(map(() => undefined));
|
|
137
177
|
} else {
|
|
138
178
|
return response$.pipe(
|
|
139
179
|
switchMap(async (response) => {
|
|
140
180
|
try {
|
|
141
|
-
const contentType =
|
|
181
|
+
const contentType = MediaType.from(
|
|
142
182
|
response.headers.get('content-type'),
|
|
143
|
-
MediaType.
|
|
183
|
+
MediaType.OctetStream
|
|
144
184
|
);
|
|
145
185
|
const decoder = this.mediaTypeDecoders.find(contentType);
|
|
146
186
|
return await decoder.decode(response, responseType);
|
|
147
187
|
} catch (error) {
|
|
148
|
-
throw await
|
|
149
|
-
error
|
|
188
|
+
throw await SundayError.fromResponse(
|
|
189
|
+
errorToMessage(error, 'Response Decoding Failed'),
|
|
150
190
|
response
|
|
151
191
|
);
|
|
152
192
|
}
|
|
@@ -155,15 +195,7 @@ export class FetchRequestFactory implements RequestFactory {
|
|
|
155
195
|
}
|
|
156
196
|
}
|
|
157
197
|
|
|
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> {
|
|
198
|
+
eventSource(requestSpec: RequestSpec<void>): ExtEventSource {
|
|
167
199
|
//
|
|
168
200
|
const adapter = (
|
|
169
201
|
url: string,
|
|
@@ -175,45 +207,55 @@ export class FetchRequestFactory implements RequestFactory {
|
|
|
175
207
|
return this.request(eventSourceSpec, requestInit);
|
|
176
208
|
};
|
|
177
209
|
|
|
178
|
-
|
|
210
|
+
return new FetchEventSource(requestSpec.pathTemplate, {
|
|
179
211
|
logger: this.logger,
|
|
180
212
|
adapter,
|
|
181
213
|
});
|
|
214
|
+
}
|
|
182
215
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
216
|
+
eventStream<E>(
|
|
217
|
+
requestSpec: RequestSpec<void>,
|
|
218
|
+
decoder: (
|
|
219
|
+
decoder: TextMediaTypeDecoder,
|
|
220
|
+
event: string | undefined,
|
|
221
|
+
id: string | undefined,
|
|
222
|
+
data: string,
|
|
223
|
+
logger?: Logger
|
|
224
|
+
) => E | undefined
|
|
225
|
+
): Observable<E> {
|
|
226
|
+
const eventSource = this.eventSource(requestSpec);
|
|
186
227
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
};
|
|
228
|
+
const jsonDecoder = this.mediaTypeDecoders.find(
|
|
229
|
+
MediaType.JSON
|
|
230
|
+
) as TextMediaTypeDecoder;
|
|
203
231
|
|
|
204
232
|
return new Observable((subscriber) => {
|
|
205
|
-
|
|
206
|
-
|
|
233
|
+
eventSource.onmessage = (event: MessageEvent<string>) => {
|
|
234
|
+
if (!event.data) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
207
237
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
238
|
+
try {
|
|
239
|
+
const decodedEvent = decoder(
|
|
240
|
+
jsonDecoder,
|
|
241
|
+
event.type,
|
|
242
|
+
event.lastEventId,
|
|
243
|
+
event.data,
|
|
244
|
+
this.logger
|
|
245
|
+
);
|
|
246
|
+
if (!decodedEvent) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
212
249
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
250
|
+
subscriber.next(decodedEvent);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
subscriber.error(error);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
eventSource.onerror = (event) => {
|
|
257
|
+
this.logger?.error?.({ event }, 'event source error');
|
|
258
|
+
};
|
|
217
259
|
|
|
218
260
|
eventSource.connect();
|
|
219
261
|
|
package/src/fetch.ts
CHANGED
|
@@ -1,30 +1,95 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Copyright 2020 Outfox, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import { ConstructableClassType } from './class-type';
|
|
16
|
+
import { MediaType } from './media-type';
|
|
3
17
|
import { Problem } from './problem';
|
|
18
|
+
import { SundayError } from './sunday-error';
|
|
19
|
+
import { Base64 } from './util/base64';
|
|
20
|
+
import { errorToMessage } from './util/error';
|
|
4
21
|
|
|
5
22
|
export async function validate(
|
|
6
23
|
response: Response,
|
|
7
|
-
dataExpected: boolean
|
|
24
|
+
dataExpected: boolean,
|
|
25
|
+
problemTypes?: Map<string, ConstructableClassType<Problem>>
|
|
8
26
|
): Promise<Response> {
|
|
9
27
|
if (response.status < 200 || response.status >= 300) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
);
|
|
28
|
+
const mediaType = MediaType.from(
|
|
29
|
+
response.headers.get('content-type'),
|
|
30
|
+
MediaType.OctetStream
|
|
31
|
+
);
|
|
32
|
+
const isProblemJSON = mediaType?.compatible(MediaType.ProblemJSON) ?? false;
|
|
33
|
+
if (!isProblemJSON) {
|
|
34
|
+
throw await Problem.fromResponse(response);
|
|
18
35
|
}
|
|
19
36
|
|
|
20
37
|
const problemData = await response.json();
|
|
21
|
-
|
|
22
|
-
throw new
|
|
38
|
+
const problemType = problemTypes?.get(problemData.type) ?? Problem;
|
|
39
|
+
throw new problemType(problemData as Problem);
|
|
23
40
|
}
|
|
24
41
|
|
|
25
42
|
if (dataExpected && (response.status === 204 || response.status === 205)) {
|
|
26
|
-
throw await
|
|
43
|
+
throw await SundayError.fromResponse('Unexpected Empty Response', response);
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
return response;
|
|
30
47
|
}
|
|
48
|
+
|
|
49
|
+
export namespace ResponseExample {
|
|
50
|
+
export function build(response: Response, bodyExample?: string): string {
|
|
51
|
+
return (
|
|
52
|
+
`HTTP/?.? ${response.status} ${response.statusText}\n` +
|
|
53
|
+
Array.from(response.headers.entries())
|
|
54
|
+
.map(([name, value]) => `${name}: ${value}\n`)
|
|
55
|
+
.join('') +
|
|
56
|
+
'\n' +
|
|
57
|
+
(bodyExample ?? '<none>')
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function bodyExcerpt(
|
|
62
|
+
response: Response,
|
|
63
|
+
maxLength: number
|
|
64
|
+
): Promise<[string, unknown]> {
|
|
65
|
+
let body: unknown;
|
|
66
|
+
let bodyExcerpt: string;
|
|
67
|
+
try {
|
|
68
|
+
if (response.headers.get('content-type')?.startsWith('text/')) {
|
|
69
|
+
const text = await response.text();
|
|
70
|
+
body = text;
|
|
71
|
+
if (text.length > maxLength) {
|
|
72
|
+
const exampleText = text.slice(0, maxLength);
|
|
73
|
+
const remainingTextLength = text.length - maxLength;
|
|
74
|
+
bodyExcerpt = `${exampleText}<<... ${remainingTextLength} characters>>`;
|
|
75
|
+
} else {
|
|
76
|
+
bodyExcerpt = text;
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
const blob = await response.blob();
|
|
80
|
+
body = blob;
|
|
81
|
+
if (blob.size > maxLength) {
|
|
82
|
+
bodyExcerpt = `<<binary data: ${blob.size} bytes>>`;
|
|
83
|
+
} else {
|
|
84
|
+
const dataSlice = await blob.slice(0, maxLength).arrayBuffer();
|
|
85
|
+
bodyExcerpt = Base64.encode(dataSlice);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// ignore errors
|
|
90
|
+
const message = errorToMessage(error);
|
|
91
|
+
body = bodyExcerpt = `<<error displaying response data: ${message}>>`;
|
|
92
|
+
}
|
|
93
|
+
return [bodyExcerpt, body];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Copyright 2020 Outfox, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
export namespace HeaderParameters {
|
|
16
|
+
export function encode(
|
|
17
|
+
parameters?: Record<string, unknown>
|
|
18
|
+
): [string, string][] {
|
|
19
|
+
if (parameters == null) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const headers: [string, string][] = [];
|
|
24
|
+
|
|
25
|
+
Object.entries(parameters).forEach(([name, parameter]) => {
|
|
26
|
+
for (const value of encodeParam(name, parameter)) {
|
|
27
|
+
headers.push([name, value]);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return headers;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function encodeParam(name: string, value: unknown): string[] {
|
|
35
|
+
if (value == null) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (value instanceof Array) {
|
|
40
|
+
const result: string[] = [];
|
|
41
|
+
|
|
42
|
+
for (const item of value) {
|
|
43
|
+
if (item == null) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
result.push(validate(name, `${item}`));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return [validate(name, `${value}`)];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const asciiRegex = /^[\x20-\x7F]*$/;
|
|
57
|
+
|
|
58
|
+
function validate(name: string, value: string): string {
|
|
59
|
+
if (!asciiRegex.test(value)) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`The encoded header value contains one or more invalid characters: header=${name}, value=${value}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
// Copyright 2020 Outfox, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
1
15
|
export * from './any-type';
|
|
2
16
|
export * from './class-type';
|
|
3
17
|
export * from './request-factory';
|
|
@@ -5,19 +19,19 @@ export * from './fetch-request-factory';
|
|
|
5
19
|
export * from './fetch-event-source';
|
|
6
20
|
export * from './url-template';
|
|
7
21
|
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';
|
|
22
|
+
export * from './media-type-codecs/media-type-decoder';
|
|
23
|
+
export * from './media-type-codecs/media-type-decoders';
|
|
24
|
+
export * from './media-type-codecs/media-type-encoder';
|
|
25
|
+
export * from './media-type-codecs/media-type-encoders';
|
|
26
|
+
export * from './media-type-codecs/json-decoder';
|
|
27
|
+
export * from './media-type-codecs/json-encoder';
|
|
28
|
+
export * from './media-type-codecs/cbor-decoder';
|
|
29
|
+
export * from './media-type-codecs/cbor-encoder';
|
|
30
|
+
export * from './media-type-codecs/binary-decoder';
|
|
31
|
+
export * from './media-type-codecs/binary-encoder';
|
|
32
|
+
export * from './media-type-codecs/www-form-url-encoder';
|
|
19
33
|
export * from './logger';
|
|
20
|
-
export * from './
|
|
34
|
+
export * from './sunday-error';
|
|
21
35
|
export * from './problem';
|
|
22
36
|
export * from './date-time-types';
|
|
23
37
|
|
package/src/logger.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
// Copyright 2020 Outfox, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
1
15
|
export interface Logger {
|
|
2
16
|
trace?(...data: unknown[]): void;
|
|
3
17
|
debug?(...data: unknown[]): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright 2020 Outfox, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import { MediaTypeDecoder } from './media-type-decoder';
|
|
16
|
+
import { AnyConstructableType } from '../any-type';
|
|
17
|
+
|
|
18
|
+
export class BinaryDecoder implements MediaTypeDecoder {
|
|
19
|
+
static default = new BinaryDecoder();
|
|
20
|
+
|
|
21
|
+
async decode<T>(response: Response, type: AnyConstructableType): Promise<T> {
|
|
22
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
23
|
+
|
|
24
|
+
if (type[0] === ArrayBuffer) {
|
|
25
|
+
return arrayBuffer as unknown as T;
|
|
26
|
+
} else if (
|
|
27
|
+
type[0] === Uint8Array ||
|
|
28
|
+
type[0] === Int8Array ||
|
|
29
|
+
type[0] === DataView
|
|
30
|
+
) {
|
|
31
|
+
return new type[0](arrayBuffer) as T;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
throw Error(
|
|
35
|
+
'Invalid value, expected ArrayBuffer, (Int|Uint)Array or DataView'
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Copyright 2020 Outfox, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import { MediaTypeEncoder } from './media-type-encoder';
|
|
16
|
+
|
|
17
|
+
export class BinaryEncoder implements MediaTypeEncoder {
|
|
18
|
+
static default = new BinaryEncoder();
|
|
19
|
+
|
|
20
|
+
encode(value: unknown): BodyInit {
|
|
21
|
+
if (
|
|
22
|
+
!ArrayBuffer.isView(value) &&
|
|
23
|
+
!(value instanceof ArrayBuffer) &&
|
|
24
|
+
!(value instanceof Blob) &&
|
|
25
|
+
!(value instanceof ReadableStream)
|
|
26
|
+
) {
|
|
27
|
+
throw Error(
|
|
28
|
+
'Invalid value, expected BufferSource, Blob or ReadableStream'
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
}
|