From d9ee7b7dd264a210520a33f725af11a6437d6434 Mon Sep 17 00:00:00 2001 From: maany Date: Fri, 14 Jul 2023 15:25:10 +0200 Subject: [PATCH] api: add list-did feature with multicall post processing pipeline #226 --- src/lib/core/dto/did-dto.ts | 6 + src/lib/core/port/primary/list-dids-ports.ts | 3 +- src/lib/core/use-case/list-dids-usecase.ts | 5 +- src/lib/core/use-case/list-dids-usecase2.ts | 77 ------------- .../use-case/list-dids/list-dids-usecase2.ts | 105 ++++++++++++++++++ .../list-dids/pipeline-element-get-did.ts | 91 +++++++++++++++ .../ioc/features/list-dids-feature.ts | 3 +- .../presenter/list-dids-presenter.ts | 2 +- src/lib/sdk/gateway-endpoints.ts | 2 +- src/lib/sdk/ioc-helpers.ts | 14 +-- src/lib/sdk/primary-ports.ts | 3 +- src/lib/sdk/usecase.ts | 5 +- 12 files changed, 223 insertions(+), 93 deletions(-) delete mode 100644 src/lib/core/use-case/list-dids-usecase2.ts create mode 100644 src/lib/core/use-case/list-dids/list-dids-usecase2.ts create mode 100644 src/lib/core/use-case/list-dids/pipeline-element-get-did.ts diff --git a/src/lib/core/dto/did-dto.ts b/src/lib/core/dto/did-dto.ts index c9fc23a69..d7b6d7e1f 100644 --- a/src/lib/core/dto/did-dto.ts +++ b/src/lib/core/dto/did-dto.ts @@ -12,6 +12,11 @@ export interface ListDIDDTO extends BaseStreamableDTO { | 'Unknown Error' } +/** + * Represents the individual data items in the stream + */ +export type ListDIDsStreamData = string + /** * Data Transfer Object for GetDIDEndpoint */ @@ -24,6 +29,7 @@ export interface DIDDTO extends DID { | 'Scope Not Found' | 'Unknown Error' | null + message?: string account: string open: boolean monotonic: boolean diff --git a/src/lib/core/port/primary/list-dids-ports.ts b/src/lib/core/port/primary/list-dids-ports.ts index 691a02ad4..b3ecdb284 100644 --- a/src/lib/core/port/primary/list-dids-ports.ts +++ b/src/lib/core/port/primary/list-dids-ports.ts @@ -2,6 +2,7 @@ import { BaseAuthenticatedInputPort } from "@/lib/sdk/primary-ports"; import { ListDIDsRequest } from "@/lib/core/usecase-models/list-dids-usecase-models"; import { BaseStreamingOutputPort } from "@/lib/sdk/primary-ports"; import { ListDIDsError, ListDIDsResponse } from "@/lib/core/usecase-models/list-dids-usecase-models"; +import { ListDIDsViewModel } from "@/lib/infrastructure/data/view-model/list-did"; /** * @interface ListDIDsInputPort to fetch a list of DIDs from the backend. @@ -11,4 +12,4 @@ export interface ListDIDsInputPort extends BaseAuthenticatedInputPort {} +export interface ListDIDsOutputPort extends BaseStreamingOutputPort {} diff --git a/src/lib/core/use-case/list-dids-usecase.ts b/src/lib/core/use-case/list-dids-usecase.ts index 7ec5d993c..157e16f3e 100644 --- a/src/lib/core/use-case/list-dids-usecase.ts +++ b/src/lib/core/use-case/list-dids-usecase.ts @@ -1,15 +1,16 @@ import { injectable } from "inversify"; import type { ListDIDsInputPort, ListDIDsOutputPort } from "@/lib/core/port/primary/list-dids-ports"; import type DIDGatewayOutputPort from "@/lib/core/port/secondary/did-gateway-output-port"; -import { ListDIDDTO } from "../dto/did-dto"; +import { ListDIDDTO, ListDIDsStreamData } from "../dto/did-dto"; import { ListDIDsError, ListDIDsRequest, ListDIDsResponse } from "../usecase-models/list-dids-usecase-models"; import { parseDIDString } from "@/lib/common/did-utils"; import { DID } from "../entity/rucio"; import { BaseStreamingUseCase } from "@/lib/sdk/usecase"; import { AuthenticatedRequestModel } from "@/lib/sdk/usecase-models"; +import { ListDIDsViewModel } from "@/lib/infrastructure/data/view-model/list-did"; @injectable() -class ListDIDsUseCase extends BaseStreamingUseCase implements ListDIDsInputPort { +class ListDIDsUseCase extends BaseStreamingUseCase implements ListDIDsInputPort { constructor( protected presenter: ListDIDsOutputPort, private didGateway: DIDGatewayOutputPort, diff --git a/src/lib/core/use-case/list-dids-usecase2.ts b/src/lib/core/use-case/list-dids-usecase2.ts deleted file mode 100644 index e90ad3770..000000000 --- a/src/lib/core/use-case/list-dids-usecase2.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { injectable } from "inversify"; -import type { ListDIDsInputPort, ListDIDsOutputPort } from "@/lib/core/port/primary/list-dids-ports"; -import type DIDGatewayOutputPort from "@/lib/core/port/secondary/did-gateway-output-port"; -import { ListDIDDTO } from "../dto/did-dto"; -import { ListDIDsError, ListDIDsRequest, ListDIDsResponse } from "../usecase-models/list-dids-usecase-models"; -import { parseDIDString } from "@/lib/common/did-utils"; -import { DID } from "../entity/rucio"; -import { BaseMultiCallStreamableUseCase, BaseStreamingUseCase } from "@/lib/sdk/usecase"; -import { AuthenticatedRequestModel } from "@/lib/sdk/usecase-models"; - -@injectable() -class ListDIDsUseCase2 extends BaseMultiCallStreamableUseCase implements ListDIDsInputPort { - validateFinalResponseModel(responseModel: ListDIDsResponse): { isValid: boolean; errorModel?: ListDIDsError | undefined; } { - throw new Error("Method not implemented."); - } - constructor( - protected presenter: ListDIDsOutputPort, - private didGateway: DIDGatewayOutputPort, - ) { - super(presenter) - this.didGateway = didGateway; - } - - validateRequestModel(requestModel: AuthenticatedRequestModel): ListDIDsError | undefined { - let scope: string; - let name: string; - try{ - let didComponents = parseDIDString(requestModel.query); - scope = didComponents.scope; - name = didComponents.name; - } catch (error: any) { - return { - status: 'error', - error: 'Invalid DID Query', - message: (error as Error).message, - } as ListDIDsError - } - } - - // async makeGatewayRequest(requestModel: AuthenticatedRequestModel): Promise { - // const { scope, name } = parseDIDString(requestModel.query); - // const listDIDDTO: ListDIDDTO = await this.didGateway.listDIDs(requestModel.rucioAuthToken, scope, name, requestModel.type); - // return listDIDDTO; - // } - - // handleGatewayError(error: ListDIDDTO): ListDIDsError { - // let errorType = 'Unknown Error' - // if(error.error === 'Invalid Auth Token') { - // errorType = 'Invalid Request' - // } - // else if(error.error !== 'Unknown Error') { - // errorType = 'Invalid DID Query' - // } - - // return { - // error: errorType, - // message: `${error.error}: ${error.message}`, - // } as ListDIDsError - // } - - // processStreamedData(dto: DID): { data: ListDIDsResponse | ListDIDsError; status: "success" | "error"; } { - // const responseModel: ListDIDsResponse = { - // status: 'success', - // name: dto.name, - // scope: dto.scope, - // did_type: dto.did_type, - // length: 0, - // bytes: 0, - // } - // return { - // data: responseModel, - // status: 'success', - // } - // } -} - -export default ListDIDsUseCase2; \ No newline at end of file diff --git a/src/lib/core/use-case/list-dids/list-dids-usecase2.ts b/src/lib/core/use-case/list-dids/list-dids-usecase2.ts new file mode 100644 index 000000000..a1ea389a5 --- /dev/null +++ b/src/lib/core/use-case/list-dids/list-dids-usecase2.ts @@ -0,0 +1,105 @@ +import { injectable } from "inversify"; +import type { ListDIDsInputPort, ListDIDsOutputPort } from "@/lib/core/port/primary/list-dids-ports"; +import type DIDGatewayOutputPort from "@/lib/core/port/secondary/did-gateway-output-port"; +import { DIDDTO, ListDIDDTO, ListDIDsStreamData } from "../../dto/did-dto"; +import { ListDIDsError, ListDIDsRequest, ListDIDsResponse } from "../../usecase-models/list-dids-usecase-models"; +import { parseDIDString } from "@/lib/common/did-utils"; +import { BaseMultiCallStreamableUseCase, BaseStreamingUseCase } from "@/lib/sdk/usecase"; +import { AuthenticatedRequestModel } from "@/lib/sdk/usecase-models"; +import { ListDIDsViewModel } from "@/lib/infrastructure/data/view-model/list-did"; +import GetDIDsPipelineElement from "./pipeline-element-get-did"; + +@injectable() +class ListDIDsUseCase extends BaseMultiCallStreamableUseCase implements ListDIDsInputPort { + + constructor( + protected presenter: ListDIDsOutputPort, + private didGateway: DIDGatewayOutputPort, + ) { + const getDIDPipelineElement = new GetDIDsPipelineElement(didGateway); + super(presenter, [getDIDPipelineElement]) + this.didGateway = didGateway; + } + + validateRequestModel(requestModel: AuthenticatedRequestModel): ListDIDsError | undefined { + let scope: string; + let name: string; + try{ + let didComponents = parseDIDString(requestModel.query); + scope = didComponents.scope; + name = didComponents.name; + } catch (error: any) { + return { + status: 'error', + error: 'Invalid DID Query', + message: (error as Error).message, + } as ListDIDsError + } + } + + async makeGatewayRequest(requestModel: AuthenticatedRequestModel): Promise { + const { scope, name } = parseDIDString(requestModel.query); + const listDIDDTO: ListDIDDTO = await this.didGateway.listDIDs(requestModel.rucioAuthToken, scope, name, requestModel.type); + return listDIDDTO; + } + + handleGatewayError(error: ListDIDDTO): ListDIDsError { + let errorType = 'Unknown Error' + if(error.error === 'Invalid Auth Token') { + errorType = 'Invalid Request' + } + else if(error.error !== 'Unknown Error') { + errorType = 'Invalid DID Query' + } + + return { + error: errorType, + message: `${error.error}: ${error.message}`, + } as ListDIDsError + } + + streamDataToStreamDTO(streamedChunk: ListDIDsStreamData, requestModel: ListDIDsRequest): DIDDTO { + const { scope, name } = parseDIDString(streamedChunk); + return { + name: name, + scope: scope, + did_type: requestModel.type, + } as DIDDTO + } + + processStreamedData(dto: DIDDTO): { data: ListDIDsResponse | ListDIDsError; status: "success" | "error"; } { + const responseModel: ListDIDsResponse = { + status: 'success', + name: dto.name, + scope: dto.scope, + did_type: dto.did_type, + length: 0, + bytes: 0, + } + return { + data: responseModel, + status: 'success', + } + } + + handleStreamError(error: ListDIDsError): void { + this.emit('error', error) + } + + validateFinalResponseModel(responseModel: ListDIDsResponse): { isValid: boolean; errorModel?: ListDIDsError | undefined; } { + return { + isValid: true, + } + } + + + + + + + + + +} + +export default ListDIDsUseCase; \ No newline at end of file diff --git a/src/lib/core/use-case/list-dids/pipeline-element-get-did.ts b/src/lib/core/use-case/list-dids/pipeline-element-get-did.ts new file mode 100644 index 000000000..50af6ea90 --- /dev/null +++ b/src/lib/core/use-case/list-dids/pipeline-element-get-did.ts @@ -0,0 +1,91 @@ +import { parseDIDString } from "@/lib/common/did-utils"; +import { BaseStreamingPostProcessingPipelineElement } from "@/lib/sdk/postprocessing-pipeline-elements"; +import { AuthenticatedRequestModel } from "@/lib/sdk/usecase-models"; +import { inject } from "inversify"; +import { DIDDTO } from "../../dto/did-dto"; +import DIDGatewayOutputPort from "../../port/secondary/did-gateway-output-port"; +import { ListDIDsError, ListDIDsRequest, ListDIDsResponse } from "../../usecase-models/list-dids-usecase-models"; + +export default class GetDIDsPipelineElement extends BaseStreamingPostProcessingPipelineElement{ + constructor(private didGateway: DIDGatewayOutputPort) { + super(); + } + async makeGatewayRequest(requestModel: AuthenticatedRequestModel, responseModel: ListDIDsResponse): Promise { + let scope: string = ''; + let name: string = ''; + try { + const parsedDID = parseDIDString(requestModel.query); + scope = parsedDID.scope; + name = parsedDID.name; + const dto: DIDDTO = await this.didGateway.getDID(requestModel.rucioAuthToken, scope, name); + return dto; + } catch (error: any) { + const errorDTO: DIDDTO = { + status: 'error', + error: 'Invalid Parameters', + message: (error as Error).message, + name: requestModel.query, + scope: requestModel.query, + did_type: requestModel.type, + account: '', + open: false, + monotonic: false, + expired_at: '', + bytes: 0, + length: 0 + } + return errorDTO; + } + } + + handleGatewayError(dto: DIDDTO): ListDIDsError { + let error: 'Unknown Error' | 'Invalid DID Query' | 'Invalid Request' = 'Unknown Error'; + switch(dto.error) { + case 'Invalid Auth Token': + error = 'Invalid Request'; + break; + case 'Data Identifier Not Found': + error = 'Invalid DID Query'; + break; + case 'Invalid Parameters': + error = 'Invalid Request'; + break; + case 'Scope Not Found': + error = 'Invalid DID Query'; + break; + case 'Unknown Error': + error = 'Unknown Error'; + break; + default: + error = 'Unknown Error'; + break; + } + + const errorModel: ListDIDsError = { + status: 'error', + name: dto.name, + error: error, + message: dto.error + ': ' + dto.message + ' for DID ' + dto.scope + ':' + dto.name, + } + return errorModel; + } + + validateDTO(dto: DIDDTO): { status: "success" | "error" | "critical"; data: ListDIDsError | DIDDTO; } { + if(dto.expired_at === '') { + dto.expired_at = 'Never'; + } + + return { + status: 'success', + data: dto + } + } + + + transformResponseModel(responseModel: ListDIDsResponse, dto: DIDDTO): ListDIDsResponse { + responseModel.bytes = dto.bytes; + responseModel.length = dto.length; + return responseModel; + } + +} \ No newline at end of file diff --git a/src/lib/infrastructure/ioc/features/list-dids-feature.ts b/src/lib/infrastructure/ioc/features/list-dids-feature.ts index e95f7fb3f..c0ff5030f 100644 --- a/src/lib/infrastructure/ioc/features/list-dids-feature.ts +++ b/src/lib/infrastructure/ioc/features/list-dids-feature.ts @@ -17,7 +17,8 @@ import INPUT_PORT from '@/lib/infrastructure/ioc/ioc-symbols-input-port' import USECASE_FACTORY from '@/lib/infrastructure/ioc/ioc-symbols-usecase-factory' import { Container } from 'inversify' import ListDIDsController from '@/lib/infrastructure/controller/list-dids-controller' -import ListDIDsUseCase from '@/lib/core/use-case/list-dids-usecase' +// import ListDIDsUseCase from '@/lib/core/use-case/list-dids-usecase' +import ListDIDsUseCase from '@/lib/core/use-case/list-dids/list-dids-usecase2' import ListDIDsPresenter from '../../presenter/list-dids-presenter' export default class ListDidsFeature extends BaseStreamableFeature< diff --git a/src/lib/infrastructure/presenter/list-dids-presenter.ts b/src/lib/infrastructure/presenter/list-dids-presenter.ts index 27b731555..06f997691 100644 --- a/src/lib/infrastructure/presenter/list-dids-presenter.ts +++ b/src/lib/infrastructure/presenter/list-dids-presenter.ts @@ -8,7 +8,7 @@ import { BaseStreamingPresenter } from '@/lib/sdk/presenter' import { DIDType } from '@/lib/core/entity/rucio' import { ListDIDsOutputPort } from '@/lib/core/port/primary/list-dids-ports' -export default class ListDIDsPresenter extends BaseStreamingPresenter implements ListDIDsOutputPort { +export default class ListDIDsPresenter extends BaseStreamingPresenter implements ListDIDsOutputPort { response: NextApiResponse constructor(response: NextApiResponse) { diff --git a/src/lib/sdk/gateway-endpoints.ts b/src/lib/sdk/gateway-endpoints.ts index 47bcdb160..1f472af73 100644 --- a/src/lib/sdk/gateway-endpoints.ts +++ b/src/lib/sdk/gateway-endpoints.ts @@ -136,7 +136,7 @@ export abstract class BaseEndpoint { */ protected initialized: boolean = false; protected request: HTTPRequest | undefined; - protected rucioHost: string = 'http://rucio-host.com'; + protected rucioHost: string = 'https://rucio-host.com'; protected envConfigGateway: EnvConfigGatewayOutputPort; constructor( diff --git a/src/lib/sdk/ioc-helpers.ts b/src/lib/sdk/ioc-helpers.ts index 5773a928a..2655f146c 100644 --- a/src/lib/sdk/ioc-helpers.ts +++ b/src/lib/sdk/ioc-helpers.ts @@ -8,7 +8,7 @@ import { BasePresenter, BaseStreamingPresenter } from './presenter'; import type { BaseInputPort, BaseOutputPort, BaseStreamingOutputPort } from './primary-ports'; import { TUseCase } from './usecase'; import TUseCaseFactory from './usecase-factory'; -import { BaseResponseModel } from './usecase-models'; +import { BaseErrorResponseModel, BaseResponseModel } from './usecase-models'; import { BaseViewModel } from './view-models'; /** @@ -159,7 +159,7 @@ export class BaseStreamableFeature< TControllerParams extends TParameters, TRequestModel, TResponseModel extends BaseResponseModel, - TErrorModel, + TErrorModel extends BaseErrorResponseModel, TViewModel extends BaseViewModel, > implements IFeature{ /** @@ -175,9 +175,9 @@ export class BaseStreamableFeature< constructor( public name: string, private Controller: new (useCaseFactory: TUseCaseFactory) => BaseController, - private UseCase: new (presenter: BaseStreamingOutputPort, ...args: any[]) => TUseCase, + private UseCase: new (presenter: BaseStreamingOutputPort, ...args: any[]) => TUseCase, private useCaseContructorArgs: any[] = [], - private Presenter: new (response: NextApiResponse, session?: IronSession) => BaseStreamingPresenter, + private Presenter: new (response: NextApiResponse, session?: IronSession) => BaseStreamingPresenter, private passSessionToPresenter: boolean = false, private symbols: IOCSymbols, ) {} @@ -212,14 +212,14 @@ export class BaseStreamableFeature< TControllerParams extends TParameters, TRequestModel, TResponseModel extends BaseResponseModel, - TErrorModel, + TErrorModel extends BaseErrorResponseModel, TViewModel extends BaseViewModel, >( appContainer: Container, Controller: new (useCaseFactory: TUseCaseFactory) => BaseController, - UseCase: new (presenter: BaseStreamingOutputPort, ...args: any[]) => TUseCase, + UseCase: new (presenter: BaseStreamingOutputPort, ...args: any[]) => TUseCase, useCaseContructorArgs: any[] = [], - Presenter: new (response: NextApiResponse, session?: IronSession) => BaseStreamingPresenter, + Presenter: new (response: NextApiResponse, session?: IronSession) => BaseStreamingPresenter, passSessionToPresenter: boolean = false, symbols: IOCSymbols, ) { diff --git a/src/lib/sdk/primary-ports.ts b/src/lib/sdk/primary-ports.ts index b3d122542..883a139d9 100644 --- a/src/lib/sdk/primary-ports.ts +++ b/src/lib/sdk/primary-ports.ts @@ -62,8 +62,9 @@ export interface BaseOutputPort { /** * A base interface for streaming output ports. - * @typeparam TViewModel The type of the view model for the streaming output port. + * @typeparam TResponseModel The type of the response model for the streaming output port. * @typeparam TErrorModel The type of the error model for the streaming output port. + * @typeparam TViewModel The type of the view model for the streaming output port. */ export interface BaseStreamingOutputPort extends Transform{ response: TWebResponse diff --git a/src/lib/sdk/usecase.ts b/src/lib/sdk/usecase.ts index c0e131252..db7d4ed83 100644 --- a/src/lib/sdk/usecase.ts +++ b/src/lib/sdk/usecase.ts @@ -334,6 +334,7 @@ export abstract class BaseStreamingUseCase< * @typeparam TErrorModel The type of the error model for the use case. * @typeparam TDTO The type of the data transfer object for the use case. * @typeparam TStreamData The type of the streamed data for the use case. + * @typeparam TStreamDTO The type of the data transfer object for the streamed data for the use case. * @typeparam TViewModel The type of the view model for the use case. */ export abstract class BaseMultiCallStreamableUseCase< @@ -429,7 +430,7 @@ export abstract class BaseMultiCallStreamableUseCase< * @param streamedData The chunk returned from the gateway's stream * @param requestModel The request model that was used to make the gateway request. */ - abstract streamDataToStreamDTO(streamedData: TStreamData, requestModel?: AuthenticatedRequestModel): TStreamDTO + abstract streamDataToStreamDTO(streamedData: TStreamData, requestModel?: TRequestModel): TStreamDTO /** * Validates the final response model after execution of all post processing pipeline elements. @@ -458,7 +459,7 @@ export abstract class BaseMultiCallStreamableUseCase< encoding: BufferEncoding, callback: TransformCallback, ): void { - const dto = this.streamDataToStreamDTO(chunk, this.requestModel) + const dto = chunk as TStreamDTO const { status, data } = this.processStreamedData(dto) if (status === 'success') { const responseModel = data as TResponseModel