Skip to content

Commit

Permalink
refactor(standard): error handling (#4516)
Browse files Browse the repository at this point in the history
* refactor(standard): error handling

* improve annos

* comments

* improve check

* add a todo

* nil check
  • Loading branch information
Rathoz authored Aug 13, 2024
1 parent 3048839 commit 4090fca
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 165 deletions.
23 changes: 10 additions & 13 deletions components/match2/commons/display_util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
local Array = require('Module:Array')
local FeatureFlag = require('Module:FeatureFlag')
local FnUtil = require('Module:FnUtil')
local ResultOrError = require('Module:ResultOrError')
local Logic = require('Module:Logic')
local TypeUtil = require('Module:TypeUtil')

local DisplayUtil = {propTypes = {}, types = {}}
Expand Down Expand Up @@ -54,18 +54,15 @@ end
---@param props table
---@return Html
function DisplayUtil.TryPureComponent(Component, props)
local resultOrError = ResultOrError.try(function() return Component(props) end)
if resultOrError:isResult() then
---@cast resultOrError Result
return resultOrError:get()
else
---@cast resultOrError Error
return mw.html.create('div')
:tag('strong'):addClass('error')
:tag('span'):addClass('scribunto-error')
:wikitext(resultOrError:getErrorJson() .. '.')
:allDone()
end
return Logic.try(function() return Component(props) end)
:catch(function(error)
return mw.html.create('div')
:tag('strong'):addClass('error')
:tag('span'):addClass('scribunto-error')
:wikitext(error:getErrorJson() .. '.')
:allDone()
end)
:get()
end

---@alias OverflowModes 'ellipsis'|'wrap'|'hidden'
Expand Down
8 changes: 4 additions & 4 deletions components/match2/commons/match_group_input.lua
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,12 @@ function MatchGroupInput.readBracket(bracketId, args, options)
local bracketDatasById = Logic.try(function()
return MatchGroupInput._fetchBracketDatas(templateId, bracketId)
end)
:catch(function(message)
if String.endsWith(message, 'does not exist') then
table.insert(warnings, message .. ' (Maybe [[Template:' .. templateId .. ']] needs to be purged?)')
:catch(function(error)
if String.endsWith(error.message, 'does not exist') then
table.insert(warnings, error.message .. ' (Maybe [[Template:' .. templateId .. ']] needs to be purged?)')
return {}
else
error(message)
error(error.message)
end
end)
:get()
Expand Down
3 changes: 2 additions & 1 deletion components/opponent/commons/player_ext_custom.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
--

local Lua = require('Module:Lua')
local PlayerExt = Lua.import('Module:Player/Ext')

return Lua.import('Module:Player/Ext')
return PlayerExt
91 changes: 79 additions & 12 deletions standard/error.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@
-- Please see https://github.com/Liquipedia/Lua-Modules to contribute
--

---@class error
---@field childErrors? error[]
---@field header string?
---@field innerError any
---@field message string
---@field originalErrors? error[]
---@field stacks? string[]
---@field is_a? function

local Array = require('Module:Array')
local Class = require('Module:Class')
local Json = require('Module:Json')
local Page = require('Module:Page')
local String = require('Module:StringUtils')
local Table = require('Module:Table')

local FILTERED_ERROR_STACK_ITEMS = {
'^Module:ResultOrError:%d+: in function <Module:ResultOrError:%d+>$',
'^%[C%]: in function \'xpcall\'$',
'^Module:ResultOrError:%d+: in function \'try\'$',
}

--[[
A minimal error class, whose purpose is to allow additional fields to be
Expand Down Expand Up @@ -47,9 +49,14 @@ preamble-like text here to give some context to the error.
error.noStack: Disables the stack trace
]]
---@class Error
---@class Error: BaseClass
---@operator call(string|table|nil|any):Error
---@field message string?
---@field message string
---@field childErrors? Error[]
---@field header string?
---@field innerError any
---@field originalErrors? Error[]
---@field stacks? string[]
local Error = Class.new(function(self, any)
-- Normalize the various ways an error can be thrown
if type(any) == 'string' then
Expand All @@ -67,7 +74,7 @@ local Error = Class.new(function(self, any)
self.message = self.message or 'Unknown error'
end)

---@param error error
---@param error Error
---@return boolean
function Error.isError(error)
return type(error) == 'table'
Expand All @@ -80,4 +87,64 @@ function Error:__tostring()
return self.message
end

--- TODO: Move to Error Display
---Builds a JSON string for use by `liquipedia.customLuaErrors` JS module with `error()`.
---@return string
function Error:getErrorJson()
local stackTrace = {}

local processStackFrame = function(frame, frameIndex)
if frameIndex == 1 and frame == '[C]: ?' then
return
end

local stackEntry = {content = frame}
local frameSplit = mw.text.split(frame, ':', true)
if (frameSplit[1] == '[C]' or frameSplit[1] == '(tail call)') then
stackEntry.prefix = frameSplit[1]
stackEntry.content = mw.text.trim(table.concat(frameSplit, ':', 2))
elseif frameSplit[1]:sub(1, 3) == 'mw.' then
stackEntry.prefix = table.concat(frameSplit, ':', 1, 2)
stackEntry.content = table.concat(frameSplit, ':', 3)
elseif frameSplit[1] == 'Module' then
local wiki = not Page.exists(table.concat(frameSplit, ':', 1, 2)) and 'commons'
or mw.text.split(mw.title.getCurrentTitle():canonicalUrl(), '/', true)[4] or 'commons'
stackEntry.link = {wiki = wiki, title = table.concat(frameSplit, ':', 1, 2), ln = frameSplit[3]}
stackEntry.prefix = table.concat(frameSplit, ':', 1, 3)
stackEntry.content = table.concat(frameSplit, ':', 4)
end

table.insert(stackTrace, stackEntry)
end

Array.forEach(self.stacks or {}, function(stack)
local stackFrames = mw.text.split(stack, '\n')
stackFrames = Array.filter(
Array.map(
Array.sub(stackFrames, 2, #stackFrames),
function(frame) return String.trim(frame) end
),
function(frame) return not Table.any(FILTERED_ERROR_STACK_ITEMS, function(_, filter)
return string.find(frame, filter) ~= nil
end) end
)
Array.forEach(stackFrames, processStackFrame)
end)

local errorSplit = mw.text.split(self.message, ':', true)
local errorText
if #errorSplit == 4 then
errorText = string.format('Lua error in %s:%s at line %s:%s.', unpack(errorSplit))
elseif #errorSplit > 4 then
errorText = string.format('Lua error in %s:%s at line %s:%s', unpack(Array.sub(errorSplit, 1, 4)))
errorText = errorText .. ':' .. table.concat(Array.sub(errorSplit, 5), ':') .. '.'
else
errorText = string.format('Lua error: %s.', self.message)
end
return Json.stringify({
errorShort = errorText,
stackTrace = stackTrace,
}, {asArray = true})
end

return Error
6 changes: 3 additions & 3 deletions standard/error_display.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ local TypeUtil = require('Module:TypeUtil')

local ErrorDisplay = {types = {}, propTypes = {}}

---@param props {limit: integer?, errors: error[]}
---@param props {limit: integer?, errors: Error[]}
---@return Html
function ErrorDisplay.ErrorList(props)
local defaultLimit = 5
Expand Down Expand Up @@ -80,7 +80,7 @@ function ErrorDisplay.Box(props)
return div:node(tbl)
end

---@param error error
---@param error Error
---@return Html
function ErrorDisplay.ErrorBox(error)
return ErrorDisplay.Box{
Expand All @@ -90,7 +90,7 @@ function ErrorDisplay.ErrorBox(error)
end

---Shows the message and stack trace of a lua error. Suitable for use in a popup.
---@param error error
---@param error Error
---@return Html
function ErrorDisplay.ErrorDetails(error)
local errorDetailsNode = mw.html.create('div'):addClass('error-details')
Expand Down
14 changes: 7 additions & 7 deletions standard/error_ext.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ local _local_errors = {}

local ErrorExt = {}

---@param error error
---@param error Error
function ErrorExt.logAndStash(error)
ErrorExt.Stash.add(error)
ErrorExt.log(error)
end

---@param error error
---@param error Error
function ErrorExt.log(error)
mw.log(ErrorExt.makeFullDetails(error))
mw.log()
Expand All @@ -37,7 +37,7 @@ local function tableOrEmpty(tbl)
return type(tbl) == 'table' and tbl or {}
end

---@param error error
---@param error Error
---@return string
function ErrorExt.makeFullDetails(error)
local parts = Array.extend(
Expand All @@ -51,7 +51,7 @@ end

---Builds a string for fields not covered by the other functions in this module.
---Returns nil if there are no extra fields.
---@param error error
---@param error Error
---@return string?
function ErrorExt.printExtraProps(error)
local extraProps = Table.copy(error)
Expand All @@ -70,7 +70,7 @@ function ErrorExt.printExtraProps(error)
end
end

---@param error error
---@param error Error
---@return string
function ErrorExt.makeFullStackTrace(error)
local parts = Array.extend(
Expand All @@ -91,7 +91,7 @@ local Stash = {}
ErrorExt.Stash = Stash

---Adds an Error instance to the local store.
---@param error error
---@param error Error
---@param storeAsPageVar boolean?
function Stash.add(error, storeAsPageVar)
if storeAsPageVar then
Expand All @@ -104,7 +104,7 @@ function Stash.add(error, storeAsPageVar)
end

---Returns all errors (locally and from page variables), and clears the store.
---@return error[]
---@return Error[]
function Stash.retrieve()
local errors = {}

Expand Down
2 changes: 1 addition & 1 deletion standard/feature_flag.lua
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ end
---@generic V
---@param flags? {[string]: boolean}
---@param f fun(): V
---@return V|error
---@return V|Error
function FeatureFlag.with(flags, f)
if Table.isEmpty(flags) then
return f()
Expand Down
16 changes: 9 additions & 7 deletions standard/logic.lua
Original file line number Diff line number Diff line change
Expand Up @@ -143,22 +143,24 @@ function Logic.tryCatch(try, catch)
end

---@param f function
---@return any?
---@return RoEResult|RoEError
function Logic.try(f)
return require('Module:ResultOrError').try(f)
local ResultOrError = require('Module:ResultOrError')
return ResultOrError.try(f)
end

---Returns the result of a function if successful. Otherwise it returns the result of the second function.
---If the first function fails, its error is logged to the console and stashed away for display.
---@param f fun(): any
---@param other? fun(error: error): any
---@param makeError? fun(error: error): error function that allows customizing Error instance being logged and stashed.
---@param other? fun(error: Error): any
---@param makeError? fun(error: Error): Error function that allows customizing Error instance being logged and stashed.
---@return any?
function Logic.tryOrElseLog(f, other, makeError)
return Logic.try(f)
:catch(function(error)
if type(error) == 'string' then
error = require('Module:Error')(error)
local Error = require('Module:Error')
if not Error.isError(error) then
error = Error(error)
end

error.header = 'Error occured while calling a function: (caught by Logic.tryOrElseLog)'
Expand All @@ -178,7 +180,7 @@ end
---Returns the result of a function if successful. Otherwise it returns nil.
---If the first function fails, its error is logged to the console and stashed away for display.
---@param f fun(): any
---@param makeError? fun(error: error): error function that allows customizing Error instance being logged and stashed.
---@param makeError? fun(error: Error): Error function that allows customizing Error instance being logged and stashed.
---@return function
function Logic.wrapTryOrLog(f, makeError)
return function(...)
Expand Down
Loading

0 comments on commit 4090fca

Please sign in to comment.