forked from microsoft/cppgraphqlgen
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGraphQLClient.h
More file actions
365 lines (312 loc) · 12.3 KB
/
GraphQLClient.h
File metadata and controls
365 lines (312 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#ifndef GRAPHQLCLIENT_H
#define GRAPHQLCLIENT_H
#include "GraphQLResponse.h"
#include "internal/DllExports.h"
#include <algorithm>
#include <iterator>
#include <optional>
#include <ranges>
#include <stdexcept>
#include <vector>
namespace graphql::client {
// Errors may specify the line number and column number where the error occurred.
struct [[nodiscard("unnecessary construction")]] ErrorLocation
{
int line {};
int column {};
};
// Errors may specify a path to the field which triggered the error. The path consists of
// field names and the indices of elements in a list.
using ErrorPathSegment = std::variant<std::string, int>;
// Error returned from the service.
struct [[nodiscard("unnecessary construction")]] Error
{
std::string message;
std::vector<ErrorLocation> locations;
std::vector<ErrorPathSegment> path;
};
// Complete response from the service, split into the unparsed graphql::response::Value in
// data and the (typically empty) collection of Errors in errors.
struct [[nodiscard("unnecessary construction")]] ServiceResponse
{
response::Value data;
std::vector<Error> errors;
};
// Split a service response into separate ServiceResponse data and errors members.
[[nodiscard("unnecessary conversion")]] GRAPHQLCLIENT_EXPORT ServiceResponse parseServiceResponse(
response::Value response);
// GraphQL types are nullable by default, but they may be wrapped with non-null or list types.
// Since nullability is a more special case in C++, we invert the default and apply that modifier
// instead when the non-null wrapper is not present in that part of the wrapper chain.
enum class [[nodiscard("unnecessary conversion")]] TypeModifier {
None,
Nullable,
List,
};
// Serialize a single variable input value.
template <typename Type>
struct Variable
{
// Serialize a single value to the variables document.
[[nodiscard("unnecessary conversion")]] static response::Value serialize(Type&& value);
};
#ifdef GRAPHQL_DLLEXPORTS
// Export all of the built-in converters
template <>
GRAPHQLCLIENT_EXPORT response::Value Variable<int>::serialize(int&& value);
template <>
GRAPHQLCLIENT_EXPORT response::Value Variable<double>::serialize(double&& value);
template <>
GRAPHQLCLIENT_EXPORT response::Value Variable<std::string>::serialize(std::string&& value);
template <>
GRAPHQLCLIENT_EXPORT response::Value Variable<bool>::serialize(bool&& value);
template <>
GRAPHQLCLIENT_EXPORT response::Value Variable<response::IdType>::serialize(
response::IdType&& value);
template <>
GRAPHQLCLIENT_EXPORT response::Value Variable<response::Value>::serialize(response::Value&& value);
#endif // GRAPHQL_DLLEXPORTS
inline namespace modified_variable {
// These types are used as scalar variables even though they are represented with a class.
template <typename Type>
concept ScalarVariableClass = std::is_same_v<Type, std::string>
|| std::is_same_v<Type, response::IdType> || std::is_same_v<Type, response::Value>;
// Any non-scalar class used in a variable is a generated INPUT_OBJECT type.
template <typename Type>
concept InputVariableClass = std::is_class_v<Type> && !ScalarVariableClass<Type>;
// Test if there are any non-None modifiers left.
template <TypeModifier... Other>
concept OnlyNoneModifiers = (... && (Other == TypeModifier::None));
// Test if the next modifier is Nullable.
template <TypeModifier Modifier>
concept NullableModifier = Modifier == TypeModifier::Nullable;
// Test if the next modifier is List.
template <TypeModifier Modifier>
concept ListModifier = Modifier == TypeModifier::List;
// Special-case an innermost nullable INPUT_OBJECT type.
template <typename Type, TypeModifier... Other>
concept InputVariableUniquePtr = InputVariableClass<Type> && OnlyNoneModifiers<Other...>;
// Serialize variable input values with chained type modifiers which add nullable or list wrappers.
template <typename Type>
struct ModifiedVariable
{
// Peel off modifiers until we get to the underlying type.
template <typename U, TypeModifier Modifier = TypeModifier::None, TypeModifier... Other>
struct VariableTraits
{
// Peel off modifiers until we get to the underlying type.
using type = typename std::conditional_t<TypeModifier::Nullable == Modifier,
typename std::conditional_t<InputVariableUniquePtr<U, Other...>, std::unique_ptr<U>,
std::optional<typename VariableTraits<U, Other...>::type>>,
typename std::conditional_t<TypeModifier::List == Modifier,
std::vector<typename VariableTraits<U, Other...>::type>, U>>;
};
template <typename U>
struct VariableTraits<U, TypeModifier::None>
{
using type = U;
};
// Peel off the none modifier. If it's included, it should always be last in the list.
template <TypeModifier Modifier = TypeModifier::None, TypeModifier... Other>
[[nodiscard("unnecessary conversion")]] static response::Value serialize(Type&& value)
requires OnlyNoneModifiers<Modifier, Other...>
{
// Just call through to the non-template method without the modifiers.
return Variable<Type>::serialize(std::move(value));
}
// Peel off nullable modifiers.
template <TypeModifier Modifier, TypeModifier... Other>
[[nodiscard("unnecessary conversion")]] static response::Value serialize(
typename VariableTraits<Type, Modifier, Other...>::type&& nullableValue)
requires NullableModifier<Modifier>
{
response::Value result;
if (nullableValue)
{
result = serialize<Other...>(std::move(*nullableValue));
nullableValue.reset();
}
return result;
}
// Peel off list modifiers.
template <TypeModifier Modifier, TypeModifier... Other>
[[nodiscard("unnecessary conversion")]] static response::Value serialize(
typename VariableTraits<Type, Modifier, Other...>::type&& listValue)
requires ListModifier<Modifier>
{
response::Value result { response::Type::List };
result.reserve(listValue.size());
if constexpr(std::is_same_v<Type,bool>){
for (auto const v: listValue)
result.emplace_back(Variable<bool>::serialize(bool{v}));
}
else{
std::ranges::for_each(listValue, [&result](auto& value) {
result.emplace_back(serialize<Other...>(std::move(value)));
});
}
listValue.clear();
return result;
}
// Peel off the none modifier. If it's included, it should always be last in the list.
template <TypeModifier Modifier = TypeModifier::None, TypeModifier... Other>
[[nodiscard("unnecessary memory copy")]] static Type duplicate(const Type& value)
requires OnlyNoneModifiers<Modifier, Other...>
{
// Just copy the value.
return Type { value };
}
// Peel off nullable modifiers.
template <TypeModifier Modifier, TypeModifier... Other>
[[nodiscard("unnecessary memory copy")]] static
typename VariableTraits<Type, Modifier, Other...>::type
duplicate(const typename VariableTraits<Type, Modifier, Other...>::type& nullableValue)
requires NullableModifier<Modifier>
{
typename VariableTraits<Type, Modifier, Other...>::type result {};
if (nullableValue)
{
if constexpr (InputVariableUniquePtr<Type, Other...>)
{
// Special case duplicating the std::unique_ptr.
result = std::make_unique<Type>(Type { *nullableValue });
}
else
{
result = duplicate<Other...>(*nullableValue);
}
}
return result;
}
// Peel off list modifiers.
template <TypeModifier Modifier, TypeModifier... Other>
[[nodiscard("unnecessary memory copy")]] static
typename VariableTraits<Type, Modifier, Other...>::type
duplicate(const typename VariableTraits<Type, Modifier, Other...>::type& listValue)
requires ListModifier<Modifier>
{
if constexpr(std::is_same_v<Type,bool>){
typename VariableTraits<Type, Modifier, Other...>::type result;
result.reserve(listValue.size());
for (auto const v: listValue)
result.push_back(v);
return result;
}
else
{
typename VariableTraits<Type, Modifier, Other...>::type result(listValue.size());
std::ranges::transform(listValue, result.begin(), duplicate<Other...>);
return result;
}
}
};
// Convenient type aliases for testing, generated code won't actually use these. These are also
// the specializations which are implemented in the GraphQLClient library, other specializations
// for input types should be generated in schemagen.
using IntVariable = ModifiedVariable<int>;
using FloatVariable = ModifiedVariable<double>;
using StringVariable = ModifiedVariable<std::string>;
using BooleanVariable = ModifiedVariable<bool>;
using IdVariable = ModifiedVariable<response::IdType>;
using ScalarVariable = ModifiedVariable<response::Value>;
} // namespace modified_variable
// Parse a single response output value. This is the inverse of Variable for output types instead of
// input types.
template <typename Type>
struct Response
{
// Parse a single value of the response document.
[[nodiscard("unnecessary conversion")]] static Type parse(response::Value&& response);
};
#ifdef GRAPHQL_DLLEXPORTS
// Export all of the built-in converters
template <>
GRAPHQLCLIENT_EXPORT int Response<int>::parse(response::Value&& response);
template <>
GRAPHQLCLIENT_EXPORT double Response<double>::parse(response::Value&& response);
template <>
GRAPHQLCLIENT_EXPORT std::string Response<std::string>::parse(response::Value&& response);
template <>
GRAPHQLCLIENT_EXPORT bool Response<bool>::parse(response::Value&& response);
template <>
GRAPHQLCLIENT_EXPORT response::IdType Response<response::IdType>::parse(response::Value&& response);
template <>
GRAPHQLCLIENT_EXPORT response::Value Response<response::Value>::parse(response::Value&& response);
#endif // GRAPHQL_DLLEXPORTS
inline namespace modified_response {
// Parse response output values with chained type modifiers that add nullable or list wrappers.
// This is the inverse of ModifiedVariable for output types instead of input types.
template <typename Type>
struct ModifiedResponse
{
// Peel off modifiers until we get to the underlying type.
template <typename U, TypeModifier Modifier = TypeModifier::None, TypeModifier... Other>
struct ResponseTraits
{
using type = typename std::conditional_t<TypeModifier::Nullable == Modifier,
std::optional<typename ResponseTraits<U, Other...>::type>,
typename std::conditional_t<TypeModifier::List == Modifier,
std::vector<typename ResponseTraits<U, Other...>::type>, U>>;
};
template <typename U>
struct ResponseTraits<U, TypeModifier::None>
{
using type = U;
};
// Peel off the none modifier. If it's included, it should always be last in the list.
template <TypeModifier Modifier = TypeModifier::None, TypeModifier... Other>
[[nodiscard("unnecessary conversion")]] static Type parse(response::Value&& response)
requires OnlyNoneModifiers<Modifier, Other...>
{
return Response<Type>::parse(std::move(response));
}
// Peel off nullable modifiers.
template <TypeModifier Modifier, TypeModifier... Other>
[[nodiscard("unnecessary conversion")]] static std::optional<
typename ResponseTraits<Type, Other...>::type>
parse(response::Value&& response)
requires NullableModifier<Modifier>
{
if (response.type() == response::Type::Null)
{
return std::nullopt;
}
return std::make_optional<typename ResponseTraits<Type, Other...>::type>(
parse<Other...>(std::move(response)));
}
// Peel off list modifiers.
template <TypeModifier Modifier, TypeModifier... Other>
[[nodiscard("unnecessary conversion")]] static std::vector<
typename ResponseTraits<Type, Other...>::type>
parse(response::Value&& response)
requires ListModifier<Modifier>
{
std::vector<typename ResponseTraits<Type, Other...>::type> result;
if (response.type() == response::Type::List)
{
auto listValue = response.release<response::ListType>();
result.reserve(listValue.size());
std::ranges::transform(listValue,
std::back_inserter(result),
[](response::Value& value) {
return parse<Other...>(std::move(value));
});
}
return result;
}
};
// Convenient type aliases for testing, generated code won't actually use these. These are also
// the specializations which are implemented in the GraphQLClient library, other specializations
// for output types should be generated in schemagen.
using IntResponse = ModifiedResponse<int>;
using FloatResponse = ModifiedResponse<double>;
using StringResponse = ModifiedResponse<std::string>;
using BooleanResponse = ModifiedResponse<bool>;
using IdResponse = ModifiedResponse<response::IdType>;
using ScalarResponse = ModifiedResponse<response::Value>;
} // namespace modified_response
} // namespace graphql::client
#endif // GRAPHQLCLIENT_H