Skip to content

Instantly share code, notes, and snippets.

@dantman
Last active December 23, 2021 03:41
Show Gist options
  • Save dantman/dd48cef249d82353d37276d955a37a34 to your computer and use it in GitHub Desktop.
Save dantman/dd48cef249d82353d37276d955a37a34 to your computer and use it in GitHub Desktop.
Basic TypeScript friendly validate/cast function based validation library experiment
import Axios, { AxiosError } from "axios";
// Casting/validation library
class DataInputError extends Error {
path: string[] = [];
static isDataInputError(error: unknown): error is DataInputError {
return error ? error instanceof DataInputError : false;
}
wrapParent(propertyName: string): DataInputError {
const parent = new DataInputError(this.message);
parent.path = [propertyName, ...this.path];
// @note Needs to also modify the message in some way to include and update the path
return parent;
}
}
function validateObject<TOut extends {}>(shape: {
[key in keyof TOut]: (data: unknown) => TOut[key];
}): (data: unknown) => TOut {
return (data: unknown): TOut => {
if (data === null || typeof data !== "object")
throw new DataInputError("is not an object");
const out: any = {};
for (const key in shape) {
const propertyValidator = shape[key];
try {
const outValue = propertyValidator(key in data ? data : undefined);
if (outValue !== undefined) out[key] = outValue;
} catch (e) {
if (DataInputError.isDataInputError(e)) {
throw e.wrapParent(key);
} else {
throw e;
}
}
}
return out;
};
}
function castObject<TOut extends {}>(shape: {
[key in keyof TOut]: (data: unknown) => TOut[key];
}): (data: unknown) => TOut {
const validate = validateObject(shape);
return (data: unknown): TOut => {
return validate(data === null || typeof data !== "object" ? {} : data);
};
}
// Validation functions that can be user defined or provided by the library as helpers
function string(): (data: unknown) => string {
return () => {
if (typeof data !== "string") throw new DataInputError("is not a string");
return data;
};
}
// Wrappers could be used to add behaviours like optional properties
function optional<T>(
cast: (data: unknown) => T
): (data: unknown) => T | undefined {
return (data: unknown): T | undefined => {
return data == null ? undefined : cast(data);
};
}
// Input data in an unknown format (query string data, form data, etc)
const data: unknown = { foo: { bar: "baz" } };
// The only actual user code used for casting the data
const cast = castObject({
foo: castObject({
bar: optional(string()),
}),
error: (err) => (Axios.isAxiosError(err) ? err : undefined),
});
const output = cast(data);
interface TheData {
foo: { bar?: string };
error?: AxiosError;
}
const theData: TheData = output;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment