UNPKG

api-core

Version:

Model-based dynamic multi-level APIs for any provider, plus multiple consumption channels

193 lines (161 loc) 6.9 kB
const parse = require('obj-parse'), deepKeys = require('deep-keys'), SimpleSchema = require('simpl-schema').default; export class Mixed { //for schemas } export const SchemaReference = SimpleSchema.oneOf(String, Mixed); export const JSONDate = SimpleSchema.oneOf(String, Number, Date); export class SubSchema extends SimpleSchema { private readonly _apiCoreOriginal: any; constructor(schema: any, options?: any, ...args: any[]) { if(!options) options = { requiredByDefault: false }; super(schema, options, ...args); this._apiCoreOriginal = schema; } get original() { return this._apiCoreOriginal } } export class ApiEdgeSchemaTransformation { applyToInput: (schema: any, model: any) => void; applyToOutput: (mode: any, schema: any) => void; affectedSchemaField: string; affectedModelFields: string[]; parsedField: any; schemaType: any; constructor(input: (schema: any, model: any) => void, output: (model: any, schema: any) => void, modelFields: string[], schemaType: any, schemaField: string = "") { this.applyToInput = input; this.applyToOutput = output; this.affectedSchemaField = schemaField; this.affectedModelFields = modelFields; this.parsedField = parse(schemaField); this.schemaType = schemaType } setSchemaField(field: string) { this.affectedSchemaField = field; this.parsedField = parse(field); } } export class ApiEdgeSchema { fields: string[] = []; schema: any|null = null; originalSchema: any|null = null; transformations: ApiEdgeSchemaTransformation[]; private fieldMatrix: { [key: string]: string[] } = {}; private renameMatrix: { [key: string]: string } = {}; transformField = (field: string) => { return this.renameMatrix[field] || field }; transformFields = (fields: string[]): string[] => { let output: string[] = []; for (const field of fields) { const transformedFields = this.fieldMatrix[field]; if(transformedFields) { transformedFields.forEach((f: string) => output.push(f)) } else { if (this.renameMatrix[field]) output.push(this.renameMatrix[field]); else output.push(field); } } return output }; cleanAndValidateModel = (model: any, modifier: boolean = false): { valid: boolean, errors?: any[] } => { if(this.schema) { const context = this.schema.newContext(); context.clean(model, { mutate: true, //Update the original object filter: true, //Remove not allowed fields autoConvert: true, //Converts values when possible removeEmptyStrings: false, trimStrings: false, getAutoValues: !modifier //Fill defaults }); model = modifier ? { $set: model } : model; context.validate(model, { modifier }); return { valid: context.isValid(), errors: context.validationErrors().map( ({ name }: any) => context.keyErrorMessage(name) ) } } return { valid: true } }; private createInputTransformer(schemaField: any, transform: string): (schema: any, model: any) => void { if(transform === "=") { return (schema: any, model: any) => schemaField.assign(model, schemaField(schema)); } else if(transform[0] === "=") { const fieldName = transform.substring(1), modelField = parse(fieldName); return (schema: any, model: any) => modelField.assign(model, schemaField(schema)); } else throw "Not Supported Transform"; } private static createOutputTransformer(schemaField: any, transform: string): (model: any, schema: any) => void { if(transform === "=") { return (model: any, schema: any) => schemaField.assign(schema, schemaField(model)); } else if(transform[0] === "=") { const fieldName = transform.substring(1), modelField = parse(fieldName); return (model: any, schema: any) => schemaField.assign(schema, modelField(model)); } else { throw "Not Supported Transform"; } } private createTransformation(schemaField: string, schema: any, typedSchema: any): ApiEdgeSchemaTransformation|undefined { const parsedSchemaField = parse(schemaField), transform: string|ApiEdgeSchemaTransformation = parsedSchemaField(schema); if(transform instanceof ApiEdgeSchemaTransformation) { transform.setSchemaField(schemaField); let transformedFields = this.fieldMatrix[transform.affectedSchemaField]; if(transformedFields) { transform.affectedModelFields.forEach((field: string) => transformedFields.push(field)) } else { this.fieldMatrix[transform.affectedSchemaField] = transform.affectedModelFields.map((field: string) => field) } this.fixFields(schemaField); return transform; } else if(typeof transform === "string") { this.renameMatrix[schemaField] = transform.substring(1); return new ApiEdgeSchemaTransformation( this.createInputTransformer(parsedSchemaField, transform), ApiEdgeSchema.createOutputTransformer(parsedSchemaField, transform), [ schemaField ], typedSchema ? typedSchema[schemaField] : Mixed, schemaField ) } } private fixFields(fieldName: string) { this.fields = this.fields.filter((field: string) => field.indexOf(fieldName+".") == -1) } constructor(schema: any, typedSchema: any = null, fields?: string[]) { this.fields = fields || deepKeys(schema, true); this.originalSchema = typedSchema; this.schema = typedSchema ? new SimpleSchema(typedSchema, { requiredByDefault: false }) : null; this.transformations = []; for(let i = 0; i < this.fields.length; ++i) { const transform = this.createTransformation(this.fields[i], schema, typedSchema); if(transform) this.transformations.push(transform) } } }