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 { IdNotUser, ECivility, EOfficeStatus, EIdnotRole } from '../../types';
import { ExternalServiceError } from '../../types/errors';
import { Logger } from '../../utils/logger';
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) {
const {
IDNOT_CLIENT_ID,
@ -67,12 +99,10 @@ export class IdNotService {
const url = `${IDNOT_ANNUARY_BASE_URL}/api/pp/v2/personnes/${idNot}/rattachements?${searchParams}`;
const response = await fetch(url, { method: 'GET' });
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 response.json();
return await IdNotService.parseJsonOrThrow(response as any, 'getUserRattachements');
}
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 json = await (
await fetch(url, {
method: 'GET'
})
).json();
return json;
const response = await fetch(url, { method: 'GET' });
if (!response.ok) {
throw new ExternalServiceError('IdNot', `Failed to fetch office rattachements: ${response.status} ${response.statusText}`);
}
return await IdNotService.parseJsonOrThrow(response as any, 'getOfficeRattachements');
}
static async getUserData(profileIdn: string) {
@ -109,13 +137,11 @@ export class IdNotService {
key: IDNOT_API_KEY
});
const userData = await (
await fetch(`${IDNOT_API_BASE_URL}/api/pp/v2/rattachements/${profileIdn}?` + searchParams, {
method: 'GET'
})
).json();
return userData;
const response = await fetch(`${IDNOT_API_BASE_URL}/api/pp/v2/rattachements/${profileIdn}?` + searchParams, { method: 'GET' });
if (!response.ok) {
throw new ExternalServiceError('IdNot', `Failed to fetch user data: ${response.status} ${response.statusText}`);
}
return await IdNotService.parseJsonOrThrow(response as any, 'getUserData');
}
static async getOfficeLocationData(locationsUrl: string) {
@ -129,13 +155,11 @@ export class IdNotService {
key: IDNOT_API_KEY
});
const officeLocationData = await (
await fetch(`${IDNOT_API_BASE_URL}${locationsUrl}?` + searchParams, {
method: 'GET'
})
).json();
return officeLocationData;
const response = await fetch(`${IDNOT_API_BASE_URL}${locationsUrl}?` + searchParams, { method: 'GET' });
if (!response.ok) {
throw new ExternalServiceError('IdNot', `Failed to fetch office location data: ${response.status} ${response.statusText}`);
}
return await IdNotService.parseJsonOrThrow(response as any, 'getOfficeLocationData');
}
static getOfficeStatus(statusName: string): EOfficeStatus {