userver: userver/storages/postgres/io/strong_typedef.hpp Source File
Loading...
Searching...
No Matches
strong_typedef.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/postgres/io/strong_typedef.hpp
4/// @brief utils::StrongTypedef I/O support
5/// @ingroup userver_postgres_parse_and_format
6
7#include <userver/storages/postgres/exceptions.hpp>
8#include <userver/storages/postgres/io/buffer_io.hpp>
9#include <userver/storages/postgres/io/buffer_io_base.hpp>
10#include <userver/storages/postgres/io/nullable_traits.hpp>
11
12#include <userver/utils/strong_typedef.hpp>
13#include <userver/utils/void_t.hpp>
14
15USERVER_NAMESPACE_BEGIN
16
17namespace storages::postgres::io {
18
19/// @page pg_strong_typedef uPg: support for C++ 'strong typedef' idiom
20///
21/// Within userver a strong typedef can be expressed as an enum class for
22/// integral types and as an instance of
23/// `USERVER_NAMESPACE::utils::StrongTypedef` template for all types. Both of
24/// them are supported transparently by the PostgresSQL driver with minor
25/// limitations. No additional code required.
26///
27/// @warning The underlying integral type for a strong typedef MUST be
28/// signed as PostgreSQL doesn't have unsigned types
29///
30/// The underlying type for a strong typedef must be mapped to a system or a
31/// user PostgreSQL data type. Strong typedef type derives nullability from
32/// underlying type.
33///
34/// Using `USERVER_NAMESPACE::utils::StrongTypedef` example:
35/// @snippet storages/postgres/tests/strong_typedef_pgtest.cpp Strong typedef
36///
37/// Using `enum class` example:
38/// @snippet storages/postgres/tests/strong_typedef_pgtest.cpp Enum typedef
39
40namespace traits {
41
42// Helper to detect if the strong typedef mapping is explicitly defined,
43// e.g. TimePointTz
44template <typename Tag, typename T, USERVER_NAMESPACE::utils::StrongTypedefOps Ops, typename Enable>
45inline constexpr bool kIsStrongTypedefDirectlyMapped =
46 // NOLINTNEXTLINE(google-readability-casting)
47 kIsMappedToUserType<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>> ||
48 // NOLINTNEXTLINE(google-readability-casting)
49 kIsMappedToSystemType<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>> ||
50 // NOLINTNEXTLINE(google-readability-casting)
51 kIsMappedToArray<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>>;
52
53template <typename Tag, typename T, USERVER_NAMESPACE::utils::StrongTypedefOps Ops, typename Enable>
54struct IsMappedToPg<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>>
55 // NOLINTNEXTLINE(google-readability-casting)
56 : BoolConstant<kIsStrongTypedefDirectlyMapped<Tag, T, Ops, Enable> || kIsMappedToPg<T>> {};
57
58// Mark that strong typedef mapping is a special case for disambiguating
59// specialization of CppToPg
60template <typename Tag, typename T, USERVER_NAMESPACE::utils::StrongTypedefOps Ops, typename Enable>
61struct IsSpecialMapping<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>>
62 // NOLINTNEXTLINE(google-readability-casting)
63 : BoolConstant<!kIsStrongTypedefDirectlyMapped<Tag, T, Ops, Enable> && kIsMappedToPg<T>> {};
64
65template <typename Tag, typename T, USERVER_NAMESPACE::utils::StrongTypedefOps Ops, typename Enable>
66struct IsNullable<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>> : IsNullable<T> {};
67
68template <typename Tag, typename T, USERVER_NAMESPACE::utils::StrongTypedefOps Ops, typename Enable>
69struct GetSetNull<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>> {
70 using ValueType = USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>;
71 using UnderlyingGetSet = GetSetNull<T>;
72 inline static bool IsNull(const ValueType& v) { return UnderlyingGetSet::IsNull(v.GetUnderlying()); }
73 inline static void SetNull(ValueType& v) { UnderlyingGetSet::SetNull(v.GetUnderlying()); }
74 inline static void SetDefault(ValueType& v) { UnderlyingGetSet::SetDefault(v.GetUnderlying()); }
75};
76
77/// A metafunction that enables an enum type serialization to its
78/// underlying type. Can be specialized.
79template <typename T, typename = USERVER_NAMESPACE::utils::void_t<>>
80struct CanUseEnumAsStrongTypedef : std::false_type {};
81
82namespace impl {
83
84template <typename T>
85constexpr bool CheckCanUseEnumAsStrongTypedef() {
86 if constexpr (CanUseEnumAsStrongTypedef<T>{}) {
87 static_assert(
88 std::is_enum_v<T>,
89 "storages::postgres::io::traits::CanUseEnumAsStrongTypedef "
90 "should be specialized only for enums"
91 );
92 static_assert(
93 std::is_signed_v<std::underlying_type_t<T>>,
94 "storages::postgres::io::traits::CanUseEnumAsStrongTypedef should be "
95 "specialized only for enums with signed underlying type"
96 );
97
98 return true;
99 }
100
101 return false;
102}
103
104} // namespace impl
105
106template <typename T>
107using EnableIfCanUseEnumAsStrongTypedef = std::enable_if_t<impl::CheckCanUseEnumAsStrongTypedef<T>()>;
108
109} // namespace traits
110
111template <typename Tag, typename T, USERVER_NAMESPACE::utils::StrongTypedefOps Ops, typename Enable>
112struct BufferFormatter<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>>
113 : detail::BufferFormatterBase<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>> {
114 using BaseType = detail::BufferFormatterBase<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>>;
115 using BaseType::BaseType;
116
117 template <typename Buffer>
118 void operator()(const UserTypes& types, Buffer& buf) const {
119 io::WriteBuffer(types, buf, this->value.GetUnderlying());
120 }
121};
122
123namespace detail {
124template <typename StrongTypedef, bool Categories = false>
125struct StrongTypedefParser : BufferParserBase<StrongTypedef> {
126 using BaseType = BufferParserBase<StrongTypedef>;
127 using UnderlyingType = typename StrongTypedef::UnderlyingType;
128
129 using BaseType::BaseType;
130
131 void operator()(const FieldBuffer& buffer) {
132 UnderlyingType& v = this->value.GetUnderlying();
133 io::ReadBuffer(buffer, v);
134 }
135};
136
137template <typename StrongTypedef>
138struct StrongTypedefParser<StrongTypedef, true> : BufferParserBase<StrongTypedef> {
139 using BaseType = BufferParserBase<StrongTypedef>;
140 using UnderlyingType = typename StrongTypedef::UnderlyingType;
141
142 using BaseType::BaseType;
143
144 void operator()(const FieldBuffer& buffer, const TypeBufferCategory& categories) {
145 UnderlyingType& v = this->value.GetUnderlying();
146 io::ReadBuffer(buffer, v, categories);
147 }
148};
149
150} // namespace detail
151
152template <typename Tag, typename T, USERVER_NAMESPACE::utils::StrongTypedefOps Ops, typename Enable>
153struct BufferParser<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>>
154 : detail::StrongTypedefParser<
155 USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>,
156 detail::kParserRequiresTypeCategories<T>> {
157 using BaseType = detail::StrongTypedefParser<
158 USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>,
159 detail::kParserRequiresTypeCategories<T>>;
160 using BaseType::BaseType;
161};
162
163// StrongTypedef template mapping specialization
164template <typename Tag, typename T, USERVER_NAMESPACE::utils::StrongTypedefOps Ops, typename Enable>
165struct CppToPg<
166 USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>,
167 std::enable_if_t<!traits::kIsStrongTypedefDirectlyMapped<Tag, T, Ops, Enable> && traits::kIsMappedToPg<T>>>
168 : CppToPg<T> {};
169
170namespace traits {
171
172template <typename Tag, typename T, USERVER_NAMESPACE::utils::StrongTypedefOps Ops, typename Enable>
173struct ParserBufferCategory<BufferParser<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops, Enable>>>
174 : ParserBufferCategory<typename traits::IO<T>::ParserType> {};
175
176} // namespace traits
177
178namespace detail {
179
180template <typename T>
181struct EnumStrongTypedefFormatter : BufferFormatterBase<T> {
182 using BaseType = BufferFormatterBase<T>;
183 using BaseType::BaseType;
184
185 template <typename Buffer>
186 void operator()(const UserTypes& types, Buffer& buf) const {
187 io::WriteBuffer(types, buf, USERVER_NAMESPACE::utils::UnderlyingValue(this->value));
188 }
189};
190
191template <typename T>
192struct EnumStrongTypedefParser : BufferParserBase<T> {
193 using BaseType = BufferParserBase<T>;
194 using ValueType = typename BaseType::ValueType;
195 using UnderlyingType = std::underlying_type_t<ValueType>;
196
197 using BaseType::BaseType;
198
199 void operator()(const FieldBuffer& buffer) {
200 UnderlyingType v;
201 io::ReadBuffer(buffer, v);
202 this->value = static_cast<ValueType>(v);
203 }
204};
205
206} // namespace detail
207
208namespace traits {
209
210template <typename T>
211struct Output<T, EnableIfCanUseEnumAsStrongTypedef<T>> {
212 using type = io::detail::EnumStrongTypedefFormatter<T>;
213};
214
215template <typename T>
216struct Input<T, EnableIfCanUseEnumAsStrongTypedef<T>> {
217 using type = io::detail::EnumStrongTypedefParser<T>;
218};
219
220template <typename T>
221struct IsMappedToPg<T, EnableIfCanUseEnumAsStrongTypedef<T>> : std::true_type {};
222
223template <typename T>
224struct IsSpecialMapping<T, EnableIfCanUseEnumAsStrongTypedef<T>> : std::true_type {};
225
226} // namespace traits
227
228// enum class strong typedef mapping specialization
229template <typename T>
230struct CppToPg<T, traits::EnableIfCanUseEnumAsStrongTypedef<T>> : CppToPg<std::underlying_type_t<T>> {};
231
232} // namespace storages::postgres::io
233
234USERVER_NAMESPACE_END