UNPKG

google-ads-api

Version:

Google Ads API Client Library for Node.js

302 lines (301 loc) 12.4 kB
"use strict"; 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, }; }