Skip to content

Commit

Permalink
refactor validationscope
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuaboud committed Jun 10, 2024
1 parent a35b935 commit 1cdc4ce
Showing 1 changed file with 64 additions and 73 deletions.
137 changes: 64 additions & 73 deletions houston-common-ui/lib/composables/validation.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { z } from "zod";
import { fromError as ZodValidationErrorFromError } from "zod-validation-error";
import {
onBeforeMount,
onBeforeUnmount,
onMounted,
onUnmounted,
ref,
computed,
type Ref,
watchEffect,
type ComputedRef,
type WatchStopHandle,
} from "vue";

export type ValidationResultAction = {
Expand All @@ -35,85 +35,76 @@ export type ValidationResult = (

export type Validator = () => ValidationResult | PromiseLike<ValidationResult>;

export type ValidationScope = Ref<Ref<ValidationResult>[]>;
export class ValidationScope {
private validatorResults: Ref<Ref<ValidationResult>[]>;
private allValidatorsOkay: ComputedRef<boolean>;

const validationScopeStack = ref<ValidationScope[]>([ref([])]);
constructor() {
this.validatorResults = ref([]);
this.allValidatorsOkay = computed(() =>
this.validatorResults.value.every((result) => result.value.type !== "error")
);
}

const pushValidationScope = (scope: ValidationScope) => {
validationScopeStack.value = [...validationScopeStack.value, scope];
};
const removeValidationScope = (scope: ValidationScope) => {
validationScopeStack.value = validationScopeStack.value.filter(
(s) => s !== scope
);
};
private addValidatorResult(result: Ref<ValidationResult>) {
this.validatorResults.value = [...this.validatorResults.value, result];
}

const getCurrentValidationScope = () =>
validationScopeStack.value[validationScopeStack.value.length - 1];
private removeValidatorResult(result: Ref<ValidationResult>) {
this.validatorResults.value = this.validatorResults.value.filter((r) => r !== result);
}

export function useValidationScope() {
const scope: ValidationScope = ref([]);
onBeforeMount(() => {
pushValidationScope(scope);
});
onUnmounted(() => {
removeValidationScope(scope);
});
const scopeValid = computed<boolean>(() =>
scope.value.every((v) => v.value.type !== "error")
);
return { scope, scopeValid };
}
useValidator(validator: Validator) {
const validationResult = ref<ValidationResult>({
type: "success",
});
const triggerUpdate = () => {
const result = validator();
Promise.resolve(result).then(
(result) =>
(validationResult.value = {
...result,
actions: result.actions?.map(({ label, callback }) => ({
label,
callback: () => Promise.resolve(callback()).then(() => triggerUpdate()),
})),
})
);
};
let stopWatcher: WatchStopHandle;
onMounted(() => {
stopWatcher = watchEffect(triggerUpdate);
this.addValidatorResult(validationResult);
});
onUnmounted(() => {
this.removeValidatorResult(validationResult);
stopWatcher?.();
});
return { validationResult, triggerUpdate };
}

export function useValidator(validator: Validator, scope?: ValidationScope) {
const validationResult = ref<ValidationResult>({
type: "success",
});
const triggerUpdate = () => {
const result = validator();
Promise.resolve(result).then(
(result) =>
(validationResult.value = {
...result,
actions: result.actions?.map(({ label, callback }) => ({
label,
callback: () =>
Promise.resolve(callback()).then(() => triggerUpdate()),
})),
})
);
};
watchEffect(triggerUpdate);
onMounted(() => {
scope ??= getCurrentValidationScope();
scope.value = [...scope.value, validationResult];
});
onBeforeUnmount(() => {
scope ??= getCurrentValidationScope();
scope.value = scope.value.filter((r) => r !== validationResult);
});
return { validationResult, triggerUpdate };
}
useZodValidator<Z extends z.ZodTypeAny = z.ZodNever>(
schema: Z,
getter: () => z.infer<Z>,
scope?: ValidationScope
) {
const validator: Validator = () =>
schema
.safeParseAsync(getter())
.then((result) =>
result.success
? validationSuccess()
: validationError(ZodValidationErrorFromError(result.error).message)
);
return this.useValidator(validator);
}

export function useZodValidator<Z extends z.ZodTypeAny = z.ZodNever>(
schema: Z,
getter: () => z.infer<Z>,
scope?: ValidationScope
) {
const validator: Validator = () =>
schema
.safeParseAsync(getter())
.then((result) =>
result.success
? validationSuccess()
: validationError(ZodValidationErrorFromError(result.error).message)
);
return useValidator(validator, scope);
isValid(): boolean {
return this.allValidatorsOkay.value;
}
}

export function validationSuccess(
actions?: ValidationResultAction[]
): ValidationResult {
export function validationSuccess(actions?: ValidationResultAction[]): ValidationResult {
return {
type: "success",
actions,
Expand Down

0 comments on commit 1cdc4ce

Please sign in to comment.