SeqAn3 3.2.0
The Modern C++ library for sequence analysis.
validators.hpp
Go to the documentation of this file.
1// -----------------------------------------------------------------------------------------------------
2// Copyright (c) 2006-2022, Knut Reinert & Freie Universität Berlin
3// Copyright (c) 2016-2022, Knut Reinert & MPI für molekulare Genetik
4// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
5// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
6// -----------------------------------------------------------------------------------------------------
7
13#pragma once
14
15#include <algorithm>
16#include <concepts>
17#include <filesystem>
18#include <fstream>
19#include <ranges>
20#include <regex>
21#include <sstream>
22
33
34namespace seqan3
35{
36
96template <typename validator_type>
97concept validator =
98 std::copyable<std::remove_cvref_t<validator_type>>
99 && requires (validator_type validator, typename std::remove_reference_t<validator_type>::option_value_type value) {
101
102 {
103 validator(value)
104 } -> std::same_as<void>;
105 {
107 } -> std::same_as<std::string>;
108 };
110
126template <arithmetic option_value_t>
128{
129public:
131 using option_value_type = option_value_t;
132
137 arithmetic_range_validator(option_value_type const min_, option_value_type const max_) : min{min_}, max{max_}
138 {}
139
144 void operator()(option_value_type const & cmp) const
145 {
146 if (!((cmp <= max) && (cmp >= min)))
147 throw validation_error{detail::to_string("Value ", cmp, " is not in range [", min, ",", max, "].")};
148 }
149
156 template <std::ranges::forward_range range_type>
158 void operator()(range_type const & range) const
159 {
160 std::for_each(range.begin(),
161 range.end(),
162 [&](auto cmp)
163 {
164 (*this)(cmp);
165 });
166 }
167
170 {
171 return detail::to_string("Value must be in range [", min, ",", max, "].");
172 }
173
174private:
176 option_value_type min{};
177
179 option_value_type max{};
180};
181
201template <typename option_value_t>
203{
204public:
206 using option_value_type = option_value_t;
207
217
223 template <std::ranges::forward_range range_type>
224 requires std::constructible_from<option_value_type, std::ranges::range_rvalue_reference_t<range_type>>
225 value_list_validator(range_type rng)
226 {
227 values.clear();
228 std::ranges::move(std::move(rng), std::back_inserter(values));
229 }
230
236 template <typename... option_types>
237 requires ((std::constructible_from<option_value_type, option_types> && ...))
238 value_list_validator(option_types &&... opts)
239 {
240 (values.emplace_back(std::forward<option_types>(opts)), ...);
241 }
243
248 void operator()(option_value_type const & cmp) const
249 {
250 if (!(std::find(values.begin(), values.end(), cmp) != values.end()))
251 throw validation_error{detail::to_string("Value ", cmp, " is not one of ", std::views::all(values), ".")};
252 }
253
259 template <std::ranges::forward_range range_type>
260 requires std::convertible_to<std::ranges::range_value_t<range_type>, option_value_type>
261 void operator()(range_type const & range) const
262 {
264 std::ranges::end(range),
265 [&](auto cmp)
266 {
267 (*this)(cmp);
268 });
269 }
270
273 {
274 return detail::to_string("Value must be one of ", std::views::all(values), ".");
275 }
276
277private:
280};
281
287template <typename option_type, typename... option_types>
288 requires (std::constructible_from<std::string, std::decay_t<option_types>> && ...
289 && std::constructible_from<std::string, std::decay_t<option_type>>)
291
293template <typename range_type>
294 requires (std::ranges::forward_range<std::decay_t<range_type>>
295 && std::constructible_from<std::string, std::ranges::range_value_t<range_type>>)
297
299template <typename option_type, typename... option_types>
301
303template <typename range_type>
304 requires (std::ranges::forward_range<std::decay_t<range_type>>)
307
323{
324public:
327
336 virtual ~file_validator_base() = default;
338
346 virtual void operator()(std::filesystem::path const & path) const = 0;
347
355 template <std::ranges::forward_range range_type>
356 requires (std::convertible_to<std::ranges::range_value_t<range_type>, std::filesystem::path const &>
357 && !std::convertible_to<range_type, std::filesystem::path const &>)
358 void operator()(range_type const & v) const
359 {
360 std::for_each(v.begin(),
361 v.end(),
362 [&](auto cmp)
363 {
364 this->operator()(cmp);
365 });
366 }
367
368protected:
375 {
376 // If no valid extensions are given we can safely return here.
377 if (extensions.empty())
378 return;
379
380 // Check if extension is available.
381 if (!path.has_extension())
382 throw validation_error{detail::to_string("The given filename ",
383 path.string(),
384 " has no extension. Expected"
385 " one of the following valid extensions:",
387 "!")};
388
389 std::string file_path{path.filename().string()};
390
391 // Leading dot indicates a hidden file is not part of the extension.
392 if (file_path.front() == '.')
393 file_path.erase(0, 1);
394
395 // Store a string_view containing all extensions for a better error message.
396 std::string const all_extensions{file_path.substr(file_path.find(".") + 1)};
397
398 // Compares the extensions in lower case.
399 auto case_insensitive_ends_with = [&](std::string const & ext)
400 {
401 return case_insensitive_string_ends_with(file_path, ext);
402 };
403
404 // Check if requested extension is present.
405 if (std::ranges::find_if(extensions, case_insensitive_ends_with) == extensions.end())
406 {
407 throw validation_error{detail::to_string("Expected one of the following valid extensions: ",
409 "! Got ",
410 all_extensions,
411 " instead!")};
412 }
413 }
414
421 {
422 // Check if input directory is readable.
424 {
425 std::error_code ec{};
426 std::filesystem::directory_iterator{path, ec}; // if directory iterator cannot be created, ec will be set.
427 if (static_cast<bool>(ec))
428 throw validation_error{detail::to_string("Cannot read the directory ", path, "!")};
429 }
430 else
431 {
432 // Must be a regular file.
434 throw validation_error{detail::to_string("Expected a regular file ", path, "!")};
435
436 std::ifstream file{path};
437 if (!file.is_open() || !file.good())
438 throw validation_error{detail::to_string("Cannot read the file ", path, "!")};
439 }
440 }
441
448 {
449 std::ofstream file{path};
450 detail::safe_filesystem_entry file_guard{path};
451
452 bool is_open = file.is_open();
453 bool is_good = file.good();
454 file.close();
455
456 if (!is_good || !is_open)
457 throw validation_error{detail::to_string("Cannot write ", path, "!")};
458
459 file_guard.remove();
460 }
461
464 {
465 if (extensions.empty())
466 return "";
467 else
468 return detail::to_string(" Valid file extensions are: [",
470 "].");
471 }
472
479 {
480 size_t const suffix_length{suffix.size()};
481 size_t const str_length{str.size()};
482 return suffix_length > str_length ? false
483 : std::ranges::equal(str.substr(str_length - suffix_length),
484 suffix,
485 [](char const chr1, char const chr2)
486 {
487 return std::tolower(chr1) == std::tolower(chr2);
488 });
489 }
490
493};
494
519template <typename file_t = void>
521{
522public:
523 static_assert(std::same_as<file_t, void> || detail::has_type_valid_formats<file_t>,
524 "Expected either a template type with a static member called valid_formats (a file type) or void.");
525
526 // Import from base class.
528
542 {
543 if constexpr (!std::same_as<file_t, void>)
544 file_validator_base::extensions = detail::valid_file_extensions<typename file_t::valid_formats>();
545 }
546
551 virtual ~input_file_validator() = default;
552
562 requires std::same_as<file_t, void>
564 {
566 }
567
568 // Import base class constructor.
571
572 // Import the base::operator()
573 using file_validator_base::operator();
574
580 virtual void operator()(std::filesystem::path const & file) const override
581 {
582 try
583 {
584 if (!std::filesystem::exists(file))
585 throw validation_error{detail::to_string("The file ", file, " does not exist!")};
586
587 // Check if file is regular and can be opened for reading.
589
590 // Check extension.
591 validate_filename(file);
592 }
593 // LCOV_EXCL_START
595 {
596 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
597 }
598 // LCOV_EXCL_STOP
599 catch (...)
600 {
602 }
603 }
604
607 {
608 return "The input file must exist and read permissions must be granted." + valid_extensions_help_page_message();
609 }
610};
611
614{
619};
620
649template <typename file_t = void>
651{
652public:
653 static_assert(std::same_as<file_t, void> || detail::has_type_valid_formats<file_t>,
654 "Expected either a template type with a static member called valid_formats (a file type) or void.");
655
656 // Import from base class.
658
665 {}
666
671 virtual ~output_file_validator() = default;
672
682 mode{mode}
683 {
685 }
686
687 // Import base constructor.
690
700 {
701 if constexpr (!std::same_as<file_t, void>)
702 return detail::valid_file_extensions<typename file_t::valid_formats>();
703 return {};
704 }
705
706 // Import the base::operator()
707 using file_validator_base::operator();
708
714 virtual void operator()(std::filesystem::path const & file) const override
715 {
716 try
717 {
719 throw validation_error{detail::to_string("The file ", file, " already exists!")};
720
721 // Check if file has any write permissions.
723
724 validate_filename(file);
725 }
726 // LCOV_EXCL_START
728 {
729 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
730 }
731 // LCOV_EXCL_STOP
732 catch (...)
733 {
735 }
736 }
737
740 {
742 return "Write permissions must be granted." + valid_extensions_help_page_message();
743 else // mode == create_new
744 return "The output file must not exist already and write permissions must be granted."
746 }
747
748private:
751};
752
770{
771public:
772 // Import from base class.
774
783 virtual ~input_directory_validator() = default;
784
785 // Import base constructor.
788
789 // Import the base::operator()
790 using file_validator_base::operator();
791
797 virtual void operator()(std::filesystem::path const & dir) const override
798 {
799 try
800 {
801 if (!std::filesystem::exists(dir))
802 throw validation_error{detail::to_string("The directory ", dir, " does not exists!")};
803
805 throw validation_error{detail::to_string("The path ", dir, " is not a directory!")};
806
807 // Check if directory has any read permissions.
809 }
810 // LCOV_EXCL_START
812 {
813 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
814 }
815 // LCOV_EXCL_STOP
816 catch (...)
817 {
819 }
820 }
821
824 {
825 return detail::to_string("An existing, readable path for the input directory.");
826 }
827};
828
846{
847public:
848 // Imported from base class.
850
859 virtual ~output_directory_validator() = default;
860
861 // Import base constructor.
864
865 // Import the base::operator().
866 using file_validator_base::operator();
867
873 virtual void operator()(std::filesystem::path const & dir) const override
874 {
875 bool dir_exists = std::filesystem::exists(dir);
876 // Make sure the created dir is deleted after we are done.
878 std::filesystem::create_directory(dir, ec); // does nothing and is not treated as error if path already exists.
879 // if error code was set or if dummy.txt could not be created within the output dir, throw an error.
880 if (static_cast<bool>(ec))
881 throw validation_error{detail::to_string("Cannot create directory: ", dir, "!")};
882
883 try
884 {
885 if (!dir_exists)
886 {
887 detail::safe_filesystem_entry dir_guard{dir};
888 validate_writeability(dir / "dummy.txt");
889 dir_guard.remove_all();
890 }
891 else
892 {
893 validate_writeability(dir / "dummy.txt");
894 }
895 }
896 // LCOV_EXCL_START
898 {
899 std::throw_with_nested(validation_error{"Unhandled filesystem error!"});
900 }
901 // LCOV_EXCL_STOP
902 catch (...)
903 {
905 }
906 }
907
910 {
911 return detail::to_string("A valid path for the output directory.");
912 }
913};
914
935{
936public:
939
943 regex_validator(std::string const & pattern_) : pattern{pattern_}
944 {}
945
950 void operator()(option_value_type const & cmp) const
951 {
952 std::regex rgx(pattern);
953 if (!std::regex_match(cmp, rgx))
954 throw validation_error{detail::to_string("Value ", cmp, " did not match the pattern ", pattern, ".")};
955 }
956
963 template <std::ranges::forward_range range_type>
964 requires std::convertible_to<std::ranges::range_reference_t<range_type>, option_value_type const &>
965 void operator()(range_type const & v) const
966 {
967 for (auto && file_name : v)
968 {
969 // note: we explicitly copy/construct any reference type other than `std::string &`
970 (*this)(static_cast<option_value_type const &>(file_name));
971 }
972 }
973
976 {
977 return detail::to_string("Value must match the pattern '", pattern, "'.");
978 }
979
980private:
982 std::string pattern;
983};
984
985namespace detail
986{
987
1000template <typename option_value_t>
1001struct default_validator
1002{
1004 using option_value_type = option_value_t;
1005
1007 void operator()(option_value_t const & /*cmp*/) const noexcept
1008 {}
1009
1012 {
1013 return "";
1014 }
1015};
1016
1030template <validator validator1_type, validator validator2_type>
1031 requires std::common_with<typename validator1_type::option_value_type, typename validator2_type::option_value_type>
1032class validator_chain_adaptor
1033{
1034public:
1036 using option_value_type =
1038
1042 validator_chain_adaptor() = delete;
1043 validator_chain_adaptor(validator_chain_adaptor const & pf) = default;
1044 validator_chain_adaptor & operator=(validator_chain_adaptor const & pf) = default;
1045 validator_chain_adaptor(validator_chain_adaptor &&) = default;
1046 validator_chain_adaptor & operator=(validator_chain_adaptor &&) = default;
1047
1052 validator_chain_adaptor(validator1_type vali1_, validator2_type vali2_) :
1053 vali1{std::move(vali1_)},
1054 vali2{std::move(vali2_)}
1055 {}
1056
1058 ~validator_chain_adaptor() = default;
1060
1069 template <typename cmp_type>
1070 requires std::invocable<validator1_type, cmp_type const> && std::invocable<validator2_type, cmp_type const>
1071 void operator()(cmp_type const & cmp) const
1072 {
1073 vali1(cmp);
1074 vali2(cmp);
1075 }
1076
1079 {
1080 return detail::to_string(vali1.get_help_page_message(), " ", vali2.get_help_page_message());
1081 }
1082
1083private:
1085 validator1_type vali1;
1087 validator2_type vali2;
1088};
1089
1090} // namespace detail
1091
1121template <validator validator1_type, validator validator2_type>
1122 requires std::common_with<typename std::remove_reference_t<validator1_type>::option_value_type,
1124auto operator|(validator1_type && vali1, validator2_type && vali2)
1125{
1126 return detail::validator_chain_adaptor{std::forward<validator1_type>(vali1), std::forward<validator2_type>(vali2)};
1127}
1128
1129} // namespace seqan3
T back_inserter(T... args)
Provides various type traits on generic types.
T begin(T... args)
A validator that checks whether a number is inside a given range.
Definition: validators.hpp:128
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside [min, max].
Definition: validators.hpp:144
option_value_t option_value_type
The type of value that this validator invoked upon.
Definition: validators.hpp:131
void operator()(range_type const &range) const
Tests whether every element in range lies inside [min, max].
Definition: validators.hpp:158
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:169
arithmetic_range_validator(option_value_type const min_, option_value_type const max_)
The constructor.
Definition: validators.hpp:137
An abstract base class for the file and directory validators.
Definition: validators.hpp:323
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:478
void validate_filename(std::filesystem::path const &path) const
Validates the given filename path based on the specified extensions.
Definition: validators.hpp:374
std::string valid_extensions_help_page_message() const
Returns the information of valid file extensions.
Definition: validators.hpp:463
virtual void operator()(std::filesystem::path const &path) const =0
Tests if the given path is a valid input, respectively output, file or directory.
std::string option_value_type
Type of values that are tested by validator.
Definition: validators.hpp:326
file_validator_base(file_validator_base &&)=default
Defaulted.
file_validator_base & operator=(file_validator_base &&)=default
Defaulted.
void validate_readability(std::filesystem::path const &path) const
Checks if the given path is readable.
Definition: validators.hpp:420
file_validator_base()=default
Defaulted.
file_validator_base(file_validator_base const &)=default
Defaulted.
std::vector< std::string > extensions
Stores the extensions.
Definition: validators.hpp:492
virtual ~file_validator_base()=default
file_validator_base & operator=(file_validator_base const &)=default
Defaulted.
void validate_writeability(std::filesystem::path const &path) const
Checks if the given path is writable.
Definition: validators.hpp:447
A validator that checks if a given path is a valid input directory.
Definition: validators.hpp:770
input_directory_validator(input_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:823
input_directory_validator()=default
Defaulted.
input_directory_validator(input_directory_validator const &)=default
Defaulted.
input_directory_validator & operator=(input_directory_validator &&)=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:797
input_directory_validator & operator=(input_directory_validator const &)=default
Defaulted.
virtual ~input_directory_validator()=default
Virtual Destructor.
A validator that checks if a given path is a valid input file.
Definition: validators.hpp:521
input_file_validator(input_file_validator const &)=default
Defaulted.
virtual ~input_file_validator()=default
Virtual destructor.
input_file_validator & operator=(input_file_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:606
input_file_validator(input_file_validator &&)=default
Defaulted.
input_file_validator(std::vector< std::string > extensions)
Constructs from a given collection of valid extensions.
Definition: validators.hpp:561
virtual void operator()(std::filesystem::path const &file) const override
Tests whether path is an existing regular file and is readable.
Definition: validators.hpp:580
input_file_validator()
Default constructor.
Definition: validators.hpp:541
input_file_validator & operator=(input_file_validator const &)=default
Defaulted.
A validator that checks if a given path is a valid output directory.
Definition: validators.hpp:846
output_directory_validator()=default
Defaulted.
output_directory_validator & operator=(output_directory_validator const &)=default
Defaulted.
virtual ~output_directory_validator()=default
Virtual Destructor.
output_directory_validator(output_directory_validator &&)=default
Defaulted.
output_directory_validator(output_directory_validator const &)=default
Defaulted.
virtual void operator()(std::filesystem::path const &dir) const override
Tests whether path is writable.
Definition: validators.hpp:873
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:909
output_directory_validator & operator=(output_directory_validator &&)=default
Defaulted.
A validator that checks if a given path is a valid output file.
Definition: validators.hpp:651
static std::vector< std::string > default_extensions()
The default extensions of file_t.
Definition: validators.hpp:699
output_file_validator(output_file_validator &&)=default
Defaulted.
output_file_validator(output_file_validator const &)=default
Defaulted.
virtual void operator()(std::filesystem::path const &file) const override
Tests whether path is does not already exists and is writable.
Definition: validators.hpp:714
output_file_validator()
Default constructor.
Definition: validators.hpp:664
output_file_validator & operator=(output_file_validator const &)=default
Defaulted.
output_file_validator & operator=(output_file_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:739
virtual ~output_file_validator()=default
Virtual Destructor.
output_file_validator(output_file_open_options const mode, std::vector< std::string > extensions=default_extensions())
Constructs from a given overwrite mode and a list of valid extensions.
Definition: validators.hpp:679
A validator that checks if a matches a regular expression pattern.
Definition: validators.hpp:935
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:975
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition: validators.hpp:950
std::string option_value_type
Type of values that are tested by validator.
Definition: validators.hpp:938
void operator()(range_type const &v) const
Tests whether every filename in list v matches the pattern.
Definition: validators.hpp:965
regex_validator(std::string const &pattern_)
Constructing from a vector.
Definition: validators.hpp:943
Argument parser exception thrown when an argument could not be casted to the according type.
Definition: exceptions.hpp:131
A validator that checks whether a value is inside a list of valid values.
Definition: validators.hpp:203
value_list_validator(option_type, option_types...) -> value_list_validator< option_type >
Deduction guide for a parameter pack.
value_list_validator()=default
Defaulted.
void operator()(option_value_type const &cmp) const
Tests whether cmp lies inside values.
Definition: validators.hpp:248
value_list_validator(value_list_validator const &)=default
Defaulted.
value_list_validator & operator=(value_list_validator const &)=default
Defaulted.
option_value_t option_value_type
Type of values that are tested by validator.
Definition: validators.hpp:206
void operator()(range_type const &range) const
Tests whether every element in range lies inside values.
Definition: validators.hpp:261
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
Definition: validators.hpp:272
value_list_validator(value_list_validator &&)=default
Defaulted.
~value_list_validator()=default
Defaulted.
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 >
Type deduction guides.
value_list_validator & operator=(value_list_validator &&)=default
Defaulted.
value_list_validator(range_type rng)
Constructing from a range.
Definition: validators.hpp:225
value_list_validator(range_type &&rng) -> value_list_validator< std::ranges::range_value_t< range_type > >
Deduction guide for ranges.
value_list_validator(option_types &&... opts)
Constructing from a parameter pack.
Definition: validators.hpp:238
T clear(T... args)
T create_directory(T... args)
T current_exception(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T equal(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:1124
constexpr auto join_with
A view adaptor that represents view consisting of the sequence obtained from flattening a view of ran...
Definition: join_with.hpp:531
T has_extension(T... args)
A type that satisfies std::is_arithmetic_v<t>.
The concept for option validators passed to add_option/positional_option.
void operator()(option_value_type const &cmp) const
Validates the value 'cmp' and throws a seqan3::validation_error on failure.
std::string get_help_page_message() const
Returns a message that can be appended to the (positional) options help page info.
using option_value_type
The type of value on which the validator is called on.
Provides various utility functions.
T is_directory(T... args)
T is_regular_file(T... args)
Provides seqan3::views::join_with.
T move(T... args)
The main SeqAn3 namespace.
Definition: aligned_sequence_concept.hpp:29
output_file_open_options
Mode of an output file: Determines whether an existing file can be (silently) overwritten.
Definition: validators.hpp:614
@ create_new
Forbid overwriting the output file.
@ open_or_create
Allow to overwrite the output file.
SeqAn specific customisations in the standard namespace.
Provides seqan3::debug_stream and related types.
T regex_match(T... args)
T rethrow_exception(T... args)
Provides seqan3::detail::safe_filesystem_entry.
T size(T... args)
T substr(T... args)
T throw_with_nested(T... args)
Auxiliary for pretty printing of exception messages.
Provides traits for seqan3::type_list.
Provides various traits for template packs.
Provides concepts that do not have equivalents in C++20.