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