@slates/proto 1.0.0-rc.25 → 1.0.0-rc.27
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/dist/index.cjs +772 -673
- package/dist/index.d.cts +460 -37
- package/dist/index.d.ts +460 -37
- package/dist/index.module.js +767 -673
- package/package.json +1 -1
- package/src/handler/provider.test.ts +170 -1
- package/src/handler/provider.ts +20 -5
- package/src/messages/auth.ts +74 -0
- package/src/types/authMethod.test.ts +36 -0
- package/src/types/authMethod.ts +2 -1
- package/src/types/error.ts +47 -0
- package/src/types/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
import { badRequestError, ServiceError } from '@lowerdeck/error';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
3
|
+
import { createApiServiceError } from '../../../provider/src/error/api';
|
|
3
4
|
import { SlateError } from '../../../provider/src/error/base';
|
|
5
|
+
import type { SlateErrorResponse } from '../../../provider/src/error/types';
|
|
6
|
+
import { type SlatesErrorResponse, slatesErrorResponse } from '../types';
|
|
4
7
|
import {
|
|
5
8
|
createSlatesProviderProtoHandler,
|
|
6
9
|
SlatesProviderProtoHandlerManager
|
|
7
10
|
} from './provider';
|
|
8
11
|
|
|
12
|
+
// Zod looseObject adds index signatures that interfaces lack; compare declared fields only.
|
|
13
|
+
type Declared<T> = T extends readonly (infer Item)[]
|
|
14
|
+
? Declared<Item>[]
|
|
15
|
+
: T extends object
|
|
16
|
+
? {
|
|
17
|
+
[Key in keyof T as string extends Key
|
|
18
|
+
? never
|
|
19
|
+
: number extends Key
|
|
20
|
+
? never
|
|
21
|
+
: Key]: Declared<T[Key]>;
|
|
22
|
+
}
|
|
23
|
+
: T;
|
|
24
|
+
|
|
25
|
+
describe('slate error response shape', () => {
|
|
26
|
+
it('keeps the provider SlateErrorResponse and the proto schema in sync', () => {
|
|
27
|
+
type Provider = Declared<SlateErrorResponse>;
|
|
28
|
+
type Proto = Declared<SlatesErrorResponse>;
|
|
29
|
+
|
|
30
|
+
expectTypeOf<Provider>().toExtend<Proto>();
|
|
31
|
+
expectTypeOf<keyof Provider>().toEqualTypeOf<keyof Proto>();
|
|
32
|
+
expectTypeOf<keyof NonNullable<Provider['upstream']>>().toEqualTypeOf<
|
|
33
|
+
keyof NonNullable<Proto['upstream']>
|
|
34
|
+
>();
|
|
35
|
+
expectTypeOf<keyof NonNullable<Provider['provider']>>().toEqualTypeOf<
|
|
36
|
+
keyof NonNullable<Proto['provider']>
|
|
37
|
+
>();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
9
41
|
describe('SlatesProviderProtoHandlerManager', () => {
|
|
10
42
|
it('serializes SlateError instances directly into JSON-RPC errors', async () => {
|
|
11
43
|
let manager = await createSlatesProviderProtoHandler(async manager => {
|
|
@@ -46,6 +78,7 @@ describe('SlatesProviderProtoHandlerManager', () => {
|
|
|
46
78
|
status: 429,
|
|
47
79
|
code: 'rate_limited'
|
|
48
80
|
},
|
|
81
|
+
upstreamCode: 'rate_limited',
|
|
49
82
|
baggage: {
|
|
50
83
|
scope: 'issues'
|
|
51
84
|
}
|
|
@@ -118,4 +151,140 @@ describe('SlatesProviderProtoHandlerManager', () => {
|
|
|
118
151
|
}
|
|
119
152
|
});
|
|
120
153
|
});
|
|
154
|
+
|
|
155
|
+
let throwFromIdentify = async (error: unknown) => {
|
|
156
|
+
let manager = await createSlatesProviderProtoHandler(async manager => {
|
|
157
|
+
manager.onRequest('slates/provider.identify', async () => {
|
|
158
|
+
throw error;
|
|
159
|
+
});
|
|
160
|
+
}).run();
|
|
161
|
+
|
|
162
|
+
let response = await SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
163
|
+
jsonrpc: '2.0',
|
|
164
|
+
id: 'req_upstream',
|
|
165
|
+
method: 'slates/provider.identify',
|
|
166
|
+
params: {}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return (response as { error: Record<string, unknown> }).error;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
it('surfaces the upstream code of API service errors as a top-level field', async () => {
|
|
173
|
+
let error = await throwFromIdentify(
|
|
174
|
+
createApiServiceError('Stripe API request failed: 402 - Card declined', {
|
|
175
|
+
upstreamStatus: 402,
|
|
176
|
+
upstreamCode: 'card_declined'
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
expect(error).toEqual({
|
|
181
|
+
code: 'request.bad',
|
|
182
|
+
kind: 'request',
|
|
183
|
+
message: 'Stripe API request failed: 402 - Card declined',
|
|
184
|
+
retryable: false,
|
|
185
|
+
status: 400,
|
|
186
|
+
upstreamCode: 'card_declined',
|
|
187
|
+
baggage: {
|
|
188
|
+
serviceError: {
|
|
189
|
+
code: 'bad_request',
|
|
190
|
+
status: 400
|
|
191
|
+
},
|
|
192
|
+
serviceErrorData: {
|
|
193
|
+
upstreamStatus: 402,
|
|
194
|
+
upstreamCode: 'card_declined'
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
expect(slatesErrorResponse.parse(error).upstreamCode).toBe('card_declined');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('omits upstreamCode when the service error has no string upstream code', async () => {
|
|
202
|
+
for (let upstreamCode of [undefined, '']) {
|
|
203
|
+
let error = await throwFromIdentify(
|
|
204
|
+
createApiServiceError('Upstream failed', { upstreamStatus: 500, upstreamCode })
|
|
205
|
+
);
|
|
206
|
+
expect(error).not.toHaveProperty('upstreamCode');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let numeric = new ServiceError(badRequestError({ message: 'Upstream failed' }));
|
|
210
|
+
numeric.data.upstreamCode = 42;
|
|
211
|
+
expect(await throwFromIdentify(numeric)).not.toHaveProperty('upstreamCode');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('drops upstreamCode values that are not code-shaped', async () => {
|
|
215
|
+
for (let upstreamCode of ['x'.repeat(129), 'has space', '`x`', 'a\nb', '<script>']) {
|
|
216
|
+
expect(
|
|
217
|
+
await throwFromIdentify(
|
|
218
|
+
createApiServiceError('Upstream failed', { upstreamStatus: 400, upstreamCode })
|
|
219
|
+
)
|
|
220
|
+
).not.toHaveProperty('upstreamCode');
|
|
221
|
+
|
|
222
|
+
let payload = await throwFromIdentify({
|
|
223
|
+
code: 'upstream.error',
|
|
224
|
+
kind: 'upstream',
|
|
225
|
+
message: 'Upstream failed',
|
|
226
|
+
upstreamCode,
|
|
227
|
+
upstream: { code: upstreamCode }
|
|
228
|
+
});
|
|
229
|
+
expect(payload).not.toHaveProperty('upstreamCode');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
expect(
|
|
233
|
+
await throwFromIdentify({
|
|
234
|
+
code: 'upstream.error',
|
|
235
|
+
kind: 'upstream',
|
|
236
|
+
message: 'Upstream failed',
|
|
237
|
+
upstreamCode: 'not a code',
|
|
238
|
+
upstream: { code: 'AADSTS700016' }
|
|
239
|
+
})
|
|
240
|
+
).toMatchObject({ upstreamCode: 'AADSTS700016' });
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('keeps upstreamCode in service error baggage for older hubs', async () => {
|
|
244
|
+
let error = await throwFromIdentify(
|
|
245
|
+
createApiServiceError('Upstream failed', { upstreamCode: 'invalid_grant' })
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
expect(error).toMatchObject({
|
|
249
|
+
upstreamCode: 'invalid_grant',
|
|
250
|
+
baggage: { serviceErrorData: { upstreamCode: 'invalid_grant' } }
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('derives upstreamCode from upstream.code for slate error payloads that predate it', async () => {
|
|
255
|
+
let error = await throwFromIdentify({
|
|
256
|
+
code: 'upstream.error',
|
|
257
|
+
kind: 'upstream',
|
|
258
|
+
message: 'Upstream failed',
|
|
259
|
+
upstream: { status: 502, code: 'BAD_GATEWAY' }
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
expect(error).toMatchObject({ code: 'upstream.error', upstreamCode: 'BAD_GATEWAY' });
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('keeps an explicit upstreamCode on slate errors', async () => {
|
|
266
|
+
let error = await throwFromIdentify(
|
|
267
|
+
new SlateError({
|
|
268
|
+
code: 'upstream.error',
|
|
269
|
+
message: 'Upstream failed',
|
|
270
|
+
upstream: { code: 'nested' },
|
|
271
|
+
upstreamCode: 'explicit'
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
expect(error).toMatchObject({ upstream: { code: 'nested' }, upstreamCode: 'explicit' });
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('carries the upstream code when a service error is converted to a SlateError', async () => {
|
|
279
|
+
let error = await throwFromIdentify(
|
|
280
|
+
SlateError.fromServiceError(
|
|
281
|
+
createApiServiceError('Rate limited', {
|
|
282
|
+
upstreamStatus: 429,
|
|
283
|
+
upstreamCode: 'RATE_LIMITED'
|
|
284
|
+
})
|
|
285
|
+
)
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
expect(error).toMatchObject({ code: 'request.bad', upstreamCode: 'RATE_LIMITED' });
|
|
289
|
+
});
|
|
121
290
|
});
|
package/src/handler/provider.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
slatesNotificationsByMethod,
|
|
13
13
|
slatesRequestsByMethod
|
|
14
14
|
} from '../messages';
|
|
15
|
+
import type { SlatesErrorResponse } from '../types';
|
|
15
16
|
|
|
16
17
|
export class SlatesProviderProtoHandlerManager {
|
|
17
18
|
#implMap = new Map<
|
|
@@ -163,9 +164,9 @@ export let createSlatesProviderProtoHandler = (
|
|
|
163
164
|
let toSlateErrorResponse = (
|
|
164
165
|
error: unknown,
|
|
165
166
|
defaults: Partial<{ code: string; message: string }> = {}
|
|
166
|
-
) => {
|
|
167
|
+
): SlatesErrorResponse => {
|
|
167
168
|
let slateError = getSlateErrorResponse(error);
|
|
168
|
-
if (slateError) return slateError;
|
|
169
|
+
if (slateError) return withUpstreamCode(slateError);
|
|
169
170
|
|
|
170
171
|
let serviceError = getServiceErrorData(error);
|
|
171
172
|
if (serviceError) {
|
|
@@ -199,9 +200,7 @@ let isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
|
199
200
|
let isStructuredValue = (value: unknown): value is Record<string, unknown> | Function =>
|
|
200
201
|
(typeof value === 'object' || typeof value === 'function') && value !== null;
|
|
201
202
|
|
|
202
|
-
let isSlateErrorPayload = (
|
|
203
|
-
value: unknown
|
|
204
|
-
): value is { code: string; message: string; kind?: string; [key: string]: unknown } =>
|
|
203
|
+
let isSlateErrorPayload = (value: unknown): value is SlatesErrorResponse =>
|
|
205
204
|
isRecord(value) &&
|
|
206
205
|
typeof value.code === 'string' &&
|
|
207
206
|
typeof value.message === 'string' &&
|
|
@@ -273,6 +272,20 @@ let getServiceErrorData = (error: unknown) => {
|
|
|
273
272
|
return null;
|
|
274
273
|
};
|
|
275
274
|
|
|
275
|
+
let UPSTREAM_CODE_PATTERN = /^[\w.:-]{1,128}$/;
|
|
276
|
+
|
|
277
|
+
// upstreamCode is copied from untrusted upstream responses to the top level of the
|
|
278
|
+
// error, so anything not code-shaped is dropped.
|
|
279
|
+
let normalizeUpstreamCode = (value: unknown): string | undefined =>
|
|
280
|
+
typeof value === 'string' && UPSTREAM_CODE_PATTERN.test(value) ? value : undefined;
|
|
281
|
+
|
|
282
|
+
let withUpstreamCode = (response: SlatesErrorResponse): SlatesErrorResponse => {
|
|
283
|
+
let { upstreamCode: rawUpstreamCode, ...rest } = response;
|
|
284
|
+
let upstreamCode =
|
|
285
|
+
normalizeUpstreamCode(rawUpstreamCode) ?? normalizeUpstreamCode(response.upstream?.code);
|
|
286
|
+
return upstreamCode ? { ...rest, upstreamCode } : rest;
|
|
287
|
+
};
|
|
288
|
+
|
|
276
289
|
let mapKind = (code: string) => {
|
|
277
290
|
if (code.startsWith('declaration.')) return 'declaration';
|
|
278
291
|
if (code.startsWith('input.')) return 'validation';
|
|
@@ -345,6 +358,7 @@ let mapServiceErrorToSlateErrorResponse = (
|
|
|
345
358
|
defaults: Partial<{ code: string; message: string }> = {}
|
|
346
359
|
) => {
|
|
347
360
|
let code = defaults.code ?? mapServiceErrorCode(error.code, error.status);
|
|
361
|
+
let upstreamCode = normalizeUpstreamCode(error.upstreamCode);
|
|
348
362
|
let extraEntries = Object.entries(error).filter(
|
|
349
363
|
([key]) =>
|
|
350
364
|
![
|
|
@@ -367,6 +381,7 @@ let mapServiceErrorToSlateErrorResponse = (
|
|
|
367
381
|
retryable: mapRetryable(code),
|
|
368
382
|
status: error.status,
|
|
369
383
|
...(mapIssues(error.errors) ? { issues: mapIssues(error.errors) } : {}),
|
|
384
|
+
...(upstreamCode ? { upstreamCode } : {}),
|
|
370
385
|
baggage: {
|
|
371
386
|
serviceError: {
|
|
372
387
|
code: error.code,
|
package/src/messages/auth.ts
CHANGED
|
@@ -208,6 +208,72 @@ export type SlatesMessageAuthAuthorizationCallbackHandleResponse = z.infer<
|
|
|
208
208
|
typeof slatesMessageAuthAuthorizationCallbackHandleResponse
|
|
209
209
|
>;
|
|
210
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Get Admin Authorization Url
|
|
213
|
+
*/
|
|
214
|
+
export let slatesMessageAuthAdminAuthorizationUrlGetRequest = z.object({
|
|
215
|
+
jsonrpc: z.literal('2.0'),
|
|
216
|
+
method: z.literal('slates/auth.admin_authorization_url.get'),
|
|
217
|
+
id: z.string(),
|
|
218
|
+
params: slatesMessageAuthAuthorizationUrlGetRequest.shape.params
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
export type SlatesMessageAuthAdminAuthorizationUrlGetRequest = z.infer<
|
|
222
|
+
typeof slatesMessageAuthAdminAuthorizationUrlGetRequest
|
|
223
|
+
>;
|
|
224
|
+
|
|
225
|
+
export let slatesMessageAuthAdminAuthorizationUrlGetResponse = z.object({
|
|
226
|
+
jsonrpc: z.literal('2.0'),
|
|
227
|
+
id: z.string(),
|
|
228
|
+
result: withRequestTraces({
|
|
229
|
+
authorizationUrl: z.string(),
|
|
230
|
+
input: z.record(z.string(), z.any()).optional(),
|
|
231
|
+
callbackState: z.record(z.string(), z.any()).optional()
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
export type SlatesMessageAuthAdminAuthorizationUrlGetResponse = z.infer<
|
|
236
|
+
typeof slatesMessageAuthAdminAuthorizationUrlGetResponse
|
|
237
|
+
>;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Handle Admin Callback
|
|
241
|
+
*/
|
|
242
|
+
export let slatesMessageAuthAdminCallbackHandleRequest = z.object({
|
|
243
|
+
jsonrpc: z.literal('2.0'),
|
|
244
|
+
method: z.literal('slates/auth.admin_callback.handle'),
|
|
245
|
+
id: z.string(),
|
|
246
|
+
params: z.object({
|
|
247
|
+
authenticationMethodId: z.string(),
|
|
248
|
+
|
|
249
|
+
code: z.string().optional(),
|
|
250
|
+
state: z.string(),
|
|
251
|
+
redirectUri: z.string(),
|
|
252
|
+
input: z.record(z.string(), z.any()),
|
|
253
|
+
clientId: z.string(),
|
|
254
|
+
clientSecret: z.string(),
|
|
255
|
+
scopes: z.array(z.string()),
|
|
256
|
+
callbackParams: z.record(z.string(), z.string()).optional(),
|
|
257
|
+
callbackState: z.record(z.string(), z.any()).optional()
|
|
258
|
+
})
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
export type SlatesMessageAuthAdminCallbackHandleRequest = z.infer<
|
|
262
|
+
typeof slatesMessageAuthAdminCallbackHandleRequest
|
|
263
|
+
>;
|
|
264
|
+
|
|
265
|
+
export let slatesMessageAuthAdminCallbackHandleResponse = z.object({
|
|
266
|
+
jsonrpc: z.literal('2.0'),
|
|
267
|
+
id: z.string(),
|
|
268
|
+
result: withRequestTraces({
|
|
269
|
+
instructions: z.string().optional()
|
|
270
|
+
})
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
export type SlatesMessageAuthAdminCallbackHandleResponse = z.infer<
|
|
274
|
+
typeof slatesMessageAuthAdminCallbackHandleResponse
|
|
275
|
+
>;
|
|
276
|
+
|
|
211
277
|
/**
|
|
212
278
|
* Handle Token Refresh
|
|
213
279
|
*/
|
|
@@ -314,6 +380,8 @@ export type SlatesAuthRequests =
|
|
|
314
380
|
| SlatesMessageAuthDefaultInputGetRequest
|
|
315
381
|
| SlatesMessageAuthAuthorizationUrlGetRequest
|
|
316
382
|
| SlatesMessageAuthAuthorizationCallbackHandleRequest
|
|
383
|
+
| SlatesMessageAuthAdminAuthorizationUrlGetRequest
|
|
384
|
+
| SlatesMessageAuthAdminCallbackHandleRequest
|
|
317
385
|
| SlatesMessageAuthTokenRefreshHandleRequest
|
|
318
386
|
| SlatesMessageAuthProfileGetRequest
|
|
319
387
|
| SlatesMessageAuthOutputGetRequest;
|
|
@@ -325,6 +393,8 @@ export type SlatesAuthResponses =
|
|
|
325
393
|
| SlatesMessageAuthDefaultInputGetResponse
|
|
326
394
|
| SlatesMessageAuthAuthorizationUrlGetResponse
|
|
327
395
|
| SlatesMessageAuthAuthorizationCallbackHandleResponse
|
|
396
|
+
| SlatesMessageAuthAdminAuthorizationUrlGetResponse
|
|
397
|
+
| SlatesMessageAuthAdminCallbackHandleResponse
|
|
328
398
|
| SlatesMessageAuthTokenRefreshHandleResponse
|
|
329
399
|
| SlatesMessageAuthProfileGetResponse
|
|
330
400
|
| SlatesMessageAuthOutputGetResponse;
|
|
@@ -339,6 +409,8 @@ export let slatesAuthResponsesByMethod = {
|
|
|
339
409
|
'slates/auth.authorization_url.get': slatesMessageAuthAuthorizationUrlGetResponse,
|
|
340
410
|
'slates/auth.authorization_callback.handle':
|
|
341
411
|
slatesMessageAuthAuthorizationCallbackHandleResponse,
|
|
412
|
+
'slates/auth.admin_authorization_url.get': slatesMessageAuthAdminAuthorizationUrlGetResponse,
|
|
413
|
+
'slates/auth.admin_callback.handle': slatesMessageAuthAdminCallbackHandleResponse,
|
|
342
414
|
'slates/auth.token_refresh.handle': slatesMessageAuthTokenRefreshHandleResponse,
|
|
343
415
|
'slates/auth.profile.get': slatesMessageAuthProfileGetResponse,
|
|
344
416
|
'slates/auth.output.get': slatesMessageAuthOutputGetResponse
|
|
@@ -352,6 +424,8 @@ export let slatesAuthRequestsByMethod = {
|
|
|
352
424
|
'slates/auth.authorization_url.get': slatesMessageAuthAuthorizationUrlGetRequest,
|
|
353
425
|
'slates/auth.authorization_callback.handle':
|
|
354
426
|
slatesMessageAuthAuthorizationCallbackHandleRequest,
|
|
427
|
+
'slates/auth.admin_authorization_url.get': slatesMessageAuthAdminAuthorizationUrlGetRequest,
|
|
428
|
+
'slates/auth.admin_callback.handle': slatesMessageAuthAdminCallbackHandleRequest,
|
|
355
429
|
'slates/auth.token_refresh.handle': slatesMessageAuthTokenRefreshHandleRequest,
|
|
356
430
|
'slates/auth.profile.get': slatesMessageAuthProfileGetRequest,
|
|
357
431
|
'slates/auth.output.get': slatesMessageAuthOutputGetRequest
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { slatesAuthenticationMethod } from './authMethod';
|
|
3
|
+
|
|
4
|
+
let authMethod = {
|
|
5
|
+
id: 'oauth',
|
|
6
|
+
name: 'OAuth',
|
|
7
|
+
type: 'auth.oauth',
|
|
8
|
+
inputSchema: {},
|
|
9
|
+
outputSchema: {},
|
|
10
|
+
docs: []
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('slatesAuthenticationMethod', () => {
|
|
14
|
+
it('parses the adminSetup capability with and without the required flag', () => {
|
|
15
|
+
for (let adminSetup of [
|
|
16
|
+
{ enabled: true },
|
|
17
|
+
{ enabled: true, required: false },
|
|
18
|
+
{ enabled: true, required: true }
|
|
19
|
+
]) {
|
|
20
|
+
let parsed = slatesAuthenticationMethod.parse({
|
|
21
|
+
...authMethod,
|
|
22
|
+
capabilities: { adminSetup }
|
|
23
|
+
});
|
|
24
|
+
expect(parsed.capabilities.adminSetup).toEqual(adminSetup);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('rejects a non-boolean required flag', () => {
|
|
29
|
+
expect(
|
|
30
|
+
slatesAuthenticationMethod.safeParse({
|
|
31
|
+
...authMethod,
|
|
32
|
+
capabilities: { adminSetup: { enabled: true, required: 'yes' } }
|
|
33
|
+
}).success
|
|
34
|
+
).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
package/src/types/authMethod.ts
CHANGED
|
@@ -32,7 +32,8 @@ export let slatesAuthenticationMethod = z.object({
|
|
|
32
32
|
getDefaultInput: z.object({ enabled: z.boolean() }).optional(),
|
|
33
33
|
handleChangedInput: z.object({ enabled: z.boolean() }).optional(),
|
|
34
34
|
handleTokenRefresh: z.object({ enabled: z.boolean() }).optional(),
|
|
35
|
-
getProfile: z.object({ enabled: z.boolean() }).optional()
|
|
35
|
+
getProfile: z.object({ enabled: z.boolean() }).optional(),
|
|
36
|
+
adminSetup: z.object({ enabled: z.boolean(), required: z.boolean().optional() }).optional()
|
|
36
37
|
}),
|
|
37
38
|
|
|
38
39
|
docs: z.array(
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import { slatesRequestTrace } from '../messages/tracing';
|
|
3
|
+
|
|
4
|
+
export let slatesErrorResponse = z.looseObject({
|
|
5
|
+
code: z.string(),
|
|
6
|
+
kind: z.string(),
|
|
7
|
+
message: z.string(),
|
|
8
|
+
retryable: z.boolean().optional(),
|
|
9
|
+
status: z.number().optional(),
|
|
10
|
+
|
|
11
|
+
issues: z
|
|
12
|
+
.array(
|
|
13
|
+
z.looseObject({
|
|
14
|
+
path: z.array(z.string()).optional(),
|
|
15
|
+
code: z.string().optional(),
|
|
16
|
+
message: z.string()
|
|
17
|
+
})
|
|
18
|
+
)
|
|
19
|
+
.optional(),
|
|
20
|
+
|
|
21
|
+
provider: z
|
|
22
|
+
.looseObject({
|
|
23
|
+
key: z.string().optional(),
|
|
24
|
+
service: z.string().optional(),
|
|
25
|
+
operation: z.string().optional()
|
|
26
|
+
})
|
|
27
|
+
.optional(),
|
|
28
|
+
|
|
29
|
+
upstream: z
|
|
30
|
+
.looseObject({
|
|
31
|
+
status: z.number().optional(),
|
|
32
|
+
code: z.string().optional(),
|
|
33
|
+
type: z.string().optional(),
|
|
34
|
+
requestId: z.string().optional(),
|
|
35
|
+
method: z.string().optional(),
|
|
36
|
+
url: z.string().optional()
|
|
37
|
+
})
|
|
38
|
+
.optional(),
|
|
39
|
+
|
|
40
|
+
// The upstream API's own error code, next to the normalized protocol `code`.
|
|
41
|
+
upstreamCode: z.string().optional(),
|
|
42
|
+
|
|
43
|
+
baggage: z.record(z.string(), z.unknown()).optional(),
|
|
44
|
+
requestTraces: z.array(slatesRequestTrace).optional()
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export type SlatesErrorResponse = z.infer<typeof slatesErrorResponse>;
|