Extend hope_kv_list validation API with:
[hope.git] / src / hope_kv_list.erl
CommitLineData
03ac148f
SK
1%%%----------------------------------------------------------------------------
2%%% Equivalent to stdlib's orddict, but with a pretty (IMO), uniform interface.
3%%%----------------------------------------------------------------------------
4-module(hope_kv_list).
5
e163be89
SK
6-include_lib("hope_kv_list.hrl").
7
465f1bde 8-behavior(hope_gen_dictionary).
03ac148f
SK
9
10-export_type(
11 [ t/2
12 ]).
13
14-export(
15 [ empty/0
16 , get/2
870172d6 17 , get/3
03ac148f
SK
18 , set/3
19 , update/3
70cf8e86 20 , pop/2
03ac148f
SK
21 , iter/2
22 , map/2
23 , filter/2
24 , fold/3
25 , of_kv_list/1
26 , to_kv_list/1
e163be89
SK
27 , find_unique_presence_violations/2 % No optional keys
28 , find_unique_presence_violations/3 % Specify optional keys
29 , validate_unique_presence/2 % No optional keys
30 , validate_unique_presence/3 % Specify optional keys
03ac148f
SK
31 ]).
32
33
34-type t(K, V) ::
35 [{K, V}].
36
e163be89
SK
37-type presence_violations(A) ::
38 % This is a hack to effectively parametarize the types of record fields.
39 % IMPORTANT: Make sure that the order of fields matches the definition of
40 % #hope_kv_list_presence_violations
41 { hope_kv_list_presence_violations
42 , [A] % keys_missing
43 , [A] % keys_duplicated
44 , [A] % keys_unsupported
45 }.
46
fa24061d
SK
47-type presence_error(A) ::
48 {keys_missing , [A]}
49 | {keys_duplicated , [A]}
50 | {keys_unsupported , [A]}
51 .
52
03ac148f
SK
53
54%% ============================================================================
55%% API
56%% ============================================================================
57
58-spec empty() ->
59 [].
60empty() ->
61 [].
62
95c3c2a7
PO
63-spec get(t(K, V), K) ->
64 hope_option:t(V).
03ac148f
SK
65get(T, K) ->
66 case lists:keyfind(K, 1, T)
67 of false -> none
68 ; {K, V} -> {some, V}
69 end.
70
870172d6
SK
71-spec get(t(K, V), K, V) ->
72 V.
73get(T, K, Default) ->
74 Vopt = get(T, K),
75 hope_option:get(Vopt, Default).
76
95c3c2a7
PO
77-spec set(t(K, V), K, V) ->
78 t(K, V).
03ac148f
SK
79set(T, K, V) ->
80 lists:keystore(K, 1, T, {K, V}).
81
95c3c2a7
PO
82-spec update(t(K, V), K, fun((hope_option:t(V)) -> V)) ->
83 t(K, V).
03ac148f
SK
84update(T, K, F) ->
85 V1Opt = get(T, K),
86 V2 = F(V1Opt),
37c6e98b 87 % TODO: Eliminate the 2nd lookup.
03ac148f
SK
88 set(T, K, V2).
89
95c3c2a7
PO
90-spec pop(t(K, V), K) ->
91 {hope_option:t(V), t(K, V)}.
70cf8e86
SK
92pop(T1, K) ->
93 case lists:keytake(K, 1, T1)
94 of {value, {K, V}, T2} -> {{some, V}, T2}
95 ; false -> {none , T1}
96 end.
97
95c3c2a7
PO
98-spec iter(t(K, V), fun((K, V) -> ok)) ->
99 ok.
c1672ac4 100iter(T, F1) ->
d10156b0 101 F2 = lift_map(F1),
c1672ac4 102 lists:foreach(F2, T).
03ac148f 103
95c3c2a7
PO
104-spec map(t(K, V), fun((K, V) -> V)) ->
105 t(K, V).
c1672ac4 106map(T, F1) ->
d10156b0
SK
107 F2 = fun ({K, _}=X) -> {K, apply_map(F1, X)} end,
108 lists:map(F2, T).
03ac148f 109
95c3c2a7
PO
110-spec filter(t(K, V), fun((K, V) -> boolean())) ->
111 t(K, V).
c1672ac4 112filter(T, F1) ->
d10156b0 113 F2 = lift_map(F1),
c1672ac4 114 lists:filter(F2, T).
03ac148f 115
95c3c2a7
PO
116-spec fold(t(K, V), fun((K, V, Acc) -> Acc), Acc) ->
117 Acc.
03ac148f
SK
118fold(T, F1, Accumulator) ->
119 F2 = fun ({K, V}, Acc) -> F1(K, V, Acc) end,
097a2aa3 120 lists:foldl(F2, Accumulator, T).
03ac148f 121
95c3c2a7
PO
122-spec to_kv_list(t(K, V)) ->
123 [{K, V}].
03ac148f
SK
124to_kv_list(T) ->
125 T.
126
95c3c2a7
PO
127-spec of_kv_list([{K, V}]) ->
128 t(K, V).
03ac148f 129of_kv_list(List) ->
37c6e98b 130 % TODO: Decide if validation is to be done here. Do so if yes.
03ac148f
SK
131 List.
132
fa24061d
SK
133-spec validate_unique_presence(t(K, _V), [K]) ->
134 hope_result:t(ok, [presence_error(K)]).
135validate_unique_presence(T, KeysRequired) ->
136 KeysOptional = [],
137 validate_unique_presence(T, KeysRequired, KeysOptional).
138
139-spec validate_unique_presence(t(K, _V), [K], [K]) ->
140 hope_result:t(ok, [presence_error(K)]).
141validate_unique_presence(T, KeysRequired, KeysOptional) ->
e163be89
SK
142 case find_unique_presence_violations(T, KeysRequired, KeysOptional)
143 of #hope_kv_list_presence_violations
144 { keys_missing = []
145 , keys_duplicated = []
146 , keys_unsupported = []
147 } ->
fa24061d 148 {ok, ok}
e163be89
SK
149 ; #hope_kv_list_presence_violations
150 { keys_missing = KeysMissing
151 , keys_duplicated = KeysDuplicated
152 , keys_unsupported = KeysUnsupported
153 } ->
154 ErrorMissing =
155 case KeysMissing
fa24061d 156 of [] -> []
e163be89 157 ; [_|_] -> [{keys_missing, KeysMissing}]
fa24061d 158 end,
e163be89
SK
159 ErrorDups =
160 case KeysDuplicated
fa24061d 161 of [] -> []
e163be89 162 ; [_|_] -> [{keys_duplicated, KeysDuplicated}]
fa24061d
SK
163 end,
164 ErrorUnsupported =
e163be89 165 case KeysUnsupported
fa24061d 166 of [] -> []
e163be89 167 ; [_|_] -> [{keys_unsupported, KeysUnsupported}]
fa24061d
SK
168 end,
169 Errors = ErrorDups ++ ErrorMissing ++ ErrorUnsupported,
170 {error, Errors}
171 end.
172
e163be89
SK
173-spec find_unique_presence_violations(t(K, _V), [K]) ->
174 presence_violations(K).
175find_unique_presence_violations(T, KeysRequired) ->
176 KeysOptional = [],
177 find_unique_presence_violations(T, KeysRequired, KeysOptional).
178
179-spec find_unique_presence_violations(t(K, _V), [K], [K]) ->
180 presence_violations(K).
181find_unique_presence_violations(T, KeysRequired, KeysOptional) ->
182 KeysSupported = KeysRequired ++ KeysOptional,
183 KeysGiven = [K || {K, _V} <- T],
184 KeysGivenUnique = lists:usort(KeysGiven),
185 KeysDuplicated = lists:usort(KeysGiven -- KeysGivenUnique),
186 KeysMissing = KeysRequired -- KeysGivenUnique,
187 KeysUnsupported = KeysGivenUnique -- KeysSupported,
188 #hope_kv_list_presence_violations
189 { keys_missing = KeysMissing
190 , keys_duplicated = KeysDuplicated
191 , keys_unsupported = KeysUnsupported
192 }.
193
03ac148f
SK
194
195%% ============================================================================
196%% Helpers
197%% ============================================================================
198
48371999
SK
199-spec lift_map(F) ->
200 G
201 when F :: fun(( K, V1 ) -> V2)
202 , G :: fun(({K, V1}) -> V2)
203 .
d10156b0
SK
204lift_map(F) ->
205 fun (X) -> apply_map(F, X) end.
206
48371999
SK
207-spec apply_map(fun((K, V1) -> V2), {K, V1}) ->
208 V2.
d10156b0
SK
209apply_map(F, {K, V}) ->
210 F(K, V).
This page took 0.035968 seconds and 4 git commands to generate.