# frozen_string_literal: true require "openapi3_parser/array_sentence" module Openapi3Parser module Validators class MutuallyExclusiveFields using ArraySentence private_class_method :new def self.call(*args, **kwargs) new.call(*args, **kwargs) end def call(validatable, mutually_exclusive_fields:, raise_on_invalid: true) mutually_exclusive = MutuallyExclusiveFieldErrors.new( mutually_exclusive_fields, validatable.input ) handle_required_errors(validatable, mutually_exclusive.required_errors, raise_on_invalid) handle_exclusive_errors(validatable, mutually_exclusive.exclusive_errors, raise_on_invalid) end private def handle_required_errors(validatable, required_errors, raise_on_invalid) return unless required_errors.any? if raise_on_invalid location_summary = validatable.context.location_summary raise Error::MissingFields, "Mutually exclusive fields for "\ "#{location_summary}: #{required_errors.first}" else validatable.add_errors(required_errors) end end def handle_exclusive_errors(validatable, exclusive_errors, raise_on_invalid) return unless exclusive_errors.any? if raise_on_invalid location_summary = validatable.context.location_summary raise Error::UnexpectedFields, "Mutually exclusive fields for "\ "#{location_summary}: "\ "#{exclusive_errors.first}" else validatable.add_errors(exclusive_errors) end end class MutuallyExclusiveFieldErrors using ArraySentence def initialize(mutually_exclusive_fields, input) @mutually_exclusive_fields = mutually_exclusive_fields @input = input end def required_errors errors[:required] end def exclusive_errors errors[:exclusive] end def errors @errors ||= begin default = { required: [], exclusive: [] } mutually_exclusive_fields .each_with_object(default) do |exclusive, errors| add_error(errors, exclusive) end end end private attr_reader :mutually_exclusive_fields, :input def add_error(errors, mutually_exclusive) fields = mutually_exclusive.fields number_non_nil = count_non_nil_fields(fields) if number_non_nil.zero? && mutually_exclusive.required errors[:required] << required_error(fields) elsif number_non_nil > 1 errors[:exclusive] << exclusive_error(fields) end end def count_non_nil_fields(fields) fields.count do |field| data = input[field] data.respond_to?(:nil_input?) ? !data.nil_input? : !data.nil? end end def required_error(fields) "One of #{fields.sentence_join} is required" end def exclusive_error(fields) "#{fields.sentence_join} are mutually exclusive fields" end end end end end