1 /**
2     Validate and parse config files.
3 
4     Copyright: © 2019 Arne Ludwig <arne.ludwig@posteo.de>
5     License: Subject to the terms of the MIT license, as written in the
6              included LICENSE file.
7     Authors: Arne Ludwig <arne.ludwig@posteo.de>
8 */
9 module darg_plus.validators;
10 
11 
12 /// Basic format of validation error messages.
13 enum validationErrorFormat = "invalid option %s: %s.";
14 
15 /**
16     General validation function.
17 
18     Params:
19         option = Name of the option to be validated
20         msg    = Error details in case of failure
21         value  = Value to be validated
22     Throws:
23         darg_plus.exception.ValidationError if `!isValid`.
24 */
25 void validate(string option)(bool isValid, lazy string msg = "must be greater than zero")
26 {
27     import darg_plus.exception : ValidationError;
28 
29     enforce!ValidationError(isValid, format!validationErrorFormat(option, msg));
30 }
31 
32 /**
33     Validates that value is positive.
34 
35     Params:
36         value  = Value to be validated
37     Throws:
38         darg_plus.exception.ValidationError
39             if value is less than or equal to zero.
40     See_also:
41         validate
42 */
43 void validatePositive(string option, V)(V value, lazy string msg = "must be greater than zero")
44 {
45     validate!option(0 < value, msg);
46 }
47 
48 /**
49     Validates that the values described by rangeString are non-negative and
50     in ascending order. Validation is skipped if rangeString is `null`.
51 
52     Params:
53         rangeString  = Range to be validated
54     Throws:
55         darg_plus.exception.ValidationError
56             unless rangeString is `null` or `0 <= x && x < y`.
57     See_also:
58         validate
59 */
60 void validateRangeNonNegativeAscending(DestType, string option)(
61     in string rangeString,
62     lazy string msg = "0 <= <from> < <to> must hold",
63 )
64 {
65     if (rangeString !is null)
66     {
67         import darg_plus..string : parseRange;
68 
69         auto rangeBounds = parseRange!DestType(rangeString);
70         auto from = rangeBounds[0];
71         auto to = rangeBounds[1];
72 
73         validate!option(0 <= from && from < to, msg);
74     }
75 }
76 
77 /**
78     Validates that the files exist.
79 
80     Params:
81         file  = File name of the file to be tested.
82         files = File names of the files to be tested.
83     Throws:
84         darg_plus.exception.ValidationError
85             unless rangeString is `null` or `0 <= x && x < y`.
86     See_also:
87         validate, std.file.exists
88 */
89 void validateFilesExist(string option)(in string[] files, lazy string msg = "cannot open file `%s`")
90 {
91     foreach (file; files)
92         validateFileExists(file, msg);
93 }
94 
95 /// ditto
96 void validateFileExists(string option)(in string file, lazy string msg = "cannot open file `%s`")
97 {
98     validate!option(file.exists, format(msg, file));
99 }
100 
101 /**
102     Validates that the file has one of the allowed extensions.
103 
104     Params:
105         file        = File name of the file to be tested.
106         extensions  = Allowed extensions including a leading dot.
107     Throws:
108         darg_plus.exception.ValidationError
109             if the extension of file is not in the list of allowed extensions.
110     See_also:
111         validate, std.algorithm.searching.endsWith
112 */
113 void validateFileExtension(string option, extensions...)(
114     in string file,
115     lazy string msg = "expected one of %-(%s, %) but got %s",
116 )
117         if (allSatisfy!(isSomeString, staticMap!(typeOf, extensions)))
118 {
119     validate(file.endsWith(extensions), format(msg, [extensions], file));
120 }
121 
122 private alias typeOf(alias T) = typeof(T);
123 
124 /**
125     Validates that the file is writable.
126 
127     Params:
128         file        = File name of the file to be tested.
129     Throws:
130         darg_plus.exception.ValidationError
131             if file cannot be opened for writing.
132     See_also:
133         validate, std.algorithm.searching.endsWith
134 */
135 void validateFileWritable(string option)(
136     string file,
137     lazy string msg = "cannot open file `%s` for writing: %s",
138 )
139 {
140     auto deleteAfterwards = !file.exists;
141 
142     try
143     {
144         cast(void) File(file, "a");
145     }
146     catch (ErrnoException e)
147     {
148         throw new CLIException(format(msg, file, e.msg));
149     }
150 
151     if (deleteAfterwards)
152         remove(file);
153 }