• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 1296

19 Jan 2026 11:25AM UTC coverage: 33.562% (+0.09%) from 33.468%
1296

push

github

badlop
mod_conversejs: Cosmetic change: sort paths alphabetically

0 of 4 new or added lines in 1 file covered. (0.0%)

11245 existing lines in 174 files now uncovered.

15580 of 46421 relevant lines covered (33.56%)

1074.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

49.24
/src/econf.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : econf.erl
3
%%% Purpose : Validator for ejabberd configuration options
4
%%%
5
%%%
6
%%% ejabberd, Copyright (C) 2002-2026   ProcessOne
7
%%%
8
%%% This program is free software; you can redistribute it and/or
9
%%% modify it under the terms of the GNU General Public License as
10
%%% published by the Free Software Foundation; either version 2 of the
11
%%% License, or (at your option) any later version.
12
%%%
13
%%% This program is distributed in the hope that it will be useful,
14
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
15
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
%%% General Public License for more details.
17
%%%
18
%%% You should have received a copy of the GNU General Public License along
19
%%% with this program; if not, write to the Free Software Foundation, Inc.,
20
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
%%%
22
%%%----------------------------------------------------------------------
23
-module(econf).
24

25
%% API
26
-export([parse/3, validate/2, fail/1, format_error/2, replace_macros/1]).
27
-export([group_dups/1]).
28
%% Simple types
29
-export([pos_int/0, pos_int/1, non_neg_int/0, non_neg_int/1]).
30
-export([int/0, int/2, number/1, octal/0]).
31
-export([binary/0, binary/1, binary/2]).
32
-export([string/0, string/1, string/2]).
33
-export([enum/1, bool/0, atom/0, any/0]).
34
%% Complex types
35
-export([url/0, url/1]).
36
-export([file/0, file/1]).
37
-export([directory/0, directory/1]).
38
-export([ip/0, ipv4/0, ipv6/0, ip_mask/0, port/0]).
39
-export([re/0, re/1, glob/0, glob/1]).
40
-export([path/0, binary_sep/1]).
41
-export([beam/0, beam/1, base64/0]).
42
-export([timeout/1, timeout/2]).
43
%% Composite types
44
-export([list/1, list/2]).
45
-export([list_or_single/1, list_or_single/2]).
46
-export([map/2, map/3]).
47
-export([either/2, and_then/2, non_empty/1]).
48
-export([options/1, options/2]).
49
%% Custom types
50
-export([acl/0, shaper/0, url_or_file/0, lang/0]).
51
-export([pem/0, queue_type/0]).
52
-export([jid/0, user/0, domain/0, resource/0]).
53
-export([db_type/1, ldap_filter/0]).
54
-export([host/0, hosts/0]).
55
-export([vcard_temp/0]).
56
-ifdef(SIP).
57
-export([sip_uri/0]).
58
-endif.
59

60
-type error_reason() :: term().
61
-type error_return() :: {error, error_reason(), yconf:ctx()}.
62
-type validator() :: yconf:validator().
63
-type validator(T) :: yconf:validator(T).
64
-type validators() :: yconf:validators().
65
-export_type([validator/0, validator/1, validators/0]).
66
-export_type([error_reason/0, error_return/0]).
67

68
%%%===================================================================
69
%%% API
70
%%%===================================================================
71
parse(File, Validators, Options) ->
72
    try yconf:parse(File, Validators, Options)
14✔
73
    catch _:{?MODULE, Reason, Ctx} ->
74
            {error, Reason, Ctx}
×
75
    end.
76

77
validate(Validator, Y) ->
78
    try yconf:validate(Validator, Y)
169✔
79
    catch _:{?MODULE, Reason, Ctx} ->
80
            {error, Reason, Ctx}
×
81
    end.
82

83
replace_macros(Y) ->
84
    yconf:replace_macros(Y).
×
85

86
-spec fail(error_reason()) -> no_return().
87
fail(Reason) ->
88
    yconf:fail(?MODULE, Reason).
×
89

90
format_error({bad_module, Mod}, Ctx)
91
  when Ctx == [listen, module];
92
       Ctx == [listen, request_handlers] ->
93
    Mods = ejabberd_config:beams(all),
