feature: implement basic querystring params validation

This commit is contained in:
Alexandr
2023-10-19 19:57:01 +03:00
parent 885bd1b04e
commit 4a065a36b5
3 changed files with 168 additions and 11 deletions
+32 -5
View File
@@ -8,7 +8,36 @@ import {
renderError,
} from "../src/common/utils.js";
import { fetchStats } from "../src/fetchers/stats-fetcher.js";
import { isLocaleAvailable } from "../src/translations.js";
import { validateQueryStringParams } from "../src/common/validate.js";
const QUERYSTRING_PARAMS_DATA_TYPE_MAP = new Map([
["username", "string"],
["hide", "enum-array"],
["hide_title", "boolean"],
["hide_border", "boolean"],
["card_width", "number"],
["hide_rank", "boolean"],
["show_icons", "boolean"],
["include_all_commits", "boolean"],
["line_height", "number"],
["title_color", "string"],
["ring_color", "string"],
["icon_color", "string"],
["text_color", "string"],
["text_bold", "boolean"],
["bg_color", "string"],
["theme", "enum"],
["cache_seconds", "number"],
["exclude_repo", "array"],
["custom_title", "string"],
["locale", "enum"],
["disable_animations", "boolean"],
["border_radius", "number"],
["border_color", "string"],
["number_format", "enum"],
["rank_icon", "enum"],
["show", "enum-array"],
]);
export default async (req, res) => {
const {
@@ -45,11 +74,9 @@ export default async (req, res) => {
return res.send(renderError("Something went wrong"));
}
if (locale && !isLocaleAvailable(locale)) {
return res.send(renderError("Something went wrong", "Language not found"));
}
try {
validateQueryStringParams(req.query, QUERYSTRING_PARAMS_DATA_TYPE_MAP);
const showStats = parseArray(show);
const stats = await fetchStats(
username,
+126
View File
@@ -0,0 +1,126 @@
// @ts-check
import { availableLocales } from "../translations.js";
import { themes } from "../../themes/index.js";
import { parseArray } from "./utils.js";
/**
* Class for handling invalid query string param errors.
*/
class InvalidQueryStringParamsError extends Error {
/**
* Constructor for InvalidQueryStringParamsError.
*
* @param {string} message - The error message.
* @param {string} secondaryMessage - The secondary error message.
*/
constructor(message, secondaryMessage) {
super(message);
this.secondaryMessage = secondaryMessage;
}
}
const QUERYSTRING_PARAMS_ENUM_VALUES = {
hide: ["stars", "commits", "prs", "issues", "contribs"],
theme: Object.keys(themes),
locale: availableLocales,
number_format: ["short", "long"],
rank_icon: ["github", "percentile", "default"],
show: [
"reviews",
"discussions_started",
"discussions_answered",
"prs_merged",
"prs_merged_percentage",
],
};
/**
* Returns the secondary error message for an invalid query string param.
*
* @param {string} param - The invalid query string param.
* @param {Map<string, string>} queryStringParamsDataTypeMap - The query string params data type map.
* @returns {string} The secondary error message.
*/
const getInvalidQueryStringParamsErrorSecondaryMessage = (
param,
queryStringParamsDataTypeMap,
) => {
const expectedDataType = queryStringParamsDataTypeMap.get(param);
if (expectedDataType === "enum" || expectedDataType === "enum-array") {
return `Expected: ${QUERYSTRING_PARAMS_ENUM_VALUES[param].join(", ")}`;
} else if (expectedDataType === "number") {
return "Expected: a number";
} else if (expectedDataType === "boolean") {
return "Expected: true or false";
} else if (expectedDataType === "array") {
return "Expected: an array";
} else if (expectedDataType === "string") {
return "Expected: a string";
} else {
throw new Error("Unexpected behavior");
}
};
/**
* Validates the query string params.
* Throws an error if a query string param is invalid.
* Does not return anything.
*
* @param {object} queryStringParams - The query string params.
* @param {Map<string, string>} queryStringParamsDataTypeMap - The query string params data type map.
* @returns {void}
*/
const validateQueryStringParams = (
queryStringParams,
queryStringParamsDataTypeMap,
) => {
for (const [param, value] of Object.entries(queryStringParams)) {
const expectedDataType = queryStringParamsDataTypeMap.get(param);
if (!expectedDataType) {
throw new InvalidQueryStringParamsError(
`Invalid query string param: ${param}`,
"Expected: a valid query string param",
);
}
if (expectedDataType === "enum") {
if (QUERYSTRING_PARAMS_ENUM_VALUES[param].includes(value)) {
continue;
}
} else if (expectedDataType === "enum-array") {
const values = parseArray(value);
if (
values.every((value) =>
QUERYSTRING_PARAMS_ENUM_VALUES[param].includes(value),
)
) {
continue;
}
} else if (expectedDataType === "number") {
if (!isNaN(value)) {
continue;
}
} else if (expectedDataType === "boolean") {
if (value === "true" || value === "false") {
continue;
}
} else if (expectedDataType === "array") {
if (Array.isArray(parseArray(value))) {
continue;
}
} else if (expectedDataType === "string") {
if (typeof value === "string") {
continue;
}
}
throw new InvalidQueryStringParamsError(
`Invalid query string param: ${param}`,
getInvalidQueryStringParamsErrorSecondaryMessage(
param,
queryStringParamsDataTypeMap,
),
);
}
};
export { validateQueryStringParams };
+10 -6
View File
@@ -6,6 +6,7 @@ import { calculateRank } from "../src/calculateRank.js";
import { renderStatsCard } from "../src/cards/stats-card.js";
import { CONSTANTS, renderError } from "../src/common/utils.js";
import { expect, it, describe, afterEach } from "@jest/globals";
import { availableLocales } from "../src/translations.js";
const stats = {
name: "Anurag Hazra",
@@ -125,8 +126,8 @@ describe("Test /api/", () => {
{
username: "anuraghazra",
hide: "issues,prs,contribs",
show_icons: true,
hide_border: true,
show_icons: "true",
hide_border: "true",
line_height: 100,
title_color: "fff",
icon_color: "fff",
@@ -255,8 +256,8 @@ describe("Test /api/", () => {
{
username: "anuraghazra",
hide: "issues,prs,contribs",
show_icons: true,
hide_border: true,
show_icons: "true",
hide_border: "true",
line_height: 100,
title_color: "fff",
ring_color: "0000ff",
@@ -301,7 +302,10 @@ describe("Test /api/", () => {
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
expect(res.send).toBeCalledWith(
renderError("Something went wrong", "Language not found"),
renderError(
"Invalid query string param: locale",
`Expected: ${availableLocales.join(", ")}`,
),
);
});
@@ -311,7 +315,7 @@ describe("Test /api/", () => {
.reply(200, { error: "Some test error message" });
const { req, res } = faker(
{ username: "anuraghazra", include_all_commits: true },
{ username: "anuraghazra", include_all_commits: "true" },
data_stats,
);