local env_config_path = os.getenv('APROXY_CONFIG_PATH') local DEFAULT_CONFIG_PATH = ".;/etc/aproxy" local config_path = env_config_path or DEFAULT_CONFIG_PATH local function findConfigFile() for _, config_directory in ipairs(string.split(config_path, ";")) do local possible_config_path = config_directory .. "/" .. "conf.lua" local fd, res = io.open(possible_config_path, "rb") if fd then local data = fd:read("*a") fd:close() return data else log('config not found at ' .. possible_config_path .. ':' .. tostring(res)) end end return nil end local function insertError(errors, key, message) errors[key] = errors[key] or {} table.insert(errors[key], message) return errors end local function fakeTableSchema(value_schema) return setmetatable({ __list = true }, { __index = function () return value_schema end }) end local SPECIAL_KEYS = {__list = true} local function validateSchema(schema, input, errors_in) local errors = errors_in or {} if schema.__list then -- generate full schema for lists that are same size as input for k, _ in pairs(input) do schema[k] = schema.__schema_value end end for key, field_schema in pairs(schema) do if not SPECIAL_KEYS[key] then assert(field_schema, 'schema not provided') local input_value = input[key] local input_type = type(input_value) local wanted_type = field_schema.type local actual_wanted_type = wanted_type if wanted_type == 'list' then actual_wanted_type = 'table' end if input_type == actual_wanted_type then if wanted_type == 'table' then -- recursive schema validation for generic tables errors[key] = errors[key] or {} validateSchema(field_schema.schema, input_value, errors[key]) if next(errors[key]) == nil then errors[key] = nil end elseif wanted_type == 'list' then -- for lists (which only have schemas for values), interpret -- it differently errors[key] = errors[key] or {} validateSchema(fakeTableSchema(field_schema.schema), input_value, errors[key]) if next(errors[key]) == nil then errors[key] = nil end end else insertError( errors, key, string.format('wanted %s but got %s', tostring(wanted_type), tostring(input_type)) ) end end end return errors end local function validateConfigFile(config_object) local all_schema_errors = {} for module_name, module_config in pairs(config_object.wantedScripts) do local module_manifest = require('scripts.' .. module_name) local config_schema = module_manifest.config local schema_errors = validateSchema(config_schema, module_config) if schema_errors then all_schema_errors[module_name] = schema_errors end end return all_schema_errors end local function loadConfigFile(options_in) local options = options_in or {} local config_file_data = assert(findConfigFile(), 'no config file found, config path: ' .. config_path) local config_file_function = assert(loadstring(config_file_data)) local config_object = config_file_function() if options.validate then local schema_errors = validateConfigFile(config_object) local total_count = table.pprint(schema_errors, {call=function() end}) if total_count > 0 then log('CONFIG ERROR') table.pprint(schema_errors, {call=log}) end end return config_object end return { loadConfigFile=loadConfigFile, validateSchema=validateSchema, }