×
94
    format("~ts: unknown ~ts: ~ts. Did you mean ~ts?",
×
95
           [yconf:format_ctx(Ctx),
96
            format_module_type(Ctx),
97
            format_module(Mod),
98
            format_module(misc:best_match(Mod, Mods))]);
99
format_error({bad_module, Mod}, Ctx)
100
  when Ctx == [modules] ->
101
    Mods = lists:filter(
×
102
             fun(M) ->
103
                     case atom_to_list(M) of
×
104
                         "mod_" ++ _ -> true;
×
105
                         "Elixir.Mod" ++ _ -> true;
×
106
                         _ -> false
×
107
                     end
108
             end, ejabberd_config:beams(all)),
109
    format("~ts: unknown ~ts: ~ts. Did you mean ~ts? "
×
110
           "If it's an external module that should get installed, "
111
           "use toplevel option: install_contrib_modules",
112
           [yconf:format_ctx(Ctx),
113
            format_module_type(Ctx),
114
            format_module(Mod),
115
            format_module(misc:best_match(Mod, Mods))]);
116
format_error({bad_export, {F, A}, Mod}, Ctx)
117
  when Ctx == [listen, module];
118
       Ctx == [listen, request_handlers];
119
       Ctx == [modules] ->
120
    Type = format_module_type(Ctx),
×
121
    Slogan = yconf:format_ctx(Ctx),
×
122
    case lists:member(Mod, ejabberd_config:beams(local)) of
×
123
        true ->
124
            format("~ts: '~ts' is not a ~ts",
×
125
                   [Slogan, format_module(Mod), Type]);
126
        false ->
127
            case lists:member(Mod, ejabberd_config:beams(external)) of
×
128
                true ->
129
                    format("~ts: third-party ~ts '~ts' doesn't export "
×
130
                           "function ~ts/~B. If it's really a ~ts, "
131
                           "consider to upgrade it",
132
                           [Slogan, Type, format_module(Mod),F, A, Type]);
133
                false ->
134
                    format("~ts: '~ts' doesn't match any known ~ts",
×
135
                           [Slogan, format_module(Mod), Type])
136
            end
137
    end;
138
format_error({unknown_option, [], _} = Why, Ctx) ->
139
    format("~ts. There are no available options",
×
140
           [yconf:format_error(Why, Ctx)]);
141
format_error({unknown_option, Known, Opt} = Why, Ctx) ->
142
    format("~ts. Did you mean ~ts? ~ts",
×
143
           [yconf:format_error(Why, Ctx),
144
            misc:best_match(Opt, Known),
145
            format_known("Available options", Known)]);
146
format_error({bad_enum, Known, Bad} = Why, Ctx) ->
147
    format("~ts. Did you mean ~ts? ~ts",
×
148
           [yconf:format_error(Why, Ctx),
149
            misc:best_match(Bad, Known),
150
            format_known("Possible values", Known)]);
151
format_error({bad_yaml, _, _} = Why, _) ->
152
    format_error(Why);
×
153
format_error(Reason, Ctx) ->
154
    yconf:format_ctx(Ctx) ++ ": " ++ format_error(Reason).
×
155

156
format_error({bad_db_type, _, Atom}) ->
157
    format("unsupported database: ~ts", [Atom]);
×
158
format_error({bad_lang, Lang}) ->
159
    format("Invalid language tag: ~ts", [Lang]);
×
160
format_error({bad_pem, Why, Path}) ->
161
    format("Failed to read PEM file '~ts': ~ts",
×
162
           [Path, pkix:format_error(Why)]);
163
format_error({bad_cert, Why, Path}) ->
164
    format_error({bad_pem, Why, Path});
×
165
format_error({bad_jwt_key, Path}) ->
166
    format("No valid JWT key found in file: ~ts", [Path]);
×
167
format_error({bad_jwt_key_set, Path}) ->
168
    format("JWK set contains multiple JWT keys in file: ~ts", [Path]);
×
169
format_error({bad_jid, Bad}) ->
170
    format("Invalid XMPP address: ~ts", [Bad]);
×
171
format_error({bad_user, Bad}) ->
172
    format("Invalid user part: ~ts", [Bad]);
