api-core
Version:
Model-based dynamic multi-level APIs for any provider, plus multiple consumption channels
193 lines (161 loc) • 6.9 kB
text/typescript
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)
}
}
}