Module:UtilsSchema

local p = {} local h = {}

local i18n = require("Module:i18n") local s = require("Module:i18n").getString local utilsMarkup = require("Module:UtilsMarkup") local utilsString = require("Module:UtilsString") local utilsTable = require("Module:UtilsTable")

function p.getTypeDefinitions(schemaName, schema) schema = h.resolveReferences(schema, h.collectReferences(schema)) local definitions = h.getTypeDoc(schemaName, schema) mw.logObject({definitions})

local definitionsList = utilsMarkup.definitionList({definitions}) return definitionsList end

function h.collectReferences(schema, references) references = references or {} h.walkSchema(function(schemaNode)		if schemaNode._id then			references[schemaNode._id] = schemaNode		end	end, schema) return references end

function h.resolveReferences(schema, references) h.walkSchema(function(schemaNode)		if schemaNode._ref then			local resolvedSchema = utilsTable.merge({}, references[schemaNode._ref], schemaNode)			utilsTable.merge(schemaNode, resolvedSchema)			schemaNode._ref = nil		end	end, schema) return schema end

function h.walkSchema(fn, schema) fn(schema) if schema.items then h.walkSchema(fn, schema.items) end if schema.properties then for i, v in ipairs(schema.properties) do			h.walkSchema(fn, v)		end end if schema.oneOf then for i, v in ipairs(schema.oneOf) do			h.walkSchema(fn, v)		end end end

function h.getTypeDoc(schemaName, schema, parentSchema) local type = schema.type local isRoot = not parentSchema local isSubtype = parentSchema and (parentSchema.type == "array" or parentSchema.oneOf) local required = schema.required or isSubtype -- Optionals don't make sense for array items — it's the array itself we would consider optional. Same goes for union types local definitions = { schemaName } if type == "record" then for _, prop in ipairs(schema.properties) do			local propDefs = h.getTypeDoc(prop.name, prop, schema) table.insert(definitions, {propDefs}) end elseif type == "array" then definitions, subtype = h.getTypeDoc(schemaName, schema.items, schema) type = s("symbols.array", subtype) elseif schema.oneOf then local subtypes = {} for _, subschema in ipairs(schema.oneOf) do			local subdefs, subtype = h.getTypeDoc("", subschema, schema) table.insert(definitions, {subdefs}) table.insert(subtypes, subtype) end type = table.concat(subtypes, s("symbols.union")) end if schema.desc then table.insert(definitions, 2, schema.desc) end return definitions, type end

function h.getTypeDefinitions(schema, schemaName, parentSchema) local type = schema.type local required = schema._required or parentSchema and (parentSchema.type == "array" or parentSchema.oneOf) local typeDescription local definitions if type == "string" or type == "number" or type == "boolean" or type == "any" then typeDescription = s("desc." .. type .. (required and ".required" or ".optional")) definitions = schema._desc elseif type == "record" then definitions = {} definitions[1] = schema._desc or nil for k, v in pairs(schema._properties) do			local propertyDefinitions = h.getTypeDefinitions(v, k)			table.insert(definitions, propertyDefinitions) end typeDescription = required and s("desc.record.required") or s("desc.record.optional") elseif type == "array" then definitions = {} local subtypeDefs, subtype = h.getTypeDefinitions(schema._items, "", schema) definitions = utilsTable.mergeArrays(definitions, utilsTable.slice(2)(subtypeDefs)) type = s("symbols.array", subtype) typeDescription = "typeDescription" elseif schema._oneOf then type = "oneOf" definitions = typeDescription = "typeDescription" else mw.addWarning(string.format("_type is %s but must be one of: string, number, boolean, record, oneOf, any", type or "nil")) end if not required then type = s("symbols.optional", type) end if not formattedType then formattedType = mw.text.nowiki(type) formattedType = utilsMarkup.code(formattedType) formattedType = "&thinsp;" .. utilsMarkup.explain(typeDescription)(formattedType) .. ""	end return {schemaName, definitions}, type end

i18n.loadStrings({	en = {		symbols = {			optional = "[%s]",			array = "{%s}",			union = "|",		},		desc = {			union = {				segment = "(%s) OR (%s)",				required= "One of: %s",				optional = "One of: %s OR nil",			},			any = {				required = "Value of any type except nil",				optional = "Value of any type including nil",			},			string = {				required = "Required string",				optional = "string or nil",			},			boolean = {				required = "Required boolean",				optional = "boolean or nil",			},			number = {				required = "Required number",				optional = "number or nil",			},			record = {				required = "Required table with a fixed set of string keys",				optional = "Table with a fixed set of string keys, or nil"			},			array = {				required = "Required array of %s",				optional = "Array of %s, or nil"			},		}	} })

return p