34
35:- module(swish_form,
36 [ validate_form/2, 37 validate_field/4, 38 input_error/2 39 ]).
57validate_form(Dict, Fields) :-
58 validate_form(Fields, Dict, Errors),
59 ( Errors == []
60 -> true
61 ; throw(error(form_error(Errors), _))
62 ).
63
64validate_form([], _, []).
65validate_form([field(Field, Value, Options)|T], Dict, Errors) :-
66 catch(validate_field(Dict, Field, Value, Options),
67 error(input_error(Field, Error),_),
68 true),
69 ( var(Error)
70 -> Errors = Errors1
71 ; Errors = [input_error(Field, Error)|Errors1]
72 ),
73 validate_form(T, Dict, Errors1).
126validate_field(Dict, Field, Value, Options) :-
127 ( Value0 = Dict.get(Field),
128 \+ is_empty(Value0)
129 -> validate_value(Options, Value0, Value, Field)
130 ; memberchk(default(Value), Options)
131 -> true
132 ; input_error(Field, required)
133 ).
134
135is_empty(Value) :-
136 text(Value),
137 normalize_space(string(""), Value).
138
139text(Value) :- atom(Value), !.
140text(Value) :- string(Value).
141
142validate_value([], Value, Value, _).
143validate_value([H|T], Value0, Value, Field) :-
144 ( validate_step(H, Value0, Value1)
145 -> true
146 ; current_type(H, _, _),
147 is_of_type(H, Value0)
148 -> Value1 = Value0
149 ; validate_failed(H, Value0, Field)
150 ),
151 validate_value(T, Value1, Value, Field).
155validate_step(alnum, Value, Value) :-
156 forall(sub_atom(Value, _, 1, _, Char),
157 char_type(Char, alnum)).
158validate_step(length >= N, Value, Value) :-
159 string_length(Value, Len),
160 Len >= N.
161validate_step(length > N, Value, Value) :-
162 string_length(Value, Len),
163 Len > N.
164validate_step(length < N, Value, Value) :-
165 string_length(Value, Len),
166 Len < N.
167validate_step(length =< N, Value, Value) :-
168 string_length(Value, Len),
169 Len =< N.
170validate_step(strip, Value0, Value) :-
171 normalize_space(string(Value), Value0).
172validate_step(term, Value0, Value) :-
173 term_string(Value, Value0).
174validate_step(alnum_and_spaces, Value, Value) :-
175 forall(sub_atom(Value, _, 1, _, Char),
176 alnum_or_space(Char)).
177validate_step(email, Value, Value) :-
178 string_codes(Value, Codes),
179 phrase(email, Codes).
180validate_step(url, Value, Value) :-
181 validate_step(url(_), Value, Value).
182validate_step(url(Scheme), Value, Value) :-
183 is_url(Scheme, Value).
184validate_step(downcase, Value0, Value) :-
185 string_lower(Value0, Value).
186validate_step(atom, Value0, Value) :-
187 atom_string(Value, Value0).
188validate_step(string, Value0, Value) :-
189 ( string(Value0)
190 -> Value = Value0
191 ; atom_string(Value0, Value)
192 ).
193validate_step(number, Value0, Value) :-
194 number_string(Value, Value0).
195validate_step(integer, Value0, Value) :-
196 number_string(Value, Value0),
197 integer(Value).
198validate_step(float, Value0, Value) :-
199 number_string(Value1, Value0),
200 Value is float(Value1).
201validate_step(oneof(List), Value0, Value) :-
202 member(Value, List),
203 string_value(Value0, Value), !.
204validate_step(password, Value, Value) :-
205 string_length(Value, Len),
206 Len >= 6.
207validate_step(default(_), Value, Value).
208
209alnum_or_space(' ') :- !.
210alnum_or_space(Char) :-
211 char_type(Char, alnum).
212
213email --> user_name, "@", domain_name.
214user_name --> user_name_char, user_name_chars.
215domain_name --> domain_name_segment, ".", domain_name_segments.
216
217user_name_chars --> user_name_char, !, user_name_chars.
218user_name_chars --> "".
219
220user_name_char -->
221 [C],
222 { between(1, 127, C),
223 ( code_type(C, alnum)
224 -> true
225 ; name_special(C)
226 )
227 }.
228
229name_special(0'.).
230name_special(0'-).
231
232domain_name_segment --> domain_name_char, domain_name_chars.
233domain_name_segments -->
234 domain_name_segment,
235 ( "."
236 -> domain_name_segments
237 ; ""
238 ).
239
240domain_name_chars --> domain_name_char, !, domain_name_chars.
241domain_name_chars --> "".
242
243domain_name_char -->
244 [C],
245 { between(1, 127, C),
246 ( code_type(C, alnum)
247 -> true
248 ; domain_special(C)
249 )
250 }.
251
252domain_special(0'-).
253domain_special(0'_).
259is_url(Scheme, URL) :-
260 ( string(URL)
261 -> true
262 ; atom(URL)
263 ),
264 uri_components(URL, Components),
265 valid_url_scheme(Scheme, Components),
266 valid_authority(Components).
267
268valid_url_scheme(SchemeReq, Components) :-
269 uri_data(scheme, Components, Scheme),
270 nonvar(Scheme),
271 is_scheme(SchemeReq, Scheme).
272
273is_scheme(Scheme, Scheme) :- !.
274is_scheme(http, https).
275
276valid_authority(Components) :-
277 uri_data(authority, Components, Authority),
278 nonvar(Authority).
285string_value(Value, Value) :- !.
286string_value(String, Value) :-
287 atom(Value),
288 atom_string(Value, String), !.
289string_value(String, Value) :-
290 number(Value),
291 number_string(String, Value1),
292 Value1 =:= Value.
293
294validate_failed(H, _Value0, Field) :-
295 input_error(Field, H).
296
297input_error(Field, Error) :-
298 throw(error(input_error(Field, Error), _)).
299
300
301 304
305:- multifile prolog:error_message//1. 306
307prolog:error_message(input_error(Field, Expected)) -->
308 [ '~w: '-[Field] ],
309 expected(Expected).
310prolog:error_message(form_error(Errors)) -->
311 field_errors(Errors).
312
313field_errors([]) --> [].
314field_errors([H|T]) -->
315 prolog:error_message(H),
316 ( {T==[]}
317 -> []
318 ; [nl],
319 field_errors(T)
320 ).
321
322
323expected(oneof(List)) --> !,
324 [ 'One of '-[] ],
325 oneof(List).
326expected(required) --> !,
327 [ 'This field is required'-[] ].
328expected(length > N) --> !,
329 [ 'Needs at more than ~d characters'-[N] ].
330expected(length >= N) --> !,
331 [ 'Needs at least ~d characters'-[N] ].
332expected(length =< N) --> !,
333 [ 'Needs at most ~d characters'-[N] ].
334expected(length < N) --> !,
335 [ 'Needs less than ~d characters'-[N] ].
336expected(matching_password) -->
337 [ 'The password does not match'-[] ].
338expected(new_user) -->
339 [ 'A user with this name already exists'-[] ].
340expected(Expected) --> !,
341 [ 'This field must hold a valid ~w'-[Expected] ].
342
343oneof([One]) --> !,
344 [ '~w'-[One] ].
345oneof([One, Two]) --> !,
346 [ '~w or ~w'-[One, Two] ].
347oneof([H|T]) -->
348 [ '~w, '-[H] ],
349 oneof(T)
Form handling utilities
This module simplifies handling input from forms in the SWISH interface. The values from a form can be turned into an object using
notebook.formData(form)
. The returned dict may be passed to a predicate inside SWISH. */