UNPKG

http-status-meaning

Version:

A comprehensive library for HTTP status codes with internationalization and use cases

572 lines (487 loc) 17.9 kB
import statusCodes from "./statusCodes.mjs"; import { availableLanguages, categoryTranslations, statusCodeTranslations, } from "./i18n.mjs"; import statusCodeUseCases from "./useCases.mjs"; // Direct mappings for specific phrases to their status codes // These have the highest priority and override any other matching const DIRECT_STATUS_MAPPINGS = { "not found": 404, "bad request": 400, "unauthorized": 401, "forbidden": 403, "internal server error": 500, "service unavailable": 503, "ok": 200, "created": 201, "continue": 100, "moved permanently": 301, "found": 302, "resource not found": 404, "server internal error": 500, "not found - the requested resource does not exist.": 404 }; // Special keywords to specific status codes mapping for direct matches const KEYWORDS_TO_CODES = { 'continue': 100, 'switching': 101, 'processing': 102, 'early hints': 103, 'ok': 200, 'created': 201, 'accepted': 202, 'non-authoritative': 203, 'no content': 204, 'reset': 205, 'partial': 206, 'multi-status': 207, 'already reported': 208, 'im used': 226, 'multiple choices': 300, 'moved permanently': 301, 'found': 302, 'see other': 303, 'not modified': 304, 'use proxy': 305, 'temporary redirect': 307, 'permanent redirect': 308, 'bad request': 400, 'unauthorized': 401, 'payment required': 402, 'forbidden': 403, 'not found': 404, 'method not allowed': 405, 'not acceptable': 406, 'proxy authentication': 407, 'request timeout': 408, 'conflict': 409, 'gone': 410, 'length required': 411, 'precondition failed': 412, 'payload too large': 413, 'uri too long': 414, 'unsupported media': 415, 'range not satisfiable': 416, 'expectation failed': 417, 'teapot': 418, 'misdirected': 421, 'unprocessable': 422, 'locked': 423, 'failed dependency': 424, 'too early': 425, 'upgrade required': 426, 'precondition required': 428, 'too many requests': 429, 'fields too large': 431, 'legal reasons': 451, 'internal server error': 500, 'not implemented': 501, 'bad gateway': 502, 'service unavailable': 503, 'gateway timeout': 504, 'http version': 505, 'variant': 506, 'insufficient storage': 507, 'loop detected': 508, 'not extended': 510, 'network authentication': 511, // Additional keywords for easier matching 'redirect': 302, 'moved': 301, 'resource': 404, 'server error': 500, 'internal error': 500, }; // Special phrases that should be excluded from fuzzy matching const NEGATIVE_PHRASES = [ 'not a ', 'is not ', 'not an ', 'invalid ', 'no such ', 'unknown ' ]; /** * Helper function to normalize and parse status codes * @param {number|string} code - The HTTP status code * @returns {number|null} - Parsed numeric code or null */ function normalizeStatusCode(code) { if (code === null || code === undefined) return null; // If it's already a number if (typeof code === 'number') return isNaN(code) ? null : code; // If it's a string that contains only digits if (typeof code === 'string' && /^\d+$/.test(code)) { return parseInt(code, 10); } return null; } /** * Check if a query is likely a status code description by looking for key markers * @param {string} query - The query to check * @returns {boolean} - True if the query is likely a status description */ function isLikelyStatusDescription(query) { if (!query || typeof query !== 'string') return false; const normalizedQuery = query.toLowerCase(); // Check if it's in our direct mappings if (DIRECT_STATUS_MAPPINGS[normalizedQuery]) { return true; } // Check for negative phrases first for (const phrase of NEGATIVE_PHRASES) { if (normalizedQuery.includes(phrase)) { return false; } } // Check for direct keyword matches for (const keyword in KEYWORDS_TO_CODES) { if (normalizedQuery === keyword || normalizedQuery.includes(keyword)) { return true; } } // Common keywords in status code descriptions const keywords = [ 'ok', 'success', 'created', 'found', 'redirect', 'not found', 'error', 'unauthorized', 'bad', 'forbidden', 'server', 'client', 'unavailable', 'timeout' ]; // Check for exact matches of common status code names const exactMatches = [ 'not found', 'bad request', 'unauthorized', 'forbidden', 'ok', 'internal server error', 'service unavailable', 'created', 'continue' ]; if (exactMatches.includes(normalizedQuery)) { return true; } // Check if any of the keywords are in the query return keywords.some(keyword => normalizedQuery === keyword || normalizedQuery.includes(` ${keyword} `) || normalizedQuery.startsWith(`${keyword} `) || normalizedQuery.endsWith(` ${keyword}`)); } /** * Check if input contains negative phrases that would make it unlikely to be a valid status code * @param {string} query - The query string to check * @returns {boolean} - True if the query contains negative phrases */ function containsNegativePhrases(query) { if (!query || typeof query !== 'string') return false; const normalizedQuery = query.toLowerCase(); return NEGATIVE_PHRASES.some(phrase => normalizedQuery.includes(phrase)); } /** * Find a status code by its description (fuzzy match) * @param {string} description - Description or part of it (e.g., "Not Found") * @returns {number|null} - The matching status code or null if not found */ export function findStatusCodeByDescription(description) { if (!description || typeof description !== 'string') return null; const lowerDescription = description.toLowerCase(); // If the description contains negative phrases, it's likely not a status code reference if (containsNegativePhrases(lowerDescription)) { return null; } // Check direct mappings first (highest priority) if (DIRECT_STATUS_MAPPINGS[lowerDescription]) { return DIRECT_STATUS_MAPPINGS[lowerDescription]; } // Check for phrases that contain "not found" but aren't negative phrases if (lowerDescription.includes("not found")) { return 404; } // Check for direct keyword matches (second priority) for (const keyword in KEYWORDS_TO_CODES) { if (lowerDescription === keyword || lowerDescription.includes(` ${keyword} `) || lowerDescription.startsWith(`${keyword} `) || lowerDescription.endsWith(` ${keyword}`)) { return KEYWORDS_TO_CODES[keyword]; } } // Reject input that doesn't look like a status description at all if not already matched if (!isLikelyStatusDescription(lowerDescription)) { return null; } // Check exact matches with status code meanings for (const [code, meaning] of Object.entries(statusCodes)) { if (meaning.toLowerCase() === lowerDescription) { return parseInt(code, 10); } // Check for the first part before the hyphen (e.g., "Not Found" in "Not Found - The...") const shortMeaning = meaning.split(' - ')[0].toLowerCase(); if (shortMeaning === lowerDescription) { return parseInt(code, 10); } } // Check if description is contained in any status code meaning // Very specific descriptions get priority const exactMatches = []; for (const [code, meaning] of Object.entries(statusCodes)) { const meaningLower = meaning.toLowerCase(); if (meaningLower.includes(lowerDescription)) { exactMatches.push({ code: parseInt(code, 10), score: lowerDescription.length }); } } if (exactMatches.length > 0) { // Sort by score (higher is better - we want the most specific match) exactMatches.sort((a, b) => b.score - a.score); return exactMatches[0].code; } // Check if any keywords match const words = lowerDescription.split(/\s+/).filter(word => word.length > 3); if (words.length > 0) { // Try to match based on significant words const candidateMatches = []; for (const [code, meaning] of Object.entries(statusCodes)) { const meaningLower = meaning.toLowerCase(); let matchScore = 0; let exactWordMatches = 0; for (const word of words) { // Ignore very common words that might cause false matches if (['this', 'that', 'with', 'from', 'have', 'invalid'].includes(word)) { continue; } // Exact word match is weighted higher const wordPattern = new RegExp(`\\b${word}\\b`, 'i'); if (wordPattern.test(meaningLower)) { exactWordMatches++; matchScore += 3; } else if (meaningLower.includes(word)) { matchScore += 1; } } // Boost score for multiple exact word matches (to avoid coincidental single-word matches) if (exactWordMatches > 1) { matchScore *= 2; } if (matchScore > 0) { candidateMatches.push({ code: parseInt(code, 10), score: matchScore }); } } // Sort by match score (highest first) candidateMatches.sort((a, b) => b.score - a.score); if (candidateMatches.length > 0 && candidateMatches[0].score > 2) { return candidateMatches[0].code; } } return null; } /** * Returns the human-readable meaning of an HTTP status code * @param {number|string} code - The HTTP status code or its description * @param {string} [language='en'] - The language for the response * @returns {string} The human-readable meaning of the status code */ export function getStatusMeaning(code, language = "en") { // Try to parse as a status code first let statusCode = normalizeStatusCode(code); // If not a valid number, try to find by description if (statusCode === null && typeof code === 'string') { statusCode = findStatusCodeByDescription(code); } // If still not found, return unknown if (statusCode === null) { return "Unknown status code"; } if (language === "en" || !availableLanguages.includes(language)) { return statusCodes[statusCode] || "Unknown status code"; } return ( statusCodeTranslations[language]?.[statusCode] || statusCodes[statusCode] || "Unknown status code" ); } /** * Returns the category of an HTTP status code * @param {number|string} code - The HTTP status code or its description * @param {string} [language='en'] - The language for the response * @returns {string} The category of the status code */ export function getStatusCategory(code, language = "en") { // Try to parse as a status code first let statusCode = normalizeStatusCode(code); // If not a valid number, try to find by description if (statusCode === null && typeof code === 'string') { statusCode = findStatusCodeByDescription(code); } // If still not found, return unknown if (statusCode === null) { return language === "en" || !availableLanguages.includes(language) ? "Unknown" : categoryTranslations[language]["Unknown"] || "Unknown"; } let category; if (statusCode >= 100 && statusCode < 200) category = "Informational"; else if (statusCode >= 200 && statusCode < 300) category = "Success"; else if (statusCode >= 300 && statusCode < 400) category = "Redirection"; else if (statusCode >= 400 && statusCode < 500) category = "Client Error"; else if (statusCode >= 500 && statusCode < 600) category = "Server Error"; else category = "Unknown"; if (language === "en" || !availableLanguages.includes(language)) { return category; } return categoryTranslations[language][category] || category; } /** * Checks if a status code is in the Informational category (1xx) * @param {number|string} code - The HTTP status code or its description * @returns {boolean} True if the code is in the Informational category */ export function isInformational(code) { // Try to parse as a status code first let statusCode = normalizeStatusCode(code); // If not a valid number, try to find by description if (statusCode === null && typeof code === 'string') { statusCode = findStatusCodeByDescription(code); } // If still not found, return false if (statusCode === null) { return false; } return statusCode >= 100 && statusCode < 200; } /** * Checks if a status code is in the Success category (2xx) * @param {number|string} code - The HTTP status code or its description * @returns {boolean} True if the code is in the Success category */ export function isSuccess(code) { // Try to parse as a status code first let statusCode = normalizeStatusCode(code); // If not a valid number, try to find by description if (statusCode === null && typeof code === 'string') { statusCode = findStatusCodeByDescription(code); } // If still not found, return false if (statusCode === null) { return false; } return statusCode >= 200 && statusCode < 300; } /** * Checks if a status code is in the Redirection category (3xx) * @param {number|string} code - The HTTP status code or its description * @returns {boolean} True if the code is in the Redirection category */ export function isRedirection(code) { // Try to parse as a status code first let statusCode = normalizeStatusCode(code); // If not a valid number, try to find by description if (statusCode === null && typeof code === 'string') { statusCode = findStatusCodeByDescription(code); } // If still not found, return false if (statusCode === null) { return false; } return statusCode >= 300 && statusCode < 400; } /** * Checks if a status code is in the Client Error category (4xx) * @param {number|string} code - The HTTP status code or its description * @returns {boolean} True if the code is in the Client Error category */ export function isClientError(code) { // Try to parse as a status code first let statusCode = normalizeStatusCode(code); // If not a valid number, try to find by description if (statusCode === null && typeof code === 'string') { statusCode = findStatusCodeByDescription(code); } // If still not found, return false if (statusCode === null) { return false; } return statusCode >= 400 && statusCode < 500; } /** * Checks if a status code is in the Server Error category (5xx) * @param {number|string} code - The HTTP status code or its description * @returns {boolean} True if the code is in the Server Error category */ export function isServerError(code) { // Try to parse as a status code first let statusCode = normalizeStatusCode(code); // If not a valid number, try to find by description if (statusCode === null && typeof code === 'string') { statusCode = findStatusCodeByDescription(code); } // If still not found, return false if (statusCode === null) { return false; } return statusCode >= 500 && statusCode < 600; } /** * Returns all status codes in a specific category * @param {string} category - The category to filter by ('informational', 'success', 'redirection', 'clientError', 'serverError') * @param {string} [language='en'] - The language for the response * @returns {Object} An object containing the filtered status codes and their meanings */ export function getStatusCodesByCategory(category, language = "en") { const result = {}; for (const code in statusCodes) { const numCode = parseInt(code); if ( (category === "informational" && isInformational(numCode)) || (category === "success" && isSuccess(numCode)) || (category === "redirection" && isRedirection(numCode)) || (category === "clientError" && isClientError(numCode)) || (category === "serverError" && isServerError(numCode)) ) { result[code] = getStatusMeaning(numCode, language); } } return result; } /** * Returns all status codes * @param {string} [language='en'] - The language for the response * @returns {Object} An object containing all status codes and their meanings */ export function getAllStatusCodes(language = "en") { const result = {}; for (const code in statusCodes) { result[code] = getStatusMeaning(parseInt(code), language); } return result; } /** * Returns common use cases for a given status code * @param {number|string} code - The HTTP status code or its description * @returns {string[]} Array of common use cases or empty array if none defined */ export function getStatusCodeUseCases(code) { // Try to parse as a status code first let statusCode = normalizeStatusCode(code); // If not a valid number, try to find by description if (statusCode === null && typeof code === 'string') { statusCode = findStatusCodeByDescription(code); } // If still not found, return empty array if (statusCode === null) { return []; } return statusCodeUseCases[statusCode] || []; } /** * Returns all supported languages * @returns {string[]} Array of language codes */ export function getSupportedLanguages() { return [...availableLanguages]; } // Export everything for browser usage export default { getStatusMeaning, getStatusCategory, isInformational, isSuccess, isRedirection, isClientError, isServerError, getStatusCodesByCategory, getAllStatusCodes, getStatusCodeUseCases, getSupportedLanguages, findStatusCodeByDescription, };