google-ads-api
Version:
Google Ads API Client Library for Node.js
302 lines (301 loc) • 12.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueryError = void 0;
exports.buildSelectClause = buildSelectClause;
exports.buildFromClause = buildFromClause;
exports.validateConstraintKeyAndValue = validateConstraintKeyAndValue;
exports.convertNumericEnumToString = convertNumericEnumToString;
exports.extractConstraintConditions = extractConstraintConditions;
exports.extractDateConstantConditions = extractDateConstantConditions;
exports.extractDateConditions = extractDateConditions;
exports.buildWhereClause = buildWhereClause;
exports.buildLimitClause = buildLimitClause;
exports.buildParametersClause = buildParametersClause;
exports.completeOrderly = completeOrderly;
exports.buildOrderClauseOld = buildOrderClauseOld;
exports.buildOrderClauseNew = buildOrderClauseNew;
exports.buildOrderClause = buildOrderClause;
exports.buildRequestOptions = buildRequestOptions;
exports.buildQuery = buildQuery;
const protos_1 = require("./protos");
const types_1 = require("./types");
var QueryKeywords;
(function (QueryKeywords) {
QueryKeywords["SELECT"] = "SELECT";
QueryKeywords["FROM"] = "FROM";
QueryKeywords["WHERE"] = "WHERE";
QueryKeywords["ORDER_BY"] = "ORDER BY";
QueryKeywords["LIMIT"] = "LIMIT";
QueryKeywords["PARAMETERS"] = "PARAMETERS";
QueryKeywords["AND"] = "AND";
QueryKeywords["OR"] = "OR";
})(QueryKeywords || (QueryKeywords = {}));
exports.QueryError = {
INVALID_CONSTRAINTS_FORMAT: "Constraints must be an array of objects or a singular object.",
INVALID_CONSTRAINT_KEY: "A constraint key must have a string value.",
INVALID_CONSTRAINT_VALUE: (key, val) =>
// @ts-ignore
`The value of the constraint ${key} must be a string, number, boolean, or an array of these types. Here, typeof ${key} is ${typeof val}.`,
INVALID_CONSTRAINT_OBJECT_FORMAT: "Must specify { key, op, val } or { key: value } when using object-style constraints.",
INVALID_DATE_CONSTANT_TYPE: (dateConstant) => `Date constant must be a string. Here, typeof date constant is ${typeof dateConstant}`,
INVALID_FROM_DATE_TYPE: (fromDate) => `From date must be a string. Here, typeof from date is ${typeof fromDate}`,
INVALID_TO_DATE_TYPE: (toDate) => `To date must be a string. Here, typeof to date is ${typeof toDate}`,
INVALID_LIMIT: "Limit must be a positive integer.",
INVALID_PARAMETERS: "Parameters must be a string.",
INVALID_ORDER: "Order must be an array.",
INVALID_ORDERLY: "OrderBy arrays must only contain strings.",
INVALID_ORDERBY: "OrderBy must be a string or an array of strings.",
INVALID_SORT_ORDER: `Sort order must be "ASC" or "DESC".`,
MISSING_FIELDS: 'Must specify at least one field in ("attributes","metrics","segments").',
MISSING_FROM_DATE: 'Expected start date range is missing - "from_date".',
UNDEFINED_ENTITY: "The entity of the query must be defined.",
};
function buildSelectClause(attributes, metrics, segments) {
if (!attributes?.length && !metrics?.length && !segments?.length) {
throw new Error(exports.QueryError.MISSING_FIELDS);
}
const selections = [
...(attributes || []),
...(metrics || []),
...(segments || []),
].join(", ");
return `${QueryKeywords.SELECT} ${selections}`;
}
function buildFromClause(entity) {
if (typeof entity === "undefined") {
throw new Error(exports.QueryError.UNDEFINED_ENTITY);
}
return ` ${QueryKeywords.FROM} ${entity}`;
}
function validateConstraintKeyAndValue(key, op, val) {
if (typeof val === "number" || typeof val === "boolean") {
return { op: "=", val: convertNumericEnumToString(key, val) };
}
if (typeof val === "string") {
if (types_1.dateConstants.includes(val)) {
return { op, val };
}
return {
op: "=",
val: new RegExp(/^'.*'$|^".*"$/g).test(val) ? val : `"${val}"`,
}; // must start and end in either single or double quotation marks
}
if (Array.isArray(val)) {
const stringifiedValue = val
.map((v) => {
if (typeof v === "string") {
return `"${v}"`;
}
else {
return convertNumericEnumToString(key, v);
}
})
.join(`, `);
return { op: "IN", val: `(${stringifiedValue})` };
}
throw new Error(exports.QueryError.INVALID_CONSTRAINT_VALUE(key, val));
}
function convertNumericEnumToString(key, val) {
// @ts-expect-error key does not always match an enum field
if (protos_1.fields.enumFields[key] && typeof val === "number") {
// @ts-expect-error typescript doesn't like accessing items in a namespace with a string
const enumStringValue = protos_1.enums[protos_1.fields.enumFields[key]][val]; // e.g. enums['CampaignStatus'][2] = "ENABLED"
if (enumStringValue) {
return `"${enumStringValue}"`;
}
}
return val;
}
function extractConstraintConditions(constraints) {
if (typeof constraints === "undefined") {
return [];
}
else if (Array.isArray(constraints)) {
return constraints.map((con) => {
if (typeof con === "object" && !Array.isArray(con) && con !== null) {
// @ts-ignore
if (con.key && con.op && typeof con.val !== "undefined") {
const { key, op, val } = con;
if (typeof key !== "string") {
throw new Error(exports.QueryError.INVALID_CONSTRAINT_KEY);
}
const validatedValue = validateConstraintKeyAndValue(key, op, val);
// @ts-ignore
return `${key} ${op} ${validatedValue.val}`;
}
else if (Object.keys(con).length === 1) {
const [[key, val]] = Object.entries(con);
const validatedValue = validateConstraintKeyAndValue(key, "=", val);
return `${key} ${validatedValue.op} ${validatedValue.val}`;
}
else {
throw new Error(exports.QueryError.INVALID_CONSTRAINT_OBJECT_FORMAT);
}
}
else if (typeof con === "string") {
return con;
}
else {
throw new Error(exports.QueryError.INVALID_CONSTRAINT_OBJECT_FORMAT);
}
});
}
else if (typeof constraints === "object" && constraints !== null) {
return Object.entries(constraints).map(([key, val]) => {
const validatedValue = validateConstraintKeyAndValue(key, "=", val);
return `${key} ${validatedValue.op} ${validatedValue.val}`;
});
}
else {
throw new Error(exports.QueryError.INVALID_CONSTRAINTS_FORMAT);
}
}
function extractDateConstantConditions(dateConstant) {
if (typeof dateConstant === "undefined") {
return [];
}
else if (typeof dateConstant !== "string") {
throw new Error(exports.QueryError.INVALID_DATE_CONSTANT_TYPE(dateConstant));
}
return [`segments.date DURING ${dateConstant}`];
}
function extractDateConditions(fromDate, toDate) {
if (typeof fromDate === "undefined" && typeof toDate === "undefined") {
return [];
}
if (typeof fromDate === "undefined" && typeof toDate !== "undefined") {
throw new Error(exports.QueryError.MISSING_FROM_DATE);
}
if (typeof fromDate !== "undefined" && typeof toDate === "undefined") {
const d = new Date();
toDate = `${d.getFullYear()}-${("0" + (d.getMonth() + 1)).slice(-2)}-${("0" + d.getDate()).slice(-2)}`;
}
if (typeof fromDate !== "string") {
throw new Error(exports.QueryError.INVALID_FROM_DATE_TYPE(fromDate));
}
if (typeof toDate !== "string") {
throw new Error(exports.QueryError.INVALID_TO_DATE_TYPE(toDate));
}
return [
`segments.date >= "${fromDate}"`,
`segments.date <= "${toDate}"`,
];
}
function buildWhereClause(constraints, dateConstant, fromDate, toDate) {
const constraintClauses = extractConstraintConditions(constraints);
const dateConstantClauses = extractDateConstantConditions(dateConstant);
const dateClauses = extractDateConditions(fromDate, toDate);
const whereClauses = [
...constraintClauses,
...dateConstantClauses,
...dateClauses,
].join(` ${QueryKeywords.AND} `);
return whereClauses.length
? ` ${QueryKeywords.WHERE} ${whereClauses}`
: ``;
}
function buildLimitClause(limit) {
if (typeof limit === "undefined") {
return ``;
}
if (typeof limit !== "number" || limit < 1 || !Number.isInteger(limit)) {
throw new Error(exports.QueryError.INVALID_LIMIT);
}
return ` ${QueryKeywords.LIMIT} ${limit}`;
}
function buildParametersClause(parameters) {
if (typeof parameters === "undefined") {
return ``;
}
if (typeof parameters !== "string") {
throw new Error(exports.QueryError.INVALID_PARAMETERS);
}
return ` ${QueryKeywords.PARAMETERS} ${parameters}`;
}
function completeOrderly(orderly, entity) {
if (!orderly.length) {
throw new Error(exports.QueryError.INVALID_ORDERLY);
}
else if (new RegExp(/^[^.\s]+(\.[^.\s]+)+$/g).test(orderly)) {
// text containing full stops
return orderly;
}
else if (new RegExp(/^[^.\s]+$/g).test(orderly)) {
// text without a full stop (e.g. resource_name)
return `${entity}.${orderly}`;
}
else {
throw new Error(exports.QueryError.INVALID_ORDERLY);
}
}
function buildOrderClauseOld(orderBy, sortOrder, entity) {
if (typeof orderBy === "undefined") {
return ``;
}
else if (typeof sortOrder === "undefined") {
sortOrder = "DESC";
}
else if (sortOrder !== "ASC" && sortOrder !== "DESC") {
throw new Error(exports.QueryError.INVALID_SORT_ORDER);
}
if (Array.isArray(orderBy)) {
const orders = orderBy
.map((orderly) => {
if (typeof orderly !== "string") {
throw new Error(exports.QueryError.INVALID_ORDERLY);
}
else {
return completeOrderly(orderly, entity);
}
})
.join(", ");
return ` ${QueryKeywords.ORDER_BY} ${orders} ${sortOrder}`;
}
else if (typeof orderBy === "string") {
return ` ${QueryKeywords.ORDER_BY} ${completeOrderly(orderBy, entity)} ${sortOrder}`;
}
else {
throw new Error(exports.QueryError.INVALID_ORDERBY);
}
}
function buildOrderClauseNew(order, entity) {
if (!order || !Array.isArray(order)) {
throw new Error(exports.QueryError.INVALID_ORDER);
}
if (!order.length) {
return "";
}
const orders = order
.map((o) => {
const orderly = completeOrderly(o.field, entity);
const sortOrder = o.sort_order ? o.sort_order : "DESC";
return `${orderly} ${sortOrder}`;
})
.join(", ");
return ` ${QueryKeywords.ORDER_BY} ${orders}`;
}
function buildOrderClause(order, orderBy, sortOrder, entity) {
if (order) {
return buildOrderClauseNew(order, entity);
}
else {
return buildOrderClauseOld(orderBy, sortOrder, entity);
}
}
function buildRequestOptions(reportOptions) {
const { page_size, page_token, validate_only, search_settings } = reportOptions;
return { page_size, page_token, validate_only, search_settings };
}
function buildQuery(reportOptions) {
const SELECT = buildSelectClause(reportOptions.attributes, reportOptions.metrics, reportOptions.segments);
const FROM = buildFromClause(reportOptions.entity);
const WHERE = buildWhereClause(reportOptions.constraints, reportOptions.date_constant, reportOptions.from_date, reportOptions.to_date);
const ORDER = buildOrderClause(reportOptions.order, reportOptions.order_by, reportOptions.sort_order, reportOptions.entity);
const LIMIT = buildLimitClause(reportOptions.limit);
const PARAMETERS = buildParametersClause(reportOptions.parameters);
const requestOptions = buildRequestOptions(reportOptions);
return {
gaqlQuery: `${SELECT}${FROM}${WHERE}${ORDER}${LIMIT}${PARAMETERS}`,
requestOptions,
};
}