Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | 4x 2x 2x 2x 10x 10x 2x 2x 2x 8x 2x 2x 2x 6x 6x 1x 5x 5x 1x 4x 4x 6x 5x 6x 4x 3x 3x 3x 2x 4x 5x 3x | // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
/**
* @module Generators/Pipeline/TransformStage
* @description Data normalisation and schema validation for MCP responses.
*
* All functions are pure and side-effect-free (except structured logging).
* Validation failures are logged but never thrown — callers should check the
* returned {@link ValidationResult} and fall back gracefully.
*/
// ─── Validation types ─────────────────────────────────────────────────────────
/** Result of a schema validation check */
export interface ValidationResult {
/** Whether the response passed all checks */
valid: boolean;
/** Human-readable list of validation failures (empty when `valid` is true) */
errors: string[];
}
/**
* Validate the first item of a content array is a non-null object with a
* `text` string field. Returns an error description or null when valid.
*
* @param item - First element of the content array
* @returns Error message or null
*/
function validateFirstContentItem(item: unknown): string | null {
if (item === null || item === undefined || typeof item !== 'object' || Array.isArray(item)) {
return "First content item is missing a 'text' string field";
}
const obj = item as Record<string, unknown>;
return typeof obj['text'] === 'string'
? null
: "First content item is missing a 'text' string field";
}
// ─── MCP Response validation ──────────────────────────────────────────────────
/**
* Validate that an MCP tool response has the standard envelope shape:
* `{ content: [{ text: string }] }`.
*
* Does **not** throw — returns a {@link ValidationResult} so callers can
* choose their own degradation strategy.
*
* @param toolName - Tool name used in warning messages
* @param data - Raw response to validate
* @returns Validation result with any error descriptions
*/
export function validateMCPResponse(toolName: string, data: unknown): ValidationResult {
const errors: string[] = [];
if (data === null || data === undefined) {
errors.push('Response is null or undefined');
console.warn(`⚠️ MCP validation failed for ${toolName}: response is null/undefined`);
return { valid: false, errors };
}
if (typeof data !== 'object' || Array.isArray(data)) {
errors.push(`Expected object, got ${Array.isArray(data) ? 'array' : typeof data}`);
console.warn(`⚠️ MCP validation failed for ${toolName}:`, errors.join(', '));
return { valid: false, errors };
}
const response = data as Record<string, unknown>;
if (!Array.isArray(response['content'])) {
errors.push("Missing or invalid 'content' array");
} else {
const content = response['content'] as unknown[];
if (content.length === 0) {
errors.push('Empty content array');
} else {
const itemError = validateFirstContentItem(content[0]);
if (itemError) errors.push(itemError);
}
}
if (errors.length > 0) {
console.warn(`⚠️ MCP validation failed for ${toolName}:`, errors.join(', '));
}
return { valid: errors.length === 0, errors };
}
// ─── Data normalisation helpers ───────────────────────────────────────────────
/**
* Normalise a date string to YYYY-MM-DD ISO 8601 format.
* Returns the input unchanged when parsing fails.
*
* @param date - Raw date string
* @returns Normalised ISO 8601 date string
*/
export function normalizeISO8601Date(date: string): string {
if (!date) return date;
try {
const parsed = new Date(date);
if (isNaN(parsed.getTime())) return date;
return parsed.toISOString().split('T')[0] ?? date;
} catch {
return date;
}
}
/**
* Sanitise a text string by collapsing internal whitespace and trimming edges.
*
* @param text - Raw text string
* @returns Sanitised text string
*/
export function sanitizeText(text: string): string {
return text.replace(/\s+/g, ' ').trim();
}
/**
* Validate that a country code matches the ISO 3166-1 alpha-2 pattern.
*
* @param code - Two-letter country code
* @returns `true` when the code matches `[A-Z]{2}`
*/
export function isValidCountryCode(code: string): boolean {
return /^[A-Z]{2}$/.test(code);
}
/**
* Validate that a language code matches the ISO 639-1 alpha-2 pattern.
*
* @param code - Two-letter language code
* @returns `true` when the code matches `[a-z]{2}`
*/
export function isValidLanguageCode(code: string): boolean {
return /^[a-z]{2}$/.test(code);
}
|