streamling errorhandler calls

This commit is contained in:
Michael Freno
2025-12-03 22:19:27 -05:00
parent 940353c1ad
commit efce61d077
18 changed files with 333 additions and 219 deletions

View File

@@ -542,28 +542,28 @@ Animation.__index = Animation
function Animation.new(props)
if type(props) ~= "table" then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("Animation", "Animation.new() requires a table argument. Using default values.")
Animation._ErrorHandler:warn("Animation", "ANIM_001")
end
props = { duration = 1, start = {}, final = {} }
end
if type(props.duration) ~= "number" or props.duration <= 0 then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("Animation", "Animation duration must be a positive number. Using 1 second.")
Animation._ErrorHandler:warn("Animation", "ANIM_002")
end
props.duration = 1
end
if type(props.start) ~= "table" then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("Animation", "Animation start must be a table. Using empty table.")
Animation._ErrorHandler:warn("Animation", "ANIM_001")
end
props.start = {}
end
if type(props.final) ~= "table" then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("Animation", "Animation final must be a table. Using empty table.")
Animation._ErrorHandler:warn("Animation", "ANIM_001")
end
props.final = {}
end
@@ -925,7 +925,7 @@ end
function Animation:apply(element)
if not element or type(element) ~= "table" then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("Animation", "Cannot apply animation to nil or non-table element.")
Animation._ErrorHandler:warn("Animation", "ANIM_003")
end
return
end
@@ -1035,7 +1035,7 @@ function Animation:chain(nextAnimation)
return nextAnimation
else
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("Animation", "chain() requires an Animation or function.")
Animation._ErrorHandler:warn("Animation", "ANIM_004")
end
return self
end
@@ -1047,7 +1047,7 @@ end
function Animation:delay(seconds)
if type(seconds) ~= "number" or seconds < 0 then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("Animation", "delay() requires a non-negative number. Using 0.")
Animation._ErrorHandler:warn("Animation", "ANIM_005")
end
seconds = 0
end
@@ -1062,7 +1062,7 @@ end
function Animation:repeatCount(count)
if type(count) ~= "number" or count < 0 then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("Animation", "repeatCount() requires a non-negative number. Using 0.")
Animation._ErrorHandler:warn("Animation", "ANIM_006")
end
count = 0
end
@@ -1138,21 +1138,21 @@ end
function Animation.keyframes(props)
if type(props) ~= "table" then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("Animation", "Animation.keyframes() requires a table argument. Using defaults.")
Animation._ErrorHandler:warn("Animation", "ANIM_007")
end
props = { duration = 1, keyframes = {} }
end
if type(props.duration) ~= "number" or props.duration <= 0 then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("Animation", "Keyframe duration must be positive. Using 1 second.")
Animation._ErrorHandler:warn("Animation", "ANIM_002")
end
props.duration = 1
end
if type(props.keyframes) ~= "table" or #props.keyframes < 2 then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("Animation", "Keyframes require at least 2 keyframes. Using empty animation.")
Animation._ErrorHandler:warn("Animation", "ANIM_008")
end
props.keyframes = {
{ at = 0, values = {} },
@@ -1224,14 +1224,14 @@ AnimationGroup.__index = AnimationGroup
function AnimationGroup.new(props)
if type(props) ~= "table" then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("AnimationGroup", "AnimationGroup.new() requires a table. Using defaults.")
Animation._ErrorHandler:warn("AnimationGroup", "ANIM_009")
end
props = { animations = {} }
end
if type(props.animations) ~= "table" or #props.animations == 0 then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("AnimationGroup", "AnimationGroup requires at least one animation.")
Animation._ErrorHandler:warn("AnimationGroup", "ANIM_010")
end
props.animations = {}
end
@@ -1246,7 +1246,9 @@ function AnimationGroup.new(props)
if self.mode ~= "parallel" and self.mode ~= "sequence" and self.mode ~= "stagger" then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("AnimationGroup", string.format("Invalid mode: %s. Using 'parallel'.", tostring(self.mode)))
Animation._ErrorHandler:warn("AnimationGroup", "ANIM_011", {
mode = tostring(self.mode),
})
end
self.mode = "parallel"
end
@@ -1512,7 +1514,7 @@ end
function AnimationGroup:apply(element)
if not element or type(element) ~= "table" then
if Animation._ErrorHandler then
Animation._ErrorHandler:warn("AnimationGroup", "Cannot apply group to nil or non-table element.")
Animation._ErrorHandler:warn("AnimationGroup", "ANIM_003")
end
return
end

View File

@@ -355,7 +355,9 @@ local function checkLargeBlurWarning(elementId, width, height, blurType)
local suggestion =
"Consider using retained mode for this component to avoid recreating blur effects every frame. Large blur operations are expensive and can cause performance issues in immediate mode."
Blur._ErrorHandler:warn("Blur", "PERF_003", message, suggestion)
Blur._ErrorHandler:warn("Blur", "PERF_003", {
area = string.format("%.0fx%.0f", width or 0, height or 0),
})
end
--- Create a new blur effect instance
@@ -388,7 +390,7 @@ end
function Blur:applyToRegion(intensity, x, y, width, height, drawFunc)
if type(drawFunc) ~= "function" then
if Blur._ErrorHandler then
Blur._ErrorHandler:warn("Blur", "applyToRegion requires a draw function.")
Blur._ErrorHandler:warn("Blur", "BLUR_001")
end
return
end
@@ -467,7 +469,7 @@ end
function Blur:applyBackdrop(intensity, x, y, width, height, backdropCanvas)
if not backdropCanvas then
if Blur._ErrorHandler then
Blur._ErrorHandler:warn("Blur", "applyBackdrop requires a backdrop canvas.")
Blur._ErrorHandler:warn("Blur", "BLUR_002")
end
return
end
@@ -586,7 +588,7 @@ function Blur:applyBackdropCached(intensity, x, y, width, height, backdropCanvas
-- Not cached, render and cache
if not backdropCanvas then
if Blur._ErrorHandler then
Blur._ErrorHandler:warn("Blur", "applyBackdrop requires a backdrop canvas.")
Blur._ErrorHandler:warn("Blur", "BLUR_002")
end
return
end

View File

@@ -55,7 +55,7 @@ end
function Color.fromHex(hexWithTag)
-- Validate input type
if type(hexWithTag) ~= "string" then
Color._ErrorHandler:warn("Color", "VAL_004", "Invalid color format", {
Color._ErrorHandler:warn("Color", "VAL_004", {
input = tostring(hexWithTag),
issue = "not a string",
fallback = "white (#FFFFFF)",
@@ -69,7 +69,7 @@ function Color.fromHex(hexWithTag)
local g = tonumber("0x" .. hex:sub(3, 4))
local b = tonumber("0x" .. hex:sub(5, 6))
if not r or not g or not b then
Color._ErrorHandler:warn("Color", "VAL_004", "Invalid color format", {
Color._ErrorHandler:warn("Color", "VAL_004", {
input = hexWithTag,
issue = "invalid hex digits",
fallback = "white (#FFFFFF)",
@@ -83,7 +83,7 @@ function Color.fromHex(hexWithTag)
local b = tonumber("0x" .. hex:sub(5, 6))
local a = tonumber("0x" .. hex:sub(7, 8))
if not r or not g or not b or not a then
Color._ErrorHandler:warn("Color", "VAL_004", "Invalid color format", {
Color._ErrorHandler:warn("Color", "VAL_004", {
input = hexWithTag,
issue = "invalid hex digits",
fallback = "white (#FFFFFFFF)",
@@ -92,7 +92,7 @@ function Color.fromHex(hexWithTag)
end
return Color.new(r / 255, g / 255, b / 255, a / 255)
else
Color._ErrorHandler:warn("Color", "VAL_004", "Invalid color format", {
Color._ErrorHandler:warn("Color", "VAL_004", {
input = hexWithTag,
expected = "#RRGGBB or #RRGGBBAA",
hexLength = #hex,

View File

@@ -351,7 +351,7 @@ function Element.new(props)
-- Validate property combinations: passwordMode disables multiline
if self.passwordMode and props.multiline then
Element._ErrorHandler:warn("Element", "passwordMode is enabled, multiline will be disabled")
Element._ErrorHandler:warn("Element", "ELEM_006")
self.multiline = false
elseif self.passwordMode then
self.multiline = false
@@ -743,15 +743,16 @@ function Element.new(props)
-- Pixel units
self.textSize = value
else
Element._ErrorHandler:error(
"Element",
string.format("Unknown textSize unit '%s'. Valid units: px, %%, vw, vh, ew, eh. Or use presets: xs, sm, md, lg, xl, xxl, 2xl, 3xl, 4xl", unit)
)
Element._ErrorHandler:error("Element", "ELEM_002", {
unit = unit,
})
end
else
-- Validate pixel textSize value
if props.textSize <= 0 then
Element._ErrorHandler:error("Element", "textSize must be greater than 0, got: " .. tostring(props.textSize))
Element._ErrorHandler:error("Element", "ELEM_001", {
value = tostring(props.textSize),
})
end
-- Pixel textSize value
@@ -3071,13 +3072,15 @@ function Element:setTransition(property, config)
end
if type(config) ~= "table" then
Element._ErrorHandler:warn("Element", "setTransition() requires a config table. Using default config.")
Element._ErrorHandler:warn("Element", "ELEM_003")
config = {}
end
-- Validate config
if config.duration and (type(config.duration) ~= "number" or config.duration < 0) then
Element._ErrorHandler:warn("Element", "transition duration must be a non-negative number. Using 0.3 seconds.")
Element._ErrorHandler:warn("Element", "ELEM_004", {
value = tostring(config.duration),
})
config.duration = 0.3
end
@@ -3095,7 +3098,7 @@ end
---@param properties table Array of property names
function Element:setTransitionGroup(groupName, config, properties)
if type(properties) ~= "table" then
Element._ErrorHandler:warn("Element", "setTransitionGroup() requires a properties array. No transitions set.")
Element._ErrorHandler:warn("Element", "ELEM_005")
return
end

View File

@@ -313,6 +313,170 @@ local ErrorCodes = {
description = "CallSite counters accumulating",
suggestion = "This indicates incrementFrame() may not be called properly. Check immediate mode frame management.",
},
-- Animation Errors (ANIM_001 - ANIM_099)
ANIM_001 = {
code = "FLEXLOVE_ANIM_001",
category = "VAL",
description = "Invalid animation configuration",
suggestion = "Animation.new() requires a table argument with duration, start, and final properties",
},
ANIM_002 = {
code = "FLEXLOVE_ANIM_002",
category = "VAL",
description = "Invalid animation duration",
suggestion = "Animation duration must be a positive number in seconds",
},
ANIM_003 = {
code = "FLEXLOVE_ANIM_003",
category = "VAL",
description = "Invalid animation target",
suggestion = "Animation can only be applied to table elements",
},
ANIM_004 = {
code = "FLEXLOVE_ANIM_004",
category = "VAL",
description = "Invalid animation chain",
suggestion = "chain() requires an Animation object or function",
},
ANIM_005 = {
code = "FLEXLOVE_ANIM_005",
category = "VAL",
description = "Invalid animation delay",
suggestion = "delay() requires a non-negative number in seconds",
},
ANIM_006 = {
code = "FLEXLOVE_ANIM_006",
category = "VAL",
description = "Invalid repeat count",
suggestion = "repeatCount() requires a non-negative number",
},
ANIM_007 = {
code = "FLEXLOVE_ANIM_007",
category = "VAL",
description = "Invalid keyframes configuration",
suggestion = "Animation.keyframes() requires a table with duration and keyframes array",
},
ANIM_008 = {
code = "FLEXLOVE_ANIM_008",
category = "VAL",
description = "Insufficient keyframes",
suggestion = "Keyframe animations require at least 2 keyframes",
},
ANIM_009 = {
code = "FLEXLOVE_ANIM_009",
category = "VAL",
description = "Invalid animation group configuration",
suggestion = "AnimationGroup.new() requires a table with animations array",
},
ANIM_010 = {
code = "FLEXLOVE_ANIM_010",
category = "VAL",
description = "Empty animation group",
suggestion = "AnimationGroup requires at least one animation",
},
ANIM_011 = {
code = "FLEXLOVE_ANIM_011",
category = "VAL",
description = "Invalid animation group mode",
suggestion = "AnimationGroup mode must be 'parallel' or 'sequence'",
},
-- Blur Errors (BLUR_001 - BLUR_099)
BLUR_001 = {
code = "FLEXLOVE_BLUR_001",
category = "VAL",
description = "Missing draw function",
suggestion = "applyToRegion requires a draw function to render the content to be blurred",
},
BLUR_002 = {
code = "FLEXLOVE_BLUR_002",
category = "VAL",
description = "Missing backdrop canvas",
suggestion = "applyBackdrop requires a backdrop canvas parameter",
},
-- FlexLove Core Errors (CORE_001 - CORE_099)
CORE_001 = {
code = "FLEXLOVE_CORE_001",
category = "VAL",
description = "Invalid callback function",
suggestion = "deferCallback expects a function argument",
},
CORE_002 = {
code = "FLEXLOVE_CORE_002",
category = "SYS",
description = "Deferred callback execution failed",
suggestion = "Check the callback function for errors. Error details included in message.",
},
CORE_003 = {
code = "FLEXLOVE_CORE_003",
category = "VAL",
description = "Invalid garbage collection strategy",
suggestion = "GC strategy must be one of: 'default', 'aggressive', 'conservative'",
},
-- Element Errors (ELEM_001 - ELEM_099)
ELEM_001 = {
code = "FLEXLOVE_ELEM_001",
category = "VAL",
description = "Invalid text size",
suggestion = "textSize must be greater than 0",
},
ELEM_002 = {
code = "FLEXLOVE_ELEM_002",
category = "VAL",
description = "Invalid text size unit",
suggestion = "textSize unit must be one of: px, %, vw, vh, ew, eh, or presets: xs, sm, md, lg, xl, xxl, 2xl, 3xl, 4xl",
},
ELEM_003 = {
code = "FLEXLOVE_ELEM_003",
category = "VAL",
description = "Invalid transition configuration",
suggestion = "setTransition() requires a table with transition properties",
},
ELEM_004 = {
code = "FLEXLOVE_ELEM_004",
category = "VAL",
description = "Invalid transition duration",
suggestion = "Transition duration must be a non-negative number in seconds",
},
ELEM_005 = {
code = "FLEXLOVE_ELEM_005",
category = "VAL",
description = "Invalid transition group",
suggestion = "setTransitionGroup() requires an array of property names",
},
ELEM_006 = {
code = "FLEXLOVE_ELEM_006",
category = "VAL",
description = "Incompatible element configuration",
suggestion = "passwordMode and multiline cannot be used together. Multiline will be disabled.",
},
-- Module Loader Warnings (MOD_001 - MOD_099)
MOD_001 = {
code = "FLEXLOVE_MOD_001",
category = "RES",
description = "Optional module not found",
suggestion = "Using stub implementation for optional module. This is expected if the module is not required.",
},
-- Utility Errors (UTIL_001 - UTIL_099)
UTIL_001 = {
code = "FLEXLOVE_UTIL_001",
category = "VAL",
description = "Text truncation warning",
suggestion = "Text was truncated to fit within the maximum allowed length",
},
-- Image/Rendering Errors (IMG_001 - IMG_099)
IMG_001 = {
code = "FLEXLOVE_IMG_001",
category = "REN",
description = "Stencil buffer not available",
suggestion = "Cannot apply corner radius to image without stencil buffer support. Check graphics capabilities.",
},
},
}
@@ -670,64 +834,33 @@ function ErrorHandler:_formatStackTrace(level)
return ""
end
--- Format an error or warning message with optional error code
--- Format an error or warning message using error code lookup
---@param module string The module name (e.g., "Element", "Units", "Theme")
---@param level string "Error" or "Warning"
---@param codeOrMessage string Error code (e.g., "VAL_001") or message
---@param messageOrDetails string|table|nil Message or details object
---@param detailsOrSuggestion table|string|nil Details or suggestion
---@param suggestionOrNil string|nil Suggestion
---@param code string Error code (e.g., "VAL_001")
---@param details table|nil Optional details object
---@return string Formatted message
function ErrorHandler:_formatMessage(module, level, codeOrMessage, messageOrDetails, detailsOrSuggestion, suggestionOrNil)
local code = nil
local message = codeOrMessage
local details = nil
local suggestion = nil
function ErrorHandler:_formatMessage(module, level, code, details)
local codeInfo = ErrorCodes.get(code)
-- Parse arguments (support multiple signatures)
if type(codeOrMessage) == "string" and ErrorCodes.get(codeOrMessage) then
-- Called with error code
code = codeOrMessage
message = messageOrDetails or ErrorCodes.describe(code)
if type(detailsOrSuggestion) == "table" then
details = detailsOrSuggestion
suggestion = suggestionOrNil or ErrorCodes.getSuggestion(code)
elseif type(detailsOrSuggestion) == "string" then
suggestion = detailsOrSuggestion
else
suggestion = ErrorCodes.getSuggestion(code)
end
else
-- Called with message only (backward compatibility)
message = codeOrMessage
if type(messageOrDetails) == "table" then
details = messageOrDetails
suggestion = detailsOrSuggestion
elseif type(messageOrDetails) == "string" then
suggestion = messageOrDetails
end
if not codeInfo then
return string.format("[FlexLove - %s] %s: Unknown error code: %s", module, level, code)
end
-- Build formatted message
local parts = {}
-- Header: [FlexLove - Module] Level [CODE]: Message
if code then
local codeInfo = ErrorCodes.get(code)
table.insert(parts, string.format("[FlexLove - %s] %s [%s]: %s", module, level, codeInfo.code, message))
else
table.insert(parts, string.format("[FlexLove - %s] %s: %s", module, level, message))
end
-- Header: [FlexLove - Module] Level [CODE]: Description
table.insert(parts, string.format("[FlexLove - %s] %s [%s]: %s", module, level, codeInfo.code, codeInfo.description))
-- Details section
if details then
if details and type(details) == "table" then
table.insert(parts, self:_formatDetails(details))
end
-- Suggestion section
if suggestion and suggestion ~= "" then
table.insert(parts, string.format("\n\nSuggestion: %s", suggestion))
if codeInfo.suggestion and codeInfo.suggestion ~= "" then
table.insert(parts, string.format("\n\nSuggestion: %s", codeInfo.suggestion))
end
return table.concat(parts, "")
@@ -807,43 +940,17 @@ end
--- Throw a critical error (stops execution)
---@param module string The module name
---@param codeOrMessage string Error code or message
---@param messageOrDetails string|table|nil Message or details
---@param detailsOrSuggestion table|string|nil Details or suggestion
---@param suggestion string|nil Suggestion
function ErrorHandler:error(module, codeOrMessage, messageOrDetails, detailsOrSuggestion, suggestion)
local formattedMessage = self:_formatMessage(module, "Error", codeOrMessage, messageOrDetails, detailsOrSuggestion, suggestion)
---@param code string Error code (e.g., "VAL_001")
---@param details table|nil Optional details object
function ErrorHandler:error(module, code, details)
local formattedMessage = self:_formatMessage(module, "Error", code, details)
-- Parse arguments for logging
local code = nil
local message = codeOrMessage
local details = nil
local logSuggestion = nil
if type(codeOrMessage) == "string" and ErrorCodes.get(codeOrMessage) then
code = codeOrMessage
message = messageOrDetails or ErrorCodes.describe(code)
if type(detailsOrSuggestion) == "table" then
details = detailsOrSuggestion
logSuggestion = suggestion or ErrorCodes.getSuggestion(code)
elseif type(detailsOrSuggestion) == "string" then
logSuggestion = detailsOrSuggestion
else
logSuggestion = ErrorCodes.getSuggestion(code)
end
else
message = codeOrMessage
if type(messageOrDetails) == "table" then
details = messageOrDetails
logSuggestion = detailsOrSuggestion
elseif type(messageOrDetails) == "string" then
logSuggestion = messageOrDetails
end
end
local codeInfo = ErrorCodes.get(code)
local message = codeInfo and codeInfo.description or code
local suggestion = codeInfo and codeInfo.suggestion or nil
-- Log the error
self:_writeLog("ERROR", LOG_LEVEL.ERROR, module, code, message, details, logSuggestion)
self:_writeLog("ERROR", LOG_LEVEL.ERROR, module, code, message, details, suggestion)
if self.includeStackTrace then
formattedMessage = formattedMessage .. self:_formatStackTrace(3)
@@ -854,41 +961,15 @@ end
--- Print a warning (non-critical, continues execution)
---@param module string The module name
---@param codeOrMessage string Warning code or message
---@param messageOrDetails string|table|nil Message or details
---@param detailsOrSuggestion table|string|nil Details or suggestion
---@param suggestion string|nil Suggestion
function ErrorHandler:warn(module, codeOrMessage, messageOrDetails, detailsOrSuggestion, suggestion)
-- Parse arguments for logging
local code = nil
local message = codeOrMessage
local details = nil
local logSuggestion = nil
if type(codeOrMessage) == "string" and ErrorCodes.get(codeOrMessage) then
code = codeOrMessage
message = messageOrDetails or ErrorCodes.describe(code)
if type(detailsOrSuggestion) == "table" then
details = detailsOrSuggestion
logSuggestion = suggestion or ErrorCodes.getSuggestion(code)
elseif type(detailsOrSuggestion) == "string" then
logSuggestion = detailsOrSuggestion
else
logSuggestion = ErrorCodes.getSuggestion(code)
end
else
message = codeOrMessage
if type(messageOrDetails) == "table" then
details = messageOrDetails
logSuggestion = detailsOrSuggestion
elseif type(messageOrDetails) == "string" then
logSuggestion = messageOrDetails
end
end
---@param code string Warning code (e.g., "VAL_001")
---@param details table|nil Optional details object
function ErrorHandler:warn(module, code, details)
local codeInfo = ErrorCodes.get(code)
local message = codeInfo and codeInfo.description or code
local suggestion = codeInfo and codeInfo.suggestion or nil
-- Log the warning
self:_writeLog("WARNING", LOG_LEVEL.WARNING, module, code, message, details, logSuggestion)
self:_writeLog("WARNING", LOG_LEVEL.WARNING, module, code, message, details, suggestion)
end
--- Validate that a value is not nil

View File

@@ -633,9 +633,9 @@ function EventHandler:_invokeCallback(element, event)
self.onEvent(element, event)
end)
else
EventHandler._ErrorHandler:error("EventHandler", "SYS_003", "FlexLove.deferCallback not available", {
EventHandler._ErrorHandler:error("EventHandler", "SYS_003", {
eventType = event.type,
}, "Ensure FlexLove module is properly loaded")
})
end
else
self.onEvent(element, event)

View File

@@ -30,7 +30,7 @@ function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, bounds
objectPosition = objectPosition or "center center"
if imageWidth <= 0 or imageHeight <= 0 or boundsWidth <= 0 or boundsHeight <= 0 then
ErrorHandler:error("ImageRenderer", "VAL_002", "Dimensions must be positive", {
ErrorHandler:error("ImageRenderer", "VAL_002", {
imageWidth = imageWidth,
imageHeight = imageHeight,
boundsWidth = boundsWidth,
@@ -116,7 +116,7 @@ function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, bounds
return ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, boundsHeight, "contain", objectPosition)
end
else
ErrorHandler:warn("ImageRenderer", "VAL_007", string.format("Invalid fit mode: '%s'. Must be one of: fill, contain, cover, scale-down, none", tostring(fitMode)), {
ErrorHandler:warn("ImageRenderer", "VAL_007", {
fitMode = fitMode,
fallback = "fill"
})
@@ -362,7 +362,7 @@ function ImageRenderer.drawTiled(image, x, y, width, height, repeatMode, opacity
end
end
else
ErrorHandler:warn("ImageRenderer", "VAL_007", string.format("Invalid repeat mode: '%s'. Using 'no-repeat'", tostring(repeatMode)), {
ErrorHandler:warn("ImageRenderer", "VAL_007", {
repeatMode = repeatMode,
fallback = "no-repeat"
})

View File

@@ -27,11 +27,13 @@ end
---@return love.ImageData -- Scaled image data
function ImageScaler.scaleNearest(sourceImageData, srcX, srcY, srcW, srcH, destW, destH)
if not sourceImageData then
ErrorHandler:error("ImageScaler", "VAL_001", "Source ImageData cannot be nil")
ErrorHandler:error("ImageScaler", "VAL_001", {
parameter = "sourceImageData"
})
end
if srcW <= 0 or srcH <= 0 or destW <= 0 or destH <= 0 then
ErrorHandler:warn("ImageScaler", "VAL_002", "Dimensions must be positive", {
ErrorHandler:warn("ImageScaler", "VAL_002", {
srcW = srcW,
srcH = srcH,
destW = destW,
@@ -95,11 +97,13 @@ end
---@return love.ImageData -- Scaled image data
function ImageScaler.scaleBilinear(sourceImageData, srcX, srcY, srcW, srcH, destW, destH)
if not sourceImageData then
ErrorHandler:error("ImageScaler", "VAL_001", "Source ImageData cannot be nil")
ErrorHandler:error("ImageScaler", "VAL_001", {
parameter = "sourceImageData"
})
end
if srcW <= 0 or srcH <= 0 or destW <= 0 or destH <= 0 then
ErrorHandler:warn("ImageScaler", "VAL_002", "Dimensions must be positive", {
ErrorHandler:warn("ImageScaler", "VAL_002", {
srcW = srcW,
srcH = srcH,
destW = destW,

View File

@@ -240,18 +240,18 @@ function LayoutEngine:layoutChildren()
-- Warn if child uses percentage sizing but parent has autosizing
if child.units and child.units.width then
if child.units.width.unit == "%" and self.element.autosizing and self.element.autosizing.width then
LayoutEngine._ErrorHandler:warn("LayoutEngine", "LAY_004", "Invalid sizing combination", {
LayoutEngine._ErrorHandler:warn("LayoutEngine", "LAY_004", {
child = child.id or "unnamed",
issue = "percentage width with parent auto-sizing",
}, "Use fixed or viewport units instead of percentage when parent has auto-sizing enabled")
})
end
end
if child.units and child.units.height then
if child.units.height.unit == "%" and self.element.autosizing and self.element.autosizing.height then
LayoutEngine._ErrorHandler:warn("LayoutEngine", "LAY_004", "Invalid sizing combination", {
LayoutEngine._ErrorHandler:warn("LayoutEngine", "LAY_004", {
child = child.id or "unnamed",
issue = "percentage height with parent auto-sizing",
}, "Use fixed or viewport units instead of percentage when parent has auto-sizing enabled")
})
end
end
end

View File

@@ -134,7 +134,10 @@ function ModuleLoader.safeRequire(modulePath, isOptional)
if ModuleLoader._ErrorHandler then
ModuleLoader._ErrorHandler:warn(
"ModuleLoader",
string.format("Optional module '%s' not found, using stub implementation", modulePath)
"MOD_001",
{
modulePath = modulePath
}
)
end

View File

@@ -271,16 +271,13 @@ function Performance:_addWarning(name, value, level)
if now - lastWarningTime >= 60 then
if self._ErrorHandler and self._ErrorHandler.warn then
local message = string.format("%s = %.2fms", name, value)
local code = level == "critical" and "PERF_002" or "PERF_001"
local suggestion = level == "critical" and "This operation is causing frame drops. Consider optimizing or reducing frequency."
or "This operation is taking longer than recommended. Monitor for patterns."
self._ErrorHandler:warn("Performance", code, message, {
self._ErrorHandler:warn("Performance", code, {
metric = name,
value = string.format("%.2fms", value),
threshold = level == "critical" and self.criticalThresholdMs or self.warningThresholdMs,
}, suggestion)
})
else
local prefix = level == "critical" and "[CRITICAL]" or "[WARNING]"
print(string.format("%s Performance: %s = %.2fms", prefix, name, value))
@@ -405,7 +402,7 @@ function Performance:logWarning(warningKey, module, message, details, suggestion
end
if self._ErrorHandler and self._ErrorHandler.warn then
self._ErrorHandler:warn(module, "PERF_001", message, details or {}, suggestion)
self._ErrorHandler:warn(module, "PERF_001", details or {})
else
print(string.format("[FlexLove - %s] Performance Warning: %s", module, message))
if suggestion then
@@ -529,12 +526,12 @@ function Performance:_sampleMemory()
if not self._shownWarnings[name] then
local message = string.format("Table '%s' growing consistently", name)
if self._ErrorHandler and self._ErrorHandler.warn then
self._ErrorHandler:warn("Performance", "MEM_001", message, {
self._ErrorHandler:warn("Performance", "MEM_001", {
table = name,
initialSize = sizes[1],
currentSize = sizes[#sizes],
growthPercent = math.floor(((sizes[#sizes] / sizes[1]) - 1) * 100),
}, "Check for memory leaks. Review cache eviction policies and ensure objects are released.")
})
end
self._shownWarnings[name] = true

View File

@@ -233,11 +233,11 @@ function Renderer:_drawImage(x, y, paddingLeft, paddingTop, contentWidth, conten
self.cornerRadius.bottomRight
)
end
Renderer._ErrorHandler:warn("Renderer", "IMG_001", "Cannot apply corner radius to image: stencil buffer not available", {
Renderer._ErrorHandler:warn("Renderer", "IMG_001", {
imagePath = self.imagePath or "unknown",
cornerRadius = cornerRadiusStr,
error = tostring(err),
}, "Ensure the active canvas has stencil=true enabled, or remove cornerRadius from images")
})
-- Continue without corner radius
hasCornerRadius = false
else
@@ -380,9 +380,9 @@ end
---@param backdropCanvas table|nil Backdrop canvas for backdrop blur
function Renderer:draw(element, backdropCanvas)
if not element then
Renderer._ErrorHandler:warn("Renderer", "SYS_002", "Element parameter required", {
Renderer._ErrorHandler:warn("Renderer", "SYS_002", {
method = "draw",
}, "Pass element as first parameter to draw()")
})
return
end

View File

@@ -270,10 +270,10 @@ function StateManager.getState(id, defaultState)
if not ErrorHandler then
ErrorHandler = require("modules.ErrorHandler")
end
ErrorHandler.error("StateManager", "SYS_001", "Invalid state ID", {
ErrorHandler.error("StateManager", "SYS_001", {
parameter = "id",
value = "nil",
}, "Provide a valid non-nil ID string to getState()")
})
end
-- Create state if it doesn't exist
@@ -306,10 +306,10 @@ function StateManager.setState(id, state)
if not ErrorHandler then
ErrorHandler = require("modules.ErrorHandler")
end
ErrorHandler.error("StateManager", "SYS_001", "Invalid state ID", {
ErrorHandler.error("StateManager", "SYS_001", {
parameter = "id",
value = "nil",
}, "Provide a valid non-nil ID string to setState()")
})
end
-- Create sparse state (remove default values)
@@ -529,12 +529,11 @@ function StateManager.getStats()
-- Warn if callSiteCounters is unexpectedly large
if callSiteCount > 1000 then
if ErrorHandler then
local message = string.format("callSiteCounters has %d entries (expected near 0 per frame)", callSiteCount)
ErrorHandler.warn("StateManager", "STATE_001", message, {
ErrorHandler.warn("StateManager", "STATE_001", {
count = callSiteCount,
expected = "near 0",
frameNumber = frameNumber,
}, "This indicates incrementFrame() may not be called properly or counters aren't being reset. Check immediate mode frame management.")
})
else
print(string.format("[StateManager] WARNING: callSiteCounters has %d entries", callSiteCount))
end

View File

@@ -370,7 +370,7 @@ local activeTheme = nil
function Theme.new(definition)
-- Validate input type first
if type(definition) ~= "table" then
Theme._ErrorHandler:warn("Theme", "THM_001", "Invalid theme definition", {
Theme._ErrorHandler:warn("Theme", "THM_001", {
error = "Theme definition must be a table, got " .. type(definition),
})
return Theme.new({ name = "fallback", components = {}, colors = {}, fonts = {} })
@@ -379,7 +379,7 @@ function Theme.new(definition)
-- Validate theme definition
local valid, err = validateThemeDefinition(definition)
if not valid then
Theme._ErrorHandler:warn("Theme", "THM_001", "Invalid theme definition", {
Theme._ErrorHandler:warn("Theme", "THM_001", {
error = tostring(err),
})
return Theme.new({ name = "fallback", components = {}, colors = {}, fonts = {} })
@@ -397,7 +397,7 @@ function Theme.new(definition)
self.atlas = image
self.atlasData = imageData
else
Theme._ErrorHandler:warn("Theme", "RES_001", "Failed to load global atlas", {
Theme._ErrorHandler:warn("Theme", "RES_001", {
theme = definition.name,
path = resolvedPath,
error = loaderr,
@@ -425,7 +425,7 @@ function Theme.new(definition)
local contentHeight = srcHeight - 2
if contentWidth <= 0 or contentHeight <= 0 then
Theme._ErrorHandler:warn("Theme", "RES_002", "Nine-patch image too small", {
Theme._ErrorHandler:warn("Theme", "RES_002", {
width = srcWidth,
height = srcHeight,
reason = "Image must be larger than 2x2 pixels to have content after stripping 1px border",
@@ -460,7 +460,7 @@ function Theme.new(definition)
comp.insets = parseResult.insets
comp._ninePatchData = parseResult
else
Theme._ErrorHandler:warn("Theme", "RES_003", "Failed to parse nine-patch image", {
Theme._ErrorHandler:warn("Theme", "RES_003", {
context = errorContext,
path = resolvedPath,
error = tostring(parseErr),
@@ -481,7 +481,7 @@ function Theme.new(definition)
comp._loadedAtlasData = imageData
end
else
Theme._ErrorHandler:warn("Theme", "RES_001", "Failed to load atlas", {
Theme._ErrorHandler:warn("Theme", "RES_001", {
context = errorContext,
path = resolvedPath,
error = tostring(loaderr),
@@ -575,12 +575,12 @@ function Theme.load(path)
if success then
definition = result
else
Theme._ErrorHandler:warn("Theme", "RES_004", "Failed to load theme file", {
Theme._ErrorHandler:warn("Theme", "RES_004", {
theme = path,
tried = themePath,
error = tostring(result),
fallback = "nil (no theme loaded)",
}, "Check that the theme file exists in the themes/ directory or provide a valid module path")
})
return nil
end
end
@@ -607,11 +607,11 @@ function Theme.setActive(themeOrName)
end
if not activeTheme then
Theme._ErrorHandler:warn("Theme", "THM_002", "Failed to set active theme", {
Theme._ErrorHandler:warn("Theme", "THM_002", {
theme = tostring(themeOrName),
reason = "Theme not found or not loaded",
fallback = "current theme unchanged",
}, "Ensure the theme is loaded with Theme.load() before setting it active")
})
-- Keep current activeTheme unchanged (fallback behavior)
end
end

View File

@@ -23,48 +23,48 @@ function Units.parse(value)
end
if type(value) ~= "string" then
Units._ErrorHandler:warn("Units", "VAL_001", "Invalid property type", {
Units._ErrorHandler:warn("Units", "VAL_001", {
property = "unit value",
expected = "string or number",
got = type(value),
}, "Using fallback: 0px")
})
return 0, "px"
end
-- Check for unit-only input (e.g., "px", "%", "vw" without a number)
local validUnits = { px = true, ["%"] = true, vw = true, vh = true, ew = true, eh = true }
if validUnits[value] then
Units._ErrorHandler:warn("Units", "VAL_005", "Invalid unit format", {
Units._ErrorHandler:warn("Units", "VAL_005", {
input = value,
expected = "number + unit (e.g., '50" .. value .. "')",
}, string.format("Add a numeric value before '%s', like '50%s'. Using fallback: 0px", value, value))
})
return 0, "px"
end
-- Check for invalid format (space between number and unit)
if value:match("%d%s+%a") then
Units._ErrorHandler:warn("Units", "VAL_005", "Invalid unit format", {
Units._ErrorHandler:warn("Units", "VAL_005", {
input = value,
issue = "contains space between number and unit",
}, "Remove spaces: use '50px' not '50 px'. Using fallback: 0px")
})
return 0, "px"
end
-- Match number followed by optional unit
local numStr, unit = value:match("^([%-]?[%d%.]+)(.*)$")
if not numStr then
Units._ErrorHandler:warn("Units", "VAL_005", "Invalid unit format", {
Units._ErrorHandler:warn("Units", "VAL_005", {
input = value,
}, "Expected format: number + unit (e.g., '50px', '10%', '2vw'). Using fallback: 0px")
})
return 0, "px"
end
local num = tonumber(numStr)
if not num then
Units._ErrorHandler:warn("Units", "VAL_005", "Invalid unit format", {
Units._ErrorHandler:warn("Units", "VAL_005", {
input = value,
issue = "numeric value cannot be parsed",
}, "Using fallback: 0px")
})
return 0, "px"
end
@@ -75,11 +75,11 @@ function Units.parse(value)
-- validUnits is already defined at the top of the function
if not validUnits[unit] then
Units._ErrorHandler:warn("Units", "VAL_005", "Invalid unit format", {
Units._ErrorHandler:warn("Units", "VAL_005", {
input = value,
unit = unit,
validUnits = "px, %, vw, vh, ew, eh",
}, string.format("Treating '%s' as pixels", value))
})
return num, "px"
end
@@ -99,10 +99,10 @@ function Units.resolve(value, unit, viewportWidth, viewportHeight, parentSize)
return value
elseif unit == "%" then
if not parentSize then
Units._ErrorHandler:warn("Units", "LAY_003", "Invalid dimensions", {
Units._ErrorHandler:warn("Units", "LAY_003", {
unit = "%",
issue = "parent dimension not available",
}, "Percentage units require a parent element with explicit dimensions. Using fallback: 0px")
})
return 0
end
return (value / 100) * parentSize
@@ -111,10 +111,10 @@ function Units.resolve(value, unit, viewportWidth, viewportHeight, parentSize)
elseif unit == "vh" then
return (value / 100) * viewportHeight
else
Units._ErrorHandler:warn("Units", "VAL_005", "Invalid unit format", {
Units._ErrorHandler:warn("Units", "VAL_005", {
unit = unit,
validUnits = "px, %, vw, vh, ew, eh",
}, string.format("Unknown unit type: '%s'. Using fallback: 0px", unit))
})
return 0
end
end

View File

@@ -342,7 +342,11 @@ local function validateEnum(value, enumTable, propName, moduleName)
table.sort(validOptions)
if ErrorHandler then
ErrorHandler:error(moduleName or "Element", string.format("%s must be one of: %s. Got: '%s'", propName, table.concat(validOptions, ", "), tostring(value)))
ErrorHandler:error(moduleName or "Element", "VAL_007", {
property = propName,
expected = table.concat(validOptions, ", "),
got = tostring(value),
})
else
error(string.format("%s must be one of: %s. Got: '%s'", propName, table.concat(validOptions, ", "), tostring(value)))
end
@@ -361,17 +365,22 @@ local function validateRange(value, min, max, propName, moduleName)
end
if type(value) ~= "number" then
if ErrorHandler then
ErrorHandler:error(moduleName or "Element", string.format("%s must be a number, got %s", propName, type(value)))
ErrorHandler:error(moduleName or "Element", "VAL_001", {
property = propName,
expected = "number",
got = type(value),
})
else
error(string.format("%s must be a number, got %s", propName, type(value)))
end
end
if value < min or value > max then
elseif value < min or value > max then
if ErrorHandler then
ErrorHandler:error(
moduleName or "Element",
string.format("%s must be between %s and %s, got %s", propName, tostring(min), tostring(max), tostring(value))
)
ErrorHandler:error(moduleName or "Element", "VAL_002", {
property = propName,
min = tostring(min),
max = tostring(max),
value = tostring(value),
})
else
error(string.format("%s must be between %s and %s, got %s", propName, tostring(min), tostring(max), tostring(value)))
end
@@ -392,7 +401,11 @@ local function validateType(value, expectedType, propName, moduleName)
local actualType = type(value)
if actualType ~= expectedType then
if ErrorHandler then
ErrorHandler:error(moduleName or "Element", string.format("%s must be %s, got %s", propName, expectedType, actualType))
ErrorHandler:error(moduleName or "Element", "VAL_001", {
property = propName,
expected = expectedType,
got = actualType,
})
else
error(string.format("%s must be %s, got %s", propName, expectedType, actualType))
end
@@ -555,6 +568,12 @@ local function sanitizeText(text, options)
-- Limit string length (use UTF-8 character count, not byte count)
local charCount = utf8.len(text)
if charCount and charCount > maxLength then
if ErrorHandler then
ErrorHandler:warn("utils", "UTIL_001", {
original = charCount,
truncated = maxLength,
})
end
-- Truncate to maxLength UTF-8 characters
local bytePos = utf8.offset(text, maxLength + 1)
if bytePos then