userver: userver/formats/parse/variant.hpp Source File
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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) {
41 throw ParseException(fmt::format("Value of '{}' cannot be parsed as {}", path, compiler::GetTypeName<Variant>()));
42}
43
44namespace impl {
45template <class T, class Value, typename Result>
46void ParseVariantSingle(const Value& value, std::optional<Result>& result) {
47 if (result) {
48 const auto old_type = std::visit([](const auto& v) -> std::type_index { return typeid(v); }, *result);
49 try {
50 value.template As<T>();
51 } catch (const std::exception&) {
52 return;
53 }
54
55 ThrowVariantAmbiguousParse<typename Value::ParseException, Result, T>(value.GetPath(), old_type);
56 } else {
57 // No result yet
58 try {
59 result = value.template As<T>();
60 } catch (const std::exception&) {
61 }
62 }
63}
64
65} // namespace impl
66
67template <class Value, typename... Types>
68auto Parse(const Value& value, formats::parse::To<std::variant<Types...>>) {
69 std::optional<std::variant<decltype(Parse(std::declval<Value>(), To<Types>{}))...>> result;
70
71 if constexpr (std::is_same_v<Value, formats::json::Value>) {
72 Value value2{value};
73 value2.DropRootPath();
74 (impl::ParseVariantSingle<Types>(value2, result), ...);
75 } else {
76 (impl::ParseVariantSingle<Types>(value, result), ...);
77 }
78
79 if (!result) {
80 ThrowVariantParseException<typename Value::ParseException, std::variant<Types...>>(value.GetPath());
81 }
82
83 return std::move(*result);
84}
85
86} // namespace formats::parse
87
88USERVER_NAMESPACE_END