diff --git a/lib/actions/google/ads/lib/api_client.d.ts b/lib/actions/google/ads/lib/api_client.d.ts index aa611fc57..adfc11217 100644 --- a/lib/actions/google/ads/lib/api_client.d.ts +++ b/lib/actions/google/ads/lib/api_client.d.ts @@ -14,5 +14,10 @@ export declare class GoogleAdsApiClient { addDataJobOperations(offlineUserDataJobResourceName: string, userIdentifiers: any[]): Promise; runJob(offlineUserDataJobResourceName: string): Promise; apiCall(method: "GET" | "POST", url: string, data?: any): Promise; + /** + * Inspects the error response for EU Political Advertising Declaration requirements. + * Re-throws a customized error message if the specific Google Ads API error is found. + */ + private handleEuPoliticalError; } export {}; diff --git a/lib/actions/google/ads/lib/api_client.js b/lib/actions/google/ads/lib/api_client.js index 66dffbbd1..05a245848 100644 --- a/lib/actions/google/ads/lib/api_client.js +++ b/lib/actions/google/ads/lib/api_client.js @@ -72,7 +72,13 @@ class GoogleAdsApiClient { ], validate_only: false, }; - return this.apiCall(method, path, body); + try { + return await this.apiCall(method, path, body); + } + catch (error) { + this.handleEuPoliticalError(error); + throw error; + } } async createDataJob(targetCid, userListResourceName, consentAdUserData, consentAdPersonalization) { const method = "POST"; @@ -92,7 +98,13 @@ class GoogleAdsApiClient { }, }, }; - return this.apiCall(method, path, body); + try { + return await this.apiCall(method, path, body); + } + catch (error) { + this.handleEuPoliticalError(error); + throw error; + } } async addDataJobOperations(offlineUserDataJobResourceName, userIdentifiers) { const method = "POST"; @@ -134,5 +146,22 @@ class GoogleAdsApiClient { } return response.data; } + /** + * Inspects the error response for EU Political Advertising Declaration requirements. + * Re-throws a customized error message if the specific Google Ads API error is found. + */ + handleEuPoliticalError(error) { + var _a, _b, _c, _d, _e, _f, _g; + const gadsError = (_f = (_e = (_d = (_c = (_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.details) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.errors) === null || _f === void 0 ? void 0 : _f[0]; + if (((_g = gadsError === null || gadsError === void 0 ? void 0 : gadsError.errorCode) === null || _g === void 0 ? void 0 : _g.mutateError) === "EU_POLITICAL_ADVERTISING_DECLARATION_REQUIRED") { + const message = "Action required: To use Customer Match for EU political advertising, " + + "you must first complete the identity verification and " + + "declare your intent in the Google Ads UI. " + + "See: https://developers.google.com/google-ads/api/docs/api-policy/eu-par"; + this.log("error", `EU Political Advertising Error: ${message}`); + error.message = message; + error.name = "EU Political Advertising Error"; + } + } } exports.GoogleAdsApiClient = GoogleAdsApiClient; diff --git a/lib/actions/google/common/error_utils.js b/lib/actions/google/common/error_utils.js index 0c0811dc7..3d0c80a8e 100644 --- a/lib/actions/google/common/error_utils.js +++ b/lib/actions/google/common/error_utils.js @@ -28,6 +28,12 @@ function sanitizeError(err) { } } function makeBetterErrorMessage(err, webhookId) { + if (err.name === "EU Political Advertising Error") { + if (webhookId) { + err.message = err.message + ` (Webhook ID: ${webhookId})`; + } + return; + } let apiError; let subError; let errorCode; diff --git a/src/actions/google/ads/lib/api_client.ts b/src/actions/google/ads/lib/api_client.ts index b05d5198e..4b7d46162 100644 --- a/src/actions/google/ads/lib/api_client.ts +++ b/src/actions/google/ads/lib/api_client.ts @@ -81,7 +81,12 @@ export class GoogleAdsApiClient { validate_only: false, } - return this.apiCall(method, path, body) + try { + return await this.apiCall(method, path, body) + } catch (error) { + this.handleEuPoliticalError(error) + throw error + } } async createDataJob( @@ -108,7 +113,12 @@ export class GoogleAdsApiClient { }, } - return this.apiCall(method, path, body) + try { + return await this.apiCall(method, path, body) + } catch (error) { + this.handleEuPoliticalError(error) + throw error + } } async addDataJobOperations(offlineUserDataJobResourceName: string, userIdentifiers: any[]) { @@ -157,4 +167,21 @@ export class GoogleAdsApiClient { return response.data } + + /** + * Inspects the error response for EU Political Advertising Declaration requirements. + * Re-throws a customized error message if the specific Google Ads API error is found. + */ + private handleEuPoliticalError(error: any) { + const gadsError = error?.response?.data?.error?.details?.[0]?.errors?.[0] + if (gadsError?.errorCode?.mutateError === "EU_POLITICAL_ADVERTISING_DECLARATION_REQUIRED") { + const message = "Action required: To use Customer Match for EU political advertising, " + + "you must first complete the identity verification and " + + "declare your intent in the Google Ads UI. " + + "See: https://developers.google.com/google-ads/api/docs/api-policy/eu-par" + this.log("error", `EU Political Advertising Error: ${message}`) + error.message = message + error.name = "EU Political Advertising Error" + } + } } diff --git a/src/actions/google/ads/test_customer_match.ts b/src/actions/google/ads/test_customer_match.ts index 424869317..d30087326 100644 --- a/src/actions/google/ads/test_customer_match.ts +++ b/src/actions/google/ads/test_customer_match.ts @@ -138,6 +138,58 @@ describe(`${action.constructor.name} class`, () => { }) } }) + + describe("Google Ads API error handling", () => { + let gaxiosStub: sinon.SinonStub + + beforeEach(() => { + gaxiosStub = adsSinonSandbox.stub(gaxios, "request") + }) + + it("customizes the error message for EU_POLITICAL_ADVERTISING_DECLARATION_REQUIRED", async () => { + const request = makeBaseRequest() + const state = JSON.parse(request.params.state_json!) + state.tokens.expiry_date = Date.now() + 24 * 60 * 60 * 1000 + request.params.state_json = JSON.stringify(state) + request.formParams.loginCid = "123456789" + request.formParams.createOrAppend = "create" + request.formParams.newListName = "Test List" + + const euPoliticalAdError = new Error("Request failed with status code 400") as any + euPoliticalAdError.response = { + status: 400, + data: { + error: { + code: 400, + message: "Request contains an invalid argument.", + details: [ + { + errors: [ + { + errorCode: { + mutateError: "EU_POLITICAL_ADVERTISING_DECLARATION_REQUIRED", + }, + message: "EU political advertising declaration is required.", + }, + ], + }, + ], + }, + }, + } + + gaxiosStub.rejects(euPoliticalAdError) + + const expectedMessage = "Action required: To use Customer Match for EU political advertising, " + + "you must first complete the identity verification and " + + "declare your intent in the Google Ads UI. " + + "See: https://developers.google.com/google-ads/api/docs/api-policy/eu-par" + + const response = await action.validateAndExecute(request) + expect(response.success).to.be.false + expect(response.message).to.include(expectedMessage) + }) + }) }) describe("oauth interface", () => { diff --git a/src/actions/google/common/error_utils.ts b/src/actions/google/common/error_utils.ts index ce1f22bbc..f280c6f3d 100644 --- a/src/actions/google/common/error_utils.ts +++ b/src/actions/google/common/error_utils.ts @@ -28,6 +28,12 @@ export function sanitizeError(err: any) { } export function makeBetterErrorMessage(err: any, webhookId?: string) { + if (err.name === "EU Political Advertising Error") { + if (webhookId) { + err.message = err.message + ` (Webhook ID: ${webhookId})` + } + return + } let apiError: any let subError: any let errorCode: number | undefined