import * as http from 'http'; import * as net from 'net'; import * as oas3 from 'openapi3-ts'; import { Readable } from 'stream'; import { ExegesisOptions, ParameterLocation, ParameterLocations } from '.'; import { Callback, HttpIncomingMessage, ParametersByLocation, ParametersMap } from './basicTypes'; import { BodyParser } from './bodyParser'; import { IValidationError, ResponseValidationResult, ValidatorFunction } from './validation'; export interface HttpHeaders { [header: string]: number | string | string[]; } export interface ExegesisRoute { path: string; } export interface ExegesisResponse { statusCode: number; statusMessage: string | undefined; headers: HttpHeaders; body: Buffer | string | Readable | any; connection: net.Socket; socket: net.Socket; ended: boolean; setStatus(status: number): this; status(status: number): this; setBody(body: any): this; /** * Set the value of a header. * @param header - the header to set. * @param value - the value to set the header to. */ header(header: string, value: number | string | string[] | undefined): this; set(header: string, value: number | string | string[] | undefined): this; /** * Set the JSON content of the response. Note that this will call `JSON.stringify()` * immediately if response validation is enabled, because there may be `toJSON()` * functions on the object or any nested values (e.g. if some values are Mongoose objects). * This means we'll need to parse that string to do validation though. If you * know your object is a pure POJO, call `res.pureJson()` instead. */ json(json: any): this; /** * Sets the JSON content of the response to the object provided. Note that * while `toJSON()` on the object or any child objects will be * respsected when the object is serialized, it will be ignored for purposes * of response validation. */ pureJson(json: any): this; end(): void; redirect(status: number, url: string): this; redirect(url: string): this; setHeader(name: string, value: number | string | string[] | undefined): void; getHeader(name: string): number | string | string[] | undefined; getHeaderNames(): string[]; getHeaders(): HttpHeaders; hasHeader(name: string): boolean; removeHeader(name: string): void; writeHead(statusCode: number, headers?: HttpHeaders): void; writeHead(statusCode: number, statusMessage?: string, headers?: HttpHeaders): void; } export interface ExegesisContextBase { readonly req: HttpIncomingMessage; readonly origRes: http.ServerResponse; readonly res: ExegesisResponse; api: any; security?: { [scheme: string]: AuthenticationSuccess; }; user?: any; parameterLocations?: ParameterLocations; makeError(statusCode: number, message: string): Error; makeValidationError(message: string, parameterLocation: ParameterLocation): Error; /** * Returns true if the response has already been sent. */ isResponseFinished(): boolean; } export interface ExegesisContext extends ExegesisContextBase { parameterLocations: ParameterLocations; params: ParametersByLocation>; requestBody: any; options: ExegesisOptions; route: ExegesisRoute; baseUrl: string; } export interface ExegesisPluginContext extends ExegesisContextBase { getParams(): Promise>>; getParams(done: Callback>>): void; getRequestBody(): Promise; getRequestBody(done: Callback): void; } export interface OAS3ApiInfo { openApiDoc: oas3.OpenAPIObject; serverPtr: string | undefined; serverObject: oas3.ServerObject | undefined; pathItemPtr: string; pathItemObject: oas3.PathItemObject; operationPtr: string | undefined; operationObject: oas3.OperationObject | undefined; requestBodyMediaTypePtr: string | undefined; requestBodyMediaTypeObject: oas3.MediaTypeObject | undefined; } export type PromiseController = (context: ExegesisContext) => any; export type CallbackController = (context: ExegesisContext, done: Callback) => void; export type Controller = PromiseController | CallbackController; export interface ControllerModule { [operationId: string]: Controller; } export interface Controllers { [controllerName: string]: ControllerModule; } export interface AuthenticationFailure { type: 'invalid' | 'missing'; status?: number; message?: string; challenge?: string; } export interface AuthenticationSuccess { type: 'success'; user?: any; roles?: string[] | undefined; scopes?: string[] | undefined; [name: string]: any; } export type AuthenticationResult = AuthenticationSuccess | AuthenticationFailure; export interface AuthenticatorInfo { in?: 'query' | 'header' | 'cookie'; name?: string; scheme?: string; } export type PromiseAuthenticator = (context: ExegesisPluginContext, info: AuthenticatorInfo) => AuthenticationResult | undefined | Promise; export type CallbackAuthenticator = (context: ExegesisPluginContext, info: AuthenticatorInfo, done: Callback) => void; export type Authenticator = PromiseAuthenticator | CallbackAuthenticator; export interface Authenticators { [scheme: string]: Authenticator; } /** * Result returned by the exegesisRunner. */ export interface HttpResult { headers: HttpHeaders; status: number; body: NodeJS.ReadableStream | undefined; } /** * A function which takes in a request and response, and returns an HttpResult. * * @throws {ValidationError} - If a validation error occurs in the parameters or the body. * @throws {HttpError} - If a non-validation error occurs, and an HTTP error code is suggested. * @throws {Error} - If any other error occurs. */ export type ExegesisRunner = (req: http.IncomingMessage, res: http.ServerResponse) => Promise; export type ParsedParameterValidator = (parameterValues: ParametersByLocation>) => IValidationError[] | null; export interface ResolvedOperation { parseParameters: () => ParametersByLocation>; validateParameters: ParsedParameterValidator; parameterLocations: ParameterLocations; bodyParser: BodyParser | undefined; validateBody: ValidatorFunction | undefined; exegesisControllerName: string | undefined; operationId: string | undefined; controllerModule: ControllerModule | undefined; controller: Controller | undefined; validateResponse(response: ExegesisResponse, validateDefaultResponses: boolean): ResponseValidationResult; authenticate(context: ExegesisContext): Promise<{ [scheme: string]: AuthenticationSuccess; } | undefined>; } export interface ResolvedPath { operation: ResolvedOperation | undefined; api: T; /** List of methods the client is allowed to send to this path. e.g. `['get', 'post']`. */ allowedMethods: string[]; /** The path of the operation being accessed. e.g. "/users/1234". */ path: string; /** * The "base" of the `path`. `${baseUrl}${path}` represents the full * URL being accessed. For OAS3, for example you can set a URL like * `https://myserver.com/v1` in the `Server` object, which would be reflected * here. */ baseUrl: string; } export interface ApiInterface { /** * Resolve an incoming request. * * @param method - The HTTP method used (e.g. 'GET'). * @param url - The URL used to retrieve this request. * @param headers - Any headers sent along with the request. * @throws {ValidationError} if some parameters cannot be parsed. */ resolve(method: string, url: string, headers: http.IncomingHttpHeaders): ResolvedPath | undefined; } export interface ExegesisPluginInstance { /** * Called exactly once, before Exegesis "compiles" the API document. * Plugins must not modify apiDoc here. * * @param data.apiDoc - the API document. */ preCompile?: ((data: { apiDoc: any; }) => void | Promise) | ((data: { apiDoc: any; }, done: Callback) => void); /** * Called before routing. Note that the context hasn't been created yet, * so you just get a raw `req` and `res` object here. */ preRouting?: ((data: { req: http.IncomingMessage; res: http.ServerResponse; }) => void | Promise) | ((data: { req: http.IncomingMessage; res: http.ServerResponse; }, done: Callback) => void); /** * Called immediately after the routing phase. Note that this is * called before Exegesis verifies routing was valid - the * `pluginContext.api` object will have information about the * matched route, but will this information may be incomplete. * For example, for OAS3 we may have matched a route, but not * matched an operation within the route. Or we may have matched * an operation but that operation may have no controller defined. * (If we failed to match a route at all, this will not be called.) * * If your API added a route to the API document, this function is a * good place to write a reply. * * @param pluginContext - the plugin context. */ postRouting?: ((pluginContext: ExegesisPluginContext) => void | Promise) | ((pluginContext: ExegesisPluginContext, done: Callback) => void); /** * Called for each request, after security phase and before input * is parsed and the controller is run. This is a good place to * do extra security checks. The `exegesis-plugin-roles` plugin, * for example, generates a 403 response here if the authenticated * user has insufficient privliedges to access this path. * * Note that this function will not be called if a previous pluing * has already written a response. * * @param pluginContext - the plugin context. */ postSecurity?: ((pluginContext: ExegesisPluginContext) => void | Promise) | ((pluginContext: ExegesisPluginContext, done: Callback) => void); /** * Called immediately after the controller has been run, but before * any response validation. This is a good place to do custom * response validation. If you have to deal with something weird * like XML, this is where you'd handle it. * * This function can modify the contents of the response. * * @param context - The exegesis plugin context. */ postController?: ((pluginContext: ExegesisContext) => void | Promise) | ((pluginContext: ExegesisContext, done: Callback) => void); /** * Called after the response validation step. This is the last step before * the response is converted to JSON and written to the output. */ postResponseValidation?: ((pluginContext: ExegesisContext) => void | Promise) | ((pluginContext: ExegesisContext, done: Callback) => void); } export interface ExegesisPlugin { info: { name: string; }; makeExegesisPlugin(data: { apiDoc: any; }): ExegesisPluginInstance; }