×
173
format_error({bad_domain, Bad}) ->
174
    format("Invalid domain: ~ts", [Bad]);
×
175
format_error({bad_resource, Bad}) ->
176
    format("Invalid resource part: ~ts", [Bad]);
×
177
format_error({bad_ldap_filter, Bad}) ->
178
    format("Invalid LDAP filter: ~ts", [Bad]);
×
179
format_error({bad_sip_uri, Bad}) ->
180
    format("Invalid SIP URI: ~ts", [Bad]);
×
181
format_error({route_conflict, R}) ->
182
    format("Failed to reuse route '~ts' because it's "
×
183
           "already registered on a virtual host",
184
           [R]);
185
format_error({listener_dup, AddrPort}) ->
186
    format("Overlapping listeners found at ~ts",
×
187
           [format_addr_port(AddrPort)]);
188
format_error({listener_conflict, AddrPort1, AddrPort2}) ->
189
    format("Overlapping listeners found at ~ts and ~ts",
×
190
           [format_addr_port(AddrPort1),
191
            format_addr_port(AddrPort2)]);
192
format_error({invalid_syntax, Reason}) ->
193
    format("~ts", [Reason]);
×
194
format_error({missing_module_dep, Mod, DepMod}) ->
195
    format("module ~ts depends on module ~ts, "
×
196
           "which is not found in the config",
197
           [Mod, DepMod]);
198
format_error(eimp_error) ->
199
    format("ejabberd is built without image converter support", []);
×
200
format_error({mqtt_codec, Reason}) ->
201
    mqtt_codec:format_error(Reason);
×
202
format_error({external_module_error, Module, Error}) ->
203
    try Module:format_error(Error)
×
204
    catch _:_ ->
205
        format("Invalid value", [])
×
206
    end;
207
format_error(Reason) ->
208
    yconf:format_error(Reason).
×
209

210
-spec format_module(atom() | string()) -> string().
211
format_module(Mod) when is_atom(Mod) ->
212
    format_module(atom_to_list(Mod));
×
213
format_module(Mod) ->
214
    case Mod of
×
215
        "Elixir." ++ M -> M;
×
216
        M -> M
×
217
    end.
218

219
format_module_type([listen, module]) ->
220
    "listening module";
×
221
format_module_type([listen, request_handlers]) ->
222
    "HTTP request handler";
×
223
format_module_type([modules]) ->
224
    "ejabberd module".
×
225

226
format_known(_, Known) when length(Known) > 20 ->
227
    "";
×
228
format_known(Prefix, Known) ->
229
    [Prefix, " are: ", format_join(Known)].
×
230

231
format_join([]) ->
232
    "(empty)";
×
233
format_join([H|_] = L) when is_atom(H) ->
234
    format_join([atom_to_binary(A, utf8) || A <- L]);
×
235
format_join(L) ->
236
    str:join(lists:sort(L), <<", ">>).
×
237

238
%% All duplicated options having list-values are grouped
239
%% into a single option with all list-values being concatenated
240
-spec group_dups(list(T)) -> list(T).
241
group_dups(Y1) ->
242
    lists:reverse(
88✔
243
      lists:foldl(
244
        fun({Option, Values}, Acc) when is_list(Values) ->
245
                case lists:keyfind(Option, 1, Acc) of
410✔
246
                    {Option, Vals} when is_list(Vals) ->
247
                        lists:keyreplace(Option, 1, Acc, {Option, Vals ++ Values});
144✔
248
                    _ ->
249
                        [{Option, Values}|Acc]
266✔
250
                end;
251
           (Other, Acc) ->
252
                [Other|Acc]
438✔
253
        end, [], Y1)).
254

255
%%%===================================================================
256
%%% Validators from yconf
257
%%%===================================================================
258
pos_int() ->
259
    yconf:pos_int().
1,204✔
260

261
pos_int(Inf) ->
262
    yconf:pos_int(Inf).
1,760✔
263

264
non_neg_int() ->
265
    yconf:non_neg_int().
632✔
266

267
non_neg_int(Inf) ->
UNCOV
268
    yconf:non_neg_int(Inf).
16✔
269

270
int() ->
271
    yconf:int().
