Jump to content

Module:ErrorHandling: Difference between revisions

// via Wikitext Extension for VSCode
Tag: Reverted
// via Wikitext Extension for VSCode
 
(3 intermediate revisions by the same user not shown)
Line 1: Line 1:
-- Module:ErrorHandling
--[[
-- A centralized error handling system for templates that provides error tracking, reporting, and graceful failure recovery. It formats errors as data attributes for the debug monitor to display in the browser console.
* Name: ErrorHandling
* Author: Mark W. Datysgeld
* Description: Centralized error handling system for templates with error tracking, reporting, and graceful failure recovery
* Notes: Formats errors as data attributes for debug monitor display in browser console; creates error contexts; provides protected function execution; supports emergency fallback displays; completely deprecates mw.log usage
]]


local ErrorHandling = {}
local ErrorHandling = {}
Line 20: Line 24:
         START_TIME = os.clock() * 1000,  -- Store in milliseconds
         START_TIME = os.clock() * 1000,  -- Store in milliseconds
         ERROR_COUNT = 0,
         ERROR_COUNT = 0,
        STATUS_COUNT = 0,
         ERRORS = {},
         ERRORS = {},
        STATUSES = {},
         HAS_CRITICAL_ERROR = false
         HAS_CRITICAL_ERROR = false
     }
     }
end
end


-- Add an error or info message to the context
-- Add an error to the context
-- @param context The error context
-- @param context The error context
-- @param source The source of the error (function name)
-- @param source The source of the error (function name)
Line 31: Line 37:
-- @param details Optional additional details about the error
-- @param details Optional additional details about the error
-- @param isCritical Whether this is a critical error (default: false)
-- @param isCritical Whether this is a critical error (default: false)
-- @param messageType Type of message: 'error' or 'info' (default: 'error')
-- @return The error context (for chaining)
-- @return The error context (for chaining)
function ErrorHandling.addError(context, source, message, details, isCritical, messageType)
function ErrorHandling.addError(context, source, message, details, isCritical)
    messageType = messageType or 'error'
     -- Increment the error count
   
    -- Initialize message type counts if not present
    if not context.MESSAGE_TYPE_COUNTS then
        context.MESSAGE_TYPE_COUNTS = { error = 0, info = 0 }
    end
   
     -- Increment the appropriate count
     context.ERROR_COUNT = context.ERROR_COUNT + 1
     context.ERROR_COUNT = context.ERROR_COUNT + 1
    context.MESSAGE_TYPE_COUNTS[messageType] = context.MESSAGE_TYPE_COUNTS[messageType] + 1
      
      
     -- Add the error to the list
     -- Add the error to the list
Line 51: Line 48:
         message = message or "Unknown error",
         message = message or "Unknown error",
         details = details or "",
         details = details or "",
         isCritical = isCritical or false,
         isCritical = isCritical or false
        messageType = messageType
     })
     })
      
      
Line 61: Line 57:
      
      
     -- Return the error context for chaining
     -- Return the error context for chaining
    return context
end
-- Add a status message to the context
-- @param context The error context
-- @param source The source of the status message (function name)
-- @param message The status message
-- @param details Optional additional details
-- @return The context (for chaining)
function ErrorHandling.addStatus(context, source, message, details)
    -- Increment the status count
    context.STATUS_COUNT = (context.STATUS_COUNT or 0) + 1
   
    -- Add the status to the list
    table.insert(context.STATUSES, {
        id = context.STATUS_COUNT,
        source = source or "unknown",
        message = message or "Unknown status",
        details = details or ""
    })
   
    -- Return the context for chaining
     return context
     return context
