/************************************************************************* * * Troven CONFIDENTIAL * __________________ * * (c) 2017-2020 Troven Ventures Pty Ltd * All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of Troven Pty Ltd and its licensors, * if any. The intellectual and technical concepts contained * herein are proprietary to Troven Pty Ltd * and its suppliers and may be covered by International and Regional Patents, * patents in process, and are protected by trade secret or copyright law. * Dissemination of this information or reproduction of this material * is strictly forbidden unless prior written permission is obtained * from Troven Pty Ltd. */ import * as _ from "lodash"; import { IOpenAPIv3 } from "../interfaces"; import { Vars } from "../helpers"; import { Operation } from "./Operation"; const REF_ID_PREFIX = "#/components/schemas/"; const X_SCHEMA_SRC = "x-schema-source"; export class Schemas { schemas: any = {}; DEBUG = false; constructor(spec?: IOpenAPIv3) { spec && this.init(spec); } /** parse OpenAPI specifications */ public init(spec: IOpenAPIv3): Schemas { if (spec.components && spec.components.schemas) { _.each(spec.components.schemas, (schema, schema_id) => { this.schema(schema, schema_id); }); } _.each(spec.paths, (resource, path) => { this.resource(resource, path); }); return this; } public operation(op: Operation) { this.method(op.spec, op.actionId, op.resource); } resource(resource: any, path: string) { this.methods(resource, path); } methods(methods: any, path: string) { _.each(methods, (oper, method) => { this.method(oper, method, path); }); } method(oper: any, method: string, path: string) { let method_ref_id = ( method + Vars.sanitize(path, "-") ).toLocaleLowerCase(); // this.DEBUG && console.log("method_ref_id: (%s) %j", path, method_ref_id); this.parameters_schemas(oper.parameters, method_ref_id + "-parameters"); if (oper.requestBody && oper.requestBody.content) { this.requestBody_schema( oper.requestBody.content, method_ref_id + "-requestBody" ); } this.responses_schemas(oper.responses, method_ref_id + "-response"); } // requestBody_schema(requestBody: any, op_ref_id: string) { _.each(requestBody.content, (content, content_type) => { let ref_id = op_ref_id + "-" + content_type; if (content.schema) { let schema = _.extend({}, content.schema); schema[X_SCHEMA_SRC] = "request"; content.schema = this.schema(schema, ref_id); } }); } parameters_schemas(parameters: any, op_ref_id: string) { _.each(parameters, (param, _name) => { let ref_id = op_ref_id + "-" + param.name; // this.DEBUG && console.log("path_ref_id: %j", ref_id); if (param.schema) { let schema = _.extend({}, param.schema); schema[X_SCHEMA_SRC] = "parameters"; param.schema = this.schema(schema, ref_id); } }); } responses_schemas(responses: any, op_ref_id: string) { _.each(responses, (resp, status_code) => { let ref_id = op_ref_id + "-" + status_code; // this.DEBUG && console.log("responses_ref_id: [%s] %j", status_code, ref_id ); this.response_schema(status_code, resp, ref_id); }); } response_schema( _status_code: string, response: any, method_ref_id: string ) { if (!response) { this.DEBUG && console.log("method_ref_id.schema.missing: [%s] %j", _status_code, method_ref_id ); return response; } // update if schema set, read otherwise _.each( response.content, (response_type: any, _content_type: string) => { if (response_type && response_type.schema) { let schema = _.extend({}, response_type.schema); schema[X_SCHEMA_SRC] = "response"; // console .log("response-content: %s [ %s ] -> %j", method_ref_id, _content_type, response_type); response_type.schema = this.schema(schema, method_ref_id); } } ); return response; } _id(id: string) { if (id.startsWith(REF_ID_PREFIX)) { id = id.substring(REF_ID_PREFIX.length); } return id; } schema(_schema: any, ref_id?: string): any { if (!_schema) return; if (_schema.$ref) { // console.debug("ref-schema skipped: "+_schema.$ref); return { $ref: _schema.$ref }; } let schema = _.extend({}, _schema); this.DEBUG && console.log("SCHEMA: %j", _schema); if (!schema[X_SCHEMA_SRC]) { schema[X_SCHEMA_SRC] = "default"; } let org_id, id = schema.$id || schema.$ref || ref_id; // local name if (id.startsWith(REF_ID_PREFIX)) { id = id.substring(REF_ID_PREFIX.length); } if (!id) throw new Error("Missing schema id: " + org_id); schema.$id = id; // schema.title = schema.title || Vars.humanize(id); if (this.schemas[id]) { // console.error("duplicate schema: "+id); } else { // this.DEBUG && console.log("add-schema: %s ==> %j", id, schema ); this.schemas[id] = schema; } _.each(schema.properties, (v: any, k) => { if (_.isObject(v)) { (v as any).title = (v as any).title || Vars.humanize(k); } }); if (schema.items) this.schema(schema.items); // schema.properties =_.orderBy(schema.properties, ["title"], ['desc']); return { $ref: REF_ID_PREFIX + id }; } get(id: string) { id = this._id(id); let schema = _.extend({}, this.schemas[id]); return schema; } strip_x(schema: any): any { _.each(schema, (_v, k) => { if (k.startsWith("x-")) delete schema[k]; }); return schema; } deref(org_schema: any, _options?: any): any { let options = _options || { id: true }; if (_.isString(org_schema)) { let id = org_schema; let found_schema = this.get(id); if (!options.id) delete org_schema["$id"]; if (options.ref) org_schema["$ref"] = id; this.DEBUG && console.log( "Schema.deref: %j -> %j --> %j", id, org_schema, found_schema ); org_schema = found_schema; } let schema = {}; _.each(org_schema, (v: any, k) => { if (k == "$ref" && v.startsWith(REF_ID_PREFIX)) { let id = this._id(v); let ref = this.deref(id, options); _.extend(schema, ref); if (options.id) schema["$id"] = id; if (options.ref) schema["$ref"] = v; this.DEBUG && console.log( "Schema.deREF$: %j (%j) -> %j == %j", id, v, schema, ref ); } else if (_.isArray(v)) { schema[k] = v; } else if (_.isObject(v) && (v as any).properties) { this.DEBUG && console.log( "Schema.xREF: %j -> %j ---> %j", k, v, org_schema ); let ref = this.deref((v as any).properties, options); this.strip_x(ref); schema[k] = ref; // this.DEBUG && console.log("deref: %j -> %j", k, ref ); } else { schema[k] = v; } }); return schema; } all(_options?: any): any { let options = _.extend({ id: false, ref: false, source: "" }, _options); let schemas = {}; _.each(this.schemas, (v, k) => { if (v.title) { schemas[k] = this.deref(v, options); } }); return schemas; } refs(): any { let org_schema = this.all({ id: true, ref: true }); let refs = []; function walk(schema: any) { _.each(schema, (v) => { refs.push({ title: v.title, $id: v.$id, $ref: v.$ref }); if (v.items) { walk(v.items); } }); } walk(org_schema); return refs; } }