253✔
272

273
int(Min, Max) ->
274
    yconf:int(Min, Max).
594✔
275

276
number(Min) ->
277
    yconf:number(Min).
375✔
278

279
octal() ->
280
    yconf:octal().
281✔
281

282
binary() ->
283
    yconf:binary().
66,403✔
284

285
binary(Re) ->
286
    yconf:binary(Re).
78✔
287

288
binary(Re, Opts) ->
289
    yconf:binary(Re, Opts).
×
290

291
enum(L) ->
292
    yconf:enum(L).
2,830✔
293

294
bool() ->
295
    yconf:bool().
22,720✔
296

297
atom() ->
298
    yconf:atom().
9,931✔
299

300
string() ->
301
    yconf:string().
143✔
302

303
string(Re) ->
304
    yconf:string(Re).
×
305

306
string(Re, Opts) ->
307
    yconf:string(Re, Opts).
×
308

309
any() ->
310
    yconf:any().
10,095✔
311

312
url() ->
313
    yconf:url().
519✔
314

315
url(Schemes) ->
316
    yconf:url(Schemes).
×
317

318
file() ->
319
    yconf:file().
177✔
320

321
file(Type) ->
UNCOV
322
    yconf:file(Type).
2✔
323

324
directory() ->
325
    yconf:directory().
×
326

327
directory(Type) ->
328
    yconf:directory(Type).
13✔
329

330
ip() ->
331
    yconf:ip().
290✔
332

333
ipv4() ->
334
    yconf:ipv4().
52✔
335

336
ipv6() ->
337
    yconf:ipv6().
52✔
338

339
ip_mask() ->
340
    yconf:ip_mask().
4,878✔
341

342
port() ->
343
    yconf:port().
396✔
344

345
re() ->
346
    yconf:re().
1✔
347

348
re(Opts) ->
349
    yconf:re(Opts).
24,389✔
350

351
glob() ->
UNCOV
352
    yconf:glob().
8✔
353

354
glob(Opts) ->
355
    yconf:glob(Opts).
24,260✔
356

357
path() ->
358
    yconf:path().
329✔
359

360
binary_sep(Sep) ->
361
    yconf:binary_sep(Sep).
13✔
362

363
timeout(Units) ->
364
    yconf:timeout(Units).
859✔
365

366
timeout(Units, Inf) ->
367
    yconf:timeout(Units, Inf).
1,186✔
368

369
base64() ->
370
    yconf:base64().
1,500✔
371

372
non_empty(F) ->
373
    yconf:non_empty(F).
32,166✔
374

375
list(F) ->
376
    yconf:list(F).
14,486✔
377

378
list(F, Opts) ->
379
    yconf:list(F, Opts).
647✔
380

381
list_or_single(F) ->
382
    yconf:list_or_single(F).
53,528✔
383

384
list_or_single(F, Opts) ->
385
    yconf:list_or_single(F, Opts).
39✔
386

387
map(F1, F2) ->
388
    yconf:map(F1, F2).
10,256✔
389

390
map(F1, F2, Opts) ->
391
    yconf:map(F1, F2, Opts).
195✔
392

393
either(F1, F2) ->
394
    yconf:either(F1, F2).
3,615✔
395

396
and_then(F1, F2) ->
397
    yconf:and_then(F1, F2).
121,424✔
398

399
options(V) ->
400
    yconf:options(V).
649✔
401

402
options(V, O) ->
403
    yconf:options(V, O).
16,910✔
404

405
%%%===================================================================
406
%%% Custom validators
407
%%%===================================================================
408
beam() ->
409
    beam([]).
117✔
410

411
beam(Exports) ->
412
    and_then(
338✔
413
      non_empty(binary()),
414
      fun(<<"Elixir.", _/binary>> = Val) ->
415
              (yconf:beam(Exports))(Val);
×
416
         (<<C, _/binary>> = Val) when C >= $A, C =< $Z ->
417
              (yconf:beam(Exports))(<<"Elixir.", Val/binary>>);
×
418
         (Val) ->
419
              (yconf:beam(Exports))(Val)
3,186✔
420
      end).
421

422
acl() ->
423
    either(
2,322✔
424
      atom(),
425
      acl:access_rules_validator()).
