Skip to content

Commit

Permalink
Simplify errors (#34)
Browse files Browse the repository at this point in the history
* simplify SDK error response
  • Loading branch information
tomas-zijdemans-vipps authored Jan 23, 2024
1 parent 5f1ea46 commit 23f5bfc
Show file tree
Hide file tree
Showing 16 changed files with 88 additions and 170 deletions.
6 changes: 3 additions & 3 deletions sample_code/callbackQR_sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const accessToken = await client.auth.getToken({
// Check if the token was retrieved successfully
if (!accessToken.ok) {
console.error("😟 Error retrieving token 😟");
console.error(accessToken.message);
console.error(accessToken.error);
Deno.exit(1);
}

Expand All @@ -42,7 +42,7 @@ const qr = await client.callbackQR.create(token, qrId, {
// Check if the QR was created successfully
if (!qr.ok) {
console.error("😟 Error creating QR 😟");
console.error(qr.message);
console.error(qr.error);
Deno.exit(1);
}

Expand All @@ -51,7 +51,7 @@ const qrInfo = await client.callbackQR.info(token, qrId);
// Check if the QR was retrieved successfully
if (!qrInfo.ok) {
console.error("😟 Error retrieving QR 😟");
console.error(qrInfo.message);
console.error(qrInfo.error);
Deno.exit(1);
}

Expand Down
2 changes: 1 addition & 1 deletion sample_code/checkout_sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const checkout = await client.checkout.create(clientId, clientSecret, {
// Check if the checkout session was created successfully
if (!checkout.ok) {
console.error("😟 Error creating checkout session 😟");
console.log(checkout.message);
console.log(checkout.error);
Deno.exit(1);
}

Expand Down
2 changes: 1 addition & 1 deletion sample_code/epayment_sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const accessToken = await client.auth.getToken({
// Check if the token was retrieved successfully
if (!accessToken.ok) {
console.error("😟 Error retrieving token 😟");
console.error(accessToken.message);
console.error(accessToken.error);
Deno.exit(1);
}

Expand Down
2 changes: 1 addition & 1 deletion sample_code/login_sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const config = await client.login.discovery();

if (!config.ok) {
console.error("😟 Error discovering configuration 😟");
console.error(config.message);
console.error(config.error);
Deno.exit(1);
}

Expand Down
2 changes: 1 addition & 1 deletion sample_code/recurring_agreement_sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const accessToken = await client.auth.getToken({
// Check if the token was retrieved successfully
if (!accessToken.ok) {
console.error("😟 Error retrieving token 😟");
console.error(accessToken.message);
console.error(accessToken.error);
Deno.exit(1);
}

Expand Down
2 changes: 1 addition & 1 deletion sample_code/recurring_charge_sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const accessToken = await client.auth.getToken({
// Check if the token was retrieved successfully
if (!accessToken.ok) {
console.error("😟 Error retrieving token 😟");
console.error(accessToken.message);
console.error(accessToken.error);
Deno.exit(1);
}

Expand Down
6 changes: 3 additions & 3 deletions sample_code/redirectQR_sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const accessToken = await client.auth.getToken({
// Check if the token was retrieved successfully
if (!accessToken.ok) {
console.error("😟 Error retrieving token 😟");
console.error(accessToken.message);
console.error(accessToken.error);
Deno.exit(1);
}

Expand All @@ -44,7 +44,7 @@ const qr = await client.redirectQR.create(token, "image/png", {
// Check if the QR was created successfully
if (!qr.ok) {
console.error("😟 Error creating QR 😟");
console.error(qr.message);
console.error(qr.error);
Deno.exit(1);
}

Expand All @@ -53,7 +53,7 @@ const qrInfo = await client.redirectQR.info(token, qrId, "image/png");
// Check if the QR was retrieved successfully
if (!qrInfo.ok) {
console.error("😟 Error retrieving QR 😟");
console.error(qrInfo.message);
console.error(qrInfo.error);
Deno.exit(1);
}

Expand Down
6 changes: 0 additions & 6 deletions src/apis/types/shared_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,3 @@ export type ProblemJSON = {
*/
instance?: string | null;
};

export type SDKError<TErr> = {
ok: false;
message: string;
error?: TErr;
};
10 changes: 2 additions & 8 deletions src/base_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import {
RequestData,
} from "./types.ts";
import { buildRequest } from "./base_client_helper.ts";
import { parseError, parseRetryError } from "./errors.ts";
import { parseError } from "./errors.ts";
import { validateRequestData } from "./validate.ts";
import { fetchRetry } from "./fetch.ts";
import { isRetryError } from "./errors.ts";

/**
* Creates a base client with the given configuration.
Expand All @@ -32,18 +31,13 @@ export const baseClient = (cfg: ClientConfig): BaseClient =>
const validated = validateRequestData(requestData, cfg);

if (typeof validated === "string") {
return { ok: false, message: validated };
return { ok: false, error: { message: validated } };
}
const request = buildRequest(cfg, requestData);
try {
const res = await fetchRetry<TOk, TErr>(request, cfg.retryRequests);
return res;
} catch (error: unknown) {
//Check if the error is a RetryError.
if (isRetryError(error)) {
return parseRetryError();
}
// Otherwise, parse the error.
return parseError<TErr>(error);
}
},
Expand Down
11 changes: 6 additions & 5 deletions src/base_client_helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,9 @@ export const getHeaders = (
* @returns The user agent string.
*/
export const getUserAgent = (): string => {
// If the sdk is loaded using require, import.meta.url will be undefined
const metaUrl: string | undefined = import.meta.url;

// If the sdk is loaded using require, import.meta.url will be undefined
if (!metaUrl) {
return "Vipps/Deno SDK/npm-require";
}
const userAgent = createSDKUserAgent(metaUrl);
return userAgent;
};
Expand All @@ -98,7 +95,11 @@ export const getUserAgent = (): string => {
* @param metaUrl - The meta URL of the module.
* @returns The user agent string.
*/
export const createSDKUserAgent = (metaUrl: string): string => {
export const createSDKUserAgent = (metaUrl: string | undefined): string => {
if (!metaUrl) {
return "Vipps/Deno SDK/npm-require";
}

const url = new URL(metaUrl);

// Check if the module was loaded from deno.land
Expand Down
91 changes: 22 additions & 69 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,5 @@
import { RetryError, STATUS_CODE } from "./deps.ts";
import { SDKError } from "./apis/types/shared_types.ts";
import { RecurringErrorFromAzure } from "./mod.ts";

/**
* Checks if the provided JSON object is an instance of RetryError.
* @param json The JSON object to check.
* @returns True if the JSON object is an instance of RetryError,
* false otherwise.
*/
export const isRetryError = (json: unknown) => {
return json instanceof RetryError;
};

/**
* Parses the error and returns an object with error details.
* @returns An object with error details.
*/
export const parseRetryError = (): SDKError<undefined> => {
return {
ok: false,
message: "Retry limit reached. Could not get a response from the server",
};
};
import { SDKError } from "./types.ts";

/**
* Parses the error and returns an object with error details.
Expand All @@ -33,9 +11,15 @@ export const parseError = <TErr>(
error: unknown,
status?: number,
): SDKError<TErr> => {
// Catch ProblemJSON with details
if (isProblemJSONwithDetail(error)) {
return { ok: false, message: error.detail };
// Catch RetryError
if (error instanceof RetryError) {
return {
ok: false,
error: {
message:
"Retry limit reached. Could not get a response from the server",
},
};
}

// Catch connection errors
Expand All @@ -45,62 +29,31 @@ export const parseError = <TErr>(
) {
return {
ok: false,
message: "Could not connect to Vipps MobilePay API",
error: { message: "Could not connect to Vipps MobilePay API" },
};
}

// Catch Forbidden
if (status === STATUS_CODE.Forbidden) {
return {
ok: false,
message:
"Your credentials are not authorized for this product, please visit portal.vipps.no",
};
}

// Catch AccessTokenError
if (
typeof error === "object" && error !== null && "error" in error &&
"error_description" in error
) {
return {
ok: false,
message: `${error.error} - ${error.error_description}`,
error: error as TErr,
};
}

// Catch Recurring Azure Error
if (
typeof error === "object" && error !== null && "responseInfo" in error &&
"result" in error
) {
const azureError = error as RecurringErrorFromAzure;
return {
ok: false,
message: azureError.result.message,
error: error as TErr,
error: {
message:
"Your credentials are not authorized for this product, please visit portal.vipps.no",
},
};
}

// Catch regular errors
if (error instanceof Error) {
return { ok: false, message: `${error.name} - ${error.message}` };
return { ok: false, error: { message: error.message } };
}

// Default to error as string
return { ok: false, message: "Unknown error" };
};
// If error is object, return it
if (typeof error === "object") {
return { ok: false, error: error as TErr };
}

/**
* Checks if the given JSON object is a ProblemJSON with a detail property.
*
* @param json The JSON object to check.
* @returns True if the JSON object is a ProblemJSON with a detail property, false otherwise.
*/
const isProblemJSONwithDetail = (json: unknown): json is { detail: string } => {
return (
typeof json === "object" && json !== null &&
"detail" in json && typeof json["detail"] === "string"
);
// Default to error as string
return { ok: false, error: { message: String(error) } };
};
11 changes: 6 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ export type ClientResponse<TOk, TErr> =
ok: true;
data: TOk;
}
| {
ok: false;
message: string;
error?: TErr;
};
| SDKError<TErr>;

export type SDKError<TErr> = {
ok: false;
error: TErr | { message: string };
};

export type ClientConfig = {
/** Vipps Subscription key for the API product. Found in the Vipps portal.
Expand Down
10 changes: 5 additions & 5 deletions tests/base_client_helper_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
buildRequest,
createSDKUserAgent,
getHeaders,
getUserAgent,
} from "../src/base_client_helper.ts";
import { ClientConfig, RequestData } from "../src/types.ts";
import { assert, assertEquals } from "./test_deps.ts";
Expand Down Expand Up @@ -115,10 +114,11 @@ Deno.test("getHeaders - Should omit headers", () => {
assert(expectedHeaders["Merchant-Serial-Number"] === undefined);
});

Deno.test("getUserAgent - Should return the correct user agent", () => {
import.meta.url = "https://deno.land/x/vipps_mobilepay_sdk@1.0.0/mod.ts";
const userAgent = getUserAgent();
assert(userAgent !== "Vipps/Deno SDK/npm-require");
Deno.test("createUserAgent - Should return the correct user agent for require", () => {
const expectedUserAgent = "Vipps/Deno SDK/npm-require";
const actualUserAgent = createSDKUserAgent(undefined);

assertEquals(actualUserAgent, expectedUserAgent);
});

Deno.test("createUserAgent - Should return the correct user agent string when loaded from deno.land/x", () => {
Expand Down
Loading

0 comments on commit 23f5bfc

Please sign in to comment.