Skip to content

Commit

Permalink
IMN-736 - pt 2 - Explicitly handle API GW errors - attribute, catalog…
Browse files Browse the repository at this point in the history
… & client (#1017)
  • Loading branch information
ecamellini authored Oct 3, 2024
1 parent efbf9ac commit c8785c2
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 82 deletions.
7 changes: 5 additions & 2 deletions packages/api-gateway/src/api/catalogApiConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
attributeRegistryApi,
catalogApi,
} from "pagopa-interop-api-clients";
import { Logger } from "pagopa-interop-commons";
import {
assertNonDraftDescriptor,
assertRegistryAttributeExists,
Expand Down Expand Up @@ -94,9 +95,11 @@ export function toApiGatewayDescriptorDocument(
}

export function toApiGatewayDescriptorIfNotDraft(
descriptor: catalogApi.EServiceDescriptor
descriptor: catalogApi.EServiceDescriptor,
eserviceId: catalogApi.EService["id"],
logger: Logger
): apiGatewayApi.EServiceDescriptor {
assertNonDraftDescriptor(descriptor, descriptor.id);
assertNonDraftDescriptor(descriptor, descriptor.id, eserviceId, logger);

return {
id: descriptor.id,
Expand Down
2 changes: 1 addition & 1 deletion packages/api-gateway/src/clients/catchClientError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z } from "zod";
import { ApiError, genericError } from "pagopa-interop-models";
import { ErrorCodes } from "../models/errors.js";

const CatchableCodes = z.enum(["404", "403"]);
const CatchableCodes = z.enum(["403", "404", "409"]);
type CatchableCodes = z.infer<typeof CatchableCodes>;
const ClientError = z.object({
message: z.string(),
Expand Down
75 changes: 61 additions & 14 deletions packages/api-gateway/src/models/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
agreementApi,
attributeRegistryApi,
authorizationApi,
catalogApi,
purposeApi,
} from "pagopa-interop-api-clients";
Expand All @@ -13,11 +14,13 @@ export const errorCodes = {
missingActivePurposeVersion: "0003",
activeAgreementByEserviceAndConsumerNotFound: "0004",
multipleAgreementForEserviceAndConsumer: "0005",
missingAvailableDescriptor: "0006",
unexpectedDescriptorState: "0007",
eserviceNotFound: "0006",
attributeNotFound: "0007",
attributeNotFoundInRegistry: "0008",
eserviceDescriptorNotFound: "0009",
keyNotFound: "0010",
attributeAlreadyExists: "0011",
clientNotFound: "0012",
};

export type ErrorCodes = keyof typeof errorCodes;
Expand Down Expand Up @@ -68,24 +71,27 @@ export function multipleAgreementForEserviceAndConsumer(
}

export function missingAvailableDescriptor(
eserviceId: catalogApi.EService["id"]
eserviceId: catalogApi.EService["id"],
logger: Logger
): ApiError<ErrorCodes> {
return new ApiError({
detail: `No available descriptors for EService ${eserviceId}`,
code: "missingAvailableDescriptor",
title: "Missing available descriptor",
});
const error = eserviceNotFound(eserviceId);
logger.warn(
`Root cause for Error "${error.title}": no available descriptors for EService ${eserviceId}`
);
return error;
}

export function unexpectedDescriptorState(
state: catalogApi.EServiceDescriptorState,
descriptorId: catalogApi.EServiceDescriptor["id"]
eserviceId: catalogApi.EService["id"],
descriptorId: catalogApi.EServiceDescriptor["id"],
logger: Logger
): ApiError<ErrorCodes> {
return new ApiError({
detail: `Unexpected Descriptor state: ${state} - id: ${descriptorId}`,
code: "unexpectedDescriptorState",
title: "Unexpected descriptor state",
});
const error = eserviceDescriptorNotFound(eserviceId, descriptorId);
logger.warn(
`Root cause for Error "${error.title}": Unexpected Descriptor state: ${state} for Descriptor ${descriptorId}`
);
return error;
}

export function attributeNotFoundInRegistry(
Expand Down Expand Up @@ -137,3 +143,44 @@ export function invalidAgreementState(
);
return error;
}

export function attributeAlreadyExists(
name: attributeRegistryApi.Attribute["name"],
code: attributeRegistryApi.Attribute["code"]
): ApiError<ErrorCodes> {
return new ApiError({
detail: `Attribute ${name} with code ${code} already exists`,
code: "attributeAlreadyExists",
title: "Attribute already exists",
});
}

export function attributeNotFound(
attributeId: attributeRegistryApi.Attribute["id"]
): ApiError<ErrorCodes> {
return new ApiError({
detail: `Attribute ${attributeId} not found`,
code: "attributeNotFound",
title: "Attribute not found",
});
}

export function clientNotFound(
clientId: authorizationApi.Client["id"]
): ApiError<ErrorCodes> {
return new ApiError({
detail: `Client ${clientId} not found`,
code: "clientNotFound",
title: "Client not found",
});
}

export function eserviceNotFound(
eserviceId: catalogApi.EService["id"]
): ApiError<ErrorCodes> {
return new ApiError({
detail: `EService ${eserviceId} not found`,
code: "eserviceNotFound",
title: "EService not found",
});
}
20 changes: 17 additions & 3 deletions packages/api-gateway/src/routers/apiGatewayRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import { agreementServiceBuilder } from "../services/agreementService.js";
import { PagoPAInteropBeClients } from "../clients/clientsProvider.js";
import { makeApiProblem } from "../models/errors.js";
import {
createCertifiedAttributeErrorMapper,
emptyErrorMapper,
getAgreementByPurposeErrorMapper,
getAgreementErrorMapper,
getAgreementsErrorMapper,
getAttributeErrorMapper,
getClientErrorMapper,
getEserviceDescriptorErrorMapper,
getEserviceErrorMapper,
Expand Down Expand Up @@ -201,7 +203,11 @@ const apiGatewayRouter = (

return res.status(200).send(apiGatewayApi.Attribute.parse(attribute));
} catch (error) {
const errorRes = makeApiProblem(error, emptyErrorMapper, ctx.logger);
const errorRes = makeApiProblem(
error,
createCertifiedAttributeErrorMapper,
ctx.logger
);
return res.status(errorRes.status).send(errorRes);
}
}
Expand All @@ -220,7 +226,11 @@ const apiGatewayRouter = (

return res.status(200).send(apiGatewayApi.Attribute.parse(attribute));
} catch (error) {
const errorRes = makeApiProblem(error, emptyErrorMapper, ctx.logger);
const errorRes = makeApiProblem(
error,
getAttributeErrorMapper,
ctx.logger
);
return res.status(errorRes.status).send(errorRes);
}
}
Expand Down Expand Up @@ -298,7 +308,11 @@ const apiGatewayRouter = (
.status(200)
.send(apiGatewayApi.EServiceDescriptors.parse(descriptors));
} catch (error) {
const errorRes = makeApiProblem(error, emptyErrorMapper, ctx.logger);
const errorRes = makeApiProblem(
error,
getEserviceErrorMapper,
ctx.logger
);
return res.status(errorRes.status).send(errorRes);
}
}
Expand Down
46 changes: 30 additions & 16 deletions packages/api-gateway/src/services/attributeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { getAllFromPaginated, WithLogger } from "pagopa-interop-commons";
import { AttributeProcessClient } from "../clients/clientsProvider.js";
import { ApiGatewayAppContext } from "../utilities/context.js";
import { toApiGatewayAttribute } from "../api/attributeApiConverter.js";
import { clientStatusCodeToError } from "../clients/catchClientError.js";
import { attributeAlreadyExists, attributeNotFound } from "../models/errors.js";

export async function getAllBulkAttributes(
attributeProcessClient: AttributeProcessClient,
Expand Down Expand Up @@ -35,12 +37,18 @@ export function attributeServiceBuilder(
): Promise<apiGatewayApi.Attribute> => {
logger.info(`Retrieving attribute ${attributeId}`);

const attribute = await attributeProcessClient.getAttributeById({
headers,
params: {
attributeId,
},
});
const attribute = await attributeProcessClient
.getAttributeById({
headers,
params: {
attributeId,
},
})
.catch((res) => {
throw clientStatusCodeToError(res, {
404: attributeNotFound(attributeId),
});
});

return toApiGatewayAttribute(attribute);
},
Expand All @@ -52,16 +60,22 @@ export function attributeServiceBuilder(
`Creating certified attribute with code ${attributeSeed.code}`
);

const attribute = await attributeProcessClient.createCertifiedAttribute(
{
code: attributeSeed.code,
name: attributeSeed.name,
description: attributeSeed.description,
},
{
headers,
}
);
const attribute = await attributeProcessClient
.createCertifiedAttribute(
{
code: attributeSeed.code,
name: attributeSeed.name,
description: attributeSeed.description,
},
{
headers,
}
)
.catch((res) => {
throw clientStatusCodeToError(res, {
409: attributeAlreadyExists(attributeSeed.name, attributeSeed.code),
});
});

return toApiGatewayAttribute(attribute);
},
Expand Down
21 changes: 14 additions & 7 deletions packages/api-gateway/src/services/authorizationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
} from "../clients/clientsProvider.js";
import { ApiGatewayAppContext } from "../utilities/context.js";
import { toApiGatewayClient } from "../api/authorizationApiConverter.js";
import { keyNotFound } from "../models/errors.js";
import { clientNotFound, keyNotFound } from "../models/errors.js";
import { clientStatusCodeToError } from "../clients/catchClientError.js";
import { readModelServiceBuilder } from "./readModelService.js";

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
Expand All @@ -33,12 +34,18 @@ export function authorizationServiceBuilder(
): Promise<apiGatewayApi.Client> => {
logger.info(`Retrieving Client ${clientId}`);

const client = await authorizationProcessClient.client.getClient({
headers,
params: {
clientId,
},
});
const client = await authorizationProcessClient.client
.getClient({
headers,
params: {
clientId,
},
})
.catch((res) => {
throw clientStatusCodeToError(res, {
404: clientNotFound(clientId),
});
});

const isAllowed = await isAllowedToGetClient(
purposeProcessClient,
Expand Down
Loading

0 comments on commit c8785c2

Please sign in to comment.