new api service and logic implemented
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Original content from ApiEvents/ValidationServiceApi/schema_converter.py
|
||||
from typing import Dict, Any, Type, get_type_hints, get_args, get_origin
|
||||
from pydantic import BaseModel, Field, EmailStr
|
||||
from enum import Enum
|
||||
import inspect
|
||||
from fastapi import APIRouter
|
||||
from datetime import datetime
|
||||
|
||||
# ... rest of the file content ...
|
||||
@@ -0,0 +1,146 @@
|
||||
from typing import Dict, Any, Type, Optional
|
||||
from pydantic import BaseModel
|
||||
from fastapi import APIRouter, Header
|
||||
|
||||
class ValidationMessages(BaseModel):
|
||||
"""Messages for Zod validation"""
|
||||
required: str
|
||||
invalid_type: str
|
||||
invalid_string: Dict[str, str] # email, url, etc
|
||||
too_small: Dict[str, str] # string, array, number
|
||||
too_big: Dict[str, str] # string, array, number
|
||||
invalid_date: str
|
||||
invalid_enum: str
|
||||
custom: Dict[str, str]
|
||||
|
||||
class SchemaField(BaseModel):
|
||||
"""Schema field definition"""
|
||||
type: str
|
||||
items: Optional[str] = None # For arrays
|
||||
values: Optional[list] = None # For enums
|
||||
validations: Optional[Dict[str, Any]] = None
|
||||
|
||||
class SchemaDefinition(BaseModel):
|
||||
"""Complete schema definition"""
|
||||
name: str
|
||||
fields: Dict[str, SchemaField]
|
||||
messages: ValidationMessages
|
||||
|
||||
class UnifiedSchemaService:
|
||||
def __init__(self):
|
||||
self.messages = {
|
||||
"tr": ValidationMessages(
|
||||
required="Bu alan zorunludur",
|
||||
invalid_type="Geçersiz tip",
|
||||
invalid_string={
|
||||
"email": "Geçerli bir e-posta adresi giriniz",
|
||||
"url": "Geçerli bir URL giriniz",
|
||||
"uuid": "Geçerli bir UUID giriniz"
|
||||
},
|
||||
too_small={
|
||||
"string": "{min} karakterden az olamaz",
|
||||
"array": "En az {min} öğe gereklidir",
|
||||
"number": "En az {min} olmalıdır"
|
||||
},
|
||||
too_big={
|
||||
"string": "{max} karakterden fazla olamaz",
|
||||
"array": "En fazla {max} öğe olabilir",
|
||||
"number": "En fazla {max} olabilir"
|
||||
},
|
||||
invalid_date="Geçerli bir tarih giriniz",
|
||||
invalid_enum="Geçersiz seçim",
|
||||
custom={
|
||||
"password_match": "Şifreler eşleşmiyor",
|
||||
"strong_password": "Şifre güçlü değil"
|
||||
}
|
||||
),
|
||||
"en": ValidationMessages(
|
||||
required="This field is required",
|
||||
invalid_type="Invalid type",
|
||||
invalid_string={
|
||||
"email": "Please enter a valid email",
|
||||
"url": "Please enter a valid URL",
|
||||
"uuid": "Please enter a valid UUID"
|
||||
},
|
||||
too_small={
|
||||
"string": "Must be at least {min} characters",
|
||||
"array": "Must contain at least {min} items",
|
||||
"number": "Must be at least {min}"
|
||||
},
|
||||
too_big={
|
||||
"string": "Must be at most {max} characters",
|
||||
"array": "Must contain at most {max} items",
|
||||
"number": "Must be at most {max}"
|
||||
},
|
||||
invalid_date="Please enter a valid date",
|
||||
invalid_enum="Invalid selection",
|
||||
custom={
|
||||
"password_match": "Passwords do not match",
|
||||
"strong_password": "Password is not strong enough"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
def get_schema_with_messages(
|
||||
self,
|
||||
model: Type[BaseModel],
|
||||
lang: str = "tr"
|
||||
) -> SchemaDefinition:
|
||||
"""Get schema definition with validation messages"""
|
||||
fields: Dict[str, SchemaField] = {}
|
||||
|
||||
for field_name, field in model.__fields__.items():
|
||||
field_info = SchemaField(
|
||||
type=self._get_field_type(field.outer_type_),
|
||||
items=self._get_items_type(field.outer_type_),
|
||||
values=self._get_enum_values(field.outer_type_),
|
||||
validations=self._get_validations(field)
|
||||
)
|
||||
fields[field_name] = field_info
|
||||
|
||||
return SchemaDefinition(
|
||||
name=model.__name__,
|
||||
fields=fields,
|
||||
messages=self.messages[lang]
|
||||
)
|
||||
|
||||
def _get_field_type(self, type_: Type) -> str:
|
||||
# Implementation similar to SchemaConverter
|
||||
pass
|
||||
|
||||
def _get_items_type(self, type_: Type) -> Optional[str]:
|
||||
# Implementation similar to SchemaConverter
|
||||
pass
|
||||
|
||||
def _get_enum_values(self, type_: Type) -> Optional[list]:
|
||||
# Implementation similar to SchemaConverter
|
||||
pass
|
||||
|
||||
def _get_validations(self, field) -> Optional[Dict[str, Any]]:
|
||||
# Implementation similar to SchemaConverter
|
||||
pass
|
||||
|
||||
router = APIRouter(prefix="/api/schema", tags=["Schema"])
|
||||
schema_service = UnifiedSchemaService()
|
||||
|
||||
@router.get("/model/{model_name}")
|
||||
async def get_model_schema(
|
||||
model_name: str,
|
||||
accept_language: Optional[str] = Header(default="tr")
|
||||
) -> SchemaDefinition:
|
||||
"""Get model schema with validation messages"""
|
||||
# You'd need to implement model lookup
|
||||
models = {
|
||||
"User": UserModel,
|
||||
"Product": ProductModel,
|
||||
# Add your models here
|
||||
}
|
||||
|
||||
if model_name not in models:
|
||||
raise ValueError(f"Model {model_name} not found")
|
||||
|
||||
lang = accept_language.split(",")[0][:2]
|
||||
return schema_service.get_schema_with_messages(
|
||||
models[model_name],
|
||||
lang if lang in ["tr", "en"] else "tr"
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
// Original content from frontend/src/validation/dynamicSchema.ts
|
||||
import { z } from 'zod';
|
||||
import axios from 'axios';
|
||||
import { zodMessages } from './zodMessages';
|
||||
|
||||
// ... rest of the file content ...
|
||||
@@ -0,0 +1,219 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
*/
|
||||
Reference in New Issue
Block a user