userver: userver/formats/parse/variant.hpp Source File
Loading...
Searching...
No Matches
variant.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/formats/parse/variant.hpp
4/// @brief Ineffective but generic parser for std::variant type
5///
6/// Parsing is performed for each of the N alternatives in variant, N-1
7/// exceptions is thrown and caught during the parsing.
8///
9/// @ingroup userver_universal userver_formats_parse
10
11#include <optional>
12#include <typeinfo>
13#include <variant>
14
15#include <fmt/format.h>
16
17#include <userver/compiler/demangle.hpp>
18#include <userver/formats/parse/to.hpp>
19
20USERVER_NAMESPACE_BEGIN
21
22namespace formats::json {
23class Value;
24}
25
26namespace formats::parse {
27
28template <typename ParseException, typename Variant, typename TypeA>
29[[noreturn]] void ThrowVariantAmbiguousParse(std::string_view path, std::type_index type_b) {
30 throw ParseException(fmt::format(
31 "Value of '{}' is ambiguous, it is parseable into multiple variants of '{}', at least '{}' and '{}'",
32 path,
33 compiler::GetTypeName<Variant>(),
34 compiler::GetTypeName<TypeA>(),
35 compiler::GetTypeName(type_b)
36 ));
37}
38
39template <class ParseException, typename Variant>
40[[noreturn]] void ThrowVariantParseException(std::string_view path, std::string_view errors) {
41 throw ParseException(fmt::format(
42 "Value of '{}' cannot be parsed as {}. Issues during parsing follow: {}",
43 path,
44 compiler::GetTypeName<Variant>(),
45 errors
46 ));
47}
48
49namespace impl {
50
51template <class T, class Value, typename Result>
52void ParseVariantSingle(const Value& value, std::optional<Result>& result, std::string& errors) {
53 if (result) {
54 const auto old_type = std::visit([](const auto& v) -> std::type_index { return typeid(v); }, *result);
55 try {
56 value.template As<T>();
57 } catch (const std::exception&) {
58 return;
59 }
60
61 using ParseException = typename Value::ParseException;
62 formats::parse::ThrowVariantAmbiguousParse<ParseException, Result, T>(value.GetPath(), old_type);
63 } else {
64 // No result yet
65 try {
66 result = value.template As<T>();
67 } catch (const std::exception& e) {
68 errors = fmt::format("{}\n* Failed to parse as {}: {}", errors, compiler::GetTypeName<T>(), e.what());
69 }
70 }
71}
72
73} // namespace impl
74
75template <class Value, typename... Types>
76std::variant<decltype(Parse(std::declval<Value>(), To<Types>{}))...>
77Parse(const Value& value, formats::parse::To<std::variant<Types...>>) {
78 std::optional<std::variant<decltype(Parse(std::declval<Value>(), To<Types>{}))...>> result;
79
80 std::string errors;
81
82 if constexpr (std::is_same_v<Value, formats::json::Value>) {
83 Value value2{value};
84 value2.DropRootPath();
85 (impl::ParseVariantSingle<Types>(value2, result, errors), ...);
86 } else {
87 (impl::ParseVariantSingle<Types>(value, result, errors), ...);
88 }
89
90 if (!result) {
91 formats::parse::ThrowVariantParseException<
92 typename Value::ParseException,
93 std::variant<Types...>>(value.GetPath(), errors);
94 }
95
96 return std::move(*result);
97}
98
99} // namespace formats::parse
100
101USERVER_NAMESPACE_END