Close endpoint failure types across service and web boundaries
This commit is contained in:
parent
fb94ea2c23
commit
390434bed1
4 changed files with 266 additions and 72 deletions
|
|
@ -129,12 +129,21 @@ export interface DeleteBatchRequest {
|
|||
}
|
||||
|
||||
|
||||
export interface DeleteItemResponse {
|
||||
path: RelativePath;
|
||||
operation_id: OperationId;
|
||||
result: MutationResult | null;
|
||||
error: string | null;
|
||||
}
|
||||
export type DeleteItemFailure = 'database_unavailable' | 'object_store_unavailable';
|
||||
|
||||
export type DeleteItemResponse =
|
||||
| {
|
||||
path: RelativePath;
|
||||
operation_id: OperationId;
|
||||
result: MutationResult;
|
||||
error: null;
|
||||
}
|
||||
| {
|
||||
path: RelativePath;
|
||||
operation_id: OperationId;
|
||||
result: null;
|
||||
error: DeleteItemFailure;
|
||||
};
|
||||
|
||||
|
||||
export interface DeleteBatchResponse {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
type DeleteBatchResponse,
|
||||
type DeleteEndpointRequest,
|
||||
type DeleteFailureCode,
|
||||
type DeleteItemFailure,
|
||||
type DeleteItemResponse,
|
||||
type DeletePlanEndpointRequest,
|
||||
type DeletePlanFailureCode,
|
||||
|
|
@ -457,12 +458,32 @@ function decodeDeleteBatchResponse(value: unknown): DeleteBatchResponse {
|
|||
|
||||
function decodeDeleteItemResponse(value: unknown): DeleteItemResponse {
|
||||
const object = record(value, 'delete item response');
|
||||
return {
|
||||
path: stringField(object, 'path'),
|
||||
operation_id: stringField(object, 'operation_id'),
|
||||
result: nullableMutationResultField(object, 'result'),
|
||||
error: nullableStringField(object, 'error')
|
||||
};
|
||||
const path = stringField(object, 'path');
|
||||
const operation_id = stringField(object, 'operation_id');
|
||||
const result = nullableMutationResultField(object, 'result');
|
||||
const error = nullableDeleteItemFailureField(object, 'error');
|
||||
if (result !== null && error === null) {
|
||||
return { path, operation_id, result, error };
|
||||
}
|
||||
if (result === null && error !== null) {
|
||||
return { path, operation_id, result, error };
|
||||
}
|
||||
throw new TypeError('delete item response must contain exactly one outcome');
|
||||
}
|
||||
|
||||
function nullableDeleteItemFailureField(
|
||||
object: Record<string, unknown>,
|
||||
field: string
|
||||
): DeleteItemFailure | null {
|
||||
const value = object[field];
|
||||
if (
|
||||
value === null ||
|
||||
value === 'database_unavailable' ||
|
||||
value === 'object_store_unavailable'
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
throw new TypeError(`${field} must be a declared delete item failure or null`);
|
||||
}
|
||||
|
||||
function record(value: unknown, label: string): Record<string, unknown> {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ test('partial delete recovery preserves only unfinished identities', () => {
|
|||
path: 'uncertain',
|
||||
operation_id: 'uncertain-id',
|
||||
result: null,
|
||||
error: 'transport interrupted'
|
||||
error: 'object_store_unavailable'
|
||||
}
|
||||
],
|
||||
() => ids.shift()
|
||||
|
|
|
|||
|
|
@ -132,6 +132,43 @@ test('shared fixtures decode through every JSON transport method', async () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('delete batch decoding enforces the typed item outcome', async () => {
|
||||
const failureClient = new BrowserProductClient(async () =>
|
||||
jsonResponse({
|
||||
results: [
|
||||
{
|
||||
path: 'folder/file.txt',
|
||||
operation_id: 'operation-1',
|
||||
result: null,
|
||||
error: 'database_unavailable'
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
const failure = requireValue(
|
||||
await failureClient.deleteBatch({ body: fixtures.delete_batch_request })
|
||||
);
|
||||
assert.equal(failure.results[0].error, 'database_unavailable');
|
||||
|
||||
const invalidClient = new BrowserProductClient(async () =>
|
||||
jsonResponse({
|
||||
results: [
|
||||
{
|
||||
path: 'folder/file.txt',
|
||||
operation_id: 'operation-1',
|
||||
result: 'succeeded',
|
||||
error: 'object_store_unavailable'
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
const invalid = await invalidClient.deleteBatch({
|
||||
body: fixtures.delete_batch_request
|
||||
});
|
||||
assert.equal(invalid.ok, false);
|
||||
assert.equal(invalid.failure.kind, 'unexpected-http');
|
||||
});
|
||||
|
||||
test('declared, undeclared, and network failures remain distinct without retries', async () => {
|
||||
const declaredClient = new BrowserProductClient(async () =>
|
||||
jsonResponse(
|
||||
|
|
|
|||
Loading…
Reference in a new issue