From e3281770335a374c371e0bfd88ff8454f1177594 Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Sat, 7 Sep 2019 11:27:02 -0700 Subject: [PATCH 01/10] Seed document placeholders --- doc/directives.md | 0 doc/fieldparams.md | 0 doc/json.md | 0 doc/parsing.md | 0 doc/resolvers.md | 0 doc/response.md | 0 doc/subscriptions.md | 0 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/directives.md create mode 100644 doc/fieldparams.md create mode 100644 doc/json.md create mode 100644 doc/parsing.md create mode 100644 doc/resolvers.md create mode 100644 doc/response.md create mode 100644 doc/subscriptions.md diff --git a/doc/directives.md b/doc/directives.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/fieldparams.md b/doc/fieldparams.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/json.md b/doc/json.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/parsing.md b/doc/parsing.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/resolvers.md b/doc/resolvers.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/response.md b/doc/response.md new file mode 100644 index 00000000..e69de29b diff --git a/doc/subscriptions.md b/doc/subscriptions.md new file mode 100644 index 00000000..e69de29b From 6eca5ff7308b55099926b8f91421ae9083cbd0c8 Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Sun, 8 Sep 2019 09:06:05 -0700 Subject: [PATCH 02/10] Write content for parsing.md --- doc/parsing.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/doc/parsing.md b/doc/parsing.md index e69de29b..5382a4e2 100644 --- a/doc/parsing.md +++ b/doc/parsing.md @@ -0,0 +1,55 @@ +# Parsing GraphQL Documents + +## PEGTL + +As mentioned in the [README](../README.md), `cppgraphqlgen` uses the +[Parsing Expression Grammar Template Library (PEGTL)](https://github.com/taocpp/PEGTL) +release 3.0.0, which is part of [The Art of C++](https://taocpp.github.io/) +library collection. I've added this as a sub-module, so you do not need to +install this separately. If you already have 3.0.0 installed where CMake can +find it, it will use that instead of the sub-module and avoid installing +another copy of PEGTL. _Note: PEGTL 3.0.0 is currently at pre-release._ + +It uses the [contrib/parse_tree.hpp](../PEGTL/include/tao/pegtl/contrib/parse_tree.hpp) +module to build an AST automatically while parsing the document. The AST and +the underlying grammar rules are tuned to the needs of `cppgraphqlgen`, but if +you have another use for a GraphQL parser you could probably make a few small +tweaks to include additional information in the rules or in the resulting AST. +You could also use the grammar without the AST module if you want to handle +the parsing callbacks another way. The grammar itself is defined in +[GraphQLGrammar.h](../include/graphqlservice/GraphQLGrammar.h), and the AST +selector callbacks are all defined in [GraphQLTree.cpp](../src/GraphQLTree.cpp). +The grammar handles both the schema definition syntax which is used in +`schemagen`, and the query/mutation/subscription operation syntax used in +`Request::resolve` and `Request::subscribe`. + +## Utilities + +The [GraphQLParse.h](../include/graphqlservice/GraphQLParse.h) header includes +several utility methods to help generate an AST from a `std::string_view` +(`parseString`), an input file (`parseFile`), or using a +[UDL](https://en.cppreference.com/w/cpp/language/user_literal) (`_graphql`) +for hardcoded documents. + +The UDL is used throughout the sample unit tests and in `schemagen` for the +hard-coded introspection schema. It will be useful for additional unit tests +against your own custom schema. + +At runtime, you will probably call `parseString` most often to handle dynamic +queries. If you have persisted queries saved to the file system or you are +using a snapshot/[Approval Testing](https://approvaltests.com/) strategy you +might also use `parseFile` to parse queries saved to text files. + +## Encoding + +The document must use a UTF-8 encoding. If you need to handle documents in +another encoding you will need to convert them to UTF-8 before parsing. + +If you need to convert the encoding at runtime, I would recommend using +`std::wstring_convert`, with the cavevat that it has been +[deprecated](https://en.cppreference.com/w/cpp/locale/wstring_convert) in +C++17. You could keep using it until it is replaced in the standard, you +could use a portable non-standard library like +[ICU](http://site.icu-project.org/design/cpp), or you could use +platform-specific conversion routines like +[WideCharToMultiByte](https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte) on Windows. \ No newline at end of file From c49ec3438fa7e713137a09ea3cb1efd5909c757a Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Sun, 8 Sep 2019 13:39:48 -0700 Subject: [PATCH 03/10] Write responses.md content --- doc/response.md | 0 doc/responses.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) delete mode 100644 doc/response.md create mode 100644 doc/responses.md diff --git a/doc/response.md b/doc/response.md deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/responses.md b/doc/responses.md new file mode 100644 index 00000000..ee842554 --- /dev/null +++ b/doc/responses.md @@ -0,0 +1,48 @@ +# Query Responses + +## Value Types + +As the comment in +[GraphQLResponse.h](../include/graphqlservice/GraphQLResponse.h) says, GraphQL +responses are not technically JSON-specific, although that is probably the most +common way of representing them. These are the primitive types that may be +represented in GraphQL, as of the +[June 2018 spec](https://facebook.github.io/graphql/June2018/#sec-Serialization-Format): + +```c++ +enum class Type : uint8_t +{ + Map, // JSON Object + List, // JSON Array + String, // JSON String + Null, // JSON null + Boolean, // JSON true or false + Int, // JSON Number + Float, // JSON Number + EnumValue, // JSON String + Scalar, // JSON any type +}; +``` + +## Common Accessors + +Anywhere that a GraphQL result, a scalar type, or a GraphQL value literal is +used, it's represented in `cppgraphqlgen` using an instance of +`graphql::response::Value`. These can be constructed with any of the types in +the `graphql::response::Type` enum, and depending on the type with which they +are initialized, different accessors will be enabled. + +Every type implements specializations for some subset of `get()` which does +not allocate any memory, `set(...)` which takes an r-value, and `release()` +which transfers ownership along with any extra allocations to the caller. +Which of these methods are supported and what C++ types they use are +determined by the `ValueTypeTraits` template and its +specializations. + +## Map and List + +`Map` and `List` types enable collection methods like `reserve(size_t)`, +`size()`, and `emplace_back(...)`. `Map` additionally implements `begin()` +and `end()` for range-based for loops and `find(const std::string&)` and +`operator[](const std::string&)` for key-based lookups. `List` has an +`operator[](size_t)` for index-based instead of key-based lookups. \ No newline at end of file From 64aee4bef1b3d2784c41006958b76c5295a44328 Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Sun, 29 Sep 2019 10:54:41 -0700 Subject: [PATCH 04/10] Write json.md content --- doc/json.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/doc/json.md b/doc/json.md index e69de29b..dd765fc8 100644 --- a/doc/json.md +++ b/doc/json.md @@ -0,0 +1,40 @@ +# Converting to/from JSON + +## `graphqljson` Library Target + +Converting between `graphql::response::Value` in [GraphQLResponse.h](../include/GraphQLResponse.h) +and JSON strings is done in an optional library target called `graphqljson`. + +## Default RapidJSON Implementation + +The included implementation uses [RapidJSON](https://github.com/Tencent/rapidjson) +release 1.1.0, but if you don't need JSON support, or you want to integrate +a different JSON library, you can set `GRAPHQL_USE_RAPIDJSON=OFF` in your +CMake configuration. + +## Using Custom JSON Libraries + +If you want to use a different JSON library, you can add implementations of +the functions in [JSONResponse.h](../include/JSONResponse.h): +```cpp +namespace graphql::response { + +std::string toJSON(Value&& response); + +Value parseJSON(const std::string& json); + +} /* namespace graphql::response */ +``` + +You will also need to update the [CMakeLists.txt](../src/CMakeLists.txt) file +in the [../src](../src) directory to add your own implementation. See the +comment in that file for more information: +```cmake +# RapidJSON is the only option for JSON serialization used in this project, but if you want +# to use another JSON library you can implement an alternate version of the functions in +# JSONResponse.cpp to serialize to and from GraphQLResponse and build graphqljson from that. +# You will also need to define how to build the graphqljson library target with your +# implementation, and you should set BUILD_GRAPHQLJSON so that the test dependencies know +# about your version of graphqljson. +option(GRAPHQL_USE_RAPIDJSON "Use RapidJSON for JSON serialization." ON) +``` \ No newline at end of file From 70dd480a9951fa40820275cb1bb0b77fdb963ba6 Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Sun, 29 Sep 2019 12:02:32 -0700 Subject: [PATCH 05/10] Write subscriptions content --- doc/subscriptions.md | 79 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/doc/subscriptions.md b/doc/subscriptions.md index e69de29b..251be8ea 100644 --- a/doc/subscriptions.md +++ b/doc/subscriptions.md @@ -0,0 +1,79 @@ +# Subscriptions + +Subscriptions in GraphQL are asynchronous and event driven. Typically you need +to have a callback installed, e.g. over a WebSocket connection, which receives +the response to a subscribed query anytime it is updated. Since this library +is protocol agnostic, it doesn't include the delivery mechanism. But it does +provide a way to register callbacks when adding a subscription, and you can +define trigger conditions when delivering an update to selectively dispatch +the subscriptions to those listeners. + +## Adding/Removing a Listener + +Subscriptions are created or removed by calling the `Request::subscribe` +and `Request::unsubscribe` methods in [GraphQLService.h](../include/GraphQLService.h): +```cpp +SubscriptionKey subscribe(SubscriptionParams&& params, SubscriptionCallback&& callback); +void unsubscribe(SubscriptionKey key); +``` +You need to fill in a `SubscriptionParams` struct with the [parsed](./parsing.md) +query and any other relevant operation parameters: +```cpp +// You can still sub-class RequestState and use that in the state parameter to Request::subscribe +// to add your own state to the service callbacks that you receive while executing the subscription +// query. +struct SubscriptionParams +{ + std::shared_ptr state; + peg::ast query; + std::string operationName; + response::Value variables; +}; +``` +The `SubscriptionCallback` signature is: +```cpp +// Subscription callbacks receive the response::Value representing the result of evaluating the +// SelectionSet against the payload. +using SubscriptionCallback = std::function)>; +``` + +## Delivering Subscription Updates + +There are currently three `Request::deliver` overrides you can choose from when +sending updates to any subscribed listeners. The first one is the simplest, +it will evaluate each subscribed query against the `subscriptionObject` +parameter (which should match the `Subscription` type in the `schema`). It will +unconditionally invoke every subscribed `SubscriptionCallback` callback with +the response to its query: +```cpp +void deliver(const SubscriptionName& name, const std::shared_ptr& subscriptionObject) const; +``` + +The second override adds argument filtering. It will look at the field +arguments in the subscription `query`, and if all of the required parameters +in the `arguments` parameter are present and are an exact match it will dispatch the callback to that subscription: +```cpp +void deliver(const SubscriptionName& name, const SubscriptionArguments& arguments, const std::shared_ptr& subscriptionObject) const; +``` + +The last override lets you customize the the way that the required arguments +are matched. Instead of an exact match or making all of the arguments required, +it will dispatch the callback if the `apply` function parameter returns true +for every required field in the subscription `query`. +```cpp +void deliver(const SubscriptionName& name, const SubscriptionFilterCallback& apply, const std::shared_ptr& subscriptionObject) const; +``` + +## Handling Multiple Operation Types + +Some service implementations (e.g. Apollo over HTTP) use a single pipe to +manage subscriptions and resolve queries or mutations. You can't necessarily +tell which operation type it is without parsing the query and searching for +a specific operation name, so it's hard to tell whether you should call +`resolve` or `subscribe` when the request is received that way. To help with +that, there's a public `Request::findOperationDefinition` method which returns +the operation type as a `std::string` along with a pointer to the AST node for +the selected operation in the parsed query: +```cpp +std::pair findOperationDefinition(const peg::ast_node& root, const std::string& operationName) const; +``` \ No newline at end of file From 2218c1ecd4b51644222bfe6567d2ac2efea2d313 Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Sun, 29 Sep 2019 13:25:45 -0700 Subject: [PATCH 06/10] Fix include file path in docs --- doc/subscriptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/subscriptions.md b/doc/subscriptions.md index 251be8ea..f67cb8b7 100644 --- a/doc/subscriptions.md +++ b/doc/subscriptions.md @@ -11,7 +11,7 @@ the subscriptions to those listeners. ## Adding/Removing a Listener Subscriptions are created or removed by calling the `Request::subscribe` -and `Request::unsubscribe` methods in [GraphQLService.h](../include/GraphQLService.h): +and `Request::unsubscribe` methods in [GraphQLService.h](../include/graphqlservice/GraphQLService.h): ```cpp SubscriptionKey subscribe(SubscriptionParams&& params, SubscriptionCallback&& callback); void unsubscribe(SubscriptionKey key); From feb16c3ea0bdf9e1d30ed820a1cc8e8eba3198a6 Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Sun, 29 Sep 2019 13:25:58 -0700 Subject: [PATCH 07/10] Write resolvers.md content --- doc/resolvers.md | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/doc/resolvers.md b/doc/resolvers.md index e69de29b..32e9742c 100644 --- a/doc/resolvers.md +++ b/doc/resolvers.md @@ -0,0 +1,90 @@ +# Field Resolvers + +GraphQL schemas define types with named fields, and each of those fields may +take arguments which alter the behavior of that field. You can think of +`fields` much like methods on an object instance in OOP (Object Oriented +Programming). Each field is implemented using a `resolver`, which may +recursively invoke additional `resolvers` for fields of the resulting objects, +e.g.: +```graphql +query { + foo(id: "bar") { + baz + } +} +``` + +This query would invoke the `resolver` for the `foo field` on the top-level +`query` object, passing it the string `"bar"` as the `id` argument. Then it +would invoke the `resolver` for the `baz` field on the result of the `foo +field resolver`. + +## Top-level Resolvers + +The `schema` type in GraphQL defines the types for top-level operation types. +By convention, these are often named after the operation type, although you +could give them different names: +```graphql +schema { + query: Query + mutation: Mutation + subscription: Subscription +} +``` + +Executing a query or mutation starts by calling `Request::resolve` from [GraphQLService.h](../include/graphqlservice/GraphQLService.h): +```cpp +std::future resolve(const std::shared_ptr& state, const peg::ast_node& root, const std::string& operationName, response::Value&& variables) const; +``` +By default, the `std::future` results are resolved on-demand but synchronously, +using `std::launch::deferred` with the `std::async` function. You can also use +an override of `Request::resolve` which lets you substitute the +`std::launch::async` option to begin executing the query on multiple threads +in parallel: +```cpp +std::future resolve(std::launch launch, const std::shared_ptr& state, const peg::ast_node& root, const std::string& operationName, response::Value&& variables) const; +``` + +### `graphql::service::Request` and `graphql::::Operations` + +Anywhere in the documentation where it mentions `graphql::service::Request` +methods, the concrete type will actually be `graphql::::Operations`. +This `class` is defined by `schemagen` and inherits from +`graphql::service::Request`. It links the top-level objects for the custom +schema to the `resolve` methods on its base class. See +`graphql::today::Operations` in [TodaySchema.h](../samples/separate/TodaySchema.h) +for an example. + +## Generated Service Schema + +The `schemagen` tool generates C++ types in the `graphql::::object` +namespace with `resolveField` methods for each `field` which parse the +arguments from the `query` and automatically dispatch the call to a `getField` +virtual method to retrieve the `field` result. On `object` types, it will also +recursively call the `resolvers` for each of the `fields` in the nested +`SelectionSet`. See for example the generated +`graphql::today::object::Appointment` object from the `today` sample in +[AppointmentObject.h](../samples/separate/AppointmentObject.h). +```cpp +std::future resolveId(service::ResolverParams&& params); +``` +In this example, the `resolveId` method invokes `getId`: +```cpp +virtual service::FieldResult getId(service::FieldParams&& params) const override; +``` + +There are also a couple of interesting quirks in this example: +1. The `Appointment object` implements and inherits from the `Node interface`, +which already declared `getId` as a pure-virtual method. That's what the +`override` keyword refers to. +2. This schema was generated with default stub implementations (without the +`schemagen --no-stubs` parameter) which speed up initial development with NYI +(Not Yet Implemented) stubs. With that parameter, there would be no +declaration of `Appointment::getId` since it would inherit a pure-virtual +declaration and the implementer would need to define an override on the +concrete implementation of `graphql::today::object::Appointment`. The NYI stub +will throw a `std::runtime_error`, which the `resolver` converts into an entry +in the `response errors` collection: +```cpp +throw std::runtime_error(R"ex(Appointment::getId is not implemented)ex"); +``` \ No newline at end of file From 8d1917be16574502ea84354c72e95a49f3416012 Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Sun, 29 Sep 2019 14:04:41 -0700 Subject: [PATCH 08/10] Write fieldparams.md content --- doc/fieldparams.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++ doc/resolvers.md | 10 ++++-- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/doc/fieldparams.md b/doc/fieldparams.md index e69de29b..1d0f262f 100644 --- a/doc/fieldparams.md +++ b/doc/fieldparams.md @@ -0,0 +1,81 @@ +# Common Field Parameters + +The `resolveField` methods generated by `schemagen` will unpack any arguments +matching the `schema` from the `query` and pass those to the `getField` method +defined by the implementer. However, the implementer might need to inspect +shared state or `directives` from the `query`, so the `resolveField` method +also packs that information into a `graphql::service::FieldParams` struct and +passes it to every `getField` method as the first parameter. + +## Details of Field Parameters + +The `graphql::service::FieldParams` struct is declared in [GraphQLService.h](../include/graphqlservice/GraphQLService.h): +```cpp +// Pass a common bundle of parameters to all of the generated Object::getField accessors in a SelectionSet +struct SelectionSetParams +{ + // The lifetime of each of these borrowed references is guaranteed until the future returned + // by the accessor is resolved or destroyed. They are owned by the OperationData shared pointer. + const std::shared_ptr& state; + const response::Value& operationDirectives; + const response::Value& fragmentDefinitionDirectives; + + // Fragment directives are shared for all fields in that fragment, but they aren't kept alive + // after the call to the last accessor in the fragment. If you need to keep them alive longer, + // you'll need to explicitly copy them into other instances of response::Value. + const response::Value& fragmentSpreadDirectives; + const response::Value& inlineFragmentDirectives; +}; + +// Pass a common bundle of parameters to all of the generated Object::getField accessors. +struct FieldParams : SelectionSetParams +{ + explicit FieldParams(const SelectionSetParams& selectionSetParams, response::Value&& directives); + + // Each field owns its own field-specific directives. Once the accessor returns it will be destroyed, + // but you can move it into another instance of response::Value to keep it alive longer. + response::Value fieldDirectives; +}; +``` + +### Request State + +The `SelectionSetParams::state` member is a reference to the +`std::shared_ptr` parameter passed to +`Request::resolve` (see [resolvers.md](./resolvers.md) for more info): +```cpp +// The RequestState is nullable, but if you have multiple threads processing requests and there's any +// per-request state that you want to maintain throughout the request (e.g. optimizing or batching +// backend requests), you can inherit from RequestState and pass it to Request::resolve to correlate the +// asynchronous/recursive callbacks and accumulate state in it. +struct RequestState : std::enable_shared_from_this +{ +}; +``` + +### Scoped Directives + +Each of the `directives` members contains the values of the `directives` and +any of their arguments which were in effect at that scope of the `query`. +Implementers may inspect those values in the call to `getField` and alter their +behavior based on those custom `directives`. + +As noted in the comments, the `fragmentSpreadDirectives` and +`inlineFragmentDirectives` are borrowed `const` references, shared accross +calls to multiple `getField` methods, but they will not be kept alive after +the relevant `SelectionSet` has been resolved. The `fieldDirectives` member is +passed by value and is not shared with other `getField` method calls, but it +will not be kept alive after that call returns. It's up to the implementer to +capture the values in these `directives` which they might need for asynchronous +evaulation after the call to the current `getField` method has returned. + +The implementer does not need to capture the values of `operationDirectives` +or `fragmentDefinitionDirectives` because those are kept alive until the +`operation` and all of its `std::future` results are resolved. Although they +passed by `const` reference, the reference should always be valid as long as +there's a pending result from the `getField` call. + +## Related Documents + +1. The `getField` methods are discussed in more detail in [resolvers.md](./resolvers.md). +2. Built-in and custom `directives` are discussed in [directives.md](./directives.md). \ No newline at end of file diff --git a/doc/resolvers.md b/doc/resolvers.md index 32e9742c..9f2b3724 100644 --- a/doc/resolvers.md +++ b/doc/resolvers.md @@ -73,7 +73,7 @@ In this example, the `resolveId` method invokes `getId`: virtual service::FieldResult getId(service::FieldParams&& params) const override; ``` -There are also a couple of interesting quirks in this example: +There are a couple of interesting quirks in this example: 1. The `Appointment object` implements and inherits from the `Node interface`, which already declared `getId` as a pure-virtual method. That's what the `override` keyword refers to. @@ -87,4 +87,10 @@ will throw a `std::runtime_error`, which the `resolver` converts into an entry in the `response errors` collection: ```cpp throw std::runtime_error(R"ex(Appointment::getId is not implemented)ex"); -``` \ No newline at end of file +``` + +Although the `id field` does not take any arguments according to the sample +[schema](../samples/today/schema.today.graphql), this example also shows how +every `getField` method takes a `graphql::service::FieldParams` struct as +its first parameter. There are more details on this in the [fieldparams.md](./fieldparams.md) +document. \ No newline at end of file From 0c07c036d7ca499633b34643f18ed1d38cd636b1 Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Sun, 29 Sep 2019 18:14:56 -0700 Subject: [PATCH 09/10] Write directives.md content --- doc/directives.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/doc/directives.md b/doc/directives.md index e69de29b..cc13f849 100644 --- a/doc/directives.md +++ b/doc/directives.md @@ -0,0 +1,19 @@ +# Directives + +Directives in GraphQL are extensible annotations which alter the runtime +evaluation of a query or which add information to the `schema` definition. +They always begin with an `@`. There are three built-in directives which this +library automatically handles: + +1. `@include(if: Boolean!)`: Only resolve this field and include it in the +results if the `if` argument evaluates to `true`. +2. `@skip(if: Boolean!)`: Only resolve this field and include it in the +results if the `if` argument evaluates to `false`. +3. `@deprecated(reason: String)`: Mark the field or enum value as deprecated +through introspection with the specified `reason` string. + +The `schema` can also define custom `directives` which are valid on different +elements of the `query`. The library does not handle them automatically, but it +will pass them to the `getField` implementations through the +`graphql::service::FieldParams` struct (see [fieldparams.md](fieldparams.md) +for more information). \ No newline at end of file From 49e5b0fd7cda2855a17da1a97f9142f57e80b098 Mon Sep 17 00:00:00 2001 From: Bill Avery Date: Sun, 29 Sep 2019 18:31:19 -0700 Subject: [PATCH 10/10] Add links to new documentation to README --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 46c2d05c..977e4216 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,20 @@ the `graphql::service` namespace. Take a look at [UnifiedToday.h](samples/today/ [UnifiedToday.cpp](samples/today/UnifiedToday.cpp) to see a sample implementation of a custom schema defined in [schema.today.graphql](samples/today/schema.today.graphql) for testing purposes. +### Additional Documentation + +There are some more targeted documents in the [doc](./doc) directory: + +* [Parsing GraphQL](./doc/parsing.md) +* [Query Responses](./doc/responses.md) +* [JSON Representation](./doc/json.md) +* [Field Resolvers](./doc/resolvers.md) +* [Field Parameters](./doc/fieldparams.md) +* [Directives](./doc/directives.md) +* [Subscriptions](./doc/subscriptions.md) + +### Samples + All of the generated files are in the [samples](samples/) directory. There are two different versions of the generated code, one which creates a single pair of files (`samples/unified/`), and one which uses the `--separate-files` flag with `schemagen` to generate individual header and source files (`samples/separate/`)