426

427
shaper() ->
428
    either(
190✔
429
      atom(),
430
      ejabberd_shaper:shaper_rules_validator()).
431

432
-spec url_or_file() -> yconf:validator({file | url, binary()}).
433
url_or_file() ->
434
    either(
×
435
      and_then(url(), fun(URL) -> {url, URL} end),
×
436
      and_then(file(), fun(File) -> {file, File} end)).
×
437

438
-spec lang() -> yconf:validator(binary()).
439
lang() ->
440
    and_then(
168✔
441
      binary(),
442
      fun(Lang) ->
443
              try xmpp_lang:check(Lang)
13✔
444
              catch _:_ -> fail({bad_lang, Lang})
×
445
              end
446
      end).
447

448
-spec pem() -> yconf:validator(binary()).
449
pem() ->
450
    and_then(
290✔
451
      path(),
452
      fun(Path) ->
453
              case pkix:is_pem_file(Path) of
13✔
454
                  true -> Path;
13✔
455
                  {false, Reason} ->
456
                      fail({bad_pem, Reason, Path})
×
457
              end
458
      end).
459

460
-spec jid() -> yconf:validator(jid:jid()).
461
jid() ->
462
    and_then(
117✔
463
      binary(),
464
      fun(Val) ->
465
              try jid:decode(Val)
×
466
              catch _:{bad_jid, _} = Reason -> fail(Reason)
×
467
              end
468
      end).
469

470
-spec user() -> yconf:validator(binary()).
471
user() ->
472
    and_then(
4,852✔
473
      binary(),
474
      fun(Val) ->
475
              case jid:nodeprep(Val) of
13✔
476
                  error -> fail({bad_user, Val});
×
477
                  U -> U
13✔
478
              end
479
      end).
480

481
-spec domain() -> yconf:validator(binary()).
482
domain() ->
483
    and_then(
24,511✔
484
      non_empty(binary()),
485
      fun(Val) ->
486
              try jid:tolower(jid:decode(Val)) of
349✔
487
                  {<<"">>, <<"xn--", _/binary>> = Domain, <<"">>} ->
488
                      unicode:characters_to_binary(idna:decode(binary_to_list(Domain)), utf8);
×
489
                  {<<"">>, Domain, <<"">>} -> Domain;
349✔
490
                  _ -> fail({bad_domain, Val})
×
491
              catch _:{bad_jid, _} ->
492
                      fail({bad_domain, Val})
×
493
              end
494
      end).
495

496
-spec resource() -> yconf:validator(binary()).
497
resource() ->
498
    and_then(
4,852✔
499
      binary(),
500
      fun(Val) ->
501
              case jid:resourceprep(Val) of
×
502
                  error -> fail({bad_resource, Val});
×
503
                  R -> R
×
504
              end
505
      end).
506

507
-spec db_type(module()) -> yconf:validator(atom()).
508
db_type(M) ->
509
    and_then(
939✔
510
      atom(),
511
      fun(T) ->
512
        case code:ensure_loaded(db_module(M, T)) of
158✔
513
          {module, _} -> T;
158✔
514
          {error, _} ->
515
            ElixirModule = "Elixir." ++ atom_to_list(T),
×
516
            case code:ensure_loaded(list_to_atom(ElixirModule)) of
×
517
              {module, _} -> list_to_atom(ElixirModule);
×
518
              {error, _} -> fail({bad_db_type, M, T})
×
519
            end
520
          end
521
      end).
522

523
-spec queue_type() -> yconf:validator(ram | file).
524
queue_type() ->
525
    enum([ram, file]).
249✔
526

527
-spec ldap_filter() -> yconf:validator(binary()).
528
ldap_filter() ->
529
    and_then(
83✔
530
      binary(),
531
      fun(Val) ->
532
              case eldap_filter:parse(Val) of
3✔
533
                  {ok, _} -> Val;
3✔
534
                  _ -> fail({bad_ldap_filter, Val})
×
535
              end
536
      end).
537

538
-ifdef(SIP).
539
sip_uri() ->
540
    and_then(
×
541
      binary(),
542
      fun(Val) ->
543
              case esip:decode_uri(Val) of
×
544
                  error -> fail({bad_sip_uri, Val});
×
545
                  URI -> URI
×
546
              end
547
      end).
