Skip to content

Commit 9cbad08

Browse files
authored
Merge pull request microsoft#165 from wravery/clientgen
Generate clients to go with cppgraphqlgen services
2 parents 44081d4 + cef14e1 commit 9cbad08

File tree

147 files changed

+8987
-2942
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

147 files changed

+8987
-2942
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ else()
8484
set(GRAPHQL_UPDATE_SAMPLES OFF CACHE BOOL "Disable regenerating samples." FORCE)
8585
endif()
8686

87+
option(GRAPHQL_BUILD_CLIENTGEN "Build the clientgen tool." ON)
88+
8789
option(GRAPHQL_UPDATE_VERSION "Regenerate graphqlservice/internal/Version.h and all of the version info rc files for Windows." ON)
8890

8991
add_subdirectory(cmake)

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ service, you can use the same GraphQL client code to access your native data sou
2323
service online. You might even be able to share some more of that code between a progressive web
2424
app and your native/hybrid app.
2525

26+
If what you're after is a way to consume a GraphQL service from C++, as of
27+
[v3.6.0](https://github.com/microsoft/cppgraphqlgen/releases/tag/v3.6.0) this project also includes
28+
a `graphqlclient` library and a `clientgen` utility to generate types matching a GraphQL request
29+
document, its variables, and all of the serialization code you need to talk to a `graphqlservice`
30+
implementation. If you want to consume another service, you will need access to the schema definition
31+
(rather than the Introspection query results), and you will need be able to send requests along with
32+
any variables to the service and parse its responses into a `graphql::response::Value` (e.g. with the
33+
`graphqljson` library) in your code.
34+
2635
# Getting Started
2736

2837
## Related projects
@@ -116,6 +125,41 @@ adds a runtime dependency on a Boost DLL. The `schemagen` tool won't run without
116125
the install of Boost, vcpkg also takes care of installing the dependencies next to `schemagen.exe` when building the
117126
Windows and UWP shared library targets (the platform triplets which don't end in `-static`).
118127

128+
### clientgen
129+
130+
The `clientgen` utility is based on `schemagen` and shares the same external dependencies. The command line arguments
131+
are almost the same, except it takes an extra file for the request document and there is no equivalent to `--no-stubs` or
132+
`--separate-files`:
133+
```
134+
Usage: clientgen [options] <schema file> <request file> <output filename prefix> <output namespace>
135+
Command line options:
136+
--version Print the version number
137+
-? [ --help ] Print the command line options
138+
-v [ --verbose ] Verbose output including generated header names as
139+
well as sources
140+
-s [ --schema ] arg Schema definition file path
141+
-r [ --request ] arg Request document file path
142+
-o [ --operation ] arg Operation name if the request document contains more
143+
than one
144+
-p [ --prefix ] arg Prefix to use for the generated C++ filenames
145+
-n [ --namespace ] arg C++ sub-namespace for the generated types
146+
--source-dir arg Target path for the <prefix>Client.cpp source file
147+
--header-dir arg Target path for the <prefix>Client.h header file
148+
--no-introspection Do not expect support for Introspection
149+
```
150+
151+
This utility should output one header and one source file for each request document. A request document may contain
152+
more than one operation, in which case you must specify the `--operation` (or `-o`) parameter to indicate which one
153+
should be used to generate the files. If you want to generate client code for more than one operation in the same
154+
document, you will need to run `clientgen` more than once and specify another operation name each time.
155+
156+
The generated code depends on the `graphqlclient` library for serialization of built-in types. If you link the generated
157+
code, you'll also need to link `graphqlclient`, `graphqlpeg` for the pre-parsed, pre-validated request AST, and
158+
`graphqlresponse` for the `graphql::response::Value` implementation.
159+
160+
Sample output for `clientgen` is in the [samples/client](samples/client) directory, and each sample is consumed by
161+
a unit test in [test/ClientTests.cpp](test/ClientTests.cpp).
162+
119163
### tests (`GRAPHQL_BUILD_TESTS=ON`)
120164

121165
- Unit testing: [Google Test](https://github.com/google/googletest) for the unit testing framework. If you don't want to

cmake/ClientGen.rc.in

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#include <winver.h>
5+
6+
#define CLIENTGEN_RC_VERSION @CLIENTGEN_RC_VERSION@
7+
#define CLIENTGEN_RC_VERSION_STR "@CLIENTGEN_RC_VERSION_STR@"
8+
9+
#ifndef DEBUG
10+
#define VER_DEBUG 0
11+
#else
12+
#define VER_DEBUG VS_FF_DEBUG
13+
#endif
14+
15+
VS_VERSION_INFO VERSIONINFO
16+
FILEVERSION CLIENTGEN_RC_VERSION
17+
PRODUCTVERSION CLIENTGEN_RC_VERSION
18+
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
19+
FILEFLAGS VER_DEBUG
20+
FILEOS VOS__WINDOWS32
21+
FILETYPE VFT_APP
22+
FILESUBTYPE VFT2_UNKNOWN
23+
BEGIN
24+
BLOCK "StringFileInfo"
25+
BEGIN
26+
BLOCK "040904B0"
27+
BEGIN
28+
VALUE "CompanyName", "Microsoft Corporation"
29+
VALUE "FileDescription", "Code generator for https://github.com/microsoft/cppgraphqlgen"
30+
VALUE "FileVersion", CLIENTGEN_RC_VERSION_STR
31+
VALUE "InternalName", "clientgen"
32+
VALUE "LegalCopyright", "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License."
33+
VALUE "OriginalFilename", "clientgen.exe"
34+
VALUE "ProductName", "CppGraphQLGen"
35+
VALUE "ProductVersion", CLIENTGEN_RC_VERSION_STR
36+
END
37+
END
38+
39+
BLOCK "VarFileInfo"
40+
BEGIN
41+
VALUE "Translation", 0x409, 1200
42+
END
43+
END

cmake/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.5.0
1+
3.6.0

doc/json.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ JSONRESPONSE_EXPORT std::string toJSON(Value&& response);
2323

2424
JSONRESPONSE_EXPORT Value parseJSON(const std::string& json);
2525

26-
} /* namespace graphql::response */
26+
} // namespace graphql::response
2727
```
2828
2929
You will also need to update the [CMakeLists.txt](../src/CMakeLists.txt) file

doc/parsing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ you have another use for a GraphQL parser you could probably make a few small
1717
tweaks to include additional information in the rules or in the resulting AST.
1818
You could also use the grammar without the AST module if you want to handle
1919
the parsing callbacks another way. The grammar itself is defined in
20-
[GraphQLGrammar.h](../include/graphqlservice/GraphQLGrammar.h), and the AST
21-
selector callbacks are all defined in [GraphQLTree.cpp](../src/GraphQLTree.cpp).
20+
[Grammar.h](../include/graphqlservice/internal/Grammar.h), and the AST
21+
selector callbacks are all defined in [SyntaxTree.cpp](../src/SyntaxTree.cpp).
2222
The grammar handles both the schema definition syntax which is used in
2323
`schemagen`, and the query/mutation/subscription operation syntax used in
2424
`Request::resolve` and `Request::subscribe`.

include/ClientGenerator.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#pragma once
5+
6+
#ifndef CLIENTGENERATOR_H
7+
#define CLIENTGENERATOR_H
8+
9+
#include "RequestLoader.h"
10+
#include "SchemaLoader.h"
11+
12+
namespace graphql::generator::client {
13+
14+
struct GeneratorPaths
15+
{
16+
const std::string headerPath;
17+
const std::string sourcePath;
18+
};
19+
20+
struct GeneratorOptions
21+
{
22+
const std::optional<GeneratorPaths> paths;
23+
const bool verbose = false;
24+
};
25+
26+
class Generator
27+
{
28+
public:
29+
// Initialize the generator with the introspection client or a custom GraphQL client.
30+
explicit Generator(
31+
SchemaOptions&& schemaOptions, RequestOptions&& requestOptions, GeneratorOptions&& options);
32+
33+
// Run the generator and return a list of filenames that were output.
34+
std::vector<std::string> Build() const noexcept;
35+
36+
private:
37+
std::string getHeaderDir() const noexcept;
38+
std::string getSourceDir() const noexcept;
39+
std::string getHeaderPath() const noexcept;
40+
std::string getSourcePath() const noexcept;
41+
const std::string& getClientNamespace() const noexcept;
42+
const std::string& getRequestNamespace() const noexcept;
43+
const std::string& getFullNamespace() const noexcept;
44+
std::string getResponseFieldCppType(
45+
const ResponseField& responseField, std::string_view currentScope = {}) const noexcept;
46+
47+
bool outputHeader() const noexcept;
48+
void outputRequestComment(std::ostream& headerFile) const noexcept;
49+
void outputGetRequestDeclaration(std::ostream& headerFile) const noexcept;
50+
bool outputResponseFieldType(std::ostream& headerFile, const ResponseField& responseField,
51+
size_t indent = 0) const noexcept;
52+
53+
bool outputSource() const noexcept;
54+
void outputGetRequestImplementation(std::ostream& sourceFile) const noexcept;
55+
bool outputModifiedResponseImplementation(std::ostream& sourceFile,
56+
const std::string& outerScope, const ResponseField& responseField) const noexcept;
57+
static std::string getTypeModifierList(const TypeModifierStack& modifiers) noexcept;
58+
59+
const SchemaLoader _schemaLoader;
60+
const RequestLoader _requestLoader;
61+
const GeneratorOptions _options;
62+
const std::string _headerDir;
63+
const std::string _sourceDir;
64+
const std::string _headerPath;
65+
const std::string _sourcePath;
66+
};
67+
68+
} // namespace graphql::generator::client
69+
70+
#endif // CLIENTGENERATOR_H

include/GeneratorLoader.h

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#pragma once
5+
6+
#ifndef GENERATORLOADER_H
7+
#define GENERATORLOADER_H
8+
9+
#include "graphqlservice/GraphQLService.h"
10+
11+
namespace graphql::generator {
12+
13+
// Types that we understand and use to generate the skeleton of a service.
14+
enum class SchemaType
15+
{
16+
Scalar,
17+
Enum,
18+
Input,
19+
Union,
20+
Interface,
21+
Object,
22+
Operation,
23+
};
24+
25+
using SchemaTypeMap = std::map<std::string_view, SchemaType>;
26+
27+
// Any type can also have a list and/or non-nullable wrapper, and those can be nested.
28+
// Since it's easier to express nullability than non-nullability in C++, we'll invert
29+
// the presence of NonNull modifiers.
30+
using TypeModifierStack = std::vector<service::TypeModifier>;
31+
32+
// Recursively visit a Type node until we reach a NamedType and we've
33+
// taken stock of all of the modifier wrappers.
34+
class TypeVisitor
35+
{
36+
public:
37+
std::pair<std::string_view, TypeModifierStack> getType();
38+
39+
void visit(const peg::ast_node& typeName);
40+
41+
private:
42+
void visitNamedType(const peg::ast_node& namedType);
43+
void visitListType(const peg::ast_node& listType);
44+
void visitNonNullType(const peg::ast_node& nonNullType);
45+
46+
std::string_view _type;
47+
TypeModifierStack _modifiers;
48+
bool _nonNull = false;
49+
};
50+
51+
// Recursively visit a Value node representing the default value on an input field
52+
// and build a JSON representation of the hardcoded value.
53+
class DefaultValueVisitor
54+
{
55+
public:
56+
response::Value getValue();
57+
58+
void visit(const peg::ast_node& value);
59+
60+
private:
61+
void visitIntValue(const peg::ast_node& intValue);
62+
void visitFloatValue(const peg::ast_node& floatValue);
63+
void visitStringValue(const peg::ast_node& stringValue);
64+
void visitBooleanValue(const peg::ast_node& booleanValue);
65+
void visitNullValue(const peg::ast_node& nullValue);
66+
void visitEnumValue(const peg::ast_node& enumValue);
67+
void visitListValue(const peg::ast_node& listValue);
68+
void visitObjectValue(const peg::ast_node& objectValue);
69+
70+
response::Value _value;
71+
};
72+
73+
} // namespace graphql::generator
74+
75+
#endif // GENERATORLOADER_H

include/GeneratorUtil.h

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#pragma once
5+
6+
#ifndef GENERATORUTIL_H
7+
#define GENERATORUTIL_H
8+
9+
#include <iostream>
10+
#include <string>
11+
#include <string_view>
12+
13+
namespace graphql::generator {
14+
15+
// RAII object to help with emitting matching include guard begin and end statements
16+
class IncludeGuardScope
17+
{
18+
public:
19+
explicit IncludeGuardScope(std::ostream& outputFile, std::string_view headerFileName) noexcept;
20+
~IncludeGuardScope() noexcept;
21+
22+
private:
23+
std::ostream& _outputFile;
24+
std::string _includeGuardName;
25+
};
26+
27+
// RAII object to help with emitting matching namespace begin and end statements
28+
class NamespaceScope
29+
{
30+
public:
31+
explicit NamespaceScope(
32+
std::ostream& outputFile, std::string_view cppNamespace, bool deferred = false) noexcept;
33+
NamespaceScope(NamespaceScope&& other) noexcept;
34+
~NamespaceScope() noexcept;
35+
36+
bool enter() noexcept;
37+
bool exit() noexcept;
38+
39+
private:
40+
bool _inside = false;
41+
std::ostream& _outputFile;
42+
std::string_view _cppNamespace;
43+
};
44+
45+
// Keep track of whether we want to add a blank separator line once some additional content is about
46+
// to be output.
47+
class PendingBlankLine
48+
{
49+
public:
50+
explicit PendingBlankLine(std::ostream& outputFile) noexcept;
51+
52+
void add() noexcept;
53+
bool reset() noexcept;
54+
55+
private:
56+
bool _pending = true;
57+
std::ostream& _outputFile;
58+
};
59+
60+
} // namespace graphql::generator
61+
62+
#endif // GENERATORUTIL_H

0 commit comments

Comments
 (0)