跳转到内容

Module:数据页校验

来自Bleap Wiki

此模块的文档可以在Module:数据页校验/doc创建

-- 数据页结构校验: 供各列表模块调用
-- 结构错误时显红字并归入 [[分类:数据页异常]], 避免静默渲染成空表
-- 背景: 2026-09 曾因数据页被写成裸数组(缺少 songs) 导致全站列表空白
local p = {}

local CAT = '[[分类:数据页异常]]'

local function fail(msg)
	return '<span class="error">数据页错误(' .. msg .. ')</span>' .. CAT
end

function p.load(title, opts)
	opts = opts or {}
	local t = mw.title.new(title)
	if not t or not t.exists then
		return nil, fail('[[' .. title .. ']] 不存在')
	end
	local raw = t:getContent()
	if not raw or raw == '' then
		return nil, fail('[[' .. title .. ']] 内容为空')
	end
	local ok, data = pcall(mw.text.jsonDecode, raw)
	if not ok then
		return nil, fail('[[' .. title .. ']] JSON 解析失败(语法错误)')
	end
	if type(data) ~= 'table' then
		return nil, fail('[[' .. title .. ']] 顶层必须是对象 {"songs":[…],"special":[…]}')
	end
	-- 可选段: tutorial(教程) / story({songs,training}) / special(石墩子模式)
	for _, key in ipairs({ 'tutorial', 'special' }) do
		if data[key] ~= nil and type(data[key]) ~= 'table' then
			return nil, fail('[[' .. title .. ']] 的 ' .. key .. ' 必须是数组')
		end
	end
	if data.story ~= nil then
		if type(data.story) ~= 'table' then
			return nil, fail('[[' .. title .. ']] 的 story 必须是对象 {songs:[…],training:[…]}')
		end
		for _, key in ipairs({ 'songs', 'training' }) do
			if data.story[key] ~= nil and type(data.story[key]) ~= 'table' then
				return nil, fail('[[' .. title .. ']] 的 story.' .. key .. ' 必须是数组')
			end
		end
	end
	if opts.songs ~= false then
		if type(data.songs) ~= 'table' then
			return nil, fail('[[' .. title .. ']] 缺少 songs 数组 —— 必须是 {"songs":[…],"special":[…]}')
		end
		if #data.songs == 0 then
			return nil, fail('[[' .. title .. ']] 的 songs 数组为空(疑似被清空)')
		end
	end
	if opts.special and type(data.special) ~= 'table' then
		return nil, fail('[[' .. title .. ']] 缺少 special 数组')
	end
	return data
end

-- 自检入口: {{#invoke:数据页校验|check|模板:曲目列表.json}}
function p.check(frame)   -- Lua 标识符不能是中文, 故用 check
	local title = frame.args[1] or '模板:曲目列表.json'
	local data, err = p.load(title)
	if err then return err end
	local story = data.story or {}
	return string.format('数据页 <code>%s</code> 结构正常:songs=%d,tutorial=%d,story.songs=%d,story.training=%d,special=%d。',
		title, #(data.songs or {}), #(data.tutorial or {}),
		#(story.songs or {}), #(story.training or {}), #(data.special or {}))
end

return p