Skip to content

Instantly share code, notes, and snippets.

@dvliman
Created August 3, 2015 19:15
Show Gist options
  • Save dvliman/573c5a57267f41d90b3f to your computer and use it in GitHub Desktop.
Save dvliman/573c5a57267f41d90b3f to your computer and use it in GitHub Desktop.
functional approach in validation. erlang.
-module(validation_utils).
-export([validate_until_first_error/2]).
-export([validate_all_errors/2]).
-type validation_args() :: [term()].
-type validation_funs() :: [{function(), atom()}].
-spec validate_until_first_error(validation_args(), validation_funs()) -> ok | {error, atom()}.
% validate_until_first_error/2 applies list of validation_funs() to each element in args
% until it stops at the first error it encounters. There is no restriction
% as it what goes to args i.e args can be [term()], [[{key, value}]], etc
% and/or what validation_funs() does, as long as it is a predicate that
% returns a true or false value. For example:
%
% say we have these 3 validation functions:
% validate_smaller(Left, Right) -> Left < Right.
% validate_equals(Left, Right) -> Left =:= Right.
% validate_larger(Left, Right) -> Left > Right.
%
% Args = [1,2]
% ValidationFuns = [{fun validate_smaller/2, must_be_smaller},
% {fun validate_larger/2, must_be_larger},
% {fun validate_equals/2, must_be_equal}],
%
% ValidationFuns are executed in order. Since we are failing on
% 'validate_larger', the rest of validation functions are simply
% returned and not evaluated from lists:dropwhile. This approach
% is pure with high order function and does not wreck havoc the
% control flow with throw/catch
%
% {error, must_be_larger} = validate_until_first_error(Args, ValidationFuns)
validate_until_first_error(Args, ValidationFuns) ->
case lists:dropwhile(fun validate/1, [{Fn, ErrReason, Args} || {Fn, ErrReason} <- ValidationFuns]) of
[] -> ok;
[{_Fn, ErrReason, _Args} | _UnexecutedValidationFuns] -> {error, ErrReason}
end.
-spec validate_all_errors(validation_args(), validation_funs()) -> ok | [{error, atom()}].
% validate_all_errors/2 runs through all list of validation_funs()
% and returns either ok or [{error, Reason}, ...]
%
% we can use validate_all to carry new state through the accumulator
validate_all_errors(Args, ValidationFuns) ->
case lists:foldl(fun validate/2, [], [{Fn, ErrReason, Args} || {Fn, ErrReason} <- ValidationFuns]) of
[] -> ok;
ErrorList -> lists:reverse(ErrorList)
end.
validate({Fn, _ErrReason, Args}) ->
apply(Fn, Args).
validate({Fn, ErrReason, Args}, ErrorList) ->
case apply(Fn, Args) of
true -> ErrorList;
false -> [{error, ErrReason} | ErrorList]
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment