wag-managment-api-service-v.../docs/improvements/validation_service/frontend/unifiedSchemaBuilder.ts

220 lines
5.4 KiB
TypeScript

import { z } from 'zod';
import axios from 'axios';
interface ValidationMessages {
required: string;
invalid_type: string;
invalid_string: Record<string, string>;
too_small: Record<string, string>;
too_big: Record<string, string>;
invalid_date: string;
invalid_enum: string;
custom: Record<string, string>;
}
interface SchemaField {
type: string;
items?: string;
values?: any[];
validations?: Record<string, any>;
}
interface SchemaDefinition {
name: string;
fields: Record<string, SchemaField>;
messages: ValidationMessages;
}
class UnifiedSchemaBuilder {
private static instance: UnifiedSchemaBuilder;
private schemaCache: Map<string, z.ZodSchema> = new Map();
private constructor() {}
static getInstance(): UnifiedSchemaBuilder {
if (!UnifiedSchemaBuilder.instance) {
UnifiedSchemaBuilder.instance = new UnifiedSchemaBuilder();
}
return UnifiedSchemaBuilder.instance;
}
async getSchema(modelName: string): Promise<z.ZodSchema> {
// Check cache first
if (this.schemaCache.has(modelName)) {
return this.schemaCache.get(modelName)!;
}
// Fetch schema definition with messages from backend
const response = await axios.get<SchemaDefinition>(
`/api/schema/model/${modelName}`,
{
headers: {
'Accept-Language': navigator.language || 'tr'
}
}
);
const schema = this.buildSchema(response.data);
this.schemaCache.set(modelName, schema);
return schema;
}
private buildSchema(definition: SchemaDefinition): z.ZodSchema {
const shape: Record<string, z.ZodTypeAny> = {};
for (const [fieldName, field] of Object.entries(definition.fields)) {
shape[fieldName] = this.buildField(field, definition.messages);
}
return z.object(shape);
}
private buildField(
field: SchemaField,
messages: ValidationMessages
): z.ZodTypeAny {
let zodField: z.ZodTypeAny;
switch (field.type) {
case 'string':
zodField = z.string({
required_error: messages.required,
invalid_type_error: messages.invalid_type
});
break;
case 'email':
zodField = z.string().email(messages.invalid_string.email);
break;
case 'number':
zodField = z.number({
required_error: messages.required,
invalid_type_error: messages.invalid_type
});
break;
case 'boolean':
zodField = z.boolean({
required_error: messages.required,
invalid_type_error: messages.invalid_type
});
break;
case 'date':
zodField = z.date({
required_error: messages.required,
invalid_type_error: messages.invalid_date
});
break;
case 'array':
zodField = z.array(
this.buildField({ type: field.items! }, messages)
);
break;
case 'enum':
zodField = z.enum(field.values as [string, ...string[]], {
required_error: messages.required,
invalid_type_error: messages.invalid_enum
});
break;
default:
zodField = z.any();
}
// Apply validations if any
if (field.validations) {
zodField = this.applyValidations(zodField, field.validations, messages);
}
return zodField;
}
private applyValidations(
field: z.ZodTypeAny,
validations: Record<string, any>,
messages: ValidationMessages
): z.ZodTypeAny {
let result = field;
if ('min_length' in validations) {
result = (result as z.ZodString).min(
validations.min_length,
messages.too_small.string.replace(
'{min}',
validations.min_length.toString()
)
);
}
if ('max_length' in validations) {
result = (result as z.ZodString).max(
validations.max_length,
messages.too_big.string.replace(
'{max}',
validations.max_length.toString()
)
);
}
if ('pattern' in validations) {
result = (result as z.ZodString).regex(
new RegExp(validations.pattern),
messages.custom[validations.pattern_message] || 'Invalid format'
);
}
if ('gt' in validations) {
result = (result as z.ZodNumber).gt(
validations.gt,
messages.too_small.number.replace(
'{min}',
(validations.gt + 1).toString()
)
);
}
if ('lt' in validations) {
result = (result as z.ZodNumber).lt(
validations.lt,
messages.too_big.number.replace(
'{max}',
(validations.lt - 1).toString()
)
);
}
return result;
}
}
// Export singleton instance
export const schemaBuilder = UnifiedSchemaBuilder.getInstance();
// Usage example:
/*
import { schemaBuilder } from './validation/unifiedSchemaBuilder';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
function UserForm() {
const [schema, setSchema] = useState<z.ZodSchema | null>(null);
useEffect(() => {
async function loadSchema() {
const userSchema = await schemaBuilder.getSchema('User');
setSchema(userSchema);
}
loadSchema();
}, []);
const form = useForm({
resolver: schema ? zodResolver(schema) : undefined
});
if (!schema) return <div>Loading...</div>;
return (
<form onSubmit={form.handleSubmit(data => console.log(data))}>
{/* Your form fields */}
</form>
);
}
*/