Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/actions/google/ads/lib/api_client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ export declare class GoogleAdsApiClient {
addDataJobOperations(offlineUserDataJobResourceName: string, userIdentifiers: any[]): Promise<any>;
runJob(offlineUserDataJobResourceName: string): Promise<any>;
apiCall(method: "GET" | "POST", url: string, data?: any): Promise<any>;
/**
* 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 {};
33 changes: 31 additions & 2 deletions lib/actions/google/ads/lib/api_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -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;
6 changes: 6 additions & 0 deletions lib/actions/google/common/error_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
31 changes: 29 additions & 2 deletions src/actions/google/ads/lib/api_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Comment on lines +84 to +89

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of duplicating the try/catch block and handleEuPoliticalError call in every API method (like createUserList and createDataJob), it is much cleaner and more maintainable to centralize this error handling inside the apiCall method itself. This avoids code duplication and ensures that all API calls (including addDataJobOperations, runJob, and any future methods) automatically benefit from the EU political advertising error interceptor.

      return this.apiCall(method, path, body)

}

async createDataJob(
Expand All @@ -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
}
Comment on lines +116 to +121

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Centralizing the error handling inside the apiCall method itself is recommended to avoid code duplication across multiple API methods.

      return this.apiCall(method, path, body)

}

async addDataJobOperations(offlineUserDataJobResourceName: string, userIdentifiers: any[]) {
Expand Down Expand Up @@ -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"
}
}
}
52 changes: 52 additions & 0 deletions src/actions/google/ads/test_customer_match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
6 changes: 6 additions & 0 deletions src/actions/google/common/error_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Comment on lines +31 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeErrors if err is null or undefined, use optional chaining (err?.name) to safely check the error name.

Suggested change
if (err.name === "EU Political Advertising Error") {
if (webhookId) {
err.message = err.message + ` (Webhook ID: ${webhookId})`
}
return
}
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
Expand Down
Loading