548
-endif.
549

550
-spec host() -> yconf:validator(binary()).
551
host() ->
552
    fun(Domain) ->
867✔
553
            Hosts = ejabberd_config:get_option(hosts),
×
554
            Domain3 = (domain())(Domain),
×
555
            case lists:member(Domain3, Hosts) of
×
556
                true -> fail({route_conflict, Domain});
×
557
                false -> Domain3
×
558
            end
559
    end.
560

561
-spec hosts() -> yconf:validator([binary()]).
562
hosts() ->
563
    list(host(), [unique]).
375✔
564

565
-spec vcard_temp() -> yconf:validator().
566
vcard_temp() ->
567
    and_then(
500✔
568
        vcard_validator(
569
            vcard_temp, undefined,
570
            [{version, undefined, binary()},
571
             {fn, undefined, binary()},
572
             {n, undefined, vcard_name()},
573
             {nickname, undefined, binary()},
574
             {photo, undefined, vcard_photo()},
575
             {bday, undefined, binary()},
576
             {adr, [], list(vcard_adr())},
577
             {label, [], list(vcard_label())},
578
             {tel, [], list(vcard_tel())},
579
             {email, [], list(vcard_email())},
580
             {jabberid, undefined, binary()},
581
             {mailer, undefined, binary()},
582
             {tz, undefined, binary()},
583
             {geo, undefined, vcard_geo()},
584
             {title, undefined, binary()},
585
             {role, undefined, binary()},
586
             {logo, undefined, vcard_logo()},
587
             {org, undefined, vcard_org()},
588
             {categories, [], list(binary())},
589
             {note, undefined, binary()},
590
             {prodid, undefined, binary()},
591
             {rev, undefined, binary()},
592
             {sort_string, undefined, binary()},
593
             {sound, undefined, vcard_sound()},
594
             {uid, undefined, binary()},
595
             {url, undefined, binary()},
596
             {class, undefined, enum([confidential, private, public])},
597
             {key, undefined, vcard_key()},
598
             {desc, undefined, binary()}]),
599
        fun(Tuple) ->
600
            list_to_tuple(tuple_to_list(Tuple) ++ [[]])
348✔
601
        end).
602

603

604
-spec vcard_name() -> yconf:validator().
605
vcard_name() ->
606
    vcard_validator(
500✔
607
      vcard_name, undefined,
608
      [{family, undefined, binary()},
609
       {given, undefined, binary()},
610
       {middle, undefined, binary()},
611
       {prefix, undefined, binary()},
612
       {suffix, undefined, binary()}]).
613

614
-spec vcard_photo() -> yconf:validator().
615
vcard_photo() ->
616
    vcard_validator(
500✔
617
      vcard_photo, undefined,
618
      [{type, undefined, binary()},
619
       {binval, undefined, base64()},
620
       {extval, undefined, binary()}]).
621

622
-spec vcard_adr() -> yconf:validator().
623
vcard_adr() ->
624
    vcard_validator(
500✔
625
      vcard_adr, [],
626
      [{home, false, bool()},
627
       {work, false, bool()},
628
       {postal, false, bool()},
629
       {parcel, false, bool()},
630
       {dom, false, bool()},
631
       {intl, false, bool()},
632
       {pref, false, bool()},
633
       {pobox, undefined, binary()},
634
       {extadd, undefined, binary()},
635
       {street, undefined, binary()},
636
       {locality, undefined, binary()},
637
       {region, undefined, binary()},
638
       {pcode, undefined, binary()},
639
       {ctry, undefined, binary()}]).
640

641
-spec vcard_label() -> yconf:validator().
642
vcard_label() ->
643
    vcard_validator(
500✔
644
      vcard_label, [],
645
      [{home, false, bool()},
646
       {work, false, bool()},
647
       {postal, false, bool()},
648
       {parcel, false, bool()},
649
       {dom, false, bool()},
650
       {intl, false, bool()},
651
       {pref, false, bool()},
652
       {line, [], list(binary())}]).
653

