Skip to content

Commit 9300ca9

Browse files
committed
feat: add detail field to PaginatedResult and extend ErrorInfo tests
- We also need the detail returned from errorDetail field in the paginated response, since this can also come over REST.
1 parent 1b41449 commit 9300ca9

4 files changed

Lines changed: 38 additions & 2 deletions

File tree

ably.d.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3384,6 +3384,10 @@ export declare interface HttpPaginatedResponse<T = any> extends PaginatedResult<
33843384
* The error message if the `X-Ably-Errormessage` HTTP header is sent in the response.
33853385
*/
33863386
errorMessage: string;
3387+
/**
3388+
* Optional map of string key-value pairs containing structured error metadata, extracted from the response body when present.
3389+
*/
3390+
errorDetail?: Record<string, string>;
33873391
/**
33883392
* The headers of the response.
33893393
*/
@@ -3706,6 +3710,7 @@ export declare class ErrorInfo extends Error {
37063710
* @param code - Ably [error code](https://github.com/ably/ably-common/blob/main/protocol/errors.json).
37073711
* @param statusCode - HTTP Status Code corresponding to this error.
37083712
* @param cause - The underlying cause of the error.
3713+
* @param detail - Optional map of string key-value pairs containing structured metadata associated with the error.
37093714
*/
3710-
constructor(message: string, code: number, statusCode: number, cause?: ErrorInfo);
3715+
constructor(message: string, code: number, statusCode: number, cause?: ErrorInfo, detail?: Record<string, string>);
37113716
}

src/common/lib/client/paginatedresource.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ export class HttpPaginatedResponse<T> extends PaginatedResult<T> {
197197
headers: ResponseHeaders;
198198
errorCode?: number | null;
199199
errorMessage?: string | null;
200+
errorDetail?: Record<string, string>;
200201

201202
constructor(
202203
resource: PaginatedResource,
@@ -212,6 +213,7 @@ export class HttpPaginatedResponse<T> extends PaginatedResult<T> {
212213
this.headers = headers;
213214
this.errorCode = err && err.code;
214215
this.errorMessage = err && err.message;
216+
this.errorDetail = err?.detail;
215217
}
216218

217219
toJSON() {
@@ -222,6 +224,7 @@ export class HttpPaginatedResponse<T> extends PaginatedResult<T> {
222224
headers: this.headers,
223225
errorCode: this.errorCode,
224226
errorMessage: this.errorMessage,
227+
errorDetail: this.errorDetail,
225228
};
226229
}
227230
}

src/common/lib/types/errorinfo.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,15 @@ export default class ErrorInfo extends Error implements IPartialErrorInfo, API.E
4141
href?: string;
4242
detail?: Record<string, string>;
4343

44-
constructor(message: string, code: number, statusCode: number, cause?: ErrorInfo) {
44+
constructor(message: string, code: number, statusCode: number, cause?: ErrorInfo, detail?: Record<string, string>) {
4545
super(message);
4646
if (typeof Object.setPrototypeOf !== 'undefined') {
4747
Object.setPrototypeOf(this, ErrorInfo.prototype);
4848
}
4949
this.code = code;
5050
this.statusCode = statusCode;
5151
this.cause = cause;
52+
this.detail = detail;
5253
}
5354

5455
toString(): string {

test/unit/errorinfo.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,33 @@ define(['chai', 'ably'], function (chai, Ably) {
6161

6262
expect(str).to.not.contain('detail=');
6363
});
64+
65+
it('should accept detail as a constructor parameter', function () {
66+
const detail = { reason: 'content policy violation', category: 'moderation' };
67+
const errorInfo = new ErrorInfo('Rejected', 42211, 400, undefined, detail);
68+
69+
expect(errorInfo).to.be.instanceOf(ErrorInfo);
70+
expect(errorInfo.message).to.equal('Rejected');
71+
expect(errorInfo.code).to.equal(42211);
72+
expect(errorInfo.statusCode).to.equal(400);
73+
expect(errorInfo.cause).to.be.undefined;
74+
expect(errorInfo.detail).to.deep.equal({ reason: 'content policy violation', category: 'moderation' });
75+
});
76+
77+
it('should accept both cause and detail as constructor parameters', function () {
78+
const cause = new ErrorInfo('Root cause', 50000, 500);
79+
const detail = { reason: 'spam' };
80+
const errorInfo = new ErrorInfo('Rejected', 42211, 400, cause, detail);
81+
82+
expect(errorInfo.cause).to.equal(cause);
83+
expect(errorInfo.detail).to.deep.equal({ reason: 'spam' });
84+
});
85+
86+
it('should leave detail undefined when not passed to constructor', function () {
87+
const errorInfo = new ErrorInfo('Some error', 50000, 500);
88+
89+
expect(errorInfo.detail).to.be.undefined;
90+
});
6491
});
6592
});
6693
});

0 commit comments

Comments
 (0)