Skip to content

Commit

Permalink
Improve validate typings
Browse files Browse the repository at this point in the history
Change-type: patch
  • Loading branch information
Page- committed Mar 12, 2024
1 parent 786e3d6 commit 6ed4746
Show file tree
Hide file tree
Showing 22 changed files with 225 additions and 136 deletions.
26 changes: 17 additions & 9 deletions src/type-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export interface DatabaseTypeFn {
castType: string;
}
export type DatabaseType = string | DatabaseTypeFn;
export interface SbvrType<Read = unknown> {
export interface SbvrType<Read = unknown, Write = any, DbWrite = unknown> {
types: {
odata: {
name: string;
Expand All @@ -14,16 +14,24 @@ export interface SbvrType<Read = unknown> {
websql: DatabaseType;
};
fetchProcessing?: FetchProcessing<Read>;
validate: (value: any, required: boolean) => Promise<any>;
validate: Validate<Write, DbWrite>;
}

export type FetchProcessing<Read> = (data: unknown) => Read | null | undefined;
export interface Validate<Write, DbWrite> {
(value: Write, required: true): Promise<DbWrite>;
(value: Write, required: false): Promise<DbWrite | null>;
(value: Write, required: boolean): Promise<DbWrite | null>;
}

const checkRequired = <T>(validateFn: (value: any) => T) => {
function runCheck(value: any, required: true): Promise<T>;
const checkRequired = <T>(validateFn: (value: any) => T | Promise<T>) => {
function runCheck(value: unknown, required: true): Promise<T>;
function runCheck(value: undefined | null, required: false): Promise<null>;
function runCheck(value: any, required: boolean): Promise<T | null>;
async function runCheck(value: any, required: boolean): Promise<T | null> {
function runCheck(value: unknown, required: boolean): Promise<T | null>;
async function runCheck(
value: unknown,
required: boolean,
): Promise<T | null> {
if (value == null) {
if (required) {
throw new Error('cannot be null');
Expand Down Expand Up @@ -53,15 +61,15 @@ export const nativeFactTypeTemplates = {

export const validate = {
checkRequired,
integer: checkRequired((value: any) => {
integer: checkRequired((value) => {
const processedValue = parseInt(value, 10);
if (Number.isNaN(processedValue)) {
throw new Error('is not a number: ' + value);
}
return processedValue;
}),
text: (length?: number) =>
checkRequired((value: any) => {
checkRequired((value) => {
if (typeof value !== 'string') {
throw new Error('is not a string: ' + value);
}
Expand All @@ -72,7 +80,7 @@ export const validate = {
}
return value;
}),
date: checkRequired((value: any) => {
date: checkRequired((value) => {
let processedValue = Number(value);
if (Number.isNaN(processedValue)) {
processedValue = value;
Expand Down
6 changes: 5 additions & 1 deletion src/types/big-integer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ export const types = {
},
};

type WriteType = number;
type DbWriteType = number;

export const nativeFactTypes = {
Integer: TypeUtils.nativeFactTypeTemplates.comparison,
Real: TypeUtils.nativeFactTypeTemplates.comparison,
};

export const validate = TypeUtils.validate.integer;
export const validate: TypeUtils.Validate<WriteType, DbWriteType> =
TypeUtils.validate.integer;
6 changes: 5 additions & 1 deletion src/types/big-serial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ export const types = {
},
};

export const validate = TypeUtils.validate.integer;
type WriteType = number;
type DbWriteType = number;

export const validate: TypeUtils.Validate<WriteType, DbWriteType> =
TypeUtils.validate.integer;
27 changes: 15 additions & 12 deletions src/types/boolean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,23 @@ export const types = {
};

type ReadType = boolean;
type WriteType = boolean | 0 | 1;
type DbWriteType = boolean;

// `BOOLEAN` on sqlite/websql is just an alias for `INTEGER` hence the `=== 1` check
export const fetchProcessing: TypeUtils.FetchProcessing<ReadType> = (data) =>
data === true || data === 1;

export const validate = TypeUtils.validate.checkRequired((originalValue) => {
// We use Number rather than parseInt as it deals with booleans and will return NaN for things like "a1"
const value = Number(originalValue);
if (value !== 0 && value !== 1) {
throw new Error(
`is not a boolean: ${JSON.stringify(
originalValue,
)} (${typeof originalValue})`,
);
}
return value === 1;
});
export const validate: TypeUtils.Validate<WriteType, DbWriteType> =
TypeUtils.validate.checkRequired((originalValue) => {
// We use Number rather than parseInt as it deals with booleans and will return NaN for things like "a1"
const value = Number(originalValue);
if (value !== 0 && value !== 1) {
throw new Error(
`is not a boolean: ${JSON.stringify(
originalValue,
)} (${typeof originalValue})`,
);
}
return value === 1;
});
6 changes: 5 additions & 1 deletion src/types/case-insensitive-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ export const types = {
},
};

export const validate = TypeUtils.validate.text();
type WriteType = string;
type DbWriteType = string;

export const validate: TypeUtils.Validate<WriteType, DbWriteType> =
TypeUtils.validate.text();
91 changes: 47 additions & 44 deletions src/types/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type ReadType = {
b: number;
a: number;
};
type WriteType = number | ReadType;
type DbWriteType = number;

export const nativeProperties = {
has: {
Expand Down Expand Up @@ -58,48 +60,49 @@ export const fetchProcessing: TypeUtils.FetchProcessing<ReadType> = (data) => {
/* eslint-enable no-bitwise */
};

export const validate = TypeUtils.validate.checkRequired((value) => {
let processedValue: number;
if (typeof value !== 'object') {
processedValue = parseInt(value, 10);
if (Number.isNaN(processedValue)) {
throw new Error('is neither an integer or color object: ' + value);
}
} else {
processedValue = 0;
Object.keys(value).forEach((component) => {
const componentValue = value[component];
if (Number.isNaN(componentValue) || componentValue > 255) {
throw new Error(
'has invalid component value of ' +
componentValue +
' for component ' +
component,
);
}
/* eslint-disable no-bitwise */
switch (component.toLowerCase()) {
case 'r':
case 'red':
processedValue |= componentValue << 16;
break;
case 'g':
case 'green':
processedValue |= componentValue << 8;
break;
case 'b':
case 'blue':
processedValue |= componentValue;
break;
case 'a':
case 'alpha':
processedValue |= componentValue << 24;
break;
default:
throw new Error('has an unknown component: ' + component);
export const validate: TypeUtils.Validate<WriteType, DbWriteType> =
TypeUtils.validate.checkRequired((value) => {
let processedValue: number;
if (typeof value !== 'object') {
processedValue = parseInt(value, 10);
if (Number.isNaN(processedValue)) {
throw new Error('is neither an integer or color object: ' + value);
}
/* eslint-enable no-bitwise */
});
}
return processedValue;
});
} else {
processedValue = 0;
Object.keys(value).forEach((component) => {
const componentValue = value[component];
if (Number.isNaN(componentValue) || componentValue > 255) {
throw new Error(
'has invalid component value of ' +
componentValue +
' for component ' +
component,
);
}
/* eslint-disable no-bitwise */
switch (component.toLowerCase()) {
case 'r':
case 'red':
processedValue |= componentValue << 16;
break;
case 'g':
case 'green':
processedValue |= componentValue << 8;
break;
case 'b':
case 'blue':
processedValue |= componentValue;
break;
case 'a':
case 'alpha':
processedValue |= componentValue << 24;
break;
default:
throw new Error('has an unknown component: ' + component);
}
/* eslint-enable no-bitwise */
});
}
return processedValue;
});
6 changes: 5 additions & 1 deletion src/types/concept-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ export const types = {
},
};

type WriteType = number;
type DbWriteType = number;

export const nativeFactTypes = {
Integer: TypeUtils.nativeFactTypeTemplates.comparison,
Real: TypeUtils.nativeFactTypeTemplates.comparison,
};

export const validate = TypeUtils.validate.integer;
export const validate: TypeUtils.Validate<WriteType, DbWriteType> =
TypeUtils.validate.integer;
5 changes: 4 additions & 1 deletion src/types/date-time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const types = {
};

type ReadType = string;
type WriteType = string | number;
type DbWriteType = Date;

export const fetchProcessing: TypeUtils.FetchProcessing<ReadType> = (data) => {
if (data == null) {
Expand Down Expand Up @@ -37,4 +39,5 @@ export const nativeNames = {
'Current Time': ['Now'],
};

export const validate = TypeUtils.validate.date;
export const validate: TypeUtils.Validate<WriteType, DbWriteType> =
TypeUtils.validate.date;
5 changes: 4 additions & 1 deletion src/types/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const types = {
};

type ReadType = string;
type WriteType = string | number;
type DbWriteType = Date;

export const fetchProcessing: TypeUtils.FetchProcessing<ReadType> = (data) => {
if (data == null) {
Expand All @@ -33,4 +35,5 @@ export const nativeFactTypes = {
},
};

export const validate = TypeUtils.validate.date;
export const validate: TypeUtils.Validate<WriteType, DbWriteType> =
TypeUtils.validate.date;
50 changes: 27 additions & 23 deletions src/types/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,30 @@ export const types = {
},
};

export const validate = TypeUtils.validate.checkRequired((value) => {
if (Buffer.isBuffer(value)) {
return value;
}
if (typeof value !== 'string') {
throw new Error(`could not be converted to binary: ${typeof value}`);
}
if (value.length % 2 !== 0) {
throw new Error(
'could not be converted to binary: hex string must have an even length',
);
}
if (!/^[a-fA-F0-9]*$/.test(value)) {
throw new Error(
'could not be converted to binary: hex string must contain only hex characters',
);
}
try {
return Buffer.from(value, 'hex');
} catch (e: any) {
throw new Error(`could not be converted to binary: ${e.message}`);
}
});
type WriteType = Buffer | string;
type DbWriteType = Buffer;

export const validate: TypeUtils.Validate<WriteType, DbWriteType> =
TypeUtils.validate.checkRequired((value) => {
if (Buffer.isBuffer(value)) {
return value;
}
if (typeof value !== 'string') {
throw new Error(`could not be converted to binary: ${typeof value}`);
}
if (value.length % 2 !== 0) {
throw new Error(
'could not be converted to binary: hex string must have an even length',
);
}
if (!/^[a-fA-F0-9]*$/.test(value)) {
throw new Error(
'could not be converted to binary: hex string must contain only hex characters',
);
}
try {
return Buffer.from(value, 'hex');
} catch (e: any) {
throw new Error(`could not be converted to binary: ${e.message}`);
}
});
6 changes: 5 additions & 1 deletion src/types/foreign-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ export const types = {
},
};

type WriteType = number;
type DbWriteType = number;

export const nativeFactTypes = {
Integer: TypeUtils.nativeFactTypeTemplates.comparison,
Real: TypeUtils.nativeFactTypeTemplates.comparison,
};

export const validate = TypeUtils.validate.integer;
export const validate: TypeUtils.Validate<WriteType, DbWriteType> =
TypeUtils.validate.integer;
18 changes: 11 additions & 7 deletions src/types/hashed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ export const types = {
},
};

export const validate = TypeUtils.validate.checkRequired(async (value) => {
if (typeof value !== 'string') {
throw new Error('is not a string');
}
const salt = await bcrypt.genSalt();
return bcrypt.hash(value, salt);
});
type WriteType = string;
type DbWriteType = string;

export const validate: TypeUtils.Validate<WriteType, DbWriteType> =
TypeUtils.validate.checkRequired(async (value) => {
if (typeof value !== 'string') {
throw new Error('is not a string');
}
const salt = await bcrypt.genSalt();
return bcrypt.hash(value, salt);
});

export const compare = bcrypt.compare.bind(bcrypt);
6 changes: 5 additions & 1 deletion src/types/integer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ export const types = {
},
};

type WriteType = number;
type DbWriteType = number;

export const nativeFactTypes = {
Integer: TypeUtils.nativeFactTypeTemplates.comparison,
Real: TypeUtils.nativeFactTypeTemplates.comparison,
};

export const validate = TypeUtils.validate.integer;
export const validate: TypeUtils.Validate<WriteType, DbWriteType> =
TypeUtils.validate.integer;
Loading

0 comments on commit 6ed4746

Please sign in to comment.