Skip to content

Instantly share code, notes, and snippets.

@jmakeig
Last active January 9, 2025 06:59
Show Gist options
  • Save jmakeig/16897238002bd89bd28aa60b402cff70 to your computer and use it in GitHub Desktop.
Save jmakeig/16897238002bd89bd28aa60b402cff70 to your computer and use it in GitHub Desktop.
Experiment to return validation assertions from API call versus throwing exceptions
type Nullable<T> = { [P in keyof T]: T[P] | null; }
type Locale = 'en-US';
type Message = string | {
[key in Locale]: string;
}
type Validation<Entity> = {
for?: keyof Entity;
message: Message;
}
/**
* The return type of an API call. For invalid results, it reflects back what was passed in (`In`) as `value` along with a `validations` property,
* listing the validation assertions.
* If the result is valid it returns the entity (`Out`).
* Make sure your entity (`Out`) doesn’t have a `validations` property itself. See `is_valid()`.
*/
type Result<In, Out> = Out | InvalidResult<In, Out>;
class InvalidResult<In, Out> {
validations: Array<Validation<Out>>;
value: In;
constructor(value: In, validations: Array<Validation<Out>>) {
this.value = value;
this.validations = validations;
}
}
type Customer = {
customer: string;
name: string;
label: string;
segment: string | null;
region: string | null;
}
function validate_customer(customer: Nullable<Customer>): Array<Validation<Customer>> {
const validations: Array<Validation<Customer>> = [];
if ((customer.name ?? '').length < 3) validations.push({ for: 'name', message: 'Short' });
if ((customer.label ?? '').length < 2) validations.push({ for: 'label', message: 'Short' });
return validations;
}
function add_customer(customer: Nullable<Customer>): Result<Nullable<Customer>, Customer> {
function is_valid_customer(customer: Nullable<Customer>, validations: Array<Validation<Customer>>): customer is Customer {
return customer && 0 === validations.length;
}
const validations = validate_customer(customer);
if (!is_valid_customer(customer, validations)) {
return new InvalidResult(customer, validations);
}
// Simulate database call
return Object.assign({}, customer);
}
/**
* Whether a result contains a validation
*/
function is_invalid<T>(result: Result<unknown, T>): result is T {
return result instanceof InvalidResult;
}
const invalid: Nullable<Customer> = { customer: null, name: null, label: null, segment: null, region: null };
//const valid: Nullable<Customer> = { customer: null, name: 'Aaa', label: 'aaa', segment: null, region: null };
const processed: Result<Nullable<Customer>, Customer> = add_customer(invalid);
if (is_invalid(processed)) {
console.error('INVALID', processed);
} else {
console.log('VALID', processed);
}
@jmakeig
Copy link
Author

jmakeig commented Jan 9, 2025

Note that is_valid() should only be used to sniff the runtime type of the payload. It shouldn’t be used to actually determine validity. The latter is is_valid_customer() above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment