diff --git a/errorhandler.php b/errorhandler.php
new file mode 100644
index 0000000..29e07d1
--- /dev/null
+++ b/errorhandler.php
@@ -0,0 +1,444 @@
+debug = $debug;
+ $this->registerDefaultHandlers();
+ }
+
+ /**
+ * register registers a custom error handler for a specific status code
+ */
+ public function register(int $status, callable $handler): void
+ {
+ $this->handlers[$status] = $handler;
+ }
+
+ /**
+ * setDefaultHandler sets the default error handler for unregistered status codes
+ */
+ public function setDefaultHandler(callable $handler): void
+ {
+ $this->defaultHandler = $handler;
+ }
+
+ /**
+ * handle handles an error with the appropriate handler
+ */
+ public function handle(Context $context, int $status, string $message = '', ?Exception $exception = null): void
+ {
+ if (isset($this->handlers[$status])) {
+ $handler = $this->handlers[$status];
+ $handler($context, $status, $message, $exception);
+ return;
+ }
+
+ if ($this->defaultHandler) {
+ ($this->defaultHandler)($context, $status, $message, $exception);
+ return;
+ }
+
+ $this->renderDefaultError($context, $status, $message, $exception);
+ }
+
+ /**
+ * handleException handles uncaught exceptions
+ */
+ public function handleException(Context $context, Exception $exception): void
+ {
+ $status = $this->getStatusFromException($exception);
+ $message = $this->debug ? $exception->getMessage() : $this->getDefaultMessage($status);
+
+ if ($this->debug) {
+ error_log($exception->getMessage() . "\n" . $exception->getTraceAsString());
+ }
+
+ $this->handle($context, $status, $message, $exception);
+ }
+
+ /**
+ * getStatusFromException determines HTTP status from exception type
+ */
+ private function getStatusFromException(Exception $exception): int
+ {
+ if ($exception instanceof HttpException) {
+ return $exception->getStatusCode();
+ }
+
+ return match(get_class($exception)) {
+ 'InvalidArgumentException' => 400,
+ 'UnauthorizedException' => 401,
+ 'ForbiddenException' => 403,
+ 'NotFoundException' => 404,
+ 'MethodNotAllowedException' => 405,
+ 'ValidationException' => 422,
+ default => 500
+ };
+ }
+
+ /**
+ * registerDefaultHandlers registers built-in error handlers
+ */
+ private function registerDefaultHandlers(): void
+ {
+ // 404 Not Found
+ $this->register(404, function(Context $context, int $status, string $message) {
+ $accept = $context->request->header('accept') ?? '';
+
+ if (str_contains($accept, 'application/json')) {
+ $context->json(['error' => $message ?: 'Not Found'], 404);
+ } else {
+ $html = $this->render404Page($message);
+ $context->html($html, 404);
+ }
+ });
+
+ // 500 Internal Server Error
+ $this->register(500, function(Context $context, int $status, string $message, ?Exception $exception) {
+ $accept = $context->request->header('accept') ?? '';
+
+ if (str_contains($accept, 'application/json')) {
+ $response = ['error' => $message ?: 'Internal Server Error'];
+ if ($this->debug && $exception) {
+ $response['trace'] = $exception->getTrace();
+ }
+ $context->json($response, 500);
+ } else {
+ $html = $this->render500Page($message, $exception);
+ $context->html($html, 500);
+ }
+ });
+ }
+
+ /**
+ * renderDefaultError renders a generic error response
+ */
+ private function renderDefaultError(Context $context, int $status, string $message, ?Exception $exception): void
+ {
+ $message = $message ?: $this->getDefaultMessage($status);
+ $accept = $context->request->header('accept') ?? '';
+
+ if (str_contains($accept, 'application/json')) {
+ $response = ['error' => $message];
+ if ($this->debug && $exception) {
+ $response['trace'] = $exception->getTrace();
+ }
+ $context->json($response, $status);
+ } else {
+ $html = $this->renderErrorPage($status, $message, $exception);
+ $context->html($html, $status);
+ }
+ }
+
+ /**
+ * getDefaultMessage gets default message for status code
+ */
+ private function getDefaultMessage(int $status): string
+ {
+ return match($status) {
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 422 => 'Unprocessable Entity',
+ 429 => 'Too Many Requests',
+ 500 => 'Internal Server Error',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ default => 'Error'
+ };
+ }
+
+ /**
+ * render404Page renders a 404 error page
+ */
+ private function render404Page(string $message): string
+ {
+ $message = htmlspecialchars($message ?: 'Not Found');
+
+ return <<
+
+
+ 404 - Not Found
+
+
+
+
+
404
+
{$message}
+
The page you are looking for could not be found.
+
Go to Homepage
+
+
+
+HTML;
+ }
+
+ /**
+ * render500Page renders a 500 error page
+ */
+ private function render500Page(string $message, ?Exception $exception): string
+ {
+ $message = htmlspecialchars($message ?: 'Internal Server Error');
+ $debugInfo = '';
+
+ if ($this->debug && $exception) {
+ $exceptionClass = get_class($exception);
+ $file = htmlspecialchars($exception->getFile());
+ $line = $exception->getLine();
+ $trace = htmlspecialchars($exception->getTraceAsString());
+
+ $debugInfo = <<
+ Debug Information
+ Exception: {$exceptionClass}
+ File: {$file}:{$line}
+ {$trace}
+
+HTML;
+ }
+
+ return <<
+
+
+ 500 - Internal Server Error
+
+
+
+
+
500
+
{$message}
+
Something went wrong on our end. Please try again later.
+ {$debugInfo}
+
+
+
+HTML;
+ }
+
+ /**
+ * renderErrorPage renders a generic error page
+ */
+ private function renderErrorPage(int $status, string $message, ?Exception $exception): string
+ {
+ $message = htmlspecialchars($message);
+ $debugInfo = '';
+
+ if ($this->debug && $exception) {
+ $exceptionClass = get_class($exception);
+ $file = htmlspecialchars($exception->getFile());
+ $line = $exception->getLine();
+ $trace = htmlspecialchars($exception->getTraceAsString());
+
+ $debugInfo = <<
+ Debug Information
+ Exception: {$exceptionClass}
+ File: {$file}:{$line}
+ {$trace}
+
+HTML;
+ }
+
+ return <<
+
+
+ {$status} - {$message}
+
+
+
+
+
{$status}
+ {$message}
+ {$debugInfo}
+
+
+
+HTML;
+ }
+}
+
+/**
+ * HttpException base class for HTTP exceptions
+ */
+class HttpException extends Exception
+{
+ protected int $statusCode;
+
+ public function __construct(int $statusCode, string $message = '', int $code = 0, Exception $previous = null)
+ {
+ $this->statusCode = $statusCode;
+ parent::__construct($message, $code, $previous);
+ }
+
+ public function getStatusCode(): int
+ {
+ return $this->statusCode;
+ }
+}
+
+/**
+ * ValidationException for validation errors
+ */
+class ValidationException extends HttpException
+{
+ private array $errors;
+
+ public function __construct(array $errors, string $message = 'Validation failed')
+ {
+ $this->errors = $errors;
+ parent::__construct(422, $message);
+ }
+
+ public function getErrors(): array
+ {
+ return $this->errors;
+ }
+}
diff --git a/validator.php b/validator.php
new file mode 100644
index 0000000..7175556
--- /dev/null
+++ b/validator.php
@@ -0,0 +1,355 @@
+data = $data;
+ $this->rules = $rules;
+ $this->messages = $messages;
+ $this->errors = [];
+
+ foreach ($rules as $field => $fieldRules) {
+ $this->validateField($field, $fieldRules);
+ }
+
+ return empty($this->errors);
+ }
+
+ /**
+ * validateField validates a single field against its rules
+ */
+ private function validateField(string $field, string|array $rules): void
+ {
+ $rules = is_string($rules) ? explode('|', $rules) : $rules;
+ $value = $this->getValue($field);
+
+ foreach ($rules as $rule) {
+ $this->applyRule($field, $value, $rule);
+ }
+ }
+
+ /**
+ * getValue gets a value from data using dot notation
+ */
+ private function getValue(string $field): mixed
+ {
+ $keys = explode('.', $field);
+ $value = $this->data;
+
+ foreach ($keys as $key) {
+ if (!is_array($value) || !array_key_exists($key, $value)) {
+ return null;
+ }
+ $value = $value[$key];
+ }
+
+ return $value;
+ }
+
+ /**
+ * applyRule applies a single validation rule
+ */
+ private function applyRule(string $field, mixed $value, string $rule): void
+ {
+ $parts = explode(':', $rule, 2);
+ $ruleName = $parts[0];
+ $parameters = isset($parts[1]) ? explode(',', $parts[1]) : [];
+
+ $passes = match($ruleName) {
+ 'required' => $this->validateRequired($value),
+ 'email' => $this->validateEmail($value),
+ 'url' => $this->validateUrl($value),
+ 'alpha' => $this->validateAlpha($value),
+ 'alphaNum' => $this->validateAlphaNum($value),
+ 'numeric' => $this->validateNumeric($value),
+ 'integer' => $this->validateInteger($value),
+ 'float' => $this->validateFloat($value),
+ 'boolean' => $this->validateBoolean($value),
+ 'array' => $this->validateArray($value),
+ 'json' => $this->validateJson($value),
+ 'date' => $this->validateDate($value),
+ 'min' => $this->validateMin($value, $parameters[0] ?? 0),
+ 'max' => $this->validateMax($value, $parameters[0] ?? 0),
+ 'between' => $this->validateBetween($value, $parameters[0] ?? 0, $parameters[1] ?? 0),
+ 'length' => $this->validateLength($value, $parameters[0] ?? 0),
+ 'in' => $this->validateIn($value, $parameters),
+ 'notIn' => $this->validateNotIn($value, $parameters),
+ 'regex' => $this->validateRegex($value, $parameters[0] ?? ''),
+ 'confirmed' => $this->validateConfirmed($field, $value),
+ 'unique' => $this->validateUnique($value, $parameters),
+ 'exists' => $this->validateExists($value, $parameters),
+ default => $this->applyCustomRule($ruleName, $value, $parameters)
+ };
+
+ if (!$passes) {
+ $this->addError($field, $ruleName, $parameters);
+ }
+ }
+
+ /**
+ * Validation rule methods
+ */
+ private function validateRequired(mixed $value): bool
+ {
+ if ($value === null) return false;
+ if (is_string($value) && trim($value) === '') return false;
+ if (is_array($value) && empty($value)) return false;
+ return true;
+ }
+
+ private function validateEmail(mixed $value): bool
+ {
+ if (!is_string($value)) return false;
+ return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
+ }
+
+ private function validateUrl(mixed $value): bool
+ {
+ if (!is_string($value)) return false;
+ return filter_var($value, FILTER_VALIDATE_URL) !== false;
+ }
+
+ private function validateAlpha(mixed $value): bool
+ {
+ if (!is_string($value)) return false;
+ return ctype_alpha($value);
+ }
+
+ private function validateAlphaNum(mixed $value): bool
+ {
+ if (!is_string($value)) return false;
+ return ctype_alnum($value);
+ }
+
+ private function validateNumeric(mixed $value): bool
+ {
+ return is_numeric($value);
+ }
+
+ private function validateInteger(mixed $value): bool
+ {
+ return filter_var($value, FILTER_VALIDATE_INT) !== false;
+ }
+
+ private function validateFloat(mixed $value): bool
+ {
+ return filter_var($value, FILTER_VALIDATE_FLOAT) !== false;
+ }
+
+ private function validateBoolean(mixed $value): bool
+ {
+ return in_array($value, [true, false, 0, 1, '0', '1', 'true', 'false'], true);
+ }
+
+ private function validateArray(mixed $value): bool
+ {
+ return is_array($value);
+ }
+
+ private function validateJson(mixed $value): bool
+ {
+ if (!is_string($value)) return false;
+ json_decode($value);
+ return json_last_error() === JSON_ERROR_NONE;
+ }
+
+ private function validateDate(mixed $value): bool
+ {
+ if (!is_string($value)) return false;
+ $date = date_parse($value);
+ return $date['error_count'] === 0 && $date['warning_count'] === 0;
+ }
+
+ private function validateMin(mixed $value, mixed $min): bool
+ {
+ if (is_numeric($value)) return $value >= $min;
+ if (is_string($value)) return strlen($value) >= $min;
+ if (is_array($value)) return count($value) >= $min;
+ return false;
+ }
+
+ private function validateMax(mixed $value, mixed $max): bool
+ {
+ if (is_numeric($value)) return $value <= $max;
+ if (is_string($value)) return strlen($value) <= $max;
+ if (is_array($value)) return count($value) <= $max;
+ return false;
+ }
+
+ private function validateBetween(mixed $value, mixed $min, mixed $max): bool
+ {
+ return $this->validateMin($value, $min) && $this->validateMax($value, $max);
+ }
+
+ private function validateLength(mixed $value, mixed $length): bool
+ {
+ if (is_string($value)) return strlen($value) == $length;
+ if (is_array($value)) return count($value) == $length;
+ return false;
+ }
+
+ private function validateIn(mixed $value, array $values): bool
+ {
+ return in_array($value, $values, true);
+ }
+
+ private function validateNotIn(mixed $value, array $values): bool
+ {
+ return !in_array($value, $values, true);
+ }
+
+ private function validateRegex(mixed $value, string $pattern): bool
+ {
+ if (!is_string($value)) return false;
+ return preg_match($pattern, $value) === 1;
+ }
+
+ private function validateConfirmed(string $field, mixed $value): bool
+ {
+ $confirmField = $field . '_confirmation';
+ return $value === $this->getValue($confirmField);
+ }
+
+ private function validateUnique(mixed $value, array $parameters): bool
+ {
+ // Placeholder for database uniqueness check
+ // Would require database connection in real implementation
+ return true;
+ }
+
+ private function validateExists(mixed $value, array $parameters): bool
+ {
+ // Placeholder for database existence check
+ // Would require database connection in real implementation
+ return true;
+ }
+
+ /**
+ * applyCustomRule applies a custom validation rule
+ */
+ private function applyCustomRule(string $ruleName, mixed $value, array $parameters): bool
+ {
+ if (!isset(self::$customRules[$ruleName])) {
+ return true; // Unknown rules pass by default
+ }
+
+ return call_user_func(self::$customRules[$ruleName], $value, $parameters, $this->data);
+ }
+
+ /**
+ * addError adds a validation error
+ */
+ private function addError(string $field, string $rule, array $parameters = []): void
+ {
+ $message = $this->messages["$field.$rule"]
+ ?? $this->messages[$rule]
+ ?? $this->getDefaultMessage($field, $rule, $parameters);
+
+ if (!isset($this->errors[$field])) {
+ $this->errors[$field] = [];
+ }
+
+ $this->errors[$field][] = $message;
+ }
+
+ /**
+ * getDefaultMessage gets a default error message
+ */
+ private function getDefaultMessage(string $field, string $rule, array $parameters): string
+ {
+ $fieldName = str_replace('_', ' ', $field);
+
+ return match($rule) {
+ 'required' => "The {$fieldName} field is required.",
+ 'email' => "The {$fieldName} must be a valid email address.",
+ 'url' => "The {$fieldName} must be a valid URL.",
+ 'alpha' => "The {$fieldName} may only contain letters.",
+ 'alphaNum' => "The {$fieldName} may only contain letters and numbers.",
+ 'numeric' => "The {$fieldName} must be a number.",
+ 'integer' => "The {$fieldName} must be an integer.",
+ 'float' => "The {$fieldName} must be a float.",
+ 'boolean' => "The {$fieldName} must be a boolean.",
+ 'array' => "The {$fieldName} must be an array.",
+ 'json' => "The {$fieldName} must be valid JSON.",
+ 'date' => "The {$fieldName} must be a valid date.",
+ 'min' => "The {$fieldName} must be at least {$parameters[0]}.",
+ 'max' => "The {$fieldName} must not be greater than {$parameters[0]}.",
+ 'between' => "The {$fieldName} must be between {$parameters[0]} and {$parameters[1]}.",
+ 'length' => "The {$fieldName} must be exactly {$parameters[0]} characters.",
+ 'in' => "The selected {$fieldName} is invalid.",
+ 'notIn' => "The selected {$fieldName} is invalid.",
+ 'regex' => "The {$fieldName} format is invalid.",
+ 'confirmed' => "The {$fieldName} confirmation does not match.",
+ 'unique' => "The {$fieldName} has already been taken.",
+ 'exists' => "The selected {$fieldName} is invalid.",
+ default => "The {$fieldName} is invalid."
+ };
+ }
+
+ /**
+ * errors returns all validation errors
+ */
+ public function errors(): array
+ {
+ return $this->errors;
+ }
+
+ /**
+ * failed checks if validation failed
+ */
+ public function failed(): bool
+ {
+ return !empty($this->errors);
+ }
+
+ /**
+ * passed checks if validation passed
+ */
+ public function passed(): bool
+ {
+ return empty($this->errors);
+ }
+
+ /**
+ * firstError gets the first error message
+ */
+ public function firstError(string $field = null): ?string
+ {
+ if ($field) {
+ return $this->errors[$field][0] ?? null;
+ }
+
+ foreach ($this->errors as $errors) {
+ if (!empty($errors)) {
+ return $errors[0];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * extend adds a custom validation rule
+ */
+ public static function extend(string $name, callable $callback): void
+ {
+ self::$customRules[$name] = $callback;
+ }
+}