function t.map(U,A)assert(t.callback(U),t.callback(A))local o=t.keys(U)local K=t.values(A)return function(U)local A,B=o(U)if not A then return false,B or""end local x,S=K(U)if not x then return false,S or""end return true end end do local U=t.keys(t.integer)function t.array(A)assert(t.callback(A))local o=t.values(A)return function(A)local K,B=U(A)if K==false then return false,string.format("\091\097\114\114\097\121\093 \037\115",B or"")end local x=#A for U in pairs(A)do if U<1 or U>x then return false,string.format("\091\097\114\114\097\121\093 \107\101\121 \037\115 \109\117\115\116 \098\101 \115\101\113\117\101\110\116\105\097\108",tostring(U))end end local S,Q=o(A)if not S then return false,string.format("\091\097\114\114\097\121\093 \037\115",Q or"")end return true end end end do local U=t.array(t.callback)function t.union(...)local A={...}assert(U(A))return function(U)for A,o in pairs(A)do if o(U)then return true end end return false,"\098\097\100 \116\121\112\101 \102\111\114 \117\110\105\111\110"end end function t.intersection(...)local A={...}assert(U(A))return function(U)for A,o in pairs(A)do local K,B=o(U)if not K then return false,B or""end end return true end end end do local U=t.map(t.string,t.callback)function t.interface(A)assert(U(A))return function(U)local o,K=t.table(U)if o==false then return false,K or""end for A,o in pairs(A)do local K,B=o(U[A])if K==false then return false,string.format("\091\105\110\116\101\114\102\097\099\101\093 \098\097\100 \118\097\108\117\101 \102\111\114 \037\115\058\n\t\037\115",A,B or"")end end return true end end function t.strictInterface(A)assert(U(A))return function(U)local o,K=t.table(U)if o==false then return false,K or""end for A,o in pairs(A)do local K,B=o(U[A])if K==false then return false,string.format("\091\105\110\116\101\114\102\097\099\101\093 \098\097\100 \118\097\108\117\101 \102\111\114 \037\115\058\n\t\037\115",A,B or"")end end for U in pairs(U)do if not A[U]then return false,string.format("\091\105\110\116\101\114\102\097\099\101\093 \117\110\101\120\112\101\099\116\101\100 \102\105\101\108\100 \'\037\115\'",U)end end return true end end end
--[[**
	ensures value is a table and all keys pass keyCheck and all values pass valueCheck

	@param keyCheck The function to use to check the keys
	@param valueCheck The function to use to check the values

	@returns A function that will return true iff the condition is passed
**--]]
function t.map(keyCheck, valueCheck)
	assert(t.callback(keyCheck), t.callback(valueCheck))
	local keyChecker = t.keys(keyCheck)
	local valueChecker = t.values(valueCheck)
	return function(value)
		local keySuccess, keyErr = keyChecker(value)
		if not keySuccess then
			return false, keyErr or ""
		end

		local valueSuccess, valueErr = valueChecker(value)
		if not valueSuccess then
			return false, valueErr or ""
		end

		return true
	end
end

do
	local arrayKeysCheck = t.keys(t.integer)
	--[[**
		ensures value is an array and all values of the array match check

		@param check The check to compare all values with

		@returns A function that will return true iff the condition is passed
	**--]]
	function t.array(check)
		assert(t.callback(check))
		local valuesCheck = t.values(check)
		return function(value)
			local keySuccess, keyErrMsg = arrayKeysCheck(value)
			if keySuccess == false then
				return false, string.format("[array] %s", keyErrMsg or "")
			end

			-- all keys are sequential
			local arraySize = #value
			for key in pairs(value) do
				if key < 1 or key > arraySize then
					return false, string.format("[array] key %s must be sequential", tostring(key))
				end
			end

			local valueSuccess, valueErrMsg = valuesCheck(value)
			if not valueSuccess then
				return false, string.format("[array] %s", valueErrMsg or "")
			end

			return true
		end
	end
end

do
	local callbackArray = t.array(t.callback)
	--[[**
		creates a union type

		@param ... The checks to union

		@returns A function that will return true iff the condition is passed
	**--]]
	function t.union(...)
		local checks = { ... }
		assert(callbackArray(checks))
		return function(value)
			for _, check in pairs(checks) do
				if check(value) then
					return true
				end
			end
			return false, "bad type for union"
		end
	end

	--[[**
		creates an intersection type

		@param ... The checks to intersect

		@returns A function that will return true iff the condition is passed
	**--]]
	function t.intersection(...)
		local checks = { ... }
		assert(callbackArray(checks))
		return function(value)
			for _, check in pairs(checks) do
				local success, errMsg = check(value)
				if not success then
					return false, errMsg or ""
				end
			end
			return true
		end
	end
end

do
	local checkInterface = t.map(t.string, t.callback)
	--[[**
		ensures value matches given interface definition

		@param checkTable The interface definition

		@returns A function that will return true iff the condition is passed
	**--]]
	function t.interface(checkTable)
		assert(checkInterface(checkTable))
		return function(value)
			local tableSuccess, tableErrMsg = t.table(value)
			if tableSuccess == false then
				return false, tableErrMsg or ""
			end

			for key, check in pairs(checkTable) do
				local success, errMsg = check(value[key])
				if success == false then
					return false, string.format("[interface] bad value for %s:\n\t%s", key, errMsg or "")
				end
			end
			return true
		end
	end

	--[[**
		ensures value matches given interface definition strictly

		@param checkTable The interface definition

		@returns A function that will return true iff the condition is passed
	**--]]
	function t.strictInterface(checkTable)
		assert(checkInterface(checkTable))
		return function(value)
			local tableSuccess, tableErrMsg = t.table(value)
			if tableSuccess == false then
				return false, tableErrMsg or ""
			end

			for key, check in pairs(checkTable) do
				local success, errMsg = check(value[key])
				if success == false then
					return false, string.format("[interface] bad value for %s:\n\t%s", key, errMsg or "")
				end
			end

			for key in pairs(value) do
				if not checkTable[key] then
					return false, string.format("[interface] unexpected field '%s'", key)
				end
			end

			return true
		end
	end
end
