Skip to main content
Glama
attribute-validator.tsβ€’18.8 kB
/** * Validation for Attio attribute types * Provides validation and type conversion for attribute values * to ensure they match Attio's expected types * * This module handles both validation and automatic type conversion * for common data format mismatches, which is particularly useful for * LLM-generated content where string representations of other data types * are common. */ /** * Supported attribute types in Attio */ export type AttributeType = | 'string' | 'number' | 'boolean' | 'date' | 'array' | 'object' | 'select' | 'record-reference'; /** * Result of attribute validation */ export interface ValidationResult<T = unknown> { /** Whether the validation was successful */ valid: boolean; /** The converted value (may differ from input if type conversion was applied) */ convertedValue?: T; /** Error message if validation failed */ error?: string; } /** * Validates an attribute value against the expected type * * This function validates that a value matches the expected attribute type, * and attempts to convert it when possible. It handles both strict validation * and auto-conversion for common type mismatches. * * @param attributeName - The name of the attribute being validated * @param value - The value to validate * @param expectedType - The expected attribute type * @returns Validation result with success status and optionally converted value * * @example * // Validate a string * validateAttributeValue('company_name', 'Acme Inc', 'string') * // Returns: { valid: true, convertedValue: 'Acme Inc' } * * @example * // Convert string to number * validateAttributeValue('employee_count', '250', 'number') * // Returns: { valid: true, convertedValue: 250 } * * @example * // Convert string to boolean * validateAttributeValue('is_active', 'yes', 'boolean') * // Returns: { valid: true, convertedValue: true } * * @example * // Handle invalid values * validateAttributeValue('revenue', 'not-a-number', 'number') * // Returns: { valid: false, error: 'Invalid number value...' } */ export function validateAttributeValue( attributeName: string, value: unknown, expectedType: AttributeType ): ValidationResult { // Handle null case first if (value === null || value === undefined) { return { valid: true, convertedValue: null }; } // Validate based on expected type switch (expectedType) { case 'boolean': return validateBooleanValue(attributeName, value); case 'number': return validateNumberValue(attributeName, value); case 'string': return validateStringValue(attributeName, value); case 'date': return validateDateValue(attributeName, value); case 'array': return validateArrayValue(attributeName, value); case 'object': return validateObjectValue(attributeName, value); case 'select': return validateSelectValue(attributeName, value); case 'record-reference': return validateRecordReferenceValue(attributeName, value); default: // If the type doesn't match any known type, pass it through return { valid: true, convertedValue: value }; } } /** * Validates a boolean value * * This function validates and converts values to boolean type. * It handles various string representations ('true', 'yes', 'on', '1') * and numeric values (1, 0) in addition to native boolean values. * * @param attributeName - The name of the attribute * @param value - The value to validate * @returns Validation result * * @example * // Native boolean * validateBooleanValue('is_active', true) * // Returns: { valid: true, convertedValue: true } * * @example * // String conversion * validateBooleanValue('is_active', 'yes') * // Returns: { valid: true, convertedValue: true } * * @example * // Numeric conversion * validateBooleanValue('is_active', 1) * // Returns: { valid: true, convertedValue: true } */ function validateBooleanValue( attributeName: string, value: unknown ): ValidationResult<boolean> { // Already a boolean - simple case if (typeof value === 'boolean') { return { valid: true, convertedValue: value }; } // Auto-conversion cases if (typeof value === 'string') { const stringValue = value.toLowerCase().trim(); // Reject empty strings explicitly if (stringValue === '') { return { valid: false, error: `Invalid boolean value for "${attributeName}". Empty strings cannot be converted to boolean.`, }; } if ( stringValue === 'true' || stringValue === 'yes' || stringValue === '1' || stringValue === 'on' ) { return { valid: true, convertedValue: true }; } if ( stringValue === 'false' || stringValue === 'no' || stringValue === '0' || stringValue === 'off' ) { return { valid: true, convertedValue: false }; } } // Number conversion if (typeof value === 'number') { if (value === 1) { return { valid: true, convertedValue: true }; } if (value === 0) { return { valid: true, convertedValue: false }; } } // Invalid boolean value return { valid: false, error: `Invalid boolean value for "${attributeName}". Expected a boolean or a string like "true"/"false", but got ${typeof value}.`, }; } /** * Validates a numeric value * * This function validates and converts values to number type. * It handles numeric strings and boolean values in addition to native numbers. * NaN values are considered invalid numbers. * * @param attributeName - The name of the attribute * @param value - The value to validate * @returns Validation result * * @example * // Native number * validateNumberValue('revenue', 1000000) * // Returns: { valid: true, convertedValue: 1000000 } * * @example * // String conversion * validateNumberValue('employee_count', '250') * // Returns: { valid: true, convertedValue: 250 } * * @example * // Boolean conversion * validateNumberValue('binary_value', true) * // Returns: { valid: true, convertedValue: 1 } */ function validateNumberValue( attributeName: string, value: unknown ): ValidationResult<number> { // Already a number - simple case if (typeof value === 'number' && !isNaN(value)) { return { valid: true, convertedValue: value }; } // Auto-conversion from string if (typeof value === 'string') { const trimmed = value.trim(); // Reject empty strings explicitly if (trimmed === '') { return { valid: false, error: `Invalid number value for "${attributeName}". Empty strings cannot be converted to numbers.`, }; } const numericValue = Number(trimmed); if (!isNaN(numericValue)) { return { valid: true, convertedValue: numericValue }; } } // Auto-conversion from boolean if (typeof value === 'boolean') { return { valid: true, convertedValue: value ? 1 : 0 }; } // Invalid number value return { valid: false, error: `Invalid number value for "${attributeName}". Expected a number or a numeric string, but got ${typeof value}.`, }; } /** * Validates a string value * * This function validates and converts values to string type. * It handles numbers, booleans, dates, and even attempts to stringify * objects in addition to native strings. * * @param attributeName - The name of the attribute * @param value - The value to validate * @returns Validation result * * @example * // Native string * validateStringValue('name', 'Acme Corporation') * // Returns: { valid: true, convertedValue: 'Acme Corporation' } * * @example * // Number conversion * validateStringValue('id_text', 12345) * // Returns: { valid: true, convertedValue: '12345' } * * @example * // Date conversion * validateStringValue('date_text', new Date('2023-01-15')) * // Returns: { valid: true, convertedValue: '2023-01-15T00:00:00.000Z' } * * @example * // Object conversion * validateStringValue('metadata_text', { id: 123, type: 'customer' }) * // Returns: { valid: true, convertedValue: '{"id":123,"type":"customer"}' } */ function validateStringValue( attributeName: string, value: unknown ): ValidationResult<string> { // Already a string - simple case if (typeof value === 'string') { return { valid: true, convertedValue: value }; } // Auto-conversion cases if (typeof value === 'number' || typeof value === 'boolean') { return { valid: true, convertedValue: String(value) }; } // Handle date objects if (value instanceof Date) { return { valid: true, convertedValue: value.toISOString() }; } // Handle objects that can be stringified if (typeof value === 'object' && value !== null) { try { return { valid: true, convertedValue: JSON.stringify(value) }; } catch { // Failed to stringify } } // Invalid string value return { valid: false, error: `Invalid string value for "${attributeName}". Expected a string or a value that can be converted to string, but got ${typeof value}.`, }; } /** * Validates a date value * * This function validates and converts values to date format. * It handles Date objects, ISO date strings, and timestamps * (both in seconds and milliseconds). * The converted value is always returned as an ISO string. * * @param attributeName - The name of the attribute * @param value - The value to validate * @returns Validation result * * @example * // Date object * validateDateValue('created_at', new Date('2023-01-15')) * // Returns: { valid: true, convertedValue: '2023-01-15T00:00:00.000Z' } * * @example * // ISO date string * validateDateValue('created_at', '2023-01-15T12:30:00Z') * // Returns: { valid: true, convertedValue: '2023-01-15T12:30:00.000Z' } * * @example * // Timestamp in milliseconds * validateDateValue('created_at', 1673784600000) // 2023-01-15T12:30:00.000Z * // Returns: { valid: true, convertedValue: '2023-01-15T12:30:00.000Z' } * * @example * // Timestamp in seconds * validateDateValue('created_at', 1673784600) // 2023-01-15T12:30:00.000Z * // Returns: { valid: true, convertedValue: '2023-01-15T12:30:00.000Z' } */ function validateDateValue( attributeName: string, value: unknown ): ValidationResult<string> { // Already a date object - simple case if (value instanceof Date) { if (!isNaN(value.getTime())) { return { valid: true, convertedValue: value.toISOString() }; } else { return { valid: false, error: `Invalid date value for "${attributeName}". The date is invalid.`, }; } } // ISO date string if (typeof value === 'string') { // Try to create a date object from the string const dateObj = new Date(value); if (!isNaN(dateObj.getTime())) { return { valid: true, convertedValue: dateObj.toISOString() }; } } // Unix timestamp (number) if (typeof value === 'number') { // Assume milliseconds if greater than 1e10 (Jan 26 1970), otherwise seconds const timestamp = value > 1e10 ? value : value * 1000; const dateObj = new Date(timestamp); if (!isNaN(dateObj.getTime())) { return { valid: true, convertedValue: dateObj.toISOString() }; } } // Invalid date value return { valid: false, error: `Invalid date value for "${attributeName}". Expected a Date object, ISO date string, or timestamp, but got ${typeof value}.`, }; } /** * Validates an array value * * This function validates and converts values to array format. * It handles native arrays and also converts single values to * single-element arrays when appropriate. * * @param attributeName - The name of the attribute * @param value - The value to validate * @returns Validation result * * @example * // Native array * validateArrayValue('tags', ['software', 'tech']) * // Returns: { valid: true, convertedValue: ['software', 'tech'] } * * @example * // Single value conversion * validateArrayValue('tags', 'software') * // Returns: { valid: true, convertedValue: ['software'] } * * @example * // Empty array * validateArrayValue('tags', []) * // Returns: { valid: true, convertedValue: [] } */ function validateArrayValue( attributeName: string, value: unknown ): ValidationResult<unknown[]> { // Already an array - simple case if (Array.isArray(value)) { return { valid: true, convertedValue: value }; } // Convert single value to array if (value !== null && value !== undefined) { return { valid: true, convertedValue: [value] }; } // Invalid array value return { valid: false, error: `Invalid array value for "${attributeName}". Expected an array, but got ${typeof value}.`, }; } /** * Validates an object value * * This function validates that a value is a proper object (not null or array). * Unlike other validators, it does not attempt to convert non-object values * due to the ambiguity of what such a conversion should produce. * * @param attributeName - The name of the attribute * @param value - The value to validate * @returns Validation result * * @example * // Valid object * validateObjectValue('metadata', { id: 123, type: 'customer' }) * // Returns: { valid: true, convertedValue: { id: 123, type: 'customer' } } * * @example * // Empty object * validateObjectValue('settings', {}) * // Returns: { valid: true, convertedValue: {} } * * @example * // Invalid (array) * validateObjectValue('metadata', ['item1', 'item2']) * // Returns: { valid: false, error: 'Invalid object value...' } */ function validateObjectValue( attributeName: string, value: unknown ): ValidationResult<Record<string, unknown>> { // Already an object - simple case if (typeof value === 'object' && value !== null && !Array.isArray(value)) { return { valid: true, convertedValue: value as Record<string, unknown> }; } // Invalid object value return { valid: false, error: `Invalid object value for "${attributeName}". Expected an object, but got ${typeof value}.`, }; } /** * Validates a select value * * This function validates and converts values for select/option fields. * It handles string values, arrays of strings, and attempts to convert * other types to strings. Since we don't have access to the valid options list, * we only validate that the format is correct, not that the values are valid options. * * @param attributeName - The name of the attribute * @param value - The value to validate * @returns Validation result * * @example * // Single option * validateSelectValue('status', 'active') * // Returns: { valid: true, convertedValue: 'active' } * * @example * // Multiple options * validateSelectValue('categories', ['software', 'tech']) * // Returns: { valid: true, convertedValue: ['software', 'tech'] } * * @example * // Converting non-string values in array * validateSelectValue('categories', ['software', 123, true]) * // Returns: { valid: true, convertedValue: ['software', '123', 'true'] } */ function validateSelectValue( attributeName: string, value: unknown ): ValidationResult<string | string[]> { // For select values, we expect strings or arrays of strings // Since we don't have access to the valid options list here, // we'll just validate that it's a string or array of strings // Single string value if (typeof value === 'string') { return { valid: true, convertedValue: value }; } // Array of strings if (Array.isArray(value)) { const allStrings = value.every((item) => typeof item === 'string'); if (allStrings) { return { valid: true, convertedValue: value }; } // Try to convert all elements to strings const convertedArray = value.map((item) => String(item)); return { valid: true, convertedValue: convertedArray }; } // Convert single value to string if (value !== null && value !== undefined) { return { valid: true, convertedValue: String(value) }; } // Invalid select value return { valid: false, error: `Invalid select value for "${attributeName}". Expected a string or array of strings, but got ${typeof value}.`, }; } /** * Validates a record reference value * * This function validates and normalizes record reference values. * It handles string IDs, objects with record_id or id properties, * and arrays of these types. The result is always normalized to a * string ID or array of string IDs. * * @param attributeName - The name of the attribute * @param value - The value to validate * @returns Validation result * * @example * // String ID * validateRecordReferenceValue('parent_company', 'rec_123456') * // Returns: { valid: true, convertedValue: 'rec_123456' } * * @example * // Object with record_id * validateRecordReferenceValue('parent_company', { record_id: 'rec_123456' }) * // Returns: { valid: true, convertedValue: 'rec_123456' } * * @example * // Array of mixed references * validateRecordReferenceValue('related_companies', [ * 'rec_123', * { record_id: 'rec_456' }, * { id: 'rec_789' } * ]) * // Returns: { valid: true, convertedValue: ['rec_123', 'rec_456', 'rec_789'] } */ function validateRecordReferenceValue( attributeName: string, value: unknown ): ValidationResult<string | string[]> { // For record references, we need IDs // Could be a string ID, an object with record_id, or an array of either // String ID if (typeof value === 'string') { return { valid: true, convertedValue: value }; } // Object with record_id if (typeof value === 'object' && value !== null && !Array.isArray(value)) { if ('record_id' in value && typeof value.record_id === 'string') { return { valid: true, convertedValue: value.record_id }; } if ('id' in value && typeof value.id === 'string') { return { valid: true, convertedValue: value.id }; } } // Array of IDs or objects if (Array.isArray(value)) { const convertedIds = value .map((item) => { if (typeof item === 'string') { return item; } if (typeof item === 'object' && item !== null) { if ('record_id' in item && typeof item.record_id === 'string') { return item.record_id; } if ('id' in item && typeof item.id === 'string') { return item.id; } } return null; }) .filter((id) => id !== null); if (convertedIds.length > 0) { return { valid: true, convertedValue: convertedIds }; } } // Invalid record reference value return { valid: false, error: `Invalid record reference value for "${attributeName}". Expected a record ID string, object with record_id, or array of either, but got ${typeof value}.`, }; }

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/kesslerio/attio-mcp-server'

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