Skip to main content
Glama

firewalla-mcp-server

data-normalizer.ts9.08 kB
/** * Simple data safety utilities for Firewalla MCP Server * Provides basic null safety and prevents crashes from malformed API data */ import type { GeographicData, SanitizationResult } from '../types.js'; /** * Basic null safety for objects - prevents crashes from null/undefined data */ export function safeAccess(data: any): any { if (data === null || data === undefined) { return null; } if (Array.isArray(data)) { return data.map(item => safeAccess(item)); } if (typeof data === 'object') { const safe: any = {}; for (const [key, value] of Object.entries(data)) { safe[key] = safeAccess(value); } return safe; } return data; } /** * Simple value sanitization - handles null/undefined and basic type safety */ export function safeValue(value: any, defaultValue?: any): any { if (value === null || value === undefined) { return defaultValue ?? null; } if (typeof value === 'number' && !Number.isFinite(value)) { return defaultValue ?? null; } return value; } /** * Basic geographic data safety - just prevents crashes and provides defaults */ export function safeGeoData(geoData: any): GeographicData { if (!geoData || typeof geoData !== 'object') { return { country: 'unknown', country_code: 'UN', continent: 'unknown', region: 'unknown', city: 'unknown', timezone: 'unknown', }; } return { country: safeValue(geoData.country || geoData.Country, 'unknown'), country_code: safeValue(geoData.country_code || geoData.countryCode, 'UN'), continent: safeValue(geoData.continent, 'unknown'), region: safeValue(geoData.region, 'unknown'), city: safeValue(geoData.city, 'unknown'), timezone: safeValue(geoData.timezone, 'unknown'), }; } /** * Safe numeric conversion */ function safeNumber(value: any): number | null { if (value === null || value === undefined) { return null; } if (typeof value === 'number') { return Number.isFinite(value) ? value : null; } if (typeof value === 'string') { const parsed = Number(value.trim()); return Number.isFinite(parsed) ? parsed : null; } return null; } /** * Safe byte count conversion */ export function safeByteCount(value: any, defaultValue: number = 0): number { const num = safeNumber(value); return num !== null ? num : defaultValue; } /** * Simple boolean conversion */ export function safeBoolean(value: any): boolean { if (typeof value === 'boolean') { return value; } if (typeof value === 'string') { return value.toLowerCase() === 'true'; } if (typeof value === 'number') { return value !== 0; } return false; } /** * Normalization configuration interface */ export interface NormalizationConfig { defaultUnknownValue: string; preserveNull: boolean; trimWhitespace: boolean; lowerCaseFields: string[]; } /** * Sanitize field value with default fallback and tracking */ export function sanitizeFieldValue( value: any, defaultValue?: any ): SanitizationResult { const modifications: string[] = []; let processedValue = value; let wasModified = false; // Handle null/undefined if (value === null || value === undefined) { processedValue = defaultValue ?? null; wasModified = true; modifications.push('replaced null with default'); } // Handle empty strings else if (typeof value === 'string' && value === '') { processedValue = defaultValue ?? ''; wasModified = true; modifications.push('replaced empty string with default'); } // Handle string trimming else if (typeof value === 'string' && value !== value.trim()) { processedValue = value.trim(); wasModified = true; modifications.push('trimmed whitespace'); } // Handle NaN numbers else if (typeof value === 'number' && !Number.isFinite(value)) { processedValue = defaultValue ?? 0; wasModified = true; modifications.push('replaced NaN with default'); } return { value: processedValue, wasModified, modifications, }; } /** * Normalize unknown fields to consistent values */ export function normalizeUnknownFields( value: any, config?: Partial<NormalizationConfig>, visited: WeakSet<object> = new WeakSet() ): any { const defaultConfig: NormalizationConfig = { defaultUnknownValue: 'unknown', preserveNull: false, trimWhitespace: true, lowerCaseFields: [], }; const actualConfig = { ...defaultConfig, ...config }; if (Array.isArray(value)) { if (visited.has(value)) { return '[Circular Reference]'; } visited.add(value); const result = value.map(item => normalizeUnknownFields(item, actualConfig, visited) ); visited.delete(value); return result; } if (value && typeof value === 'object') { if (visited.has(value)) { return '[Circular Reference]'; } visited.add(value); const normalized: any = {}; for (const [key, val] of Object.entries(value)) { normalized[key] = normalizeUnknownFields(val, actualConfig, visited); } visited.delete(value); return normalized; } // Handle string values if (typeof value === 'string') { const trimmed = actualConfig.trimWhitespace ? value.trim() : value; const lower = trimmed.toLowerCase(); if ( ['unknown', '', 'n/a', 'none', 'null', 'undefined'].includes(lower) || trimmed === '' ) { return actualConfig.defaultUnknownValue; } return trimmed; } // Handle null/undefined if (value === null || value === undefined) { return actualConfig.preserveNull ? null : actualConfig.defaultUnknownValue; } return value; } /** * Batch normalize array of objects */ export function batchNormalize( items: any[], transformers: Record<string, (value: any) => any> ): any[] { if (!Array.isArray(items)) { return []; } return items.map(item => { // Start with all original fields const normalized: any = { ...item }; // Apply transformations for (const [key, transformer] of Object.entries(transformers)) { // Only apply transformation if the field exists in the item if (key in item) { normalized[key] = transformer(item[key]); } } return normalized; }); } /** * Alias for safeByteCount for backward compatibility */ export const sanitizeByteCount = safeByteCount; /** * Ensure consistent geographic data formatting */ export function ensureConsistentGeoData( geoData: any ): GeographicData & { data_quality?: string } { if (!geoData || typeof geoData !== 'object') { return { country: 'unknown', country_code: 'UN', continent: 'unknown', region: 'unknown', city: 'unknown', timezone: 'unknown', data_quality: 'missing', }; } // Helper function to title case const toTitleCase = (str: string): string => { if (!str) { return 'unknown'; } return str.toLowerCase().replace(/\b\w/g, l => l.toUpperCase()); }; // Helper function to handle country code const normalizeCountryCode = (code: any): string => { if (!code || typeof code !== 'string') { return 'UN'; } // Only accept 2-letter ISO codes with letters only if (code.length !== 2 || !/^[a-zA-Z]{2}$/.test(code)) { return 'UN'; } return code.toUpperCase(); }; // Helper function to handle ASN const normalizeASN = (asn: any): number | undefined => { if (asn === null || asn === undefined) { return undefined; } const num = typeof asn === 'string' ? parseInt(asn, 10) : asn; return Number.isNaN(num) ? undefined : num; }; // Helper function to handle boolean values const normalizeBoolean = (value: any): boolean | undefined => { if (value === null || value === undefined) { return undefined; } if (typeof value === 'boolean') { return value; } if (typeof value === 'string') { const lower = value.toLowerCase(); return lower === 'true' || lower === 'yes' || lower === '1'; } if (typeof value === 'number') { return value !== 0; } return undefined; }; return { country: toTitleCase(geoData.Country || geoData.country || ''), country_code: normalizeCountryCode( geoData.CountryCode || geoData.countryCode || geoData.country_code ), continent: toTitleCase(geoData.Continent || geoData.continent || ''), region: toTitleCase(geoData.Region || geoData.region || ''), city: toTitleCase(geoData.CITY || geoData.City || geoData.city || ''), timezone: geoData.timezone || 'unknown', asn: normalizeASN(geoData.ASN || geoData.asn), isp: toTitleCase(geoData.isp || geoData.ISP || ''), organization: toTitleCase( geoData.organization || geoData.Organization || '' ), hosting_provider: geoData.hosting_provider || geoData.HostingProvider || undefined, is_vpn: normalizeBoolean(geoData.is_vpn), is_cloud_provider: normalizeBoolean(geoData.is_cloud_provider), is_proxy: normalizeBoolean(geoData.is_proxy), }; }

Latest Blog Posts

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/amittell/firewalla-mcp-server'

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