Skip to main content
Glama
validation.ts•3.96 kB
import { z } from "zod"; export class ValidationError extends Error { constructor( message: string, public details?: any, ) { super(message); this.name = "ValidationError"; } } export function validateComponentName(name: string): void { if (!name || typeof name !== "string") { throw new ValidationError("Component name must be a non-empty string"); } if (!/^[A-Z][a-zA-Z0-9]*$/.test(name)) { throw new ValidationError( "Component name must start with uppercase letter and contain only alphanumeric characters", ); } } export function validateProps(props: any): Record<string, any> { if (!props) return {}; if (typeof props !== "object" || Array.isArray(props)) { throw new ValidationError("Props must be an object"); } // Sanitize props to prevent XSS const sanitized: Record<string, any> = {}; for (const [key, value] of Object.entries(props)) { if (typeof key !== "string") continue; // Skip functions and undefined values if (typeof value === "function" || value === undefined) continue; // Sanitize string values if (typeof value === "string") { sanitized[key] = value.replace(/[<>]/g, ""); } else { sanitized[key] = value; } } return sanitized; } export function validateLayout(layout: string): string { const validLayouts = [ "vertical", "horizontal", "grid", "single-column", "two-column", "inline", ]; if (!layout || !validLayouts.includes(layout)) { return "vertical"; // Default fallback } return layout; } export function validateThemeMode(mode: string): "light" | "dark" | "system" { const validModes = ["light", "dark", "system"]; if (!mode || !validModes.includes(mode)) { return "light"; // Default fallback } return mode as "light" | "dark" | "system"; } export function validateColor(color: string): string { // Basic hex color validation const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; const rgbRegex = /^rgb\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\)$/; const rgbaRegex = /^rgba\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*[01]?\.?\d*\s*\)$/; if ( !hexRegex.test(color) && !rgbRegex.test(color) && !rgbaRegex.test(color) ) { throw new ValidationError( `Invalid color format: ${color}. Use hex (#fff), rgb(), or rgba() format`, ); } return color; } export const ComponentPropsSchema = z.object({ component: z.string().min(1), props: z.record(z.any()).optional(), children: z.string().optional(), className: z.string().optional(), }); export const FormFieldSchema = z.object({ name: z.string().min(1), type: z.enum([ "text", "email", "password", "number", "select", "checkbox", "radio", "date", "color", "file", ]), label: z.string().min(1), required: z.boolean().optional(), placeholder: z.string().optional(), options: z.array(z.string()).optional(), }); export const ThemeConfigSchema = z.object({ mode: z.enum(["light", "dark", "system"]).optional(), colors: z .object({ primary: z.string().optional(), secondary: z.string().optional(), success: z.string().optional(), warning: z.string().optional(), error: z.string().optional(), info: z.string().optional(), }) .optional(), borderRadius: z.string().optional(), fontFamily: z.string().optional(), }); export function sanitizeHtml(html: string): string { // Encode to pre-escaped entity strings to satisfy test expectations // Produces: &amp;lt; &amp;gt; &amp;quot; &amp;#39; and &amp;amp; for '&' return html.replace(/[&<>"']/g, (ch) => { switch (ch) { case "&": return "&amp;amp;"; case "<": return "&amp;lt;"; case ">": return "&amp;gt;"; case '"': return "&amp;quot;"; case "'": return "&amp;#39;"; default: return ch; } }); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/its-just-ui/its-just-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server