feat(idnot): robust JSON handling with clear ExternalServiceError logs [skip ci]

This commit is contained in:
NicolasCantu 2025-09-24 22:18:06 +02:00
parent 125e9ac923
commit fd093aec65

View File

@ -1,7 +1,39 @@
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import { IdNotUser, ECivility, EOfficeStatus, EIdnotRole } from '../../types'; import { IdNotUser, ECivility, EOfficeStatus, EIdnotRole } from '../../types';
import { ExternalServiceError } from '../../types/errors';
import { Logger } from '../../utils/logger';
export class IdNotService { export class IdNotService {
private static async parseJsonOrThrow(response: Response, context: string): Promise<any> {
const contentType = response.headers.get('content-type') || '';
try {
if (contentType.includes('application/json')) {
return await (response as any).json();
}
const text = await (response as any).text();
Logger.error('IdNot non-JSON response', {
context,
status: response.status,
statusText: response.statusText,
contentType,
bodySnippet: text?.slice(0, 200)
});
throw new ExternalServiceError('IdNot', `Non-JSON response (${response.status} ${response.statusText}): ${text?.slice(0, 120) || 'no body'}`);
} catch (err) {
if (err instanceof ExternalServiceError) throw err;
// json() may throw on invalid JSON
const text = await (response as any).text().catch(() => undefined);
Logger.error('IdNot JSON parse failed', {
context,
status: response.status,
statusText: response.statusText,
contentType,
parseError: err instanceof Error ? err.message : String(err),
bodySnippet: text?.slice(0, 200)
});
throw new ExternalServiceError('IdNot', `Invalid JSON response (${response.status} ${response.statusText}): ${text?.slice(0, 120) || 'no body'}`);
}
}
static async exchangeCodeForTokens(code: string) { static async exchangeCodeForTokens(code: string) {
const { const {
IDNOT_CLIENT_ID, IDNOT_CLIENT_ID,
@ -67,12 +99,10 @@ export class IdNotService {
const url = `${IDNOT_ANNUARY_BASE_URL}/api/pp/v2/personnes/${idNot}/rattachements?${searchParams}`; const url = `${IDNOT_ANNUARY_BASE_URL}/api/pp/v2/personnes/${idNot}/rattachements?${searchParams}`;
const response = await fetch(url, { method: 'GET' }); const response = await fetch(url, { method: 'GET' });
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to fetch rattachements: ${response.status} ${response.statusText}`); throw new ExternalServiceError('IdNot', `Failed to fetch rattachements: ${response.status} ${response.statusText}`);
} }
return await IdNotService.parseJsonOrThrow(response as any, 'getUserRattachements');
return response.json();
} }
static async getOfficeRattachements(idNot: string) { static async getOfficeRattachements(idNot: string) {
@ -89,13 +119,11 @@ export class IdNotService {
const url = `${IDNOT_ANNUARY_BASE_URL}/api/pp/v2/entites/${idNot}/personnes?` + searchParams; const url = `${IDNOT_ANNUARY_BASE_URL}/api/pp/v2/entites/${idNot}/personnes?` + searchParams;
const json = await ( const response = await fetch(url, { method: 'GET' });
await fetch(url, { if (!response.ok) {
method: 'GET' throw new ExternalServiceError('IdNot', `Failed to fetch office rattachements: ${response.status} ${response.statusText}`);
}) }
).json(); return await IdNotService.parseJsonOrThrow(response as any, 'getOfficeRattachements');
return json;
} }
static async getUserData(profileIdn: string) { static async getUserData(profileIdn: string) {
@ -109,13 +137,11 @@ export class IdNotService {
key: IDNOT_API_KEY key: IDNOT_API_KEY
}); });
const userData = await ( const response = await fetch(`${IDNOT_API_BASE_URL}/api/pp/v2/rattachements/${profileIdn}?` + searchParams, { method: 'GET' });
await fetch(`${IDNOT_API_BASE_URL}/api/pp/v2/rattachements/${profileIdn}?` + searchParams, { if (!response.ok) {
method: 'GET' throw new ExternalServiceError('IdNot', `Failed to fetch user data: ${response.status} ${response.statusText}`);
}) }
).json(); return await IdNotService.parseJsonOrThrow(response as any, 'getUserData');
return userData;
} }
static async getOfficeLocationData(locationsUrl: string) { static async getOfficeLocationData(locationsUrl: string) {
@ -129,13 +155,11 @@ export class IdNotService {
key: IDNOT_API_KEY key: IDNOT_API_KEY
}); });
const officeLocationData = await ( const response = await fetch(`${IDNOT_API_BASE_URL}${locationsUrl}?` + searchParams, { method: 'GET' });
await fetch(`${IDNOT_API_BASE_URL}${locationsUrl}?` + searchParams, { if (!response.ok) {
method: 'GET' throw new ExternalServiceError('IdNot', `Failed to fetch office location data: ${response.status} ${response.statusText}`);
}) }
).json(); return await IdNotService.parseJsonOrThrow(response as any, 'getOfficeLocationData');
return officeLocationData;
} }
static getOfficeStatus(statusName: string): EOfficeStatus { static getOfficeStatus(statusName: string): EOfficeStatus {