fortify-schema
Version:
A modern TypeScript validation library designed around familiar interface syntax and powerful conditional validation. Experience schema validation that feels natural to TypeScript developers while unlocking advanced runtime validation capabilities.
588 lines (492 loc) β’ 18 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Custom Error Messages - Fortify Schema</title>
<meta name="description" content="Learn how to use custom error messages in Fortify Schema for better user experience and clearer validation feedback.">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: #333;
background: #f5f7fa;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 60px 20px;
text-align: center;
margin-bottom: 40px;
border-radius: 10px;
}
header h1 {
font-size: 3em;
margin-bottom: 10px;
}
header p {
font-size: 1.3em;
opacity: 0.9;
}
.badge {
display: inline-block;
background: rgba(255, 255, 255, 0.2);
padding: 5px 15px;
border-radius: 20px;
font-size: 0.9em;
margin-top: 15px;
}
nav {
background: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
nav h3 {
margin-bottom: 15px;
color: #667eea;
}
nav ul {
list-style: none;
}
nav ul li {
margin: 8px 0;
}
nav ul li a {
color: #555;
text-decoration: none;
transition: color 0.3s;
}
nav ul li a:hover {
color: #667eea;
}
.content {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
h2 {
color: #667eea;
margin: 30px 0 20px 0;
font-size: 2em;
border-bottom: 3px solid #667eea;
padding-bottom: 10px;
}
h3 {
color: #764ba2;
margin: 25px 0 15px 0;
font-size: 1.5em;
}
h4 {
color: #555;
margin: 20px 0 10px 0;
font-size: 1.2em;
}
code {
background: #f4f4f4;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
color: #e83e8c;
}
pre {
background: #2d2d2d;
color: #f8f8f2;
padding: 20px;
border-radius: 8px;
overflow-x: auto;
margin: 20px 0;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
pre code {
background: none;
color: inherit;
padding: 0;
}
.example {
background: #f8f9fa;
border-left: 4px solid #667eea;
padding: 20px;
margin: 20px 0;
border-radius: 5px;
}
.example-title {
font-weight: bold;
color: #667eea;
margin-bottom: 10px;
}
.good {
border-left-color: #28a745;
}
.good .example-title {
color: #28a745;
}
.good::before {
content: "β
";
}
.bad {
border-left-color: #dc3545;
}
.bad .example-title {
color: #dc3545;
}
.bad::before {
content: "β ";
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin: 30px 0;
}
.feature-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 25px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.feature-card h4 {
color: white;
margin-bottom: 10px;
}
.syntax-box {
background: #fff3cd;
border: 2px solid #ffc107;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
}
.syntax-box h3 {
color: #856404;
margin-top: 0;
}
ul, ol {
margin: 15px 0 15px 30px;
}
li {
margin: 8px 0;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background: #667eea;
color: white;
}
tr:hover {
background: #f5f5f5;
}
.tip {
background: #d1ecf1;
border-left: 4px solid #17a2b8;
padding: 15px;
margin: 20px 0;
border-radius: 5px;
}
.tip strong {
color: #0c5460;
}
footer {
text-align: center;
padding: 40px 20px;
color: #666;
margin-top: 50px;
}
footer a {
color: #667eea;
text-decoration: none;
}
footer a:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
header h1 {
font-size: 2em;
}
.content {
padding: 20px;
}
.feature-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>π― Custom Error Messages</h1>
<p>Provide user-friendly, context-specific validation feedback</p>
<span class="badge">New in v2.2.x</span>
</header>
<nav>
<h3>π Table of Contents</h3>
<ul>
<li><a href="#syntax">Syntax</a></li>
<li><a href="#basic-usage">Basic Usage</a></li>
<li><a href="#advanced">Advanced Examples</a></li>
<li><a href="#best-practices">Best Practices</a></li>
<li><a href="#type-inference">Type Inference</a></li>
<li><a href="#combining">Combining Features</a></li>
<li><a href="#faq">FAQ</a></li>
</ul>
</nav>
<div class="content">
<section id="syntax">
<h2>π Syntax</h2>
<div class="syntax-box">
<h3>Basic Format</h3>
<pre><code>"type --> Your custom error message"</code></pre>
</div>
<p>The <code>--></code> operator separates the type definition from your custom error message.</p>
<h4>Key Points:</h4>
<ul>
<li>Spaces around <code>--></code> are optional</li>
<li>Works with all type definitions, constraints, and modifiers</li>
<li>TypeScript inference correctly strips the message</li>
<li>Minimal performance impact</li>
</ul>
<div class="example">
<div class="example-title">All these formats work:</div>
<pre><code>// With spaces
"string --> Please provide your name"
// No space before
"string--> Please provide your name"
// No spaces at all
"string-->Please provide your name"</code></pre>
</div>
</section>
<section id="basic-usage">
<h2>π Basic Usage</h2>
<h3>Simple Type Validation</h3>
<pre><code>import { Interface } from "fortify-schema";
const schema = Interface({
name: "string --> Please provide your name",
age: "number --> Age must be a valid number",
email: "email --> Please enter a valid email address",
});
const result = schema.safeParse({
name: 123, // Wrong type
});
console.log(result.errors[0].message);
// Output: "Validation failed: Please provide your name in field \"name\""</code></pre>
<h3>With Constraints</h3>
<pre><code>const ProductSchema = Interface({
price: "number(0.01,) --> Price must be greater than 0",
quantity: "int(1,1000) --> Quantity must be between 1 and 1000",
name: "string(3,100) --> Product name must be 3-100 characters",
});</code></pre>
<h3>With Union Types</h3>
<pre><code>const StatusSchema = Interface({
status: "pending|approved|rejected --> Status must be pending, approved, or rejected",
role: "admin|user|guest --> Invalid role selected",
});</code></pre>
</section>
<section id="advanced">
<h2>π₯ Advanced Examples</h2>
<div class="feature-grid">
<div class="feature-card">
<h4>Required Fields</h4>
<p>Combine with <code>!</code> modifier</p>
<pre><code>email: "email! --> Email is required"</code></pre>
</div>
<div class="feature-card">
<h4>Optional Fields</h4>
<p>Combine with <code>?</code> modifier</p>
<pre><code>phone: "string? --> Phone must be valid if provided"</code></pre>
</div>
<div class="feature-card">
<h4>Array Validation</h4>
<p>Works with arrays too</p>
<pre><code>tags: "string[] --> Must be an array of strings"</code></pre>
</div>
<div class="feature-card">
<h4>Complex Constraints</h4>
<p>Multiple constraints</p>
<pre><code>username: "string(3,20)! --> Username must be 3-20 chars"</code></pre>
</div>
</div>
<h3>Nested Objects</h3>
<pre><code>const ProfileSchema = Interface({
user: {
name: "string --> Name is required",
email: "email --> Please provide a valid email",
settings: {
theme: "light|dark --> Theme must be light or dark",
notifications: "boolean --> Must be enabled or disabled",
},
},
});</code></pre>
</section>
<section id="best-practices">
<h2>β¨ Best Practices</h2>
<h3>1. Be Clear and Actionable</h3>
<div class="example bad">
<div class="example-title">Bad</div>
<pre><code>name: "string --> Invalid"</code></pre>
</div>
<div class="example good">
<div class="example-title">Good</div>
<pre><code>name: "string --> Please provide your full name"</code></pre>
</div>
<h3>2. Include Constraint Details</h3>
<div class="example bad">
<div class="example-title">Bad</div>
<pre><code>age: "number(18,65) --> Invalid age"</code></pre>
</div>
<div class="example good">
<div class="example-title">Good</div>
<pre><code>age: "number(18,65) --> Age must be between 18 and 65"</code></pre>
</div>
<h3>3. Use Consistent Tone</h3>
<pre><code>const schema = Interface({
email: "email --> Please enter a valid email address",
phone: "string --> Please provide your phone number",
address: "string --> Please enter your full address",
});</code></pre>
<h3>4. Keep Messages Concise</h3>
<div class="tip">
<strong>π‘ Tip:</strong> Aim for messages under 100 characters. Users should understand the issue at a glance.
</div>
<h3>5. Consider Internationalization</h3>
<pre><code>// Use error codes for multi-language support
const schema = Interface({
email: "email --> ERR_INVALID_EMAIL",
});
// Map codes to localized messages
const messages = {
en: { ERR_INVALID_EMAIL: "Please enter a valid email" },
es: { ERR_INVALID_EMAIL: "Ingrese un correo vΓ‘lido" },
fr: { ERR_INVALID_EMAIL: "Entrez un e-mail valide" },
};</code></pre>
</section>
<section id="type-inference">
<h2>π― Type Inference</h2>
<p>TypeScript correctly infers types even with custom error messages:</p>
<pre><code>const schema = Interface({
status: "pending|approved|rejected --> Invalid status",
count: "number --> Count must be a number",
});
// TypeScript knows:
// status: "pending" | "approved" | "rejected"
// count: number
const data = schema.parse({
status: "pending", // β
TypeScript accepts this
// status: "invalid", // β TypeScript error: not in union
count: 42,
});</code></pre>
<div class="tip">
<strong>π‘ Pro Tip:</strong> The custom error message is completely transparent to TypeScript's type system. You get full type safety without any compromises!
</div>
</section>
<section id="combining">
<h2>π Combining with Other Features</h2>
<h3>With Conditional Validation</h3>
<pre><code>const schema = Interface({
accountType: "free|premium --> Account type must be free or premium",
maxProjects: "when accountType=free *? int(1,3) --> Free: 1-3 projects : int(1,100) --> Premium: 1-100 projects",
});</code></pre>
<h3>With Record Types</h3>
<pre><code>const ConfigSchema = Interface({
settings: "record<string, string> --> Settings must be key-value pairs",
metadata: "record<string, any> --> Metadata must be an object",
});</code></pre>
<h3>Mixed Required and Optional</h3>
<pre><code>const FormSchema = Interface({
// Required with custom message
email: "email! --> Email is required",
// Optional with custom message
phone: "string? --> Phone is optional but must be valid",
// Required with constraints
password: "string(8,)! --> Password must be at least 8 characters",
});</code></pre>
</section>
<section id="faq">
<h2>β FAQ</h2>
<h3>Can I use <code>--></code> in the error message itself?</h3>
<p>Yes! Only the first <code>--></code> is used as the separator:</p>
<pre><code>name: "string --> Use format: FirstName --> LastName"
// Type: string
// Message: "Use format: FirstName --> LastName"</code></pre>
<h3>What if I don't provide a custom message?</h3>
<p>The default error message is used:</p>
<pre><code>name: "string" // Default: "Expected String, but received ..."</code></pre>
<h3>Can I use emojis?</h3>
<p>Absolutely! Emojis work perfectly:</p>
<pre><code>email: "email --> π§ Please provide a valid email address",
age: "number(18,) --> π You must be 18 or older"</code></pre>
<h3>Does this affect performance?</h3>
<p>Minimal impact. The message is extracted once during schema creation. Validation performance is nearly identical.</p>
<h3>Which validators support custom messages?</h3>
<table>
<tr>
<th>Feature</th>
<th>Supported</th>
</tr>
<tr>
<td>Basic types (string, number, boolean)</td>
<td>β
Yes</td>
</tr>
<tr>
<td>Format types (email, url, uuid)</td>
<td>β
Yes</td>
</tr>
<tr>
<td>Constraints (min, max, length)</td>
<td>β
Yes</td>
</tr>
<tr>
<td>Union types</td>
<td>β
Yes</td>
</tr>
<tr>
<td>Arrays</td>
<td>β
Yes</td>
</tr>
<tr>
<td>Required fields (!)</td>
<td>β
Yes</td>
</tr>
<tr>
<td>Optional fields (?)</td>
<td>β
Yes</td>
</tr>
<tr>
<td>Nested objects</td>
<td>β
Yes</td>
</tr>
</table>
</section>
</div>
<footer>
<p>Made with β€οΈ by the <a href="https://github.com/Nehonix-Team">Nehonix Team</a></p>
<p>
<a href="https://github.com/Nehonix-Team/fortify-schema">GitHub</a> β’
<!-- <a href="https://discord.gg/nehonix">Discord</a> β’ -->
<a href="https://www.npmjs.com/package/fortify-schema">NPM</a>
</p>
</footer>
</div>
</body>
</html>