654
-spec vcard_tel() -> yconf:validator().
655
vcard_tel() ->
656
    vcard_validator(
500✔
657
      vcard_tel, [],
658
      [{home, false, bool()},
659
       {work, false, bool()},
660
       {voice, false, bool()},
661
       {fax, false, bool()},
662
       {pager, false, bool()},
663
       {msg, false, bool()},
664
       {cell, false, bool()},
665
       {video, false, bool()},
666
       {bbs, false, bool()},
667
       {modem, false, bool()},
668
       {isdn, false, bool()},
669
       {pcs, false, bool()},
670
       {pref, false, bool()},
671
       {number, undefined, binary()}]).
672

673
-spec vcard_email() -> yconf:validator().
674
vcard_email() ->
675
    vcard_validator(
500✔
676
      vcard_email, [],
677
      [{home, false, bool()},
678
       {work, false, bool()},
679
       {internet, false, bool()},
680
       {pref, false, bool()},
681
       {x400, false, bool()},
682
       {userid, undefined, binary()}]).
683

684
-spec vcard_geo() -> yconf:validator().
685
vcard_geo() ->
686
    vcard_validator(
500✔
687
      vcard_geo, undefined,
688
      [{lat, undefined, binary()},
689
       {lon, undefined, binary()}]).
690

691
-spec vcard_logo() -> yconf:validator().
692
vcard_logo() ->
693
    vcard_validator(
500✔
694
      vcard_logo, undefined,
695
      [{type, undefined, binary()},
696
       {binval, undefined, base64()},
697
       {extval, undefined, binary()}]).
698

699
-spec vcard_org() -> yconf:validator().
700
vcard_org() ->
701
    vcard_validator(
500✔
702
      vcard_org, undefined,
703
      [{name, undefined, binary()},
704
       {units, [], list(binary())}]).
705

706
-spec vcard_sound() -> yconf:validator().
707
vcard_sound() ->
708
    vcard_validator(
500✔
709
      vcard_sound, undefined,
710
      [{phonetic, undefined, binary()},
711
       {binval, undefined, base64()},
712
       {extval, undefined, binary()}]).
713

714
-spec vcard_key() -> yconf:validator().
715
vcard_key() ->
716
    vcard_validator(
500✔
717
      vcard_key, undefined,
718
      [{type, undefined, binary()},
719
       {cred, undefined, binary()}]).
720

721
%%%===================================================================
722
%%% Internal functions
723
%%%===================================================================
724
-spec db_module(module(), atom()) -> module().
725
db_module(M, Type) ->
726
    try list_to_atom(atom_to_list(M) ++ "_" ++ atom_to_list(Type))
158✔
727
    catch _:system_limit ->
728
            fail({bad_length, 255})
×
729
    end.
730

731
format_addr_port({IP, Port}) ->
732
    IPStr = case tuple_size(IP) of
×
733
                4 -> inet:ntoa(IP);
×
734
                8 -> "[" ++ inet:ntoa(IP) ++ "]"
×
735
            end,
736
    IPStr ++ ":" ++ integer_to_list(Port).
×
737

738
-spec format(iolist(), list()) -> string().
739
format(Fmt, Args) ->
740
    lists:flatten(io_lib:format(Fmt, Args)).
×
741

742
-spec vcard_validator(atom(), term(), [{atom(), term(), validator()}]) -> validator().
743
vcard_validator(Name, Default, Schema) ->
744
    Defaults = [{Key, Val} || {Key, Val, _} <- Schema],
6,000✔
745
    and_then(
6,000✔
746
      options(
747
        maps:from_list([{Key, Fun} || {Key, _, Fun} <- Schema]),
45,500✔
748
        [{return, map}, {unique, true}]),
749
      fun(Options) ->
750
              merge(Defaults, Options, Name, Default)
3,828✔
751
      end).
752

753
-spec merge([{atom(), term()}], #{atom() => term()}, atom(), T) -> tuple() | T.
754
merge(_, Options, _, Default) when Options == #{} ->
755
    Default;
×
756
merge(Defaults, Options, Name, _) ->
757
    list_to_tuple([Name|[maps:get(Key, Options, Val) || {Key, Val} <- Defaults]]).
3,828✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc