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 import darg_plus.exception : ValidationError; 13 static import std.exception; 14 15 /** 16 General validation function. 17 18 Params: 19 value = Value to be validated 20 msg = Error details in case of failure 21 Throws: 22 darg_plus.exception.ValidationError if `!isValid`. 23 */ 24 alias validate = std.exception.enforce!ValidationError; 25 26 /** 27 Validates that value is positive. 28 29 Params: 30 value = Value to be validated 31 msg = Error details in case of failure 32 Throws: 33 darg_plus.exception.ValidationError 34 if value is less than or equal to zero. 35 See_also: 36 validate 37 */ 38 void validatePositive(V)(V value, lazy string msg = "must be greater than zero") 39 { 40 validate(0 < value, msg); 41 } 42 43 /** 44 Validates that the values described by rangeString are non-negative and 45 in ascending order. Validation is skipped if rangeString is `null`. 46 47 Params: 48 rangeString = Range to be validated 49 msg = Error details in case of failure 50 Throws: 51 darg_plus.exception.ValidationError 52 unless rangeString is `null` or `0 <= x && x < y`. 53 See_also: 54 validate 55 */ 56 void validateRangeNonNegativeAscending(DestType)( 57 in string rangeString, 58 lazy string msg = "0 <= <from> < <to> must hold", 59 ) 60 { 61 if (rangeString !is null) 62 { 63 import darg_plus.string : parseRange; 64 65 auto rangeBounds = parseRange!DestType(rangeString); 66 auto from = rangeBounds[0]; 67 auto to = rangeBounds[1]; 68 69 validate(0 <= from && from < to, msg); 70 } 71 } 72 73 74 import std.range.primitives : isInputRange; 75 76 /** 77 Validates that the files exist. 78 79 Params: 80 file = File name of the file to be tested. 81 files = File names of the files to be tested. 82 msg = Error details in case of failure 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(R)(R files, lazy string msg = "cannot open file `%s`") if (isInputRange!R) 90 { 91 foreach (file; files) 92 validateFileExists(file, msg); 93 } 94 95 /// ditto 96 void validateFileExists(S)(in S file, lazy string msg = "cannot open file `%s`") 97 { 98 import std.file : exists; 99 import std.format : format; 100 101 validate(file.exists, format(msg, file)); 102 } 103 104 105 import std.meta : 106 allSatisfy, 107 staticMap; 108 import std.traits : isSomeString; 109 110 /** 111 Validates that the file has one of the allowed extensions. 112 113 Params: 114 extensions = Allowed extensions including a leading dot. 115 file = File name of the file to be tested. 116 msg = Error details in case of failure 117 Throws: 118 darg_plus.exception.ValidationError 119 if the extension of file is not in the list of allowed extensions. 120 See_also: 121 validate, std.algorithm.searching.endsWith 122 */ 123 void validateFileExtension(extensions...)( 124 in string file, 125 lazy string msg = "invalid file extension: expected one of %-(%s, %) but got %s", 126 ) 127 if (allSatisfy!(isSomeString, staticMap!(typeOf, extensions))) 128 { 129 import std.algorithm : endsWith; 130 import std.format : format; 131 132 validate(file.endsWith(extensions), format(msg, [extensions], file)); 133 } 134 135 private alias typeOf(alias T) = typeof(T); 136 137 /** 138 Validates that the file is writable. 139 140 Params: 141 file = File name of the file to be tested. 142 msg = Error details in case of failure 143 Throws: 144 darg_plus.exception.ValidationError 145 if file cannot be opened for writing. 146 See_also: 147 validate, std.algorithm.searching.endsWith 148 */ 149 void validateFileWritable(string file, lazy string msg = "cannot open file `%s` for writing: %s") 150 { 151 import std.exception : ErrnoException; 152 import std.file : 153 exists, 154 remove; 155 import std.format : format; 156 import std.stdio : File; 157 158 auto deleteAfterwards = !file.exists; 159 160 scope (exit) 161 if (deleteAfterwards) 162 remove(file); 163 164 try 165 { 166 cast(void) File(file, "a"); 167 } 168 catch (ErrnoException e) 169 { 170 validate(false, format(msg, file, e.msg)); 171 } 172 } 173 174 /** 175 Validates that `dir` is a directory and files can be created inside of it. 176 177 Params: 178 dir = Path of the directory to be tested. 179 msg = Error details in case of failure 180 Throws: 181 darg_plus.exception.ValidationError 182 if `dir` is not a directory or files cannot be created within. 183 See_also: 184 validate, std.algorithm.searching.endsWith 185 */ 186 void validateWritableDirectory(string dir, lazy string msg = "`%s` is not a writable directory: %s") 187 { 188 import core.sys.posix.stdlib : mkstemp; 189 import core.sys.posix.stdio : 190 fclose, 191 fdopen; 192 import std.exception : 193 errnoEnforce, 194 ErrnoException; 195 import std.file : 196 isDir, 197 remove; 198 import std.format : format; 199 import std.path : buildPath; 200 import std.string : 201 fromStringz, 202 toStringz; 203 import std.traits : ReturnType; 204 205 validate(isDir(dir), format(msg, dir, "not a directory")); 206 207 char* tempFileName; 208 209 scope (exit) 210 { 211 if (tempFileName !is null) 212 remove(fromStringz(tempFileName)); 213 } 214 215 try 216 { 217 tempFileName = cast(char*) toStringz(buildPath(dir, ".iswritable-XXXXXX")); 218 219 auto fd = mkstemp(tempFileName); 220 221 errnoEnforce(fd != -1, "cannot create temporary file"); 222 223 fclose(fdopen(fd, "r+")); 224 } 225 catch (ErrnoException e) 226 { 227 validate(false, format(msg, dir, e.msg)); 228 } 229 }