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 }