import {IChassisContext, IOperation} from "../interfaces"; import * as _ from "lodash"; import { OpenAPIPlugin } from "../plugins"; import { assert } from "console"; /** * Represents an OAS3 `operation`. The operation is executed by `middleware` feature. * * It combines the OAS3 specification with a flexible request/response processing pipeline. * * The `pipeline` supports `before` pre-processing and `after` post-processing of requests. * * The `pipeline` provides support for OAS3 `security` policy enforcement. * * Every stage of the `pipeline` supports logging and auditing. * * TODO: support request and response validation * */ const OPER_PARAMS = [ "operationId", "summary", "description", "security", "requestBody", "responses", "parameters", "tags" ]; export class Operation implements IOperation { resource: string; method: string; actionId: string; operationId: string; featureId: string; spec: any; // see OpenAPIv3 feature: any; // options for the feature security: any; // security - see OpenAPIv3 ix_parameters: any = { path: {}, body: {}, query: {} }; ix_responses: any = { 200: {}, 400: {}, 404: {}, 500: {}, default: { description: "default response", content: { "application/json": {} } } }; /** * Create a new RequestFlow pipeline * * @param openapi * @param operationId * @param method * @param resource * @param _spec */ public constructor (public context: IChassisContext, actionId: string, resource: string, _spec: any) { this.context = context; this.spec = this.standardize( _spec ); this.feature = _spec.feature || {}; this.featureId = this.feature.featureId || "api.pipeline"; this.spec.path = resource; // this.resource = APIs.resource_path_to_express_path(resource); this.resource = resource; this.method = _spec.method || actionId; this.actionId = this.feature.actionId || actionId; let resource_name = resource == "/"?"root":resource; this.operationId = this.spec.operationId = (_spec.operationId || this.actionId+"-"+resource_name).replace(/[\W_]+/g,"-"); this.process_spec(this.spec); } process_spec(spec: any) { let self = this; // index paramters by `in` field this.ix_parameters = { path: {}, body: {}, query: {} }; _.each(spec.parameters, (p) => { let p_in = self.ix_parameters[p.in] = self.ix_parameters[p.in] || {}; let p_name = p["x-rename"] || p.name; p_in[p_name] = { required: p.required?true:false, schema: p.schema||{} } ; }); // ensure a default response if (_.isEmpty(spec.responses)) { spec.responses = { default: this.ix_responses.default }; } else { // index responses by `status` _.each(spec.responses, (r, status) => { let r_status = self.ix_responses[status] = self.ix_responses[status] || {}; _.each(r, (content, mime) => { if (content && content.schema) r_status[mime.toLowerCase()] = { schema: content.schema }; }); }); } let plugin = this.context.plugins.get("openapi") as OpenAPIPlugin; assert(plugin, "plugin.openapi not initialized"); plugin.openapi.schemas.resource(spec,this.resource); // console.log("OP.schema: %j", spec); } standardize = function(_oper: any) { let oper = _.extend( { operationId: "unknown", summary: "", description: "", security: [], responses: {}, parameters: [], tags: [] }, _.pick(_oper, OPER_PARAMS)); delete oper['x-feature']; delete oper['feature']; return oper; } parameterKeys(type: string): string[] { return _.keys(this.ix_parameters[type]); } getParameterSpec(from: string, name: string): any { let params = this.ix_parameters[from]; if (!params) return null; let p_name = params["x-rename"] || name; return params[p_name]; } getValidParameter(_req: any, from: string, name: string) { let _spec = this.getParameterSpec(from, name); false && _spec; // TODO: extract named var from req & validate return null; } getRequestSchema(content_type?: string): any { if (!this.spec.requestBody || !this.spec.requestBody.content) { return null; } content_type = content_type || "application/json"; let schema = this.spec.requestBody.content[content_type]; if (schema) return schema; return this.spec.requestBody.content.default; } respond(res: any ): any { let _response = this.response(res.status || "default"); let content_type = res.headers["content-type"] || false; let _payload = _response[content_type] || { code: "api:response:unknown", content_type: content_type }; res.send(_payload); return _payload; } response = function(status: string): any { return this.ix_responses[status] || {}; } }