Skip to content

Commit

Permalink
SDK-3306: Export common error types
Browse files Browse the repository at this point in the history
This exports common error types for convenience, such as BadRequestError
for HTTP responses with status 400, or NotFoundError for HTTP responses
wih status 404.

Each class overrides the `response` getter to constrain the status code.
The `problemDetails` getter is unchanged.
  • Loading branch information
NSeydoux committed Jul 2, 2024
1 parent bbe9741 commit acf01ce
Show file tree
Hide file tree
Showing 26 changed files with 1,419 additions and 0 deletions.
44 changes: 44 additions & 0 deletions src/http/wellKnown/badRequestError.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Copyright Inrupt Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import { describe, it, expect } from "@jest/globals";
import { BadRequestError, BAD_REQUEST_STATUS } from "./badRequestError";
import { mockResponse } from "./wellKnown.mock";

describe("BadRequestError", () => {
it("builds an Error object when provided an response with status 400", () => {
const response = mockResponse({ status: BAD_REQUEST_STATUS });
const e = new BadRequestError(
response,
"Some response body",
"Some error message",
);
expect(e.response.status).toBe(BAD_REQUEST_STATUS);
});

it("throws when provided a status code not equal to 400", () => {
const response = mockResponse({ status: 499 });
expect(() => {
// The object is built to check an error is thrown.
// eslint-disable-next-line no-new
new BadRequestError(response, "Some response body", "Some error message");
}).toThrow();
});
});
63 changes: 63 additions & 0 deletions src/http/wellKnown/badRequestError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// Copyright Inrupt Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import { InruptClientError } from "../../clientError";
import type { ErrorResponse } from "../errorResponse";
import { ClientHttpError } from "../httpError";

export const BAD_REQUEST_STATUS = 400 as const;

export type BadRequestErrorResponse = Readonly<
ErrorResponse & {
status: typeof BAD_REQUEST_STATUS;
}
>;

/**
* Runtime error thrown on HTTP Bad Request (400) response.
*
* @see {@link https://www.rfc-editor.org/rfc/rfc9110#status.400 | RFC 9110 (15.5.1.) 400 Bad Request}
*/
export class BadRequestError extends ClientHttpError {
constructor(
responseMetadata: {
status: number;
statusText: string;
headers: Headers;
url: string;
},
responseBody: string,
message: string,
options?: ErrorOptions,
) {
super(responseMetadata, responseBody, message, options);
if (responseMetadata.status !== BAD_REQUEST_STATUS) {
throw new InruptClientError(
`Unexpected status found building BadRequestError: expected ${BAD_REQUEST_STATUS}, found ${responseMetadata.status}`,
);
}
}

get response(): BadRequestErrorResponse {
return super.response as BadRequestErrorResponse;
}
}

export default BadRequestError;
44 changes: 44 additions & 0 deletions src/http/wellKnown/conflictError.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Copyright Inrupt Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import { describe, it, expect } from "@jest/globals";
import { mockResponse } from "./wellKnown.mock";
import { ConflictError, CONFLICT_STATUS } from "./conflictError";

describe("ConflictError", () => {
it("builds an Error object when provided an response with status 409", () => {
const response = mockResponse({ status: CONFLICT_STATUS });
const e = new ConflictError(
response,
"Some response body",
"Some error message",
);
expect(e.response.status).toBe(CONFLICT_STATUS);
});

it("throws when provided a status code not equal to 409", () => {
const response = mockResponse({ status: 499 });
expect(() => {
// The object is built to check an error is thrown.
// eslint-disable-next-line no-new
new ConflictError(response, "Some response body", "Some error message");
}).toThrow();
});
});
63 changes: 63 additions & 0 deletions src/http/wellKnown/conflictError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// Copyright Inrupt Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import { InruptClientError } from "../../clientError";
import type { ErrorResponse } from "../errorResponse";
import { ClientHttpError } from "../httpError";

export const CONFLICT_STATUS = 409 as const;

export type ConflictErrorResponse = Readonly<
ErrorResponse & {
status: typeof CONFLICT_STATUS;
}
>;

/**
* Runtime error thrown on HTTP Conflict (409) response.
*
* @see {@link https://www.rfc-editor.org/rfc/rfc9110#status.409 | RFC 9110 (15.5.10.) 409 Conflict}
*/
export class ConflictError extends ClientHttpError {
constructor(
responseMetadata: {
status: number;
statusText: string;
headers: Headers;
url: string;
},
responseBody: string,
message: string,
options?: ErrorOptions,
) {
super(responseMetadata, responseBody, message, options);
if (responseMetadata.status !== CONFLICT_STATUS) {
throw new InruptClientError(
`Unexpected status found building ConflictError: expected ${CONFLICT_STATUS}, found ${responseMetadata.status}`,
);
}
}

get response(): ConflictErrorResponse {
return super.response as ConflictErrorResponse;
}
}

export default ConflictError;
44 changes: 44 additions & 0 deletions src/http/wellKnown/forbiddenError.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Copyright Inrupt Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import { describe, it, expect } from "@jest/globals";
import { mockResponse } from "./wellKnown.mock";
import { ForbiddenError, FORBIDDEN_STATUS } from "./forbiddenError";

describe("ForbiddenError", () => {
it("builds an Error object when provided an response with status 403", () => {
const response = mockResponse({ status: FORBIDDEN_STATUS });
const e = new ForbiddenError(
response,
"Some response body",
"Some error message",
);
expect(e.response.status).toBe(FORBIDDEN_STATUS);
});

it("throws when provided a status code not equal to 403", () => {
const response = mockResponse({ status: 499 });
expect(() => {
// The object is built to check an error is thrown.
// eslint-disable-next-line no-new
new ForbiddenError(response, "Some response body", "Some error message");
}).toThrow();
});
});
63 changes: 63 additions & 0 deletions src/http/wellKnown/forbiddenError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// Copyright Inrupt Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import { InruptClientError } from "../../clientError";
import type { ErrorResponse } from "../errorResponse";
import { ClientHttpError } from "../httpError";

export const FORBIDDEN_STATUS = 403 as const;

export type ForbiddenErrorResponse = Readonly<
ErrorResponse & {
status: typeof FORBIDDEN_STATUS;
}
>;

/**
* Runtime error thrown on HTTP Forbidden (403) response.
*
* @see {@link https://www.rfc-editor.org/rfc/rfc9110#status.403 | RFC 9110 (15.5.4.) 403 Forbidden}
*/
export class ForbiddenError extends ClientHttpError {
constructor(
responseMetadata: {
status: number;
statusText: string;
headers: Headers;
url: string;
},
responseBody: string,
message: string,
options?: ErrorOptions,
) {
super(responseMetadata, responseBody, message, options);
if (responseMetadata.status !== FORBIDDEN_STATUS) {
throw new InruptClientError(
`Unexpected status found building ForbiddenError: expected ${FORBIDDEN_STATUS}, found ${responseMetadata.status}`,
);
}
}

get response(): ForbiddenErrorResponse {
return super.response as ForbiddenErrorResponse;
}
}

export default ForbiddenError;
44 changes: 44 additions & 0 deletions src/http/wellKnown/goneError.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Copyright Inrupt Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import { describe, it, expect } from "@jest/globals";
import { mockResponse } from "./wellKnown.mock";
import { GoneError, GONE_STATUS } from "./goneError";

describe("GoneError", () => {
it("builds an Error object when provided an response with status 410", () => {
const response = mockResponse({ status: GONE_STATUS });
const e = new GoneError(
response,
"Some response body",
"Some error message",
);
expect(e.response.status).toBe(GONE_STATUS);
});

it("throws when provided a status code not equal to 410", () => {
const response = mockResponse({ status: 499 });
expect(() => {
// The object is built to check an error is thrown.
// eslint-disable-next-line no-new
new GoneError(response, "Some response body", "Some error message");
}).toThrow();
});
});
Loading

0 comments on commit acf01ce

Please sign in to comment.