Goltergaul/definition

Allow returning a custom error message from Lambda Definitions

Closed this issue · 3 comments

If a Lambda definition fails then it currently returns a rather generic error message that only mentions the name of the lambda. This is not providing enough information to users to correct the bad input in some cases though. for example when building a definition that checks if a provided array includes only uniqe values then you might want to point out which value is not unique:

::Definition::Lambda(:unique_values) do |array|
  next unless array.uniq.size == array.size

  conform_with(array)
end

In this case something like this would be nice:

::Definition::Lambda(:unique_values) do |array|
  duplicates = array.select{ |e| array.count(e) > 1 }.uniq

  if duplicates.empty?
    conform_with(array)
  else
    fail_with("Array contains duplicate values: #{duplicates.join(', ')}")
  end
end

At the same time the version without fail_with should still work

Other usecases that come to mind:

  • An array needs to contain some specific items and you want to tell the user which ones are missing
  • You do validation across multiple fields, e.g. If field A is present, then field B must be present as well

Imagine there's a party Definition which requires

  • unique guests lists (you don't want to send multiple invitations to the same recipient)
  • unique gifts (since every guest should receive the unique present)

Assuming we make use of the unique_values lambda you described above, a simple Definition could look like this

party = Definition.Keys do
  required :guests, unique_values
  required :gifts, uniq_values
end

When I think of custom error messages in that party context, I would like to set the custom error messages on the Key level rather than the Lambda level:

party = Definition.Keys do
  required :guests, unique_values(error_message: "No guest should receive multiple invitations")
  required :gifts, uniq_values(error_message: "Don't give away the same present multiple times")
end

I like the way you proposed it on the Lambda level, but maybe configuring it on the Key level is also worth a consideration.

you can achieve such a multipurpose definition with this simple trick:

def UniqueArrayElements(error_message)
  ::Definition.Lambda do |array|
    duplicates = array.select{ |e| array.count(e) > 1 }.uniq

    if duplicates.empty?
      conform_with(array)
    else
      fail_with(error_message)
    end
  end
end

party = Definition.Keys do
  required :guests, UniqueArrayElements("No guest should receive multiple invitations")
  required :gifts, UniqueArrayElements("Don't give away the same present multiple times")
end

error messages on a key level (as part of the keys definition) is hard because the definition behind the key could be a deeply nested composed definition. then you'd have the issue that all the sub errors and the custom error would need to be brought together somehow in a meaningful way