Sharg 1.2.3-rc.1
The argument parser for bio-c++ tools.
Loading...
Searching...
No Matches
validators.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2006-2026, Knut Reinert & Freie Universität Berlin
2// SPDX-FileCopyrightText: 2016-2026, Knut Reinert & MPI für molekulare Genetik
3// SPDX-License-Identifier: BSD-3-Clause
4
10#pragma once
11
12#include <algorithm>
13#include <any>
14#include <concepts>
15#include <exception>
16#include <fstream>
17#include <ranges>
18#include <regex>
19
22#include <sharg/exceptions.hpp>
23
24namespace sharg
25{
26
47// clang-format off
48template <typename validator_type>
51 requires(validator_type validator,
53 {
54 {validator(value)} -> std::same_as<void>;
55 {validator.get_help_page_message()} -> std::same_as<std::string>;
56 };
57// clang-format on
58
76template <typename option_value_t>
79{
80public:
82 using option_value_type = option_value_t;
83
92 min{min_},
93 max{max_},
94 valid_range_str{"[" + std::to_string(min_) + "," + std::to_string(max_) + "]"}
95 {}
96
104 void operator()(option_value_type const & cmp) const
105 {
106 if (!((cmp <= max) && (cmp >= min)))
107 throw validation_error{"Value " + std::to_string(cmp) + " is not in range " + valid_range_str + "."};
108 }
109
119 template <std::ranges::forward_range range_type>
121 void operator()(range_type const & range) const
122 {
123 std::for_each(range.begin(),
124 range.end(),
125 [&](auto cmp)
126 {
127 (*this)(cmp);
128 });
129 }
130
137 {
138 return std::string{"Value must be in range "} + valid_range_str + ".";
139 }
140
141private:
143 option_value_type min{};
144
146 option_value_type max{};
147
149 std::string valid_range_str{};
150};
151
173template <parsable option_value_t>
175{
176public:
178 using option_value_type = option_value_t;
179
189
198 template <std::ranges::forward_range range_type>
200 value_list_validator(range_type rng) // No &&, because rng will be moved.
201 {
202 std::move(rng.begin(), rng.end(), std::back_inserter(values));
203 }
204
213 template <typename... option_types>
215 value_list_validator(option_types &&... opts)
216 {
217 (values.emplace_back(std::forward<option_types>(opts)), ...);
218 }
220
228 void operator()(option_value_type const & cmp) const
229 {
230 if (!(std::find(values.begin(), values.end(), cmp) != values.end()))
231 throw validation_error{detail::to_string("Value ", cmp, " is not one of ", values, ".")};
232 }
233
242 template <std::ranges::forward_range range_type>
245 void operator()(range_type const & range) const
246 {
248 std::ranges::end(range),
249 [&](auto && cmp)
250 {
251 (*this)(cmp);
252 });
253 }
254
261 {
262 return detail::to_string("Value must be one of ", values, ".");
263 }
264
265private:
268};
269
275template <typename option_type, typename... option_types>
279
281template <typename range_type>
282 requires (std::ranges::forward_range<std::decay_t<range_type>>
285
287template <typename option_type, typename... option_types>
289
291template <typename range_type>
292 requires (std::ranges::forward_range<std::decay_t<range_type>>)
295
312{
313public:
316
325 virtual ~file_validator_base() = default;
327
337 virtual void operator()(std::filesystem::path const & path) const = 0;
338
349 template <std::ranges::forward_range range_type>
352 void operator()(range_type const & v) const
353 {
354 std::for_each(v.begin(),
355 v.end(),
356 [&](auto && cmp)
357 {
358 this->operator()(cmp);
359 });
360 }
361
362protected:
369 {
370 // If no valid extensions are given we can safely return here.
371 if (extensions.empty())
372 return;
373
374 // Check if extension is available.
375 if (!path.has_extension())
376 {
377 throw validation_error{"The given filename " + path.string()
378 + " has no extension. Expected one of the "
379 "following valid extensions:"
380 + extensions_str + "!"};
381 }
382
383 std::string file_path{path.filename().string()};
384
385 // Leading dot indicates a hidden file is not part of the extension.
386 if (file_path.front() == '.')
387 file_path.erase(0, 1);
388
389 // Store a string_view containing all extensions for a better error message.
390 std::string const all_extensions{file_path.substr(file_path.find('.') + 1)};
391
392 // Compares the extensions in lower case.
393 auto case_insensitive_ends_with = [&](std::string const & ext)
394 {
395 return case_insensitive_string_ends_with(file_path, ext);
396 };
397
398 // Check if requested extension is present.
399 if (std::find_if(extensions.begin(), extensions.end(), case_insensitive_ends_with) == extensions.end())
400 {
401 throw validation_error{"Expected one of the following valid extensions: " + extensions_str + "! Got "
402 + all_extensions + " instead!"};
403 }
404 }
405
412 {
413 // Check if input directory is readable.
415 {
416 std::error_code ec{};
417 // if directory iterator cannot be created, ec will be set.
418 std::filesystem::directory_iterator{path, ec}; // NOLINT(bugprone-unused-raii)
419 if (static_cast<bool>(ec))
420 throw validation_error{"Cannot read the directory \"" + path.string() + "\"!"};
421 }
422 else
423 {
424 // Must be a regular file.
426 throw validation_error{"Expected a regular file \"" + path.string() + "\"!"};
427
428 std::ifstream file{path};
429 if (!file.is_open() || !file.good())
430 throw validation_error{"Cannot read the file \"" + path.string() + "\"!"};
431 }
432 }
433
443 {
444 // Contingency check. This case should already be handled by the output_file_validator.
445 // Opening a file handle on a directory would delete its contents.
446 // LCOV_EXCL_START
448 throw validation_error{"\"" + path.string() + "\" is a directory. Cannot validate writeability."};
449 // LCOV_EXCL_STOP
450
451 // Validation happens before the application runs, and the application may still fail. Truncating or removing
452 // an existing file here would destroy it. Opening it for appending checks the permissions without changing it.
453 if (std::filesystem::exists(path))
454 {
455 std::ofstream file{path, std::ios::app};
456 if (!file.is_open() || !file.good())
457 throw validation_error{"Cannot write \"" + path.string() + "\"!"};
458 return;
459 }
460
461 std::ofstream file{path};
462 sharg::detail::safe_filesystem_entry file_guard{path};
463
464 bool is_open = file.is_open();
465 bool is_good = file.good();
466 file.close();
467
468 if (!is_good || !is_open)
469 throw validation_error{"Cannot write \"" + path.string() + "\"!"};
470
471 file_guard.remove();
472 }
473
476 {
477 if (extensions.empty())
478 return "";
479 else
480 return "Valid file extensions are: " + extensions_str + ".";
481 }
482
489 {
490 size_t const suffix_length{suffix.size()};
491 size_t const str_length{str.size()};
492
493 if (suffix_length > str_length)
494 return false;
495
496 for (size_t j = 0, s_start = str_length - suffix_length; j < suffix_length; ++j)
497 if (std::tolower(str[s_start + j]) != std::tolower(suffix[j]))
498 return false;
499
500 return true;
501 }
502
505
508};
509
534{
535public:
536 // Import from base class.
538
547 virtual ~input_file_validator() = default;
548
560
561 // Import base class constructor.
564
565 // Import the base::operator()
566 using file_validator_base::operator();
567
576 virtual void operator()(std::filesystem::path const & file) const override
577 {
578 try
579 {
580 if (!std::filesystem::exists(file))
581 throw validation_error{"The file \"" + file.string() + "\" does not exist!"};
582
583 // Check if file is regular and can be opened for reading.
585
586 // Check extension.
587 validate_filename(file);
588 }
589 // LCOV_EXCL_START
591 {
592 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
593 }
594 // LCOV_EXCL_STOP
595 catch (...)
596 {
598 }
599 }
600
606 {
607 return "The input file must exist and read permissions must be granted."
610 }
611};
612
617enum class output_file_open_options : uint8_t
618{
623};
624
653{
654public:
655 // Import from base class.
657
666 virtual ~output_file_validator() = default;
667
682
693 requires ((std::constructible_from<std::string, decltype(extensions)> && ...))
695 {}
696
706
714 explicit output_file_validator(auto &&... extensions)
715 requires ((std::constructible_from<std::string, decltype(extensions)> && ...))
716 : output_file_validator{std::vector<std::string>{std::forward<decltype(extensions)>(extensions)...}}
717 {}
718
719 // Import base constructor.
720 using file_validator_base::file_validator_base;
722
723 // Import the base::operator()
724 using file_validator_base::operator();
725
734 virtual void operator()(std::filesystem::path const & file) const override
735 {
737 throw validation_error{"\"" + file.string() + "\" is a directory. Expected a file."};
738
739 try
740 {
741 if ((open_mode == output_file_open_options::create_new) && std::filesystem::exists(file))
742 throw validation_error{"The file \"" + file.string() + "\" already exists!"};
743
744 // Check if file has any write permissions.
745 validate_writeability(file);
746
747 validate_filename(file);
748 }
749 // LCOV_EXCL_START
751 {
752 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
753 }
754 // LCOV_EXCL_STOP
755 catch (...)
756 {
758 }
759 }
760
767 {
768 if (open_mode == output_file_open_options::open_or_create)
769 {
770 return "Write permissions must be granted."
771 + ((valid_extensions_help_page_message().empty()) ? std::string{} : std::string{" "})
772 + valid_extensions_help_page_message();
773 }
774 else // open_mode == create_new
775 {
776 return "The output file must not exist already and write permissions must be granted."
777 + ((valid_extensions_help_page_message().empty()) ? std::string{} : std::string{" "})
778 + valid_extensions_help_page_message();
779 }
780 }
781
782private:
784 output_file_open_options open_mode{output_file_open_options::create_new};
785};
786
806{
807public:
808 // Import from base class.
810
819 virtual ~input_directory_validator() = default;
820
821 // Import base constructor.
822 using file_validator_base::file_validator_base;
824
825 // Import the base::operator()
826 using file_validator_base::operator();
827
836 virtual void operator()(std::filesystem::path const & dir) const override
837 {
838 try
839 {
840 if (!std::filesystem::exists(dir))
841 throw validation_error{"The directory \"" + dir.string() + "\" does not exists!"};
842
844 throw validation_error{"The path \"" + dir.string() + "\" is not a directory!"};
845
846 // Check if directory has any read permissions.
847 validate_readability(dir);
848 }
849 // LCOV_EXCL_START
851 {
852 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
853 }
854 // LCOV_EXCL_STOP
855 catch (...)
856 {
858 }
859 }
860
867 {
868 return "An existing, readable path for the input directory.";
869 }
870};
871
891{
892public:
893 // Imported from base class.
895
904 virtual ~output_directory_validator() = default;
905
906 // Import base constructor.
907 using file_validator_base::file_validator_base;
909
910 // Import the base::operator().
911 using file_validator_base::operator();
912
921 virtual void operator()(std::filesystem::path const & dir) const override
922 {
923 bool dir_exists = std::filesystem::exists(dir);
924 // Make sure the created dir is deleted after we are done.
926 std::filesystem::create_directory(dir, ec); // does nothing and is not treated as error if path already exists.
927 // if error code was set or if dummy.txt could not be created within the output dir, throw an error.
928 if (static_cast<bool>(ec))
929 throw validation_error{"Cannot create directory: \"" + dir.string() + "\"!"};
930
931 try
932 {
933 if (!dir_exists)
934 {
935 sharg::detail::safe_filesystem_entry dir_guard{dir};
936 validate_writeability(dir / "dummy.txt");
937 dir_guard.remove_all();
938 }
939 else
940 {
941 validate_writeability(dir / "dummy.txt");
942 }
943 }
944 // LCOV_EXCL_START
946 {
947 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
948 }
949 // LCOV_EXCL_STOP
950 catch (...)
951 {
953 }
954 }
955
962 {
963 return "A valid path for the output directory.";
964 }
965};
966
989{
990public:
993
1000 regex_validator(std::string const & pattern_) : pattern{pattern_}
1001 {}
1002
1010 void operator()(option_value_type const & cmp) const
1011 {
1012 std::regex rgx(pattern);
1013 if (!std::regex_match(cmp, rgx))
1014 throw validation_error{"Value " + cmp + " did not match the pattern " + pattern + "."};
1015 }
1016
1026 template <std::ranges::forward_range range_type>
1028 void operator()(range_type const & v) const
1029 {
1030 for (auto && entry : v)
1031 {
1032 // note: we explicitly copy/construct any reference type other than `std::string &`
1033 (*this)(static_cast<std::string const &>(entry));
1034 }
1035 }
1036
1043 {
1044 return "Value must match the pattern '" + pattern + "'.";
1045 }
1046
1047private:
1049 std::string pattern;
1050};
1051
1052namespace detail
1053{
1054
1066struct default_validator
1067{
1069 using option_value_type = std::any;
1070
1072 template <typename option_value_t>
1073 void operator()(option_value_t const & /*cmp*/) const noexcept
1074 {}
1075
1077 std::string get_help_page_message() const
1078 {
1079 return "";
1080 }
1081};
1082
1096template <validator validator1_type, validator validator2_type>
1098class validator_chain_adaptor
1099{
1100public:
1102 using option_value_type =
1104
1108 validator_chain_adaptor() = delete;
1109 validator_chain_adaptor(validator_chain_adaptor const & pf) = default;
1110 validator_chain_adaptor & operator=(validator_chain_adaptor const & pf) = default;
1111 validator_chain_adaptor(validator_chain_adaptor &&) = default;
1112 validator_chain_adaptor & operator=(validator_chain_adaptor &&) = default;
1113
1118 validator_chain_adaptor(validator1_type vali1_, validator2_type vali2_) :
1119 vali1{std::move(vali1_)},
1120 vali2{std::move(vali2_)}
1121 {}
1122
1124 ~validator_chain_adaptor() = default;
1126
1135 template <typename cmp_type>
1137 void operator()(cmp_type const & cmp) const
1138 {
1139 vali1(cmp);
1140 vali2(cmp);
1141 }
1142
1144 std::string get_help_page_message() const
1145 {
1146 return vali1.get_help_page_message() + " " + vali2.get_help_page_message();
1147 }
1148
1149private:
1151 validator1_type vali1;
1153 validator2_type vali2;
1154};
1155
1156} // namespace detail
1157
1189template <validator validator1_type, validator validator2_type>
1192auto operator|(validator1_type && vali1, validator2_type && vali2)
1193{
1194 return detail::validator_chain_adaptor{std::forward<validator1_type>(vali1), std::forward<validator2_type>(vali2)};
1195}
1196
1197} // namespace sharg
T back_inserter(T... args)
T begin(T... args)
A validator that checks whether a number is inside a given range.
Definition validators.hpp:79
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:136
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside [min, max].
Definition validators.hpp:104
option_value_t option_value_type
The type of value that this validator invoked upon.
Definition validators.hpp:82
arithmetic_range_validator(option_value_type const min_, option_value_type const max_)
The constructor.
Definition validators.hpp:91
void operator()(range_type const &range) const
Tests whether every element in range lies inside [min, max].
Definition validators.hpp:121
An abstract base class for the file and directory validators.
Definition validators.hpp:312
file_validator_base()=default
Defaulted.
std::vector< std::string > extensions
Stores the extensions.
Definition validators.hpp:504
void validate_readability(std::filesystem::path const &path) const
Checks if the given path is readable.
Definition validators.hpp:411
void validate_filename(std::filesystem::path const &path) const
Validates the given filename path based on the specified extensions.
Definition validators.hpp:368
virtual void operator()(std::filesystem::path const &path) const =0
Tests if the given path is a valid input, respectively output, file or directory.
file_validator_base & operator=(file_validator_base const &)=default
Defaulted.
std::string extensions_str
The extension range as a std:;string for pretty printing.
Definition validators.hpp:507
virtual ~file_validator_base()=default
std::string valid_extensions_help_page_message() const
Returns the information of valid file extensions.
Definition validators.hpp:475
bool case_insensitive_string_ends_with(std::string_view str, std::string_view suffix) const
Helper function that checks if a string is a suffix of another string. Case insensitive.
Definition validators.hpp:488
std::string option_value_type
Type of values that are tested by validator.
Definition validators.hpp:315
file_validator_base(file_validator_base const &)=default
Defaulted.
file_validator_base(file_validator_base &&)=default
Defaulted.
file_validator_base & operator=(file_validator_base &&)=default
Defaulted.
void validate_writeability(std::filesystem::path const &path) const
Checks if the given path is writable.
Definition validators.hpp:442
A validator that checks if a given path is a valid input directory.
Definition validators.hpp:806
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:866
input_directory_validator()=default
Defaulted.
input_directory_validator(input_directory_validator &&)=default
Defaulted.
input_directory_validator & operator=(input_directory_validator const &)=default
Defaulted.
virtual ~input_directory_validator()=default
Virtual Destructor.
input_directory_validator(input_directory_validator const &)=default
Defaulted.
virtual void operator()(std::filesystem::path const &dir) const override
Tests whether path is an existing directory and is readable.
Definition validators.hpp:836
input_directory_validator & operator=(input_directory_validator &&)=default
Defaulted.
A validator that checks if a given path is a valid input file.
Definition validators.hpp:534
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:605
input_file_validator(input_file_validator &&)=default
Defaulted.
input_file_validator(input_file_validator const &)=default
Defaulted.
input_file_validator()=default
Defaulted.
input_file_validator & operator=(input_file_validator const &)=default
Defaulted.
virtual ~input_file_validator()=default
Virtual destructor.
input_file_validator(std::vector< std::string > extensions)
Constructs from a given collection of valid extensions.
Definition validators.hpp:555
virtual void operator()(std::filesystem::path const &file) const override
Tests whether path is an existing regular file and is readable.
Definition validators.hpp:576
input_file_validator & operator=(input_file_validator &&)=default
Defaulted.
A validator that checks if a given path is a valid output directory.
Definition validators.hpp:891
output_directory_validator(output_directory_validator &&)=default
Defaulted.
output_directory_validator(output_directory_validator const &)=default
Defaulted.
output_directory_validator & operator=(output_directory_validator &&)=default
Defaulted.
virtual void operator()(std::filesystem::path const &dir) const override
Tests whether path is writable.
Definition validators.hpp:921
virtual ~output_directory_validator()=default
Virtual Destructor.
output_directory_validator & operator=(output_directory_validator const &)=default
Defaulted.
output_directory_validator()=default
Defaulted.
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:961
A validator that checks if a given path is a valid output file.
Definition validators.hpp:653
output_file_validator()=default
Defaulted.
output_file_validator(auto &&... extensions)
Constructs from a parameter pack of valid extensions.
Definition validators.hpp:714
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:766
virtual void operator()(std::filesystem::path const &file) const override
Tests whether path is does not already exists and is writable.
Definition validators.hpp:734
virtual ~output_file_validator()=default
Virtual Destructor.
output_file_validator(output_file_validator &&)=default
Defaulted.
output_file_validator(std::vector< std::string > const &extensions)
Constructs from a list of valid extensions.
Definition validators.hpp:703
output_file_validator(output_file_open_options const mode, std::vector< std::string > extensions)
Constructs from a given overwrite mode and a list of valid extensions.
Definition validators.hpp:676
output_file_validator(output_file_open_options const mode, auto &&... extensions)
Constructs from a given overwrite mode and a parameter pack of valid extensions.
Definition validators.hpp:692
output_file_validator & operator=(output_file_validator const &)=default
Defaulted.
output_file_validator(output_file_validator const &)=default
Defaulted.
output_file_validator & operator=(output_file_validator &&)=default
Defaulted.
A validator that checks if a matches a regular expression pattern.
Definition validators.hpp:989
void operator()(range_type const &v) const
Tests whether every entry in list v matches the pattern.
Definition validators.hpp:1028
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:1042
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition validators.hpp:1010
regex_validator(std::string const &pattern_)
Constructing from a vector.
Definition validators.hpp:1000
Parser exception thrown when an argument could not be casted to the according type.
Definition exceptions.hpp:180
A validator that checks whether a value is inside a list of valid values.
Definition validators.hpp:175
value_list_validator(option_type, option_types...) -> value_list_validator< option_type >
Deduction guide for a parameter pack.
value_list_validator & operator=(value_list_validator const &)=default
Defaulted.
value_list_validator(value_list_validator const &)=default
Defaulted.
option_value_t option_value_type
Type of values that are tested by validator.
Definition validators.hpp:178
value_list_validator()=default
Defaulted.
value_list_validator(range_type rng)
Constructing from a range.
Definition validators.hpp:200
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition validators.hpp:260
value_list_validator(value_list_validator &&)=default
Defaulted.
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition validators.hpp:228
value_list_validator(range_type &&rng) -> value_list_validator< std::string >
Deduction guide for ranges over a value type convertible to std::string.
value_list_validator(option_type, option_types...) -> value_list_validator< std::string >
Given a parameter pack of types that are convertible to std::string, delegate to value type std::stri...
~value_list_validator()=default
Defaulted.
value_list_validator & operator=(value_list_validator &&)=default
Defaulted.
value_list_validator(option_types &&... opts)
Constructing from a parameter pack.
Definition validators.hpp:215
value_list_validator(range_type &&rng) -> value_list_validator< std::ranges::range_value_t< range_type > >
Deduction guide for ranges.
The concept for option validators passed to add_option/positional_option.
Definition validators.hpp:49
T create_directory(T... args)
T current_exception(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
Provides parser related exceptions.
T exists(T... args)
T filename(T... args)
T find(T... args)
T for_each(T... args)
auto operator|(validator1_type &&vali1, validator2_type &&vali2)
Enables the chaining of validators.
Definition validators.hpp:1192
T has_extension(T... args)
T is_directory(T... args)
T is_regular_file(T... args)
T is_same_v
T move(T... args)
T regex_match(T... args)
T rethrow_exception(T... args)
Provides sharg::detail::safe_filesystem_entry.
T size(T... args)
T substr(T... args)
T throw_with_nested(T... args)
Provides sharg::detail::to_string.
T to_string(T... args)
output_file_open_options
Mode of an output file: Determines whether an existing file can be (silently) overwritten.
Definition validators.hpp:618
@ create_new
Forbid overwriting the output file.
@ open_or_create
Allow to overwrite the output file.
Hide me