CourseInsights / src / queryProcessor / QueryValidator.ts
QueryValidator.ts
Raw
import { InsightDatasetKind, InsightError, ResultTooLargeError } from "../controller/IInsightFacade";
import QueryValidatorWhere from "./QueryValidatorWhere";
import QueryValidatorOptions from "./QueryValidatorOptions";
import { ValidatorInfo } from "./QueryProcessor";
import QueryValidatorTransformations from "./QueryValidatorTransformations";
import QueryPersister from "./QueryPersister";

export default class QueryValidator {
	constructor() {}

	private static mfieldsSections: string[] = ["avg", "pass", "fail", "audit", "year"];
	private static sfieldsSections: string[] = ["dept", "id", "instructor", "title", "uuid"];
	private static mfieldsRooms: string[] = ["lat", "lon", "seats"];
	private static sfieldsRooms: string[] = [
		"fullname",
		"shortname",
		"number",
		"address",
		"name",
		"type",
		"furniture",
		"href",
	];
	private static max = 3;

	public static validateQuery(query: any, validateInfo: ValidatorInfo): boolean {
		if (query === null || Array.isArray(query) || typeof query !== "object") {
			throw new InsightError("Query is not an object!");
		}
		if ("WHERE" in query && "OPTIONS" in query && Object.keys(query).length === 2) {
			QueryValidatorWhere.validateQueryWhere(query.WHERE, validateInfo);
			QueryValidatorOptions.validateQueryOptions(query.OPTIONS, validateInfo);
			return true;
		} else if (
			"TRANSFORMATIONS" in query &&
			"WHERE" in query &&
			"OPTIONS" in query &&
			Object.keys(query).length === this.max
		) {
			QueryValidatorWhere.validateQueryWhere(query.WHERE, validateInfo);
			QueryValidatorTransformations.validateTransformations(query.TRANSFORMATIONS, validateInfo);
			QueryValidatorOptions.validateQueryOptions(query.OPTIONS, validateInfo);
			return true;
		}
		throw new InsightError("Wrong object!");
	}

	public static validateMKey(mkey: any, validateInfo: ValidatorInfo): boolean {
		if (typeof mkey === "string" && mkey.includes("_")) {
			const index = mkey.indexOf("_");
			const idstring = mkey.substring(0, index);
			const key = mkey.substring(index + 1);
			this.validateIdString(idstring, validateInfo);
			if (validateInfo.type === "") {
				if (this.mfieldsSections.includes(key)) {
					validateInfo.type = "sections";
					return true;
				} else if (this.mfieldsRooms.includes(key)) {
					validateInfo.type = "rooms";
					return true;
				}
			} else {
				if (validateInfo.type === "sections" && this.mfieldsSections.includes(key)) {
					return true;
				} else if (validateInfo.type === "rooms" && this.mfieldsRooms.includes(key)) {
					return true;
				}
			}
		}
		throw new InsightError("Invalid mkey!");
	}

	public static validateSKey(skey: any, validateInfo: ValidatorInfo): boolean {
		if (typeof skey === "string" && skey.includes("_")) {
			const index = skey.indexOf("_");
			const idstring = skey.substring(0, index);
			const key = skey.substring(index + 1);
			this.validateIdString(idstring, validateInfo);
			if (validateInfo.type === "") {
				if (this.sfieldsSections.includes(key)) {
					validateInfo.type = "sections";
					return true;
				} else if (this.sfieldsRooms.includes(key)) {
					validateInfo.type = "rooms";
					return true;
				}
			} else {
				if (validateInfo.type === "sections" && this.sfieldsSections.includes(key)) {
					return true;
				} else if (validateInfo.type === "rooms" && this.sfieldsRooms.includes(key)) {
					return true;
				}
			}
		}
		throw new InsightError("Invalid skey!");
	}

	public static validateKey(key: any, validateInfo: ValidatorInfo): string {
		if (typeof key === "string" && key.includes("_")) {
			const index = key.indexOf("_");
			const idstring = key.substring(0, index);
			const akey = key.substring(index + 1);
			this.validateIdString(idstring, validateInfo);
			if (validateInfo.type === "") {
				if (this.sfieldsSections.includes(akey) || this.mfieldsSections.includes(akey)) {
					validateInfo.type = "sections";
					return key;
				} else if (this.sfieldsRooms.includes(akey) || this.mfieldsRooms.includes(akey)) {
					validateInfo.type = "rooms";
					return key;
				}
			} else {
				if (
					validateInfo.type === "sections" &&
					(this.sfieldsSections.includes(akey) || this.mfieldsSections.includes(akey))
				) {
					return key;
				} else if (
					validateInfo.type === "rooms" &&
					(this.sfieldsRooms.includes(akey) || this.mfieldsRooms.includes(akey))
				) {
					return key;
				}
			}
		}
		throw new InsightError("Invalid key!");
	}

	public static validateIdString(idstring: any, validateInfo: ValidatorInfo): boolean {
		if (idstring.length > 0 && !idstring.includes("_")) {
			if (validateInfo.id === "") {
				validateInfo.id = idstring;
			} else if (validateInfo.id !== idstring) {
				throw new InsightError("Can only query one dataset!");
			}
			return true;
		}
		throw new InsightError("Invalid id string!");
	}

	public static validateAnykey(anykey: any, validateInfo: ValidatorInfo): string {
		if (typeof anykey === "string") {
			if (anykey.includes("_")) {
				return this.validateKey(anykey, validateInfo);
			} else if (anykey.length > 0) {
				return anykey;
			}
		}
		throw new InsightError("anykey is not string!");
	}

	public static validateSize(size: number): void {
		const MAXSIZE = 5000;
		if (size > MAXSIZE) {
			throw new ResultTooLargeError(`InsightResult is over 5000, ${size}`);
		}
	}

	public static async validateKind(id: string, kind: InsightDatasetKind): Promise<void> {
		const insights = await new QueryPersister().persistQuery("data/_insightDatasets.json");
		try {
			const insight = insights.find((dataset: any) => dataset.id === id);
			if (insight ? insight.kind === kind : false) {
				return;
			}
		} catch (_e) {
			throw new InsightError("validateKind! no id or wrong dataset kind");
		}
	}
}