end
end
Line 73: Line 91:
     end
     end
      
      
     -- Determine message type for styling
     -- Build minimal data attribute div
     local errorCount = context.MESSAGE_TYPE_COUNTS and context.MESSAGE_TYPE_COUNTS.error or context.ERROR_COUNT
     local divAttributes = {
    local infoCount = context.MESSAGE_TYPE_COUNTS and context.MESSAGE_TYPE_COUNTS.info or 0
        ['data-template-error'] = "1",
     local messageType = "error" -- default
        ['data-error-count'] = tostring(context.ERROR_COUNT)
     }
      
      
     if errorCount == 0 and infoCount > 0 then
     -- Add individual error attributes with minimal naming
         messageType = "info"
    for _, err in ipairs(context.ERRORS) do
    elseif errorCount > 0 and infoCount > 0 then
        divAttributes['data-error-' .. err.id .. '-source'] = err.source
         messageType = "mixed"
        divAttributes['data-error-' .. err.id .. '-msg'] = err.message
         if err.details and err.details ~= "" then
            divAttributes['data-error-' .. err.id .. '-details'] = err.details
         end
     end
     end
      
      
     -- Build visible output based on message type
     -- Build attribute string efficiently
     local output = {}
     local attrStr = ""
     local headerText, countText, itemLabel
     for k, v in pairs(divAttributes) do
   
         attrStr = attrStr .. ' ' .. k .. '="' .. v .. '"'
    if messageType == "info" then
        headerText = "Template Message"
        countText = "Found " .. infoCount .. " report" .. (infoCount > 1 and "s" or "")
         itemLabel = "Report"
    elseif messageType == "mixed" then
        headerText = "Template Report"
        countText = "Found " .. context.ERROR_COUNT .. " item" .. (context.ERROR_COUNT > 1 and "s" or "") ..
                  " (" .. errorCount .. " error" .. (errorCount > 1 and "s" or "") ..
                  ", " .. infoCount .. " info)"
        itemLabel = "Item"
    else
        headerText = "Template Error"
        countText = "Found " .. errorCount .. " error" .. (errorCount > 1 and "s" or "")
        itemLabel = "Error"
     end
     end
      
      
     -- Add header
     -- Create hidden div with minimal footprint
     table.insert(output, headerText)
     return string.format('<div style="display:none"%s></div>', attrStr)
    table.insert(output, countText)
end
   
 
    -- Add individual messages
-- Format the status context for debugging output using data attributes
    for _, err in ipairs(context.ERRORS) do
-- @param context The error context
        local prefix = (err.messageType == "info") and "Report" or "Error"
-- @return HTML string with data attributes containing status information
        local msgText = prefix .. " in " .. err.source .. ": " .. err.message
