io.h Source File

CPP API: io.h Source File
io.h
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2020-2026 MEmilio
3 *
4 * Authors: Daniel Abele, Wadim Koslow
5 *
6 * Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20 
21 #ifndef MIO_IO_IO_H
22 #define MIO_IO_IO_H
23 
27 #include "boost/outcome/result.hpp"
28 #include "boost/outcome/try.hpp"
29 #include "boost/optional.hpp"
30 
31 #include <bitset>
32 #include <concepts>
33 #include <cstddef>
34 #include <filesystem>
35 #include <string>
36 #include <tuple>
37 #include <iostream>
38 #include <type_traits>
39 #include <utility>
40 
41 namespace mio
42 {
43 
49 enum class StatusCode
50 {
51  OK = 0, //must be 0 because std::error_code stores ints and expects 0 to be no error
52  UnknownError = 1, //errors must be non-zero
53  OutOfRange,
59 };
60 
66 enum IOFlags
67 {
71  IOF_None = 0,
72 
78 
83  IOF_OmitValues = 1 << 1,
84 
90 };
91 
92 } // namespace mio
93 
94 namespace std
95 {
96 
97 //make enum compatible with std::error_code
98 template <>
99 struct is_error_code_enum<mio::StatusCode> : true_type {
100 };
101 
102 } // namespace std
103 
104 namespace mio
105 {
106 
107 namespace details
108 {
112 class StatusCodeCategory : public std::error_category
113 {
114 public:
118  virtual const char* name() const noexcept override final
119  {
120  return "StatusCode";
121  }
122 
126  virtual std::string message(int c) const override final
127  {
128  switch (static_cast<StatusCode>(c)) {
129  case StatusCode::OK:
130  return "No error";
132  return "Invalid range";
134  return "Invalid value";
136  return "Invalid file format";
138  return "Key not found";
140  return "Invalid type";
142  return "File not found";
143  default:
144  return "Unknown Error";
145  }
146  }
147 
151  virtual std::error_condition default_error_condition(int c) const noexcept override final
152  {
153  switch (static_cast<StatusCode>(c)) {
154  case StatusCode::OK:
155  return std::error_condition();
157  return std::make_error_condition(std::errc::argument_out_of_domain);
162  return std::make_error_condition(std::errc::invalid_argument);
164  return std::make_error_condition(std::errc::no_such_file_or_directory);
165  default:
166  return std::error_condition(c, *this);
167  }
168  }
169 };
170 } // namespace details
171 
176 {
178  return c;
179 }
180 
185 inline std::error_code make_error_code(StatusCode e)
186 {
187  return {static_cast<int>(e), status_code_category()};
188 }
189 
197 class IOStatus
198 {
199 public:
203  IOStatus() = default;
204 
210  IOStatus(std::error_code ec, std::string msg = {})
211  : m_ec(ec)
212  , m_msg(std::move(msg))
213  {
214  }
215 
220  bool operator==(const IOStatus& other) const
221  {
222  return other.m_ec == m_ec;
223  }
224  bool operator!=(const IOStatus& other) const
225  {
226  return !(*this == other);
227  }
233  const std::error_code& code() const
234  {
235  return m_ec;
236  }
237 
241  const std::string& message() const
242  {
243  return m_msg;
244  }
245 
252  bool is_ok() const
253  {
254  return !(bool)m_ec;
255  }
256  operator bool() const
257  {
258  return is_ok();
259  }
267  bool is_error() const
268  {
269  return !is_ok();
270  }
271 
275  std::string formatted_message() const
276  {
277  if (is_error()) {
278  return m_ec.message() + ": " + m_msg;
279  }
280  return {};
281  }
282 
283 private:
284  std::error_code m_ec = {};
285  std::string m_msg = {};
286 };
287 
291 inline void PrintTo(const IOStatus& status, std::ostream* os)
292 {
293  *os << status.formatted_message();
294 }
295 
300 inline const std::error_code& make_error_code(const IOStatus& status)
301 {
302  return status.code();
303 }
304 
353 template <class T>
354 using IOResult = boost::outcome_v2::unchecked<T, IOStatus>;
355 
360 inline auto success()
361 {
363 }
364 
370 template <class T>
371 auto success(T&& t)
372 {
373  return boost::outcome_v2::success(std::forward<T>(t));
374 }
375 
381 inline auto failure(const IOStatus& s)
382 {
383  return boost::outcome_v2::failure(s);
384 }
385 
392 inline auto failure(std::error_code c, const std::string& msg = "")
393 {
394  return failure(IOStatus{c, msg});
395 }
396 
407 template <class T>
408 using Tag = boost::outcome_v2::in_place_type_t<T>;
409 
410 //forward declaration only, see below
411 template <class IOContext, class T>
412 void serialize(IOContext& io, const T& t);
413 
414 //forward declaration only, see below
415 template <class IOContext, class T>
416 IOResult<T> deserialize(IOContext& io, Tag<T> tag);
417 
418 //utility for apply function implementation
419 namespace details
420 {
421 //multiple nested IOResults are redundant and difficult to use.
422 //so transform IOResult<IOResult<T>> into IOResult<T>.
423 template <class T>
425  using Type = IOResult<T>;
426 };
427 template <class T>
429  using Type = typename FlattenIOResult<T>::Type;
430 };
431 template <class T>
433 
434 //check if a type is an IOResult or something else
435 template <class T>
436 struct IsIOResult : std::false_type {
437 };
438 template <class T>
439 struct IsIOResult<IOResult<T>> : std::true_type {
440 };
441 
442 //if F(T...) returns an IOResult<U>, apply returns that directly
443 //if F(T...) returns a different type U, apply returns IOResult<U>
444 template <class F, class... T>
445 using ApplyResultT = FlattenIOResultT<std::invoke_result_t<F, T...>>;
446 
448 template <class F, class... T>
449 ApplyResultT<F, T...> eval(F f, const IOResult<T>&... rs)
450 {
451  if constexpr (IsIOResult<std::invoke_result_t<F, T...>>::value) {
452  // case for functions that do internal validation
453  return f(rs.value()...);
454  }
455  else {
456  // case for functions that can't fail, because all values are acceptable
457  return success(f(rs.value()...));
458  }
459 }
460 
461 } // namespace details
462 
481 template <class IOContext, class F, class... T>
482 details::ApplyResultT<F, T...> apply(IOContext& io, F f, const IOResult<T>&... rs)
483 {
484  //store the status of each argument in an array to check them.
485  //it would be possible to write a recursive template that checks one argument
486  //after the other without copying each status. This would probably be slightly faster
487  //and easier to optimize for the compiler. but gdb crashes trying to resolve the
488  //very long symbol in case of many arguments. Successful results are very cheap to copy
489  //and slightly worse performance in the case of an error is probably acceptable.
490 
491  //check for errors in the arguments
492  auto status = std::array{(rs ? IOStatus{} : rs.error())...};
493  auto iter_err = std::find_if(std::begin(status), std::end(status), [](auto& s) {
494  return s.is_error();
495  });
496 
497  //evaluate f if all succesful
498  auto result =
499  iter_err == std::end(status) ? details::eval(f, rs...) : details::ApplyResultT<F, T...>(failure(*iter_err));
500 
501  if (!result) {
502  //save error in context
503  //if the error was generated by the applied function, the context hasn't seen it yet.
504  //this allows the context to fail fast and not continue to parse more.
505  io.set_error(result.error());
506  }
507  return result;
508 }
509 
510 template <class IOContext, class F, class... T>
511 details::ApplyResultT<F, T...> apply(IOContext& io, F f, const std::tuple<IOResult<T>...>& results)
512 {
513  return std::apply(
514  [&](auto&&... rs) {
515  return apply(io, f, rs...);
516  },
517  results);
518 }
521 //detect a serialize member function
522 template <class T, class IOContext>
523 concept HasSerialize = requires(IOContext& ctxt, const T& t) { t.serialize(ctxt); };
524 
525 //detect a static deserialize member function
526 template <class T, class IOContext>
527 concept HasDeserialize = requires(IOContext& ctxt) {
528  { T::deserialize(ctxt) } -> std::same_as<IOResult<T>>;
529 };
530 
531 //utility for (de-)serializing tuple-like objects
532 namespace details
533 {
534 template <size_t Idx>
536 {
537  return "Element" + std::to_string(Idx);
538 }
539 
540 //recursive tuple serialization for each tuple element
541 //store one tuple element after the other in the IOObject
542 template <size_t Idx, class IOObj, class Tup>
543 void serialize_tuple_element(IOObj& obj, const Tup& tup)
544 {
545  if constexpr (Idx < std::tuple_size_v<Tup>) {
546  //serialize one element, then recurse
547  obj.add_element(make_tuple_element_name<Idx>(), std::get<Idx>(tup));
548  serialize_tuple_element<Idx + 1>(obj, tup);
549  }
550  // else: end of recursion, no more elements to serialize
551 }
552 
553 //recursive tuple deserialization for each tuple element
554 //read one tuple element after the other from the IOObject
555 //argument pack rs contains the elements that have been read already
556 template <class IOObj, class Tup, class... Ts>
558 {
559  if constexpr (sizeof...(Ts) < std::tuple_size_v<Tup>) {
560  //get the next element of the tuple from the IO object
561  const size_t Idx = sizeof...(Ts);
562  auto r = obj.expect_element(make_tuple_element_name<Idx>(), Tag<std::tuple_element_t<Idx, Tup>>{});
563  //recurse, append the new element to the pack of arguments
564  return deserialize_tuple_element(obj, tag, rs..., r);
565  }
566  else {
567  //end of recursion
568  //number of arguments in rs is the same as the size of the tuple
569  //no more elements to read, so finalize the object
570  return mio::apply(
571  obj,
572  [](const Ts&... ts) {
573  return Tup(ts...);
574  },
575  rs...);
576  }
577 }
578 
579 } // namespace details
580 
588 template <class IOContext, template <class...> class Tup, class... T>
589  requires std::same_as<Tup<T...>, std::pair<T...>> || std::same_as<Tup<T...>, std::tuple<T...>>
590 void serialize_internal(IOContext& io, const Tup<T...>& tup)
591 {
592  auto obj = io.create_object("Tuple");
593  details::serialize_tuple_element<0>(obj, tup);
594 }
595 
604 template <class IOContext, template <class...> class Tup, class... T>
605  requires std::same_as<Tup<T...>, std::pair<T...>> || std::same_as<Tup<T...>, std::tuple<T...>>
606 IOResult<Tup<T...>> deserialize_internal(IOContext& io, Tag<Tup<T...>> tag)
607 {
608  auto obj = io.expect_object("Tuple");
609  return details::deserialize_tuple_element(obj, tag);
610 }
611 
619 template <class IOContext, class M>
620 void serialize_internal(IOContext& io, const Eigen::EigenBase<M>& mat)
621 {
622  auto obj = io.create_object("Matrix");
623  obj.add_element("Rows", mat.rows());
624  obj.add_element("Columns", mat.cols());
625  obj.add_list("Elements", begin(static_cast<const M&>(mat)), end(static_cast<const M&>(mat)));
626 }
627 
638 template <class IOContext, IsMatrixExpression M>
639 IOResult<M> deserialize_internal(IOContext& io, Tag<M> /*tag*/)
640 {
641  auto obj = io.expect_object("Matrix");
642  auto rows = obj.expect_element("Rows", Tag<Eigen::Index>{});
643  auto cols = obj.expect_element("Columns", Tag<Eigen::Index>{});
644  auto elements = obj.expect_list("Elements", Tag<typename M::Scalar>{});
645  return mio::apply(
646  io,
647  [](auto&& r, auto&& c, auto&& v) {
648  auto m = M{r, c};
649  for (auto i = Eigen::Index(0); i < r; ++i) {
650  for (auto j = Eigen::Index(0); j < c; ++j) {
651  m(i, j) = v[i * c + j];
652  }
653  }
654  return m;
655  },
656  rows, cols, elements);
657 }
658 
666 template <class IOContext, size_t N>
667 void serialize_internal(IOContext& io, const std::bitset<N> bitset)
668 {
669  std::array<bool, N> bits;
670  for (size_t i = 0; i < N; i++) {
671  bits[i] = bitset[i];
672  }
673  auto obj = io.create_object("BitSet");
674  obj.add_list("bitset", bits.begin(), bits.end());
675 }
676 
685 template <class IOContext, size_t N>
686 IOResult<std::bitset<N>> deserialize_internal(IOContext& io, Tag<std::bitset<N>> tag)
687 {
688  mio::unused(tag);
689  auto obj = io.expect_object("BitSet");
690  auto bits = obj.expect_list("bitset", Tag<bool>{});
691 
692  return apply(
693  io,
694  [](auto&& bits_) -> IOResult<std::bitset<N>> {
695  if (bits_.size() != N) {
696  return failure(StatusCode::InvalidValue,
697  "Incorrent number of booleans to deserialize bitset. Expected " + std::to_string(N) +
698  ", got " + std::to_string(bits_.size()) + ".");
699  }
700  std::bitset<N> bitset;
701  for (size_t i = 0; i < N; i++) {
702  bitset[i] = bits_[i];
703  }
704  return bitset;
705  },
706  bits);
707 }
708 
716 template <class IOContext, class E>
717  requires std::is_enum_v<E>
718 void serialize_internal(IOContext& io, E e)
719 {
720  mio::serialize(io, std::underlying_type_t<E>(e));
721 }
722 
733 template <class IOContext, class E>
734  requires std::is_enum_v<E>
735 IOResult<E> deserialize_internal(IOContext& io, Tag<E> /*tag*/)
736 {
737  BOOST_OUTCOME_TRY(auto&& i, mio::deserialize(io, mio::Tag<std::underlying_type_t<E>>{}));
738  return success(E(i));
739 }
740 
748 template <class IOContext, HasSerialize<IOContext> T>
749 void serialize_internal(IOContext& io, const T& t)
750 {
751  t.serialize(io);
752 }
753 
762 template <class IOContext, HasDeserialize<IOContext> T>
763 IOResult<T> deserialize_internal(IOContext& io, Tag<T> /*tag*/)
764 {
765  return T::deserialize(io);
766 }
767 
773 template <class C>
774 concept IsContainer =
775  requires(C c, const C& cc) {
776  cc.begin() != cc.end();
777  C(c.begin(), c.end());
778  }
779  // Eigen types may pass as container, but we want to handle them separately
780  && !std::is_base_of_v<Eigen::EigenBase<C>, C>;
781 
789 template <class IOContext, IsContainer Container>
790  requires(!HasSerialize<IOContext, Container>)
791 void serialize_internal(IOContext& io, const Container& container)
792 {
793  auto obj = io.create_object("List");
794  obj.add_list("Items", container.begin(), container.end());
795 } // namespace mio
796 
805 template <class IOContext, IsContainer Container>
806  requires(!HasSerialize<IOContext, Container>)
808 {
809  auto obj = io.expect_object("List");
810  auto i = obj.expect_list("Items", Tag<typename Container::value_type>{});
811  return apply(
812  io,
813  [](auto&& i_) {
814  return Container(i_.begin(), i_.end());
815  },
816  i);
817 }
818 
836 template <class IOContext, class T>
837 void serialize(IOContext& io, const T& t)
838 {
840  serialize_internal(io, t);
841 }
842 
860 template <class IOContext, class T>
861 IOResult<T> deserialize(IOContext& io, Tag<T> tag)
862 {
864  return deserialize_internal(io, tag);
865 }
866 
870 std::string get_current_dir_name();
871 
879 IOResult<bool> create_directory(const std::filesystem::path& path, std::string& abs_path, bool create_parents = false);
880 
887 IOResult<bool> create_directory(const std::filesystem::path& path, bool create_parents = false);
888 
897 std::filesystem::path create_directories_or_exit(const std::filesystem::path& path, bool create_parents = true);
898 
906 bool file_exists(std::string const& rel_path, std::string& abs_path);
907 
908 } // namespace mio
909 
910 #endif // MIO_IO_IO_H
IOStatus represents the result of an operation.
Definition: io.h:198
const std::string & message() const
get the message that contains additional information about errors.
Definition: io.h:241
const std::error_code & code() const
get the error code.
Definition: io.h:233
IOStatus(std::error_code ec, std::string msg={})
constructor with error code and message
Definition: io.h:210
std::string m_msg
Check if the status represents failure.
Definition: io.h:285
std::string formatted_message() const
Get a string that combines the error code and the message.
Definition: io.h:275
bool operator!=(const IOStatus &other) const
equality comparison operators.
Definition: io.h:224
IOStatus()=default
default constructor, represents success
std::error_code m_ec
Check if the status represents failure.
Definition: io.h:284
bool is_ok() const
Check if the status represents success.
Definition: io.h:252
bool operator==(const IOStatus &other) const
equality comparison operators.
Definition: io.h:220
bool is_error() const
Check if the status represents failure.
Definition: io.h:267
category that describes StatusCode in std::error_code
Definition: io.h:113
virtual std::error_condition default_error_condition(int c) const noexcept override final
convert to standard error code to make it comparable.
Definition: io.h:151
virtual std::string message(int c) const override final
convert enum to string message.
Definition: io.h:126
virtual const char * name() const noexcept override final
name of the status
Definition: io.h:118
trait_value< T >::RETURN_TYPE & value(T &x)
Definition: ad.hpp:3308
void serialize_tuple_element(IOObj &obj, const Tup &tup)
Definition: io.h:543
std::string make_tuple_element_name()
Definition: io.h:535
ApplyResultT< F, T... > eval(F f, const IOResult< T > &... rs)
Evaluates a function f using values of the given IOResults as arguments, assumes all IOResults are su...
Definition: io.h:449
FlattenIOResultT< std::invoke_result_t< F, T... > > ApplyResultT
Definition: io.h:445
typename FlattenIOResult< T >::Type FlattenIOResultT
Definition: io.h:432
IOResult< Tup > deserialize_tuple_element(IOObj &obj, Tag< Tup > tag, const IOResult< Ts > &... rs)
Definition: io.h:557
A collection of classes to simplify handling of matrix shapes in meta programming.
Definition: models/abm/analyze_result.h:30
requires details::IsElementReference< M > RowMajorIterator< M, false > end(M &m)
create a non-const end iterator for the matrix m.
Definition: eigen_util.h:449
concept HasSerialize
Definition: io.h:523
IOResult< bool > create_directory(const std::filesystem::path &rel_path, std::string &abs_path, bool create_parents)
Creates a directory in the file system.
Definition: io.cpp:35
IOResult< T > deserialize(IOContext &io, Tag< T > tag)
Restores an object from the data stored in an IO context.
Definition: io.h:861
auto failure(const IOStatus &s)
Create an object that is implicitly convertible to an error IOResult<T>.
Definition: io.h:381
std::string get_current_dir_name()
Returns the current working directory name.
Definition: io.cpp:29
StatusCode
code to indicate the result of an operation.
Definition: io.h:50
void serialize(IOContext &io, const T &t)
Save data that describes an object in a format determined by the given context.
Definition: io.h:837
boost::outcome_v2::in_place_type_t< T > Tag
Type that is used for overload resolution.
Definition: io.h:408
auto i
Definition: io.h:810
details::ApplyResultT< F, T... > apply(IOContext &io, F f, const IOResult< T > &... rs)
Evaluate a function with zero or more unpacked IOResults as arguments.
Definition: io.h:482
std::error_code make_error_code(StatusCode e)
Convert StatusCode to std::error_code.
Definition: io.h:185
requires(!std::is_trivial_v< T >) void BinarySerializerObject
Definition: binary_serializer.h:333
std::filesystem::path create_directories_or_exit(const std::filesystem::path &path, bool create_parents)
Creates directories in the file system, or exits the program with an error code.
Definition: io.cpp:64
concept HasDeserialize
Definition: io.h:527
const details::StatusCodeCategory & status_code_category()
singleton StatusCodeCategory instance.
Definition: io.h:175
auto success()
Create an object that is implicitly convertible to a succesful IOResult<void>.
Definition: io.h:360
return apply(io, [](auto &&i_) { return Container(i_.begin(), i_.end());}, i)
auto success(T &&t)
Create an object that is implicitly convertible to a succesful IOResult.
Definition: io.h:371
void unused(T &&...)
Does nothing, can be used to mark variables as not used.
Definition: compiler_diagnostics.h:30
requires details::IsElementReference< M > RowMajorIterator< M, false > begin(M &m)
create a non-const iterator to first element of the matrix m.
Definition: eigen_util.h:421
bool file_exists(std::string const &rel_path, std::string &abs_path)
Check if a file exists.
Definition: io.cpp:75
concept IsContainer
Concept to check whether C is a STL compatible container.
Definition: io.h:774
const Container & container
Definition: io.h:792
IOFlags
flags to determine the behavior of the serialization process.
Definition: io.h:67
@ IOF_None
default behavior.
Definition: io.h:71
@ IOF_OmitDistributions
Don't serialize distributions for types that contain both a specific value and a distribution from wh...
Definition: io.h:77
@ IOF_OmitValues
Don't serialize the current value for types that contain both a specific value and a distribution fro...
Definition: io.h:83
@ IOF_IncludeTypeInfo
Include type info in the serialization.
Definition: io.h:89
void PrintTo(const IOStatus &status, std::ostream *os)
gtest printer for IOStatus.
Definition: io.h:291
auto failure(std::error_code c, const std::string &msg="")
Create an object that is implicitly convertible to an error IOResult<T>.
Definition: io.h:392
boost::outcome_v2::unchecked< T, IOStatus > IOResult
Value-or-error type for operations that return a value but can fail.
Definition: io.h:354
IOResult< T > deserialize_internal(IOContext &io, Tag< T > tag)
Deserialization implementation for the default serialization feature.
Definition: default_serialize.h:236
void serialize_internal(IOContext &io, const T &a)
Serialization implementation for the default serialization feature.
Definition: default_serialize.h:213
Definition: io.h:95
typename FlattenIOResult< T >::Type Type
Definition: io.h:429
Definition: io.h:424
IOResult< T > Type
Definition: io.h:425
Definition: io.h:436