185 lines
5.7 KiB
JavaScript
185 lines
5.7 KiB
JavaScript
const MamiArgs = (argName, input, builder) => {
|
|
if(input !== undefined && typeof input !== 'object')
|
|
throw `${argName} must be an object or undefined`;
|
|
if(typeof builder !== 'function')
|
|
throw 'builder must be a function';
|
|
|
|
const args = new Map;
|
|
|
|
builder(name => {
|
|
if(typeof name !== 'string')
|
|
throw 'name must be a string';
|
|
|
|
const checkDefined = () => {
|
|
if(args.has(name))
|
|
throw `${name} has already been defined`;
|
|
};
|
|
checkDefined();
|
|
|
|
let created = false;
|
|
const checkCreated = () => {
|
|
if(created)
|
|
throw 'argument has already been defined';
|
|
};
|
|
|
|
const info = {
|
|
name: name,
|
|
type: undefined,
|
|
filter: undefined,
|
|
constraint: undefined,
|
|
fallback: undefined,
|
|
throw: undefined,
|
|
required: undefined,
|
|
min: undefined,
|
|
max: undefined,
|
|
};
|
|
|
|
const blueprint = {
|
|
type: value => {
|
|
if(typeof value !== 'string' && !Array.isArray(value))
|
|
throw 'type must be a javascript type or array of valid string values';
|
|
|
|
checkCreated();
|
|
|
|
info.type = value;
|
|
return blueprint;
|
|
},
|
|
default: value => {
|
|
checkCreated();
|
|
info.fallback = value;
|
|
|
|
if(info.type === undefined && info.constraint === undefined)
|
|
info.type = typeof info.fallback;
|
|
|
|
return blueprint;
|
|
},
|
|
filter: value => {
|
|
if(typeof value !== 'function')
|
|
throw 'filter must be a function';
|
|
|
|
checkCreated();
|
|
|
|
info.filter = value;
|
|
return blueprint;
|
|
},
|
|
constraint: value => {
|
|
if(typeof value !== 'function')
|
|
throw 'constraint must be a function';
|
|
|
|
checkCreated();
|
|
|
|
info.constraint = value;
|
|
return blueprint;
|
|
},
|
|
throw: value => {
|
|
checkCreated();
|
|
info.throw = value === undefined || value === true;
|
|
return blueprint;
|
|
},
|
|
required: value => {
|
|
checkCreated();
|
|
info.required = value === undefined || value === true;
|
|
|
|
if(info.required && info.throw === undefined)
|
|
info.throw = true;
|
|
|
|
return blueprint;
|
|
},
|
|
min: value => {
|
|
checkCreated();
|
|
if(typeof value !== 'number')
|
|
throw 'value must be a number';
|
|
|
|
info.min = value;
|
|
return blueprint;
|
|
},
|
|
max: value => {
|
|
checkCreated();
|
|
if(typeof value !== 'number')
|
|
throw 'value must be a number';
|
|
|
|
info.max = value;
|
|
return blueprint;
|
|
},
|
|
done: () => {
|
|
checkCreated();
|
|
checkDefined();
|
|
|
|
args.set(name, info);
|
|
},
|
|
};
|
|
|
|
return blueprint;
|
|
});
|
|
|
|
const inputIsNull = input === null;
|
|
if(inputIsNull)
|
|
input = {};
|
|
|
|
const output = {};
|
|
|
|
for(const [name, info] of args) {
|
|
let value = info.fallback;
|
|
|
|
if(info.name in input) {
|
|
const defaultOrThrow = ex => {
|
|
if(info.throw)
|
|
throw ex;
|
|
value = info.fallback;
|
|
};
|
|
|
|
value = input[info.name];
|
|
|
|
if(info.type !== undefined) {
|
|
if(Array.isArray(info.type)) {
|
|
if(!info.type.includes(value))
|
|
defaultOrThrow(`${info.name} must match an enum value`);
|
|
} else {
|
|
const type = typeof value;
|
|
let resolved = false;
|
|
|
|
if(type !== info.type) {
|
|
if(type === 'string') {
|
|
if(info.type === 'number') {
|
|
value = parseFloat(value);
|
|
resolved = true;
|
|
|
|
if(info.min !== undefined && value < info.min)
|
|
value = info.min;
|
|
else if(info.max !== undefined && value > info.max)
|
|
value = info.max;
|
|
} else if(info.type === 'boolean') {
|
|
value = !!value;
|
|
resolved = true;
|
|
}
|
|
} else if(info.type === 'string') {
|
|
value = value.toString();
|
|
resolved = true;
|
|
}
|
|
} else resolved = true;
|
|
|
|
if(!resolved)
|
|
defaultOrThrow(`${info.name} must be of type ${info.type}`);
|
|
}
|
|
}
|
|
|
|
if(info.constraint !== undefined && !info.constraint(value))
|
|
defaultOrThrow(`${info.name} did not fit within constraints`);
|
|
|
|
// you could have a devious streak with this one cuz the checks aren't rerun
|
|
// but surely you wouldn't fuck yourself over like that
|
|
if(info.filter !== undefined)
|
|
value = info.filter(value);
|
|
} else if(info.required) {
|
|
if(inputIsNull)
|
|
throw `${argName} must be a non-null object`;
|
|
|
|
throw `${argName} is missing required key ${info.name}`;
|
|
}
|
|
|
|
output[info.name] = value;
|
|
}
|
|
|
|
return output;
|
|
};
|