module Eco module API module Organization class PresetsFactory ABILITIES = File.join(__dir__, 'presets_values.json') INTEGRITY = File.join(__dir__, 'presets_integrity.json') class << self def all_abilities(hash = {}) Hash[abilities.each_with_object(nil).to_a].merge(hash) end def abilities_model @abilities_model ||= JSON.load(File.open(ABILITIES)) end def integrity_model @integrity_model ||= JSON.load(File.open(INTEGRITY)) end def abilities @abilities ||= abilities_model.keys end end def initialize(enviro: nil, policy_groups: nil) msg = "Expecting Environment object. Given: #{enviro.class}" fatal(msg) if enviro && !enviro.is_a?(Eco::API::Common::Session::Environment) @enviro = enviro @policy_groups = policy_groups end # @return [Array] all the abilities def keys self.class.abilities end def valid?(preset) validate(preset).empty? end def validate(preset) [].tap do |errors| if (err = preset_errors(preset)) errors << "{ '#{key}' preset -> #{err}}" end if (err = preset_integrity(preset)) errors << "{ '#{key}' preset -> #{err}}" end end end private def compile(*presets) presets.compact.reduce({}) do |p_1, p_2| merge(p_1, p_2) end end def merge(preset_1, preset_2) keys = preset_1.keys | preset_2.keys abilities_model.each_with_object({}) do |(key, values), result| next unless keys.include?(key) idx = [ values.index(preset_1[key]), values.index(preset_2[key]) ].compact.max result[key] = idx && values[idx] end end def preset_errors(preset) return "No preset given" unless preset errors = preset.map do |k, v| value_exists?(k, v) ? nil : "#{k}:#{v}" end.compact " Unknown: {#{errors.join(", ")}}" if errors.length.positive? end def preset_integrity(preset) # rubocop:disable Metrics/AbcSize preset.each_with_object([]) do |(ability, value), errors| next unless (checks = integrity_model[ability]) suberrors = [] checks.each do |check| next unless check["value"] == value check["conditions"].each do |cond, targets| case cond when "at_least" targets.each do |other, minimum| min_idx = ability_value_idx(other, minimum) oth_idx = ability_value_idx(other, preset[other]) next if min_idx <= oth_idx suberrors << "'#{other}' should be at least '#{minimum}'" end when "one_of" unless targets.any? {|other, expected| preset[other] == expected} suberrors << targets.each_with_object([]) do |(other, expected), out| out << "'#{other}': '#{expected}'" end.join(", ").then do |msg| "there should be at least one of: {#{msg}}" end end else warn("Unsuported integrity condition statement '#{cond}' in '#{ability}' with level '#{value}'") end end end unless suberrors.empty? errors << "Incorrect value '#{value}' for '#{ability}' - reasons: {#{suberrors.join(", ")}}" end end.then do |errors| " Integrity errors: { #{errors.join(", ")} }" unless errors.empty? end end def integrity_model self.class.integrity_model end def value_exists?(ability, value) abilities_model_inverted.dig(ability, value) end def abilities_model_inverted @abilities_model_inverted ||= abilities_model.transform_values do |values| values.each_with_object({}) {|v, h| h[v] = true } end end def ability_value_idx(ability, value) abilities_model[ability].index(value) || -1 end def abilities_model self.class.abilities_model end def policy_groups return @policy_groups if @policy_groups.is_a?(Eco::API::Organization::PolicyGroups) @policy_groups ||= @enviro&.api&.policy_groups.to_a unless @policy_groups.is_a?(Eco::API::Organization::PolicyGroups) @policy_groups = Eco::API::Organization::PolicyGroups.new(@policy_groups) end @policy_groups end def fatal(msg) raise msg unless @enviro @enviro.logger.fatal(msg) raise msg end def warn(msg) raise msg unless @enviro @enviro.logger.warn(msg) end end end end end