diff --git a/CreateMessage/__tests__/handler.test.ts b/CreateMessage/__tests__/handler.test.ts index 0263c835..9b53ffb8 100644 --- a/CreateMessage/__tests__/handler.test.ts +++ b/CreateMessage/__tests__/handler.test.ts @@ -179,7 +179,7 @@ describe("createMessageDocument", () => { expect(mockMessageModel.create).toHaveBeenCalledTimes(1); expect(response.isRight()).toBeTruthy(); - expect(response.getOrElse(undefined)).toMatchObject({ + expect(response.fold(l=>undefined,r=>r)).toMatchObject({ fiscalCode, id: messageId, indexedId: messageId, @@ -242,7 +242,7 @@ describe("forkOrchestrator", () => { serviceVersion: service.version }) ); - expect(response.getOrElse(undefined)).toEqual("orchestratorId"); + expect(response.fold(l=>undefined,r=>r)).toEqual("orchestratorId"); } ) ); diff --git a/CreateMessage/handler.ts b/CreateMessage/handler.ts index 8874de2f..8e231c1d 100644 --- a/CreateMessage/handler.ts +++ b/CreateMessage/handler.ts @@ -376,7 +376,7 @@ export function CreateMessageHandler( telemetryClient.trackEvent({ name: "api.messages.create", properties: { - error: isSuccess ? undefined : r.kind, + error: isSuccess ? "" : r.kind, hasDefaultEmail: Boolean( messagePayload.default_addresses && messagePayload.default_addresses.email diff --git a/CreateService/handler.ts b/CreateService/handler.ts index 0a8530e9..7c4ef0a6 100644 --- a/CreateService/handler.ts +++ b/CreateService/handler.ts @@ -50,11 +50,12 @@ import { } from "italia-ts-commons/lib/strings"; import { APIClient } from "../clients/admin"; import { Service } from "../generated/api-admin/Service"; +import { ServiceMetadata } from "../generated/api-admin/ServiceMetadata"; import { Subscription } from "../generated/api-admin/Subscription"; import { UserInfo } from "../generated/api-admin/UserInfo"; import { ServicePayload } from "../generated/definitions/ServicePayload"; import { ServiceWithSubscriptionKeys } from "../generated/definitions/ServiceWithSubscriptionKeys"; -import { withApiRequestWrapper } from "../utils/api"; +import { withEmbodimentApiRequestWrapper } from "../utils/api"; import { getLogger, ILogger } from "../utils/logging"; import { ErrorResponses, IResponseErrorUnauthorized } from "../utils/responses"; @@ -89,7 +90,7 @@ const createSubscriptionTask = ( subscriptionId: NonEmptyString, productName: NonEmptyString ): TaskEither => - withApiRequestWrapper( + withEmbodimentApiRequestWrapper( logger, () => apiClient.createSubscription({ @@ -107,7 +108,7 @@ const getUserTask = ( apiClient: APIClient, userEmail: EmailString ): TaskEither => - withApiRequestWrapper( + withEmbodimentApiRequestWrapper( logger, () => apiClient.getUser({ @@ -124,7 +125,7 @@ const createServiceTask = ( sandboxFiscalCode: FiscalCode, adb2cTokenName: NonEmptyString ): TaskEither => - withApiRequestWrapper( + withEmbodimentApiRequestWrapper( logger, () => apiClient.createService({ @@ -135,7 +136,7 @@ const createServiceTask = ( service_metadata: { ...servicePayload.service_metadata, token_name: adb2cTokenName - } + } as ServiceMetadata } }), 200 @@ -175,7 +176,7 @@ export function CreateServiceHandler( servicePayload, subscriptionId, (sandboxFiscalCode as unknown) as FiscalCode, - userInfo.token_name + (userInfo.token_name as unknown) as NonEmptyString ).map(service => { telemetryClient.trackEvent({ name: "api.services.create", diff --git a/CreatedMessageOrchestrator/utils.ts b/CreatedMessageOrchestrator/utils.ts index fd965a6c..83b2427d 100644 --- a/CreatedMessageOrchestrator/utils.ts +++ b/CreatedMessageOrchestrator/utils.ts @@ -40,7 +40,7 @@ export const trackMessageProcessing = ( properties: event.properties, tagOverrides: { samplingEnabled: "false" } }) - : null; + : void 0; export enum MessageProcessingEventNames { DECODE_INPUT = "api.messages.create.decodeinput", diff --git a/GetService/handler.ts b/GetService/handler.ts index 3e613d42..66569393 100644 --- a/GetService/handler.ts +++ b/GetService/handler.ts @@ -40,7 +40,7 @@ import { APIClient } from "../clients/admin"; import { Service } from "../generated/api-admin/Service"; import { SubscriptionKeys } from "../generated/api-admin/SubscriptionKeys"; import { ServiceWithSubscriptionKeys } from "../generated/definitions/ServiceWithSubscriptionKeys"; -import { withApiRequestWrapper } from "../utils/api"; +import { withEmbodimentApiRequestWrapper } from "../utils/api"; import { getLogger, ILogger } from "../utils/logging"; import { ErrorResponses } from "../utils/responses"; import { serviceOwnerCheckTask } from "../utils/subscription"; @@ -69,7 +69,7 @@ const getServiceTask = ( apiClient: APIClient, serviceId: string ): TaskEither => - withApiRequestWrapper( + withEmbodimentApiRequestWrapper( logger, () => apiClient.getService({ @@ -83,7 +83,7 @@ const getSubscriptionKeysTask = ( apiClient: APIClient, serviceId: string ): TaskEither => - withApiRequestWrapper( + withEmbodimentApiRequestWrapper( logger, () => apiClient.getSubscriptionKeys({ diff --git a/GetSubscriptionsFeed/utils.ts b/GetSubscriptionsFeed/utils.ts index 9ba27cfb..f909a1e7 100644 --- a/GetSubscriptionsFeed/utils.ts +++ b/GetSubscriptionsFeed/utils.ts @@ -19,7 +19,7 @@ type TableEntry = Readonly<{ * @see https://docs.microsoft.com/en-us/rest/api/storageservices/query-timeout-and-pagination */ export type PagedQuery = ( - currentToken: TableService.TableContinuationToken + currentToken: TableService.TableContinuationToken | undefined ) => Promise>>; /** @@ -32,7 +32,7 @@ export const getPagedQuery = (tableService: TableService, table: string) => ( tableService.queryEntities( table, tableQuery, - currentToken, + currentToken as TableService.TableContinuationToken, ( error: Error, result: TableService.QueryEntitiesResult, @@ -51,16 +51,16 @@ async function* iterateOnPages( pagedQuery: PagedQuery ): AsyncIterableIterator> { // tslint:disable-next-line: no-let - let token: TableService.TableContinuationToken = null; + let token: TableService.TableContinuationToken | undefined = undefined; do { // query for a page of entries - const errorOrResults = await pagedQuery(token); + const errorOrResults : Either> = await pagedQuery(token); if (isLeft(errorOrResults)) { // throw an exception in case of error throw errorOrResults.value; } // call the async callback with the current page of entries - const results = errorOrResults.value; + const results : TableService.QueryEntitiesResult = errorOrResults.value; yield results.entries; // update the continuation token, the loop will continue until // the token is defined diff --git a/GetUserServices/__tests__/handler.test.ts b/GetUserServices/__tests__/handler.test.ts index 93be2028..aa55e5da 100644 --- a/GetUserServices/__tests__/handler.test.ts +++ b/GetUserServices/__tests__/handler.test.ts @@ -108,7 +108,7 @@ describe("GetUserServicesHandler", () => { expect(result.kind).toBe("IResponseSuccessJson"); if (result.kind === "IResponseSuccessJson") { expect(result.value).toEqual({ - items: aUserInfo.subscriptions.map(it => it.id) + items: (aUserInfo.subscriptions as Subscription[]).map(it => it.id) }); } }); diff --git a/GetUserServices/handler.ts b/GetUserServices/handler.ts index 2bd856a3..28133aae 100644 --- a/GetUserServices/handler.ts +++ b/GetUserServices/handler.ts @@ -36,9 +36,10 @@ import { ContextMiddleware } from "@pagopa/io-functions-commons/dist/src/utils/m import { identity } from "fp-ts/lib/function"; import { TaskEither } from "fp-ts/lib/TaskEither"; import { APIClient } from "../clients/admin"; +import { Subscription } from "../generated/api-admin/Subscription"; import { UserInfo } from "../generated/api-admin/UserInfo"; import { ServiceIdCollection } from "../generated/definitions/ServiceIdCollection"; -import { withApiRequestWrapper } from "../utils/api"; +import { withEmbodimentApiRequestWrapper } from "../utils/api"; import { getLogger, ILogger } from "../utils/logging"; import { ErrorResponses } from "../utils/responses"; @@ -61,7 +62,7 @@ const getUserTask = ( apiClient: APIClient, userEmail: EmailString ): TaskEither => - withApiRequestWrapper( + withEmbodimentApiRequestWrapper( logger, () => apiClient.getUser({ @@ -84,8 +85,8 @@ export function GetUserServicesHandler( ) .map(userInfo => ResponseSuccessJson({ - items: userInfo.subscriptions.map(it => - ServiceId.encode(it.id as NonEmptyString) + items: (userInfo.subscriptions as readonly Subscription[]).map(it => + ServiceId.encode((it.id as unknown) as NonEmptyString) ) }) ) diff --git a/RegenerateServiceKey/handler.ts b/RegenerateServiceKey/handler.ts index 7e30933f..943a8659 100644 --- a/RegenerateServiceKey/handler.ts +++ b/RegenerateServiceKey/handler.ts @@ -44,7 +44,7 @@ import { TaskEither } from "fp-ts/lib/TaskEither"; import { APIClient } from "../clients/admin"; import { SubscriptionKeys } from "../generated/definitions/SubscriptionKeys"; import { SubscriptionKeyTypePayload } from "../generated/definitions/SubscriptionKeyTypePayload"; -import { withApiRequestWrapper } from "../utils/api"; +import { withEmbodimentApiRequestWrapper } from "../utils/api"; import { getLogger, ILogger } from "../utils/logging"; import { ErrorResponses, IResponseErrorUnauthorized } from "../utils/responses"; import { serviceOwnerCheckTask } from "../utils/subscription"; @@ -80,7 +80,7 @@ const regenerateServiceKeyTask = ( serviceId: NonEmptyString, subscriptionKeyTypePayload: SubscriptionKeyTypePayload ): TaskEither> => - withApiRequestWrapper( + withEmbodimentApiRequestWrapper( logger, () => apiClient.RegenerateSubscriptionKeys({ diff --git a/UpdateService/handler.ts b/UpdateService/handler.ts index f8921441..dd6e7ad2 100644 --- a/UpdateService/handler.ts +++ b/UpdateService/handler.ts @@ -47,11 +47,13 @@ import { SubscriptionKeys } from "../generated/api-admin/SubscriptionKeys"; import { UserInfo } from "../generated/api-admin/UserInfo"; import { ServicePayload } from "../generated/definitions/ServicePayload"; import { ServiceWithSubscriptionKeys } from "../generated/definitions/ServiceWithSubscriptionKeys"; -import { withApiRequestWrapper } from "../utils/api"; +import { withEmbodimentApiRequestWrapper } from "../utils/api"; import { getLogger, ILogger } from "../utils/logging"; import { ErrorResponses, IResponseErrorUnauthorized } from "../utils/responses"; import { serviceOwnerCheckTask } from "../utils/subscription"; +import { ServiceMetadata } from "../generated/api-admin/ServiceMetadata"; + type ResponseTypes = | IResponseSuccessJson | IResponseErrorUnauthorized @@ -82,7 +84,7 @@ const getSubscriptionKeysTask = ( apiClient: APIClient, serviceId: NonEmptyString ): TaskEither => - withApiRequestWrapper( + withEmbodimentApiRequestWrapper( logger, () => apiClient.getSubscriptionKeys({ @@ -96,7 +98,7 @@ const getServiceTask = ( apiClient: APIClient, serviceId: NonEmptyString ): TaskEither => - withApiRequestWrapper( + withEmbodimentApiRequestWrapper( logger, () => apiClient.getService({ @@ -110,7 +112,7 @@ const getUserTask = ( apiClient: APIClient, userEmail: EmailString ): TaskEither => - withApiRequestWrapper( + withEmbodimentApiRequestWrapper( logger, () => apiClient.getUser({ @@ -127,7 +129,7 @@ const updateServiceTask = ( retrievedService: Service, adb2cTokenName: NonEmptyString ): TaskEither => - withApiRequestWrapper( + withEmbodimentApiRequestWrapper( logger, () => apiClient.updateService({ @@ -138,7 +140,7 @@ const updateServiceTask = ( service_metadata: { ...servicePayload.service_metadata, token_name: adb2cTokenName - } + } as ServiceMetadata }, service_id: serviceId }), @@ -172,7 +174,7 @@ export function UpdateServiceHandler( servicePayload, serviceId, retrievedService, - userInfo.token_name + (userInfo.token_name as unknown) as NonEmptyString ) ) .chain(service => { diff --git a/UploadOrganizationLogo/handler.ts b/UploadOrganizationLogo/handler.ts index 9e59cddd..64d34010 100644 --- a/UploadOrganizationLogo/handler.ts +++ b/UploadOrganizationLogo/handler.ts @@ -42,7 +42,7 @@ import { identity } from "fp-ts/lib/function"; import { TaskEither } from "fp-ts/lib/TaskEither"; import { APIClient } from "../clients/admin"; import { Logo } from "../generated/api-admin/Logo"; -import { withApiRequestWrapper } from "../utils/api"; +import { withEmptyApiRequestWrapper } from "../utils/api"; import { getLogger, ILogger } from "../utils/logging"; import { ErrorResponses, @@ -80,7 +80,7 @@ const uploadOrganizationLogoTask = ( organizationFiscalCode: OrganizationFiscalCode, logo: Logo ): TaskEither => - withApiRequestWrapper( + withEmptyApiRequestWrapper( logger, () => apiClient.uploadOrganizationLogo({ diff --git a/UploadServiceLogo/handler.ts b/UploadServiceLogo/handler.ts index ad9aed76..f40e8aad 100644 --- a/UploadServiceLogo/handler.ts +++ b/UploadServiceLogo/handler.ts @@ -43,7 +43,7 @@ import { identity } from "fp-ts/lib/function"; import { TaskEither } from "fp-ts/lib/TaskEither"; import { APIClient } from "../clients/admin"; import { Logo } from "../generated/api-admin/Logo"; -import { withApiRequestWrapper } from "../utils/api"; +import { withEmptyApiRequestWrapper } from "../utils/api"; import { getLogger, ILogger } from "../utils/logging"; import { ErrorResponses, IResponseErrorUnauthorized } from "../utils/responses"; import { serviceOwnerCheckTask } from "../utils/subscription"; @@ -79,7 +79,7 @@ const uploadServiceLogoTask = ( serviceId: string, logo: Logo ): TaskEither> => - withApiRequestWrapper( + withEmptyApiRequestWrapper( logger, () => apiClient.uploadServiceLogo({ @@ -87,7 +87,7 @@ const uploadServiceLogoTask = ( service_id: serviceId }), 201 - ).map(_ => ResponseSuccessJson(undefined)); + ).map(ResponseSuccessJson); /** * Handles requests for upload a service logo by a service ID and a base64 logo' s string. diff --git a/package.json b/package.json index 00e55660..f7996c03 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@types/express": "^4.16.0", "@types/html-to-text": "^1.4.31", "@types/jest": "^24.0.15", + "@types/node-fetch": "^2.5.8", "@types/nodemailer": "^4.6.2", "auto-changelog": "^2.2.1", "danger": "^4.0.2", diff --git a/tsconfig.json b/tsconfig.json index 75296d95..5b7ece23 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "outDir": "dist", "rootDir": ".", "sourceMap": true, - "strict": false, + "strict": true, "resolveJsonModule": true }, "exclude": [ "Dangerfile.ts", diff --git a/utils/__tests__/arbitraries.ts b/utils/__tests__/arbitraries.ts index 63dad142..1c16e36d 100644 --- a/utils/__tests__/arbitraries.ts +++ b/utils/__tests__/arbitraries.ts @@ -10,15 +10,17 @@ import * as fc from "fast-check"; import { some } from "fp-ts/lib/Option"; import { NonNegativeInteger, - WithinRangeInteger + WithinRangeInteger, + IWithinRangeIntegerTag } from "italia-ts-commons/lib/numbers"; import { EmailString, FiscalCode, NonEmptyString, - PatternString + PatternString, } from "italia-ts-commons/lib/strings"; + // // custom fastcheck arbitraries // @@ -82,9 +84,9 @@ export const newMessageArb = fc markdown, subject } - }).getOrElse(undefined) + }).fold(l=>undefined, r=>r) ) - .filter(_ => _ !== undefined); + .filter(_ => _ !== undefined) as fc.Arbitrary; export const newMessageWithDefaultEmailArb = fc .tuple(newMessageArb, fc.emailAddress()) @@ -100,14 +102,14 @@ export const messageTimeToLiveArb = fc // tslint:disable-next-line:no-useless-cast .map(_ => _ as number & WithinRangeInteger<3600, 604800>); -export const amountArb = fc +export const amountArb = fc .integer(1, 9999999999) .map(_ => WithinRangeInteger(1, 9999999999) .decode(_) - .getOrElse(undefined) + .fold(l=>undefined, r=>r) ) - .filter(_ => _ !== undefined); + .filter(_ => _ != undefined) as fc.Arbitrary>; export const maxAmountArb = fc .integer(0, 9999999999) diff --git a/utils/api.ts b/utils/api.ts index c00ac253..6284c8b8 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -1,4 +1,5 @@ import { + fromEither, fromLeft, TaskEither, taskEither, @@ -13,10 +14,19 @@ import { toErrorServerResponse } from "./responses"; -export const withApiRequestWrapper = ( +/** + * Wrap the input API call into a TaskEither, returning a response type T (if both the response status code match the input successStatusCode and the response decode successfully) or an ErrorResponses. + * TYPE HAZARD: this function support only a single successful response type T: if the api response body value is not undefined will be decoded in a T object, otherwise returns an ErrorResponses. + * @see withEmptyApiRequestWrapper + * @param logger - the Logger instance used to log errors + * @param apiCallWithParams - the API call as a promise + * @param successStatusCode - the successful status code used to accept the response as valid and decode it into an object T + * @returns a TaskEither wrapping the API call + */ +export const withEmbodimentApiRequestWrapper = ( logger: ILogger, apiCallWithParams: () => Promise< - t.Validation> + t.Validation> >, successStatusCode: 200 | 201 | 202 = 200 ): TaskEither => @@ -26,17 +36,63 @@ export const withApiRequestWrapper = ( logger.logUnknown(errs); return toDefaultResponseErrorInternal(errs); } - ).foldTaskEither( - err => fromLeft(err), - errorOrResponse => - errorOrResponse.fold( - errs => { - logger.logErrors(errs); - return fromLeft(toDefaultResponseErrorInternal(errs)); - }, - responseType => - responseType.status !== successStatusCode - ? fromLeft(toErrorServerResponse(responseType)) - : taskEither.of(responseType.value) - ) - ); + ) + .map(fromEither) + .foldTaskEither( + apiCallError => fromLeft(apiCallError), + apiCallResponse => + apiCallResponse.foldTaskEither( + parseResponseError => { + logger.logErrors(parseResponseError); + return fromLeft( + toDefaultResponseErrorInternal(parseResponseError) + ); + }, + response => + response.status === successStatusCode && + response.value !== undefined + ? taskEither.of(response.value) + : fromLeft(toErrorServerResponse(response)) + ) + ); + +/** + * Wrap the input API call into a TaskEither, returning an empty response (if the repsonse status code match the input successStatusCode) or an ErrorResponses. + * @param logger - the Logger instance used to log errors + * @param apiCallWithParams - the API call as a promise + * @param successStatusCode - the successful status code used to accept the response as valid and decode it into an object T + * @returns a TaskEither wrapping the API call + */ +export const withEmptyApiRequestWrapper = ( + logger: ILogger, + apiCallWithParams: () => Promise< + t.Validation> + >, + successStatusCode: 200 | 201 | 202 = 200 +): TaskEither => + tryCatch( + () => apiCallWithParams(), + errs => { + logger.logUnknown(errs); + return toDefaultResponseErrorInternal(errs); + } + ) + .map(fromEither) + .foldTaskEither( + apiCallError => fromLeft(apiCallError), + apiCallResponse => + apiCallResponse.foldTaskEither( + parseResponseError => { + logger.logErrors(parseResponseError); + return fromLeft( + toDefaultResponseErrorInternal(parseResponseError) + ); + }, + response => + response.status === successStatusCode + ? taskEither.of(undefined) + : fromLeft( + toErrorServerResponse(response) + ) + ) + ); diff --git a/yarn.lock b/yarn.lock index fa3e3d71..c5d64640 100644 --- a/yarn.lock +++ b/yarn.lock @@ -645,6 +645,14 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== +"@types/node-fetch@^2.5.8": + version "2.5.8" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb" + integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + "@types/node@*": version "13.13.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.0.tgz#30d2d09f623fe32cde9cb582c7a6eda2788ce4a8" @@ -1465,7 +1473,7 @@ colorspace@1.1.x: color "3.0.x" text-hex "1.0.x" -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -2458,6 +2466,15 @@ form-data@^2.5.0: combined-stream "^1.0.6" mime-types "^2.1.12" +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"