function ErrorHandling.formatStatusOutput(context)
        if err.details and err.details ~= "" then
    -- If no statuses, return empty string
            msgText = msgText .. " (" .. err.details .. ")"
    if not context.STATUSES or #context.STATUSES == 0 then
        end
        return ""
        table.insert(output, msgText)
     end
     end
      
      
     -- Build minimal data attribute div
     -- Build minimal data attribute div
     local divAttributes = {
     local divAttributes = {
         ['data-template-error'] = "1",
         ['data-template-status'] = "1",
         ['data-error-count'] = tostring(context.ERROR_COUNT),
         ['data-status-count'] = tostring(#context.STATUSES)
        ['data-message-type'] = messageType
     }
     }
      
      
    -- Add message type counts if available
     -- Add individual status attributes with minimal naming
    if context.MESSAGE_TYPE_COUNTS then
     for _, stat in ipairs(context.STATUSES) do
        divAttributes['data-error-type-count'] = tostring(errorCount)
         divAttributes['data-status-' .. stat.id .. '-source'] = stat.source
        divAttributes['data-info-type-count'] = tostring(infoCount)
         divAttributes['data-status-' .. stat.id .. '-msg'] = stat.message
    end
         if stat.details and stat.details ~= "" then
   
             divAttributes['data-status-' .. stat.id .. '-details'] = stat.details
     -- Add individual error attributes with minimal naming
     for _, err in ipairs(context.ERRORS) do
         divAttributes['data-error-' .. err.id .. '-source'] = err.source
         divAttributes['data-error-' .. err.id .. '-msg'] = err.message
        divAttributes['data-error-' .. err.id .. '-type'] = err.messageType or 'error'
         if err.details and err.details ~= "" then
             divAttributes['data-error-' .. err.id .. '-details'] = err.details
         end
         end
     end
     end
Line 147: Line 146:
     end
     end
      
      
     -- Create visible output with data attributes
     -- Create hidden div with minimal footprint
    local visibleOutput = table.concat(output, "\n")
     return string.format('<div style="display:none"%s></div>', attrStr)
     return string.format('<div class="template-message template-message-%s"%s>\n%s\n</div>',  
end
                        messageType, attrStr, visibleOutput)
 
-- Formats and combines both error and status outputs
-- @param context The context object
-- @return A string containing HTML for both errors and statuses
function ErrorHandling.formatCombinedOutput(context)
    local errorOutput = ErrorHandling.formatOutput(context)
    local statusOutput = ErrorHandling.formatStatusOutput(context)
   
    -- Simply concatenate the two outputs. If one is empty, it won't affect the other.
    -- A newline is added to ensure they are on separate lines in the HTML source.
    if errorOutput ~= "" and statusOutput ~= "" then
        return errorOutput .. "\n" .. statusOutput
    elseif errorOutput ~= "" then
        return errorOutput
    else
        return statusOutput
    end
end
end



Latest revision as of 03:01, 25 August 2025

Documentation for this module may be created at Module:ErrorHandling/doc

--[[
* Name: ErrorHandling
* Author: Mark W. Datysgeld
* Description: Centralized error handling system for templates with error tracking, reporting, and graceful failure recovery
* Notes: Formats errors as data attributes for debug monitor display in browser console; creates error contexts; provides protected function execution; supports emergency fallback displays; completely deprecates mw.log usage
]]

local ErrorHandling = {}

-- Standard error messages for common scenarios
ErrorHandling.STANDARD_MESSAGES = {
    TEMPLATE_RENDER_ERROR = "<!-- Error rendering template -->",
    MODULE_LOAD_ERROR = "<!-- Error loading module -->",
    PROPERTY_ERROR = "<!-- Error processing semantic properties -->"
    -- Add more standard messages as needed
}

-- Creates a new error context for a template
-- @param templateName The name of the template (for error reporting)
-- @return A new error context table
function ErrorHandling.createContext(templateName)
    return {
        TEMPLATE_NAME = templateName or "UnknownTemplate",
        START_TIME = os.clock() * 1000,  -- Store in milliseconds
        ERROR_COUNT = 0,
        STATUS_COUNT = 0,
        ERRORS = {},
        STATUSES = {},
        HAS_CRITICAL_ERROR = false
    }
end

-- Add an error to the context
-- @param context The error context
-- @param source The source of the error (function name)
-- @param message The error message
-- @param details Optional additional details about the error
-- @param isCritical Whether this is a critical error (default: false)
-- @return The error context (for chaining)
function ErrorHandling.addError(context, source, message, details, isCritical)
    -- Increment the error count
    context.ERROR_COUNT = context.ERROR_COUNT + 1
    
    -- Add the error to the list
    table.insert(context.ERRORS, {
        id = context.ERROR_COUNT,
        source = source or "unknown",
        message = message or "Unknown error",
        details = details or "",
        isCritical = isCritical or false
    })
    
    -- Update critical error flag if needed
    if isCritical then
        context.HAS_CRITICAL_ERROR = true
    end
    
    -- Return the error context for chaining
    return context
end

-- Add a status message to the context
-- @param context The error context
-- @param source The source of the status message (function name)
-- @param message The status message
-- @param details Optional additional details
-- @return The context (for chaining)
function ErrorHandling.addStatus(context, source, message, details)
    -- Increment the status count
    context.STATUS_COUNT = (context.STATUS_COUNT or 0) + 1
    
    -- Add the status to the list
    table.insert(context.STATUSES, {
        id = context.STATUS_COUNT,
        source = source or "unknown",
        message = message or "Unknown status",
        details = details or ""
    })
    
    -- Return the context for chaining
    return context
end

-- Format the error context for debugging output using data attributes
-- @param context The error context
-- @return HTML string with data attributes containing error information
function ErrorHandling.formatOutput(context)
    -- If no errors, return empty string
    if context.ERROR_COUNT == 0 then
        return ""
    end
    
    -- Build minimal data attribute div
    local divAttributes = {
        ['data-template-error'] = "1",
        ['data-error-count'] = tostring(context.ERROR_COUNT)
    }
    
    -- Add individual error attributes with minimal naming
    for _, err in ipairs(context.ERRORS) do
        divAttributes['data-error-' .. err.id .. '-source'] = err.source
        divAttributes['data-error-' .. err.id .. '-msg'] = err.message
        if err.details and err.details ~= "" then
            divAttributes['data-error-' .. err.id .. '-details'] = err.details
        end
    end
    
    -- Build attribute string efficiently
    local attrStr = ""
    for k, v in pairs(divAttributes) do
        attrStr = attrStr .. ' ' .. k .. '="' .. v .. '"'
    end
    
    -- Create hidden div with minimal footprint
    return string.format('<div style="display:none"%s></div>', attrStr)
end

-- Format the status context for debugging output using data attributes
-- @param context The error context
-- @return HTML string with data attributes containing status information
function ErrorHandling.formatStatusOutput(context)
    -- If no statuses, return empty string
    if not context.STATUSES or #context.STATUSES == 0 then
        return ""
    end
    
    -- Build minimal data attribute div
    local divAttributes = {
        ['data-template-status'] = "1",
        ['data-status-count'] = tostring(#context.STATUSES)
    }
    
    -- Add individual status attributes with minimal naming
    for _, stat in ipairs(context.STATUSES) do
        divAttributes['data-status-' .. stat.id .. '-source'] = stat.source
        divAttributes['data-status-' .. stat.id .. '-msg'] = stat.message
        if stat.details and stat.details ~= "" then
            divAttributes['data-status-' .. stat.id .. '-details'] = stat.details
        end
    end
    
    -- Build attribute string efficiently
    local attrStr = ""
    for k, v in pairs(divAttributes) do
        attrStr = attrStr .. ' ' .. k .. '="' .. v .. '"'
    end
    
    -- Create hidden div with minimal footprint
    return string.format('<div style="display:none"%s></div>', attrStr)
end

-- Formats and combines both error and status outputs
-- @param context The context object
-- @return A string containing HTML for both errors and statuses
function ErrorHandling.formatCombinedOutput(context)
    local errorOutput = ErrorHandling.formatOutput(context)
    local statusOutput = ErrorHandling.formatStatusOutput(context)
    
    -- Simply concatenate the two outputs. If one is empty, it won't affect the other.
    -- A newline is added to ensure they are on separate lines in the HTML source.
    if errorOutput ~= "" and statusOutput ~= "" then
        return errorOutput .. "\n" .. statusOutput
    elseif errorOutput ~= "" then
        return errorOutput
    else
        return statusOutput
    end
end

-- Create emergency display for catastrophic failures with minimal markup
-- @param args The template arguments
-- @param errorSource The source of the error
-- @param errorMessage The error message
-- @param templateType The type of template (e.g. "Event", "Person")
-- @return HTML string with a minimal template display and error data
function ErrorHandling.createEmergencyDisplay(args, errorSource, errorMessage, templateType)
    -- Extract critical information for minimal display
    local title = args.name or ("Unnamed " .. (templateType or "Item"))
    
    -- Create minimal fallback with error data attributes
    return string.format(
        '{| class="template-table" cellpadding="2"\n' ..
        '|-\n| class="template-title template-title-%s" | <span>%s</span>\n' ..
        '|}\n' ..
        '<div style="display:none" data-template-error="1" data-critical="1" ' ..
        'data-error-1-source="%s" data-error-1-msg="%s"></div>',
        string.lower(templateType or "generic"),
        title,
        errorSource,
        errorMessage
    )
end

-- Protect a function with pcall and error handling
-- @param context The error context
-- @param funcName Name of the function (for error reporting)
-- @param func The function to protect
-- @param fallback Value to return if the function fails
-- @param ... Arguments to pass to the function
-- @return The result of the function, or fallback value on error
function ErrorHandling.protect(context, funcName, func, fallback, ...)
    local success, result = pcall(func, ...)
    
    if not success then
        ErrorHandling.addError(
            context,
            funcName,
            "Function execution failed",
            tostring(result),
            false
        )
        return fallback
    end
    
    return result
end

-- Get a standard error message with fallback
-- @param messageKey The key of the standard message to retrieve
-- @param defaultMessage Optional fallback message if the key is not found
-- @return The standard message or the default message
function ErrorHandling.getMessage(messageKey, defaultMessage)
    if ErrorHandling.STANDARD_MESSAGES and ErrorHandling.STANDARD_MESSAGES[messageKey] then
        return ErrorHandling.STANDARD_MESSAGES[messageKey]
    end
    return defaultMessage or "<!-- Error -->"
end

-- Protected module loading
-- @param context The error context
-- @param moduleName Name of the module to load
-- @param isCritical Whether failure to load is critical (default: false)
-- @return The loaded module, or an empty table on failure
function ErrorHandling.safeRequire(context, moduleName, isCritical)
    local success, module = pcall(require, moduleName)
    if not success then
        -- Record the error
        ErrorHandling.addError(
            context,
            "ModuleLoading",
            "Failed to load module",
            moduleName,
            isCritical or false
        )
        
        -- Return an empty table as fallback
        return {}
    end
    return module
end

return ErrorHandling