@vinejs/compiler
Version:
Low level compiler for VineJS validator
449 lines (448 loc) • 11.6 kB
TypeScript
/**
* Represenation of a ref id
*/
export type RefIdentifier = `ref://${number}`;
/**
* Allowed values for refs
*/
export type Refs = Record<RefIdentifier, ValidationRule | TransformFn<any, any> | ParseFn | ConditionalFn<any>>;
/**
* Refs store to track runtime values as refs with
* type safety
*/
export type RefsStore = {
toJSON(): Refs;
/**
* Track a value inside refs
*/
track(value: Refs[keyof Refs]): RefIdentifier;
/**
* Track a validation inside refs
*/
trackValidation(validation: ValidationRule): RefIdentifier;
/**
* Track input value parser inside refs
*/
trackParser(fn: ParseFn): RefIdentifier;
/**
* Track output value transformer inside refs
*/
trackTransformer(fn: TransformFn<any, any>): RefIdentifier;
/**
* Track a conditional inside refs
*/
trackConditional(fn: ConditionalFn<any>): RefIdentifier;
};
/**
* The context shared with the entire validation pipeline.
* Each field gets its own context object.
*/
export type FieldContext = {
/**
* Field value
*/
value: unknown;
/**
* The data property is the top-level object under validation.
*/
data: any;
/**
* Shared metadata across the entire validation lifecycle. It can be
* used to pass data between validation rules
*/
meta: Record<string, any>;
/**
* Mutate the value of field under validation.
*/
mutate(newValue: any, field: FieldContext): void;
/**
* Report error to the error reporter
*/
report: ErrorReporterContract['report'];
/**
* Does this field has valid data-type for which it
* is validated.
*
* For literal nodes, the first validation rule should
* set this to true.
*
* @default: false
*/
isValidDataType: boolean;
/**
* Is this field valid.
*
* @default: true
*/
isValid: boolean;
/**
* Is this field has value defined.
*/
isDefined: boolean;
/**
* Returns the nested path to the field. The parts
* are joined by a dot notation.
*/
getFieldPath(): string;
/**
* Wildcard path for the field. The value is a nested
* pointer to the field under validation.
*
* In case of arrays, the `*` wildcard is used.
*/
wildCardPath: string;
/**
* The parent property is the parent of the field. It could be an
* array or an object.
*/
parent: any;
/**
* Name of the field under validation. In case of an array, the field
* name will be a number
*/
name: string | number;
/**
* Is this field an array member
*/
isArrayMember: boolean;
} & Record<string, any>;
/**
* The shape of validation rule picked from the
* refs
*/
export type ValidationRule = {
/**
* Performs validation
*/
validator(value: unknown, options: any, field: FieldContext): any;
/**
* Options to pass
*/
options?: any;
};
/**
* The shape of parse function picked from the refs
*/
export type ParseFn = (value: unknown, ctx: Pick<FieldContext, 'data' | 'parent' | 'meta'>) => any;
/**
* The shape of transform function picked from the refs
*/
export type TransformFn<Input, Output> = (value: Input, field: FieldContext) => Output;
/**
* The shape of conditional function used for narrowing down unions.
*/
export type ConditionalFn<Input> = (value: Input, field: FieldContext) => boolean;
/**
* Shape of a validation rule accepted by the compiler
*/
export type ValidationNode = {
/**
* Specify the validation node name
*/
name?: string;
/**
* Rule implementation function id.
*/
ruleFnId: RefIdentifier;
/**
* Is this an async rule. This flag helps creating an optimized output
*/
isAsync: boolean;
/**
* The rules are skipped when the value of a field is "null" or "undefined".
* Unless, the "implicit" flag is true
*/
implicit: boolean;
};
/**
* Shape of field inside a schema.
*/
export type FieldNode = {
/**
* Should the validation cycle stop after the first error.
* Defaults to true
*/
bail: boolean;
/**
* Field name refers to the name of the field under the data
* object
*/
fieldName: string;
/**
* Name of the output property. This allows validating a field with a different name, but
* storing its output value with a different name.
*/
propertyName: string;
/**
* Are we expecting this field to be undefined or null
*/
isOptional: boolean;
/**
* Are we expecting this field to be null
*/
allowNull: boolean;
/**
* The reference id for the parse method. Parse method is called to mutate the
* initial value. The function is executed always even when value is undefined
* or null.
*
* @see [[ParseFn]]
*/
parseFnId?: RefIdentifier;
/**
* A set of validations to apply on the field
*/
validations: ValidationNode[];
};
/**
* Shape of a single field accepted by the compiler
*/
export type LiteralNode = FieldNode & {
type: 'literal';
/**
* Function to validate the literal data type and update the
* value of "field.isValidDataType" property
*/
dataTypeValidatorFnId?: RefIdentifier;
/**
* Transform the output value of a field. The output of this method is the
* final source of truth. The function is executed at the time of writing the
* value to the output.
*/
transformFnId?: RefIdentifier;
};
/**
* Shape of the object node accepted by the compiler
*/
export type ObjectNode = FieldNode & {
type: 'object';
/**
* Whether or not to allow unknown properties. When disabled, the
* output object will have only validated properties.
*
* Default: false
*/
allowUnknownProperties: boolean;
/**
* Object known properties
*/
properties: CompilerNodes[];
/**
* A collection of object groups to merge into the main object.
* Each group is a collection of conditionals with a sub-object
* inside them.
*/
groups: ObjectGroupNode[];
};
/**
* A compiler object group produces a single sub object based upon
* the defined conditions.
*/
export type ObjectGroupNode = {
type: 'group';
/**
* An optional function to call when all of the conditions
* are false.
*/
elseConditionalFnRefId?: RefIdentifier;
/**
* Conditions to evaluate
*/
conditions: {
/**
* The conditional function reference id
*/
conditionalFnRefId: RefIdentifier;
/**
* Schema to use when condition is true
*/
schema: {
type: 'sub_object';
/**
* Object known properties
*/
properties: CompilerNodes[];
/**
* A collection of object groups to merge into the main object.
* Each group is a collection of conditionals with a sub-object
* inside them.
*/
groups: ObjectGroupNode[];
};
}[];
};
/**
* Shape of the tuple node accepted by the compiler
*/
export type TupleNode = FieldNode & {
type: 'tuple';
/**
* Whether or not to allow unknown properties. When disabled, the
* output array will have only validated properties.
*
* Default: false
*/
allowUnknownProperties: boolean;
/**
* Tuple known properties
*/
properties: CompilerNodes[];
};
/**
* Shape of the record node accepted by the compiler
*/
export type RecordNode = FieldNode & {
type: 'record';
/**
* Captures object elements
*/
each: CompilerNodes;
};
/**
* Shape of the array node accepted by the compiler
*/
export type ArrayNode = FieldNode & {
type: 'array';
/**
* Captures array elements
*/
each: CompilerNodes;
};
/**
* Shape of the union node accepted by the compiler. A union is a combination
* of conditionals.
*/
export type UnionNode = {
type: 'union';
/**
* Field name refers to the name of the field under the data
* object
*/
fieldName: string;
/**
* Name of the output property. This allows validating a field with a different name, but
* storing its value with a different name.
*/
propertyName: string;
/**
* An optional function to call when all of the conditions
* are false.
*/
elseConditionalFnRefId?: RefIdentifier;
/**
* Conditions to evaluate
*/
conditions: {
/**
* The conditional function reference id
*/
conditionalFnRefId: RefIdentifier;
/**
* Schema to use when condition is true
*/
schema: CompilerNodes;
}[];
};
/**
* The root of the schema
*/
export type RootNode = {
type: 'root';
/**
* Schema at the root level
*/
schema: CompilerNodes;
};
/**
* Known tree nodes accepted by the compiler
*/
export type CompilerNodes = LiteralNode | ObjectNode | ArrayNode | UnionNode | RecordNode | TupleNode;
/**
* Properties of a parent node as the compiler loops through the
* rules tree and constructs JS code.
*/
export type CompilerParent = {
type: 'array' | 'object' | 'tuple' | 'record' | 'root';
/**
* Wildcard path to the field
*/
wildCardPath: string;
/**
* Name of the variable for the parent property. The variable name
* is used to lookup values from the parent
*/
variableName: string;
/**
* Nested path to the parent field. If the parent is nested inside
* an object or array.
*/
fieldPathExpression: string;
/**
* The expression for the output value.
*/
outputExpression: string;
};
/**
* Compiler field is used to compute the variable and property
* names for the JS output.
*/
export type CompilerField = {
parentExpression: string;
parentValueExpression: string;
fieldNameExpression: string;
fieldPathExpression: string;
variableName: string;
wildCardPath: string;
valueExpression: string;
outputExpression: string;
isArrayMember: boolean;
};
/**
* The error reporter is used for reporting validation
* errors.
*/
export interface ErrorReporterContract {
/**
* A boolean to known if there are one or more
* errors.
*/
hasErrors: boolean;
/**
* Creates an instance of an exception to throw
*/
createError(): Error;
/**
* Report error for a field
*/
report(message: string, rule: string, field: FieldContext, args?: Record<string, any>): any;
}
/**
* Messages provider is used to resolve validation error messages
* during validation.
*/
export interface MessagesProviderContact {
/**
* Returns a validation message for a given field + rule. The args
* may get passed by a validation rule to share additional context.
*/
getMessage(defaultMessage: string, rule: string, field: FieldContext, args?: Record<string, any>): string;
}
/**
* Options accepted by the compiler
*/
export type CompilerOptions = {
/**
* Convert empty string values to null for sake of
* normalization
*/
convertEmptyStringsToNull: boolean;
/**
* Provide messages to use for required, object and
* array validations.
*/
messages?: Partial<{
required: string;
object: string;
array: string;
}>;
};