From ab0ec7f64cd64ee1506a109fd66af6f6b7290bba Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 30 Nov 2020 18:51:50 -0300 Subject: [PATCH 01/42] fix: INPUT_OBJECT is not a scalar type --- src/Validation.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Validation.cpp b/src/Validation.cpp index b25a2dbe..7dcda0d2 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -1112,6 +1112,7 @@ constexpr bool ValidateExecutableVisitor::isScalarType(introspection::TypeKind k case introspection::TypeKind::OBJECT: case introspection::TypeKind::INTERFACE: case introspection::TypeKind::UNION: + case introspection::TypeKind::INPUT_OBJECT: return false; default: From f074ae6af2480bf438673b012e98bef4b395e804 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 30 Nov 2020 14:22:00 -0300 Subject: [PATCH 02/42] ValidateExecutableVisitor use a single, shared, introspection query Doing the introspection query all the time is hurting performance, this does not change, so a single query can be done with all the fields, build a validation tree and then query it on all validations. This is the first part, moving the top-level query to be read-only. The next commits will eliminate other `executeQuery()`, then a shared context will be created and hosted by the `Response` class, which can be discovered using introspection or fed using JSON (schema.json). --- include/Validation.h | 2 +- src/Validation.cpp | 315 ++++++++++++++++++------------------------- 2 files changed, 135 insertions(+), 182 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index 44a9158e..e06e2947 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -172,7 +172,7 @@ class ValidateExecutableVisitor private: response::Value executeQuery(std::string_view query) const; - static ValidateTypeFieldArguments getArguments(response::ListType&& argumentsMember); + static ValidateTypeFieldArguments getArguments(const response::ListType& argumentsMember); using FieldTypes = std::map; using TypeFields = std::map; diff --git a/src/Validation.cpp b/src/Validation.cpp index 7dcda0d2..9688bb88 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -11,6 +11,75 @@ namespace graphql::service { +constexpr std::string_view introspectionQuery = R"gql( +query IntrospectionQuery { + __schema { + queryType { name } + mutationType { name } + subscriptionType { name } + types { ...FullType } + directives { + name + locations + args { ...InputValue } + } + } +} + +fragment FullType on __Type { + kind + name + fields(includeDeprecated: true) { + name + args { ...InputValue } + type { ...TypeRef } + } + inputFields { ...InputValue } + interfaces { ...TypeRef } + enumValues(includeDeprecated: true) { name } + possibleTypes { ...TypeRef } +} + +fragment InputValue on __InputValue { + name + type { ...TypeRef } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } +} +)gql"; + bool ValidateArgumentVariable::operator==(const ValidateArgumentVariable& other) const { return name == other.name; @@ -354,120 +423,59 @@ ValidateType ValidateVariableTypeVisitor::getType() ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) : _service(service) { - auto data = executeQuery(R"gql(query { - __schema { - queryType { - name - } - mutationType { - name - } - subscriptionType { - name - } - types { - name - kind - possibleTypes { - name - } - enumValues(includeDeprecated: true) { - name - } - } - directives { - name - locations - args { - name - defaultValue - type { - ...nestedType - } - } - } - } - } - - fragment nestedType on __Type { - kind - name - ofType { - ...nestedType - } - })gql"); - auto members = data.release(); - auto itrData = std::find_if(members.begin(), - members.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(__schema)gql"; - }); - - if (itrData != members.end() && itrData->second.type() == response::Type::Map) - { - members = itrData->second.release(); - - for (auto& member : members) + // TODO: we should execute this query only once per schema, + // maybe it can be done and cached inside the Request itself to allow + // this. Alternatively it could be provided at compile-time such as schema.json + // that is parsed, this would allow us to drop introspection from the Request + // and still have it to work + auto data = executeQuery(introspectionQuery); + const auto& itrSchema = data.find(R"gql(__schema)gql"); + if (itrSchema != data.end() && itrSchema->second.type() == response::Type::Map) + { + for (auto itr = itrSchema->second.begin(); itr < itrSchema->second.end(); itr++) { + const auto& member = *itr; if (member.second.type() == response::Type::Map) { - auto typeMembers = member.second.release(); - auto itrType = std::find_if(typeMembers.begin(), - typeMembers.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(name)gql"; - }); - - if (itrType != typeMembers.end() + const auto& itrType = member.second.find(R"gql(name)gql"); + if (itrType != member.second.end() && itrType->second.type() == response::Type::String) { if (member.first == R"gql(queryType)gql") { - _operationTypes[strQuery] = itrType->second.release(); + _operationTypes[strQuery] = itrType->second.get(); } else if (member.first == R"gql(mutationType)gql") { - _operationTypes[strMutation] = - itrType->second.release(); + _operationTypes[strMutation] = itrType->second.get(); } else if (member.first == R"gql(subscriptionType)gql") { _operationTypes[strSubscription] = - itrType->second.release(); + itrType->second.get(); } } } else if (member.second.type() == response::Type::List && member.first == R"gql(types)gql") { - auto entries = member.second.release(); - - for (auto& entry : entries) + const auto& entries = member.second.get(); + for (const auto& entry : entries) { if (entry.type() != response::Type::Map) { continue; } - auto typeMembers = entry.release(); - auto itrName = std::find_if(typeMembers.begin(), - typeMembers.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(name)gql"; - }); - auto itrKind = std::find_if(typeMembers.begin(), - typeMembers.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(kind)gql"; - }); + const auto& itrName = entry.find(R"gql(name)gql"); + const auto& itrKind = entry.find(R"gql(kind)gql"); - if (itrName != typeMembers.end() - && itrName->second.type() == response::Type::String - && itrKind != typeMembers.end() + if (itrName != entry.end() && itrName->second.type() == response::Type::String + && itrKind != entry.end() && itrKind->second.type() == response::Type::EnumValue) { - auto name = itrName->second.release(); - auto kind = + const auto& name = itrName->second.get(); + const auto& kind = ModifiedArgument::convert(itrKind->second); if (!isScalarType(kind)) @@ -478,44 +486,29 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) } else { - auto itrPossibleTypes = std::find_if(typeMembers.begin(), - typeMembers.end(), - [](const std::pair& - entry) noexcept { - return entry.first == R"gql(possibleTypes)gql"; - }); - - if (itrPossibleTypes != typeMembers.end() + const auto& itrPossibleTypes = entry.find(R"gql(possibleTypes)gql"); + if (itrPossibleTypes != entry.end() && itrPossibleTypes->second.type() == response::Type::List) { std::set matchingTypes; - auto matchingTypeEntries = - itrPossibleTypes->second.release(); + const auto& matchingTypeEntries = + itrPossibleTypes->second.get(); - for (auto& matchingTypeEntry : matchingTypeEntries) + for (const auto& matchingTypeEntry : matchingTypeEntries) { if (matchingTypeEntry.type() != response::Type::Map) { continue; } - auto matchingTypeMembers = - matchingTypeEntry.release(); - auto itrMatchingTypeName = - std::find_if(matchingTypeMembers.begin(), - matchingTypeMembers.end(), - [](const std::pair& - entry) noexcept { - return entry.first == R"gql(name)gql"; - }); - - if (itrMatchingTypeName != matchingTypeMembers.end() + const auto& itrMatchingTypeName = + matchingTypeEntry.find(R"gql(name)gql"); + if (itrMatchingTypeName != matchingTypeEntry.end() && itrMatchingTypeName->second.type() == response::Type::String) { - matchingTypes.insert( - itrMatchingTypeName->second - .release()); + matchingTypes.insert(itrMatchingTypeName->second + .get()); } } @@ -528,41 +521,29 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) } else if (kind == introspection::TypeKind::ENUM) { - auto itrEnumValues = std::find_if(typeMembers.begin(), - typeMembers.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(enumValues)gql"; - }); - - if (itrEnumValues != typeMembers.end() + const auto& itrEnumValues = entry.find(R"gql(enumValues)gql"); + if (itrEnumValues != entry.end() && itrEnumValues->second.type() == response::Type::List) { std::set enumValues; - auto enumValuesEntries = - itrEnumValues->second.release(); + const auto& enumValuesEntries = + itrEnumValues->second.get(); - for (auto& enumValuesEntry : enumValuesEntries) + for (const auto& enumValuesEntry : enumValuesEntries) { if (enumValuesEntry.type() != response::Type::Map) { continue; } - auto enumValuesMembers = - enumValuesEntry.release(); - auto itrEnumValuesName = std::find_if(enumValuesMembers.begin(), - enumValuesMembers.end(), - [](const std::pair& - entry) noexcept { - return entry.first == R"gql(name)gql"; - }); - - if (itrEnumValuesName != enumValuesMembers.end() + const auto& itrEnumValuesName = + enumValuesEntry.find(R"gql(name)gql"); + if (itrEnumValuesName != enumValuesEntry.end() && itrEnumValuesName->second.type() == response::Type::String) { - enumValues.insert(itrEnumValuesName->second - .release()); + enumValues.insert( + itrEnumValuesName->second.get()); } } @@ -584,34 +565,24 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) else if (member.second.type() == response::Type::List && member.first == R"gql(directives)gql") { - auto entries = member.second.release(); + const auto& entries = member.second.get(); - for (auto& entry : entries) + for (const auto& entry : entries) { if (entry.type() != response::Type::Map) { continue; } - auto directiveMembers = entry.release(); - auto itrName = std::find_if(directiveMembers.begin(), - directiveMembers.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(name)gql"; - }); - auto itrLocations = std::find_if(directiveMembers.begin(), - directiveMembers.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(locations)gql"; - }); + const auto& itrName = entry.find(R"gql(name)gql"); + const auto& itrLocations = entry.find(R"gql(locations)gql"); - if (itrName != directiveMembers.end() - && itrName->second.type() == response::Type::String - && itrLocations != directiveMembers.end() + if (itrName != entry.end() && itrName->second.type() == response::Type::String + && itrLocations != entry.end() && itrLocations->second.type() == response::Type::List) { ValidateDirective directive; - auto locations = itrLocations->second.release(); + const auto& locations = itrLocations->second.get(); for (const auto& location : locations) { @@ -625,20 +596,15 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) location)); } - auto itrArgs = std::find_if(directiveMembers.begin(), - directiveMembers.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(args)gql"; - }); - - if (itrArgs != directiveMembers.end() + const auto& itrArgs = entry.find(R"gql(args)gql"); + if (itrArgs != entry.end() && itrArgs->second.type() == response::Type::List) { directive.arguments = - getArguments(itrArgs->second.release()); + getArguments(itrArgs->second.get()); } - _directives[itrName->second.release()] = + _directives[itrName->second.get()] = std::move(directive); } } @@ -1046,46 +1012,33 @@ void ValidateExecutableVisitor::visitSelection(const peg::ast_node& selection) } } -ValidateTypeFieldArguments ValidateExecutableVisitor::getArguments(response::ListType&& args) +ValidateTypeFieldArguments ValidateExecutableVisitor::getArguments(const response::ListType& args) { ValidateTypeFieldArguments result; - for (auto& arg : args) + for (const auto& arg : args) { if (arg.type() != response::Type::Map) { continue; } - auto members = arg.release(); - auto itrName = std::find_if(members.begin(), - members.end(), - [](const std::pair& argEntry) noexcept { - return argEntry.first == R"gql(name)gql"; - }); - auto itrType = std::find_if(members.begin(), - members.end(), - [](const std::pair& argEntry) noexcept { - return argEntry.first == R"gql(type)gql"; - }); - auto itrDefaultValue = std::find_if(members.begin(), - members.end(), - [](const std::pair& argEntry) noexcept { - return argEntry.first == R"gql(defaultValue)gql"; - }); + const auto& itrName = arg.find(R"gql(name)gql"); + const auto& itrType = arg.find(R"gql(type)gql"); + const auto& itrDefaultValue = arg.find(R"gql(defaultValue)gql"); - if (itrName != members.end() && itrName->second.type() == response::Type::String - && itrType != members.end() && itrType->second.type() == response::Type::Map) + if (itrName != arg.end() && itrName->second.type() == response::Type::String + && itrType != arg.end() && itrType->second.type() == response::Type::Map) { ValidateArgument argument; - argument.defaultValue = (itrDefaultValue != members.end() + argument.defaultValue = (itrDefaultValue != arg.end() && itrDefaultValue->second.type() == response::Type::String); argument.nonNullDefaultValue = argument.defaultValue && itrDefaultValue->second.get() != R"gql(null)gql"; - argument.type = std::move(itrType->second); + argument.type = ValidateType(itrType->second); - result[itrName->second.release()] = std::move(argument); + result[itrName->second.get()] = std::move(argument); } } From eb9308324f2af165aba2761aaa715326b2d8bed9 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 30 Nov 2020 14:45:57 -0300 Subject: [PATCH 03/42] ValidateExecutableVisitor::getScopedTypeFields() use read only data This is an incremental commit, just make use of the read-only data instead of `release` primitives, allows sharing the query results. --- src/Validation.cpp | 57 ++++++++++++---------------------------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/src/Validation.cpp b/src/Validation.cpp index 9688bb88..969954e8 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -1671,14 +1671,8 @@ ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor: })gql"; auto data = executeQuery(oss.str()); - auto members = data.release(); - auto itrResponse = std::find_if(members.begin(), - members.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(__type)gql"; - }); - - if (itrResponse != members.end() && itrResponse->second.type() == response::Type::Map) + const auto& itrResponse = data.find(R"gql(__type)gql"); + if (itrResponse != data.end() && itrResponse->second.type() == response::Type::Map) { std::map fields; response::Value scalarKind(response::Type::EnumValue); @@ -1687,58 +1681,37 @@ ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor: scalarKind.set(R"gql(SCALAR)gql"); nonNullKind.set(R"gql(NON_NULL)gql"); - members = itrResponse->second.release(); - itrResponse = std::find_if(members.begin(), - members.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(fields)gql"; - }); - - if (itrResponse != members.end() && itrResponse->second.type() == response::Type::List) + const auto& itrFields = itrResponse->second.find(R"gql(fields)gql"); + if (itrFields != itrResponse->second.end() && itrFields->second.type() == response::Type::List) { - auto entries = itrResponse->second.release(); + const auto& entries = itrFields->second.get(); - for (auto& entry : entries) + for (const auto& entry : entries) { if (entry.type() != response::Type::Map) { continue; } - members = entry.release(); + const auto& itrFieldName = entry.find(R"gql(name)gql"); + const auto& itrFieldType = entry.find(R"gql(type)gql"); - auto itrFieldName = std::find_if(members.begin(), - members.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(name)gql"; - }); - auto itrFieldType = std::find_if(members.begin(), - members.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(type)gql"; - }); - - if (itrFieldName != members.end() + if (itrFieldName != entry.end() && itrFieldName->second.type() == response::Type::String - && itrFieldType != members.end() + && itrFieldType != entry.end() && itrFieldType->second.type() == response::Type::Map) { - auto fieldName = itrFieldName->second.release(); + const auto& fieldName = itrFieldName->second.get(); ValidateTypeField subField; - subField.returnType = std::move(itrFieldType->second); - - auto itrArgs = std::find_if(members.begin(), - members.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(args)gql"; - }); + subField.returnType = ValidateType(itrFieldType->second); - if (itrArgs != members.end() + const auto& itrArgs = entry.find(R"gql(args)gql"); + if (itrArgs != entry.end() && itrArgs->second.type() == response::Type::List) { subField.arguments = - getArguments(itrArgs->second.release()); + getArguments(itrArgs->second.get()); } fields[std::move(fieldName)] = std::move(subField); From d97f0147220329b4ffbca7bc9148d351fc36b83d Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 30 Nov 2020 14:54:41 -0300 Subject: [PATCH 04/42] ValidateExecutableVisitor::getInputTypeFields() use read only data This is an incremental commit, just make use of the read-only data instead of `release` primitives, allows sharing the query results. --- src/Validation.cpp | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/Validation.cpp b/src/Validation.cpp index 969954e8..798c31a6 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -1809,29 +1809,18 @@ ValidateExecutableVisitor::InputTypeFields::const_iterator ValidateExecutableVis })gql"; auto data = executeQuery(oss.str()); - auto members = data.release(); - auto itrResponse = std::find_if(members.begin(), - members.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(__type)gql"; - }); + const auto& itrResponse = data.find(R"gql(__type)gql"); - if (itrResponse != members.end() && itrResponse->second.type() == response::Type::Map) + if (itrResponse != data.end() && itrResponse->second.type() == response::Type::Map) { std::map fields; - members = itrResponse->second.release(); - itrResponse = std::find_if(members.begin(), - members.end(), - [](const std::pair& entry) noexcept { - return entry.first == R"gql(inputFields)gql"; - }); - - if (itrResponse != members.end() && itrResponse->second.type() == response::Type::List) + const auto& itrFields = itrResponse->second.find(R"gql(inputFields)gql"); + if (itrFields != itrResponse->second.end() && itrFields->second.type() == response::Type::List) { itrType = _inputTypeFields .insert({ name, - getArguments(itrResponse->second.release()) }) + getArguments(itrFields->second.get()) }) .first; } } From 76e3ba68d78373cc6e13dd228b01f69cdaf824ab Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 30 Nov 2020 15:27:07 -0300 Subject: [PATCH 05/42] refactor ValidateExecutableVisitor, split field get x add Split the fields getter and cache/insertion into the map so they can be used later in a different way. There should be no code differences, just moving around the internal branch to its own function. In the next commits, this will be removed from the getter, as it will be query-only as the types will be all cached beforehand. --- include/Validation.h | 6 ++ src/Validation.cpp | 183 ++++++++++++++++++++++--------------------- 2 files changed, 101 insertions(+), 88 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index e06e2947..76f632ed 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -235,6 +235,12 @@ class ValidateExecutableVisitor EnumValues _enumValues; ScalarTypes _scalarTypes; + // builds the validation context (lookup maps) + TypeFields::const_iterator addTypeFields( + const std::string& typeName, const response::Value& typeDescriptionMap); + InputTypeFields::const_iterator addInputTypeFields( + const std::string& typeName, const response::Value& typeDescriptionMap); + // These members store information that's specific to a single query and changes every time we // visit a new one. They must be reset in between queries. ExecutableNodes _fragmentDefinitions; diff --git a/src/Validation.cpp b/src/Validation.cpp index 798c31a6..4a5ee173 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -1637,7 +1637,7 @@ ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor: getScopedTypeFields() { auto typeKind = getScopedTypeKind(); - auto itrType = _typeFields.find(_scopedType); + ValidateExecutableVisitor::TypeFields::const_iterator itrType = _typeFields.find(_scopedType); if (itrType == _typeFields.cend() && typeKind && !isScalarType(*typeKind)) { @@ -1674,113 +1674,116 @@ ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor: const auto& itrResponse = data.find(R"gql(__type)gql"); if (itrResponse != data.end() && itrResponse->second.type() == response::Type::Map) { - std::map fields; - response::Value scalarKind(response::Type::EnumValue); - response::Value nonNullKind(response::Type::EnumValue); + itrType = addTypeFields(_scopedType, itrResponse->second); + } + } - scalarKind.set(R"gql(SCALAR)gql"); - nonNullKind.set(R"gql(NON_NULL)gql"); + return itrType; +} - const auto& itrFields = itrResponse->second.find(R"gql(fields)gql"); - if (itrFields != itrResponse->second.end() && itrFields->second.type() == response::Type::List) - { - const auto& entries = itrFields->second.get(); +ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor::addTypeFields( + const std::string& typeName, const response::Value& typeDescriptionMap) +{ + std::map fields; + response::Value scalarKind(response::Type::EnumValue); + response::Value nonNullKind(response::Type::EnumValue); - for (const auto& entry : entries) - { - if (entry.type() != response::Type::Map) - { - continue; - } + scalarKind.set(R"gql(SCALAR)gql"); + nonNullKind.set(R"gql(NON_NULL)gql"); - const auto& itrFieldName = entry.find(R"gql(name)gql"); - const auto& itrFieldType = entry.find(R"gql(type)gql"); + const auto& itrFields = typeDescriptionMap.find(R"gql(fields)gql"); + if (itrFields != typeDescriptionMap.end() && itrFields->second.type() == response::Type::List) + { + const auto& entries = itrFields->second.get(); - if (itrFieldName != entry.end() - && itrFieldName->second.type() == response::Type::String - && itrFieldType != entry.end() - && itrFieldType->second.type() == response::Type::Map) - { - const auto& fieldName = itrFieldName->second.get(); - ValidateTypeField subField; + for (const auto& entry : entries) + { + if (entry.type() != response::Type::Map) + { + continue; + } - subField.returnType = ValidateType(itrFieldType->second); + const auto& itrFieldName = entry.find(R"gql(name)gql"); + const auto& itrFieldType = entry.find(R"gql(type)gql"); - const auto& itrArgs = entry.find(R"gql(args)gql"); - if (itrArgs != entry.end() - && itrArgs->second.type() == response::Type::List) - { - subField.arguments = - getArguments(itrArgs->second.get()); - } + if (itrFieldName != entry.end() && itrFieldName->second.type() == response::Type::String + && itrFieldType != entry.end() + && itrFieldType->second.type() == response::Type::Map) + { + const auto& fieldName = itrFieldName->second.get(); + ValidateTypeField subField; - fields[std::move(fieldName)] = std::move(subField); - } - } + subField.returnType = ValidateType(itrFieldType->second); - if (_scopedType == _operationTypes[strQuery]) + const auto& itrArgs = entry.find(R"gql(args)gql"); + if (itrArgs != entry.end() && itrArgs->second.type() == response::Type::List) { - response::Value objectKind(response::Type::EnumValue); - - objectKind.set(R"gql(OBJECT)gql"); + subField.arguments = getArguments(itrArgs->second.get()); + } - ValidateTypeField schemaField; - response::Value schemaType(response::Type::Map); - response::Value notNullSchemaType(response::Type::Map); + fields[std::move(fieldName)] = std::move(subField); + } + } - schemaType.emplace_back(R"gql(kind)gql", response::Value(objectKind)); - schemaType.emplace_back(R"gql(name)gql", response::Value(R"gql(__Schema)gql")); - notNullSchemaType.emplace_back(R"gql(kind)gql", response::Value(nonNullKind)); - notNullSchemaType.emplace_back(R"gql(ofType)gql", std::move(schemaType)); - schemaField.returnType = std::move(notNullSchemaType); - fields[R"gql(__schema)gql"] = std::move(schemaField); + if (typeName == _operationTypes[strQuery]) + { + response::Value objectKind(response::Type::EnumValue); - ValidateTypeField typeField; - response::Value typeType(response::Type::Map); + objectKind.set(R"gql(OBJECT)gql"); - typeType.emplace_back(R"gql(kind)gql", response::Value(objectKind)); - typeType.emplace_back(R"gql(name)gql", response::Value(R"gql(__Type)gql")); - typeField.returnType = std::move(typeType); + ValidateTypeField schemaField; + response::Value schemaType(response::Type::Map); + response::Value notNullSchemaType(response::Type::Map); - ValidateArgument nameArgument; - response::Value typeNameArg(response::Type::Map); - response::Value nonNullTypeNameArg(response::Type::Map); + schemaType.emplace_back(R"gql(kind)gql", response::Value(objectKind)); + schemaType.emplace_back(R"gql(name)gql", response::Value(R"gql(__Schema)gql")); + notNullSchemaType.emplace_back(R"gql(kind)gql", response::Value(nonNullKind)); + notNullSchemaType.emplace_back(R"gql(ofType)gql", std::move(schemaType)); + schemaField.returnType = std::move(notNullSchemaType); + fields[R"gql(__schema)gql"] = std::move(schemaField); - typeNameArg.emplace_back(R"gql(kind)gql", response::Value(scalarKind)); - typeNameArg.emplace_back(R"gql(name)gql", response::Value(R"gql(String)gql")); - nonNullTypeNameArg.emplace_back(R"gql(kind)gql", response::Value(nonNullKind)); - nonNullTypeNameArg.emplace_back(R"gql(ofType)gql", std::move(typeNameArg)); - nameArgument.type = std::move(nonNullTypeNameArg); + ValidateTypeField typeField; + response::Value typeType(response::Type::Map); - typeField.arguments[R"gql(name)gql"] = std::move(nameArgument); + typeType.emplace_back(R"gql(kind)gql", response::Value(objectKind)); + typeType.emplace_back(R"gql(name)gql", response::Value(R"gql(__Type)gql")); + typeField.returnType = std::move(typeType); - fields[R"gql(__type)gql"] = std::move(typeField); - } - } + ValidateArgument nameArgument; + response::Value typeNameArg(response::Type::Map); + response::Value nonNullTypeNameArg(response::Type::Map); - ValidateTypeField typenameField; - response::Value typenameType(response::Type::Map); - response::Value notNullTypenameType(response::Type::Map); + typeNameArg.emplace_back(R"gql(kind)gql", response::Value(scalarKind)); + typeNameArg.emplace_back(R"gql(name)gql", response::Value(R"gql(String)gql")); + nonNullTypeNameArg.emplace_back(R"gql(kind)gql", response::Value(nonNullKind)); + nonNullTypeNameArg.emplace_back(R"gql(ofType)gql", std::move(typeNameArg)); + nameArgument.type = std::move(nonNullTypeNameArg); - typenameType.emplace_back(R"gql(kind)gql", response::Value(scalarKind)); - typenameType.emplace_back(R"gql(name)gql", response::Value(R"gql(String)gql")); - notNullTypenameType.emplace_back(R"gql(kind)gql", response::Value(nonNullKind)); - notNullTypenameType.emplace_back(R"gql(ofType)gql", std::move(typenameType)); - typenameField.returnType = std::move(notNullTypenameType); - fields[R"gql(__typename)gql"] = std::move(typenameField); + typeField.arguments[R"gql(name)gql"] = std::move(nameArgument); - itrType = _typeFields.insert({ _scopedType, std::move(fields) }).first; + fields[R"gql(__type)gql"] = std::move(typeField); } } - return itrType; + ValidateTypeField typenameField; + response::Value typenameType(response::Type::Map); + response::Value notNullTypenameType(response::Type::Map); + + typenameType.emplace_back(R"gql(kind)gql", response::Value(scalarKind)); + typenameType.emplace_back(R"gql(name)gql", response::Value(R"gql(String)gql")); + notNullTypenameType.emplace_back(R"gql(kind)gql", response::Value(nonNullKind)); + notNullTypenameType.emplace_back(R"gql(ofType)gql", std::move(typenameType)); + typenameField.returnType = std::move(notNullTypenameType); + fields[R"gql(__typename)gql"] = std::move(typenameField); + + return _typeFields.insert({ typeName, std::move(fields) }).first; } ValidateExecutableVisitor::InputTypeFields::const_iterator ValidateExecutableVisitor:: getInputTypeFields(const std::string& name) { auto typeKind = getTypeKind(name); - auto itrType = _inputTypeFields.find(name); + InputTypeFields::const_iterator itrType = _inputTypeFields.find(name); if (itrType == _inputTypeFields.cend() && typeKind && *typeKind == introspection::TypeKind::INPUT_OBJECT) @@ -1813,22 +1816,26 @@ ValidateExecutableVisitor::InputTypeFields::const_iterator ValidateExecutableVis if (itrResponse != data.end() && itrResponse->second.type() == response::Type::Map) { - std::map fields; - - const auto& itrFields = itrResponse->second.find(R"gql(inputFields)gql"); - if (itrFields != itrResponse->second.end() && itrFields->second.type() == response::Type::List) - { - itrType = _inputTypeFields - .insert({ name, - getArguments(itrFields->second.get()) }) - .first; - } + itrType = addInputTypeFields(name, itrResponse->second); } } return itrType; } +ValidateExecutableVisitor::InputTypeFields::const_iterator ValidateExecutableVisitor:: + addInputTypeFields(const std::string& typeName, const response::Value& typeDescriptionMap) +{ + const auto& itrFields = typeDescriptionMap.find(R"gql(inputFields)gql"); + if (itrFields != typeDescriptionMap.end() && itrFields->second.type() == response::Type::List) + { + return _inputTypeFields + .insert({ typeName, getArguments(itrFields->second.get()) }) + .first; + } + return _inputTypeFields.cend(); +} + template std::string ValidateExecutableVisitor::getFieldType( const _FieldTypes& fields, const std::string& name) From 61738bc4c55114e89916023ab37927f8d5344e55 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 30 Nov 2020 15:41:43 -0300 Subject: [PATCH 06/42] refactor ValidateExecutableVisitor to handle scalar/enum introspection --- include/Validation.h | 2 ++ src/Validation.cpp | 71 ++++++++++++++++++++++++-------------------- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index 76f632ed..7a931aaa 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -236,6 +236,8 @@ class ValidateExecutableVisitor ScalarTypes _scalarTypes; // builds the validation context (lookup maps) + void addScalar(const std::string& scalarName); + void addEnum(const std::string& enumName, const response::Value& enumDescriptionMap); TypeFields::const_iterator addTypeFields( const std::string& typeName, const response::Value& typeDescriptionMap); InputTypeFields::const_iterator addInputTypeFields( diff --git a/src/Validation.cpp b/src/Validation.cpp index 4a5ee173..a98deac4 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -521,41 +521,11 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) } else if (kind == introspection::TypeKind::ENUM) { - const auto& itrEnumValues = entry.find(R"gql(enumValues)gql"); - if (itrEnumValues != entry.end() - && itrEnumValues->second.type() == response::Type::List) - { - std::set enumValues; - const auto& enumValuesEntries = - itrEnumValues->second.get(); - - for (const auto& enumValuesEntry : enumValuesEntries) - { - if (enumValuesEntry.type() != response::Type::Map) - { - continue; - } - - const auto& itrEnumValuesName = - enumValuesEntry.find(R"gql(name)gql"); - if (itrEnumValuesName != enumValuesEntry.end() - && itrEnumValuesName->second.type() - == response::Type::String) - { - enumValues.insert( - itrEnumValuesName->second.get()); - } - } - - if (!enumValues.empty()) - { - _enumValues[name] = std::move(enumValues); - } - } + addEnum(name, entry); } else if (kind == introspection::TypeKind::SCALAR) { - _scalarTypes.insert(name); + addScalar(name); } _typeKinds[std::move(name)] = kind; @@ -1836,6 +1806,43 @@ ValidateExecutableVisitor::InputTypeFields::const_iterator ValidateExecutableVis return _inputTypeFields.cend(); } +void ValidateExecutableVisitor::addEnum( + const std::string& enumName, const response::Value& enumDescriptionMap) +{ + const auto& itrEnumValues = enumDescriptionMap.find(R"gql(enumValues)gql"); + if (itrEnumValues != enumDescriptionMap.end() + && itrEnumValues->second.type() == response::Type::List) + { + std::set enumValues; + const auto& enumValuesEntries = itrEnumValues->second.get(); + + for (const auto& enumValuesEntry : enumValuesEntries) + { + if (enumValuesEntry.type() != response::Type::Map) + { + continue; + } + + const auto& itrEnumValuesName = enumValuesEntry.find(R"gql(name)gql"); + if (itrEnumValuesName != enumValuesEntry.end() + && itrEnumValuesName->second.type() == response::Type::String) + { + enumValues.insert(itrEnumValuesName->second.get()); + } + } + + if (!enumValues.empty()) + { + _enumValues[enumName] = std::move(enumValues); + } + } +} + +void ValidateExecutableVisitor::addScalar(const std::string& scalarName) +{ + _scalarTypes.insert(scalarName); +} + template std::string ValidateExecutableVisitor::getFieldType( const _FieldTypes& fields, const std::string& name) From 2f1a802ba20016d7acc355b6cd3a360694fcc962 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 30 Nov 2020 16:04:57 -0300 Subject: [PATCH 07/42] refactor ValidateExecutableVisitor, split non-scalar introspection This handles OBJECT, INTERFACE, UNION and INPUT_OBJECT types. It should have no behavior change, just moving code around. Minor adjustments were made to cope with the iterator return --- include/Validation.h | 2 ++ src/Validation.cpp | 84 +++++++++++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index 7a931aaa..07b6ca74 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -238,6 +238,8 @@ class ValidateExecutableVisitor // builds the validation context (lookup maps) void addScalar(const std::string& scalarName); void addEnum(const std::string& enumName, const response::Value& enumDescriptionMap); + void addObject(const std::string& name, const response::Value& typeDescriptionMap); + void addInterfaceOrUnion(const std::string& name, const response::Value& typeDescriptionMap); TypeFields::const_iterator addTypeFields( const std::string& typeName, const response::Value& typeDescriptionMap); InputTypeFields::const_iterator addInputTypeFields( diff --git a/src/Validation.cpp b/src/Validation.cpp index a98deac4..139f8656 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -482,41 +482,11 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) { if (kind == introspection::TypeKind::OBJECT) { - _matchingTypes[name].insert(name); + addObject(name, entry); } else { - const auto& itrPossibleTypes = entry.find(R"gql(possibleTypes)gql"); - if (itrPossibleTypes != entry.end() - && itrPossibleTypes->second.type() == response::Type::List) - { - std::set matchingTypes; - const auto& matchingTypeEntries = - itrPossibleTypes->second.get(); - - for (const auto& matchingTypeEntry : matchingTypeEntries) - { - if (matchingTypeEntry.type() != response::Type::Map) - { - continue; - } - - const auto& itrMatchingTypeName = - matchingTypeEntry.find(R"gql(name)gql"); - if (itrMatchingTypeName != matchingTypeEntry.end() - && itrMatchingTypeName->second.type() - == response::Type::String) - { - matchingTypes.insert(itrMatchingTypeName->second - .get()); - } - } - - if (!matchingTypes.empty()) - { - _matchingTypes[name] = std::move(matchingTypes); - } - } + addInterfaceOrUnion(name, entry); } } else if (kind == introspection::TypeKind::ENUM) @@ -1838,6 +1808,56 @@ void ValidateExecutableVisitor::addEnum( } } +void ValidateExecutableVisitor::addObject( + const std::string& name, const response::Value& typeDescriptionMap) +{ + auto itr = _matchingTypes.find(name); + if (itr != _matchingTypes.cend()) + { + itr->second.insert(name); + } + else + { + _matchingTypes.insert({ name, { name } }); + } +} + +void ValidateExecutableVisitor::addInterfaceOrUnion( + const std::string& name, const response::Value& typeDescriptionMap) +{ + const auto& itrPossibleTypes = typeDescriptionMap.find(R"gql(possibleTypes)gql"); + if (itrPossibleTypes != typeDescriptionMap.end() + && itrPossibleTypes->second.type() == response::Type::List) + { + const auto& matchingTypeEntries = itrPossibleTypes->second.get(); + auto itr = _matchingTypes.find(name); + + for (const auto& matchingTypeEntry : matchingTypeEntries) + { + if (matchingTypeEntry.type() != response::Type::Map) + { + continue; + } + + const auto& itrMatchingTypeName = matchingTypeEntry.find(R"gql(name)gql"); + if (itrMatchingTypeName != matchingTypeEntry.end() + && itrMatchingTypeName->second.type() == response::Type::String) + { + const auto& matchingTypeName = + itrMatchingTypeName->second.get(); + if (itr != _matchingTypes.cend()) + { + itr->second.insert(matchingTypeName); + } + else + { + itr = _matchingTypes.insert({ name, { matchingTypeName } }).first; + } + } + } + } +} + void ValidateExecutableVisitor::addScalar(const std::string& scalarName) { _scalarTypes.insert(scalarName); From 6b445419c9efdf26548c114ceefad24ce5f18d73 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 7 Dec 2020 12:34:59 -0300 Subject: [PATCH 08/42] refactor ValidateExecutableVisitor, split directives handling It should have no behavior change, just moving code around. --- include/Validation.h | 2 ++ src/Validation.cpp | 50 ++++++++++++++++++++++++-------------------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index 07b6ca74..3e506450 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -240,6 +240,8 @@ class ValidateExecutableVisitor void addEnum(const std::string& enumName, const response::Value& enumDescriptionMap); void addObject(const std::string& name, const response::Value& typeDescriptionMap); void addInterfaceOrUnion(const std::string& name, const response::Value& typeDescriptionMap); + void addDirective(const std::string& name, const response::ListType& locations, + const response::Value& descriptionMap); TypeFields::const_iterator addTypeFields( const std::string& typeName, const response::Value& typeDescriptionMap); InputTypeFields::const_iterator addInputTypeFields( diff --git a/src/Validation.cpp b/src/Validation.cpp index 139f8656..a16c6769 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -521,31 +521,10 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) && itrLocations != entry.end() && itrLocations->second.type() == response::Type::List) { - ValidateDirective directive; + const auto& name = itrName->second.get(); const auto& locations = itrLocations->second.get(); - for (const auto& location : locations) - { - if (location.type() != response::Type::EnumValue) - { - continue; - } - - directive.locations.insert( - ModifiedArgument::convert( - location)); - } - - const auto& itrArgs = entry.find(R"gql(args)gql"); - if (itrArgs != entry.end() - && itrArgs->second.type() == response::Type::List) - { - directive.arguments = - getArguments(itrArgs->second.get()); - } - - _directives[itrName->second.get()] = - std::move(directive); + addDirective(name, locations, entry); } } } @@ -1863,6 +1842,31 @@ void ValidateExecutableVisitor::addScalar(const std::string& scalarName) _scalarTypes.insert(scalarName); } +void ValidateExecutableVisitor::addDirective(const std::string& name, + const response::ListType& locations, const response::Value& descriptionMap) +{ + ValidateDirective directive; + + for (const auto& location : locations) + { + if (location.type() != response::Type::EnumValue) + { + continue; + } + + directive.locations.insert( + ModifiedArgument::convert(location)); + } + + const auto& itrArgs = descriptionMap.find(R"gql(args)gql"); + if (itrArgs != descriptionMap.end() && itrArgs->second.type() == response::Type::List) + { + directive.arguments = getArguments(itrArgs->second.get()); + } + + _directives[name] = std::move(directive); +} + template std::string ValidateExecutableVisitor::getFieldType( const _FieldTypes& fields, const std::string& name) From 633565e9eb1d962ec677344e78d5873346cedc44 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 30 Nov 2020 16:30:54 -0300 Subject: [PATCH 09/42] ValidateExecutableVisitor process fields and input fields in one go This uses the information being queried in the introspection and allows the fields and input fields to be processed in one go. --- include/Validation.h | 11 ++-- src/Validation.cpp | 126 ++++++++++++------------------------------- 2 files changed, 39 insertions(+), 98 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index 3e506450..2ff484ef 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -186,8 +186,8 @@ class ValidateExecutableVisitor bool matchesScopedType(const std::string& name) const; - TypeFields::const_iterator getScopedTypeFields(); - InputTypeFields::const_iterator getInputTypeFields(const std::string& name); + TypeFields::const_iterator getScopedTypeFields() const; + InputTypeFields::const_iterator getInputTypeFields(const std::string& name) const; static const ValidateType& getValidateFieldType(const FieldTypes::mapped_type& value); static const ValidateType& getValidateFieldType(const InputFieldTypes::mapped_type& value); template @@ -239,13 +239,12 @@ class ValidateExecutableVisitor void addScalar(const std::string& scalarName); void addEnum(const std::string& enumName, const response::Value& enumDescriptionMap); void addObject(const std::string& name, const response::Value& typeDescriptionMap); + void addInputObject(const std::string& name, const response::Value& typeDescriptionMap); void addInterfaceOrUnion(const std::string& name, const response::Value& typeDescriptionMap); void addDirective(const std::string& name, const response::ListType& locations, const response::Value& descriptionMap); - TypeFields::const_iterator addTypeFields( - const std::string& typeName, const response::Value& typeDescriptionMap); - InputTypeFields::const_iterator addInputTypeFields( - const std::string& typeName, const response::Value& typeDescriptionMap); + void addTypeFields(const std::string& typeName, const response::Value& typeDescriptionMap); + void addInputTypeFields(const std::string& typeName, const response::Value& typeDescriptionMap); // These members store information that's specific to a single query and changes every time we // visit a new one. They must be reset in between queries. diff --git a/src/Validation.cpp b/src/Validation.cpp index a16c6769..fba201a5 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -484,6 +484,10 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) { addObject(name, entry); } + else if (kind == introspection::TypeKind::INPUT_OBJECT) + { + addInputObject(name, entry); + } else { addInterfaceOrUnion(name, entry); @@ -1553,54 +1557,12 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, } ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor:: - getScopedTypeFields() + getScopedTypeFields() const { - auto typeKind = getScopedTypeKind(); - ValidateExecutableVisitor::TypeFields::const_iterator itrType = _typeFields.find(_scopedType); - - if (itrType == _typeFields.cend() && typeKind && !isScalarType(*typeKind)) - { - std::ostringstream oss; - - oss << R"gql(query { - __type(name: ")gql" - << _scopedType << R"gql(") { - fields(includeDeprecated: true) { - name - type { - ...nestedType - } - args { - name - defaultValue - type { - ...nestedType - } - } - } - } - } - - fragment nestedType on __Type { - kind - name - ofType { - ...nestedType - } - })gql"; - - auto data = executeQuery(oss.str()); - const auto& itrResponse = data.find(R"gql(__type)gql"); - if (itrResponse != data.end() && itrResponse->second.type() == response::Type::Map) - { - itrType = addTypeFields(_scopedType, itrResponse->second); - } - } - - return itrType; + return _typeFields.find(_scopedType); } -ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor::addTypeFields( +void ValidateExecutableVisitor::addTypeFields( const std::string& typeName, const response::Value& typeDescriptionMap) { std::map fields; @@ -1695,64 +1657,24 @@ ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor: typenameField.returnType = std::move(notNullTypenameType); fields[R"gql(__typename)gql"] = std::move(typenameField); - return _typeFields.insert({ typeName, std::move(fields) }).first; + _typeFields.insert({ typeName, std::move(fields) }); } ValidateExecutableVisitor::InputTypeFields::const_iterator ValidateExecutableVisitor:: - getInputTypeFields(const std::string& name) + getInputTypeFields(const std::string& name) const { - auto typeKind = getTypeKind(name); - InputTypeFields::const_iterator itrType = _inputTypeFields.find(name); - - if (itrType == _inputTypeFields.cend() && typeKind - && *typeKind == introspection::TypeKind::INPUT_OBJECT) - { - std::ostringstream oss; - - oss << R"gql(query { - __type(name: ")gql" - << name << R"gql(") { - inputFields { - name - defaultValue - type { - ...nestedType - } - } - } - } - - fragment nestedType on __Type { - kind - name - ofType { - ...nestedType - } - })gql"; - - auto data = executeQuery(oss.str()); - const auto& itrResponse = data.find(R"gql(__type)gql"); - - if (itrResponse != data.end() && itrResponse->second.type() == response::Type::Map) - { - itrType = addInputTypeFields(name, itrResponse->second); - } - } - - return itrType; + return _inputTypeFields.find(name); } -ValidateExecutableVisitor::InputTypeFields::const_iterator ValidateExecutableVisitor:: - addInputTypeFields(const std::string& typeName, const response::Value& typeDescriptionMap) +void ValidateExecutableVisitor::addInputTypeFields( + const std::string& typeName, const response::Value& typeDescriptionMap) { const auto& itrFields = typeDescriptionMap.find(R"gql(inputFields)gql"); if (itrFields != typeDescriptionMap.end() && itrFields->second.type() == response::Type::List) { - return _inputTypeFields - .insert({ typeName, getArguments(itrFields->second.get()) }) - .first; + _inputTypeFields.insert( + { typeName, getArguments(itrFields->second.get()) }); } - return _inputTypeFields.cend(); } void ValidateExecutableVisitor::addEnum( @@ -1799,6 +1721,24 @@ void ValidateExecutableVisitor::addObject( { _matchingTypes.insert({ name, { name } }); } + + addTypeFields(name, typeDescriptionMap); +} + +void ValidateExecutableVisitor::addInputObject( + const std::string& name, const response::Value& typeDescriptionMap) +{ + auto itr = _matchingTypes.find(name); + if (itr != _matchingTypes.cend()) + { + itr->second.insert(name); + } + else + { + _matchingTypes.insert({ name, { name } }); + } + + addInputTypeFields(name, typeDescriptionMap); } void ValidateExecutableVisitor::addInterfaceOrUnion( @@ -1835,6 +1775,8 @@ void ValidateExecutableVisitor::addInterfaceOrUnion( } } } + + addTypeFields(name, typeDescriptionMap); } void ValidateExecutableVisitor::addScalar(const std::string& scalarName) From 369c3a91b91d36748d3c9a30aab241414e212409 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 30 Nov 2020 21:43:55 -0300 Subject: [PATCH 10/42] ValidateExecutableVisitor doesn't hold service anymore This is another step to split the visitor from the lookup data structures, in the future the lookup will be shared. --- include/Validation.h | 3 --- src/Validation.cpp | 48 ++++++++++++++++---------------------------- 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index 2ff484ef..652fee44 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -170,8 +170,6 @@ class ValidateExecutableVisitor std::vector getStructuredErrors(); private: - response::Value executeQuery(std::string_view query) const; - static ValidateTypeFieldArguments getArguments(const response::ListType& argumentsMember); using FieldTypes = std::map; @@ -213,7 +211,6 @@ class ValidateExecutableVisitor bool validateVariableType(bool isNonNull, const ValidateType& variableType, const schema_location& position, const ValidateType& inputType); - const Request& _service; std::vector _errors; using OperationTypes = std::map; diff --git a/src/Validation.cpp b/src/Validation.cpp index fba201a5..7edce4a6 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -421,14 +421,29 @@ ValidateType ValidateVariableTypeVisitor::getType() } ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) - : _service(service) { // TODO: we should execute this query only once per schema, // maybe it can be done and cached inside the Request itself to allow // this. Alternatively it could be provided at compile-time such as schema.json // that is parsed, this would allow us to drop introspection from the Request // and still have it to work - auto data = executeQuery(introspectionQuery); + auto ast = peg::parseString(introspectionQuery); + // This is taking advantage of the fact that during validation we can choose to execute + // unvalidated queries against the Introspection schema. This way we can use fragment + // cycles to expand an arbitrary number of wrapper types. + ast.validated = true; + + std::shared_ptr state; + const std::string operationName; + response::Value variables(response::Type::Map); + auto result = service.resolve(state, ast, operationName, std::move(variables)).get(); + const auto& itrData = result.find(std::string { strData }); + if (itrData == result.end()) + { + return; + } + + const auto& data = itrData->second; const auto& itrSchema = data.find(R"gql(__schema)gql"); if (itrSchema != data.end() && itrSchema->second.type() == response::Type::Map) { @@ -536,35 +551,6 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) } } -response::Value ValidateExecutableVisitor::executeQuery(std::string_view query) const -{ - auto ast = peg::parseString(query); - - // This is taking advantage of the fact that during validation we can choose to execute - // unvalidated queries against the Introspection schema. This way we can use fragment - // cycles to expand an arbitrary number of wrapper types. - ast.validated = true; - - response::Value data(response::Type::Map); - std::shared_ptr state; - const std::string operationName; - response::Value variables(response::Type::Map); - auto result = _service.resolve(state, ast, operationName, std::move(variables)).get(); - auto members = result.release(); - auto itrResponse = std::find_if(members.begin(), - members.end(), - [](const std::pair& entry) noexcept { - return entry.first == strData; - }); - - if (itrResponse != members.end()) - { - data = std::move(itrResponse->second); - } - - return data; -} - void ValidateExecutableVisitor::visit(const peg::ast_node& root) { // Visit all of the fragment definitions and check for duplicates. From 3e5610d118dd892dfe7efcc161945ffc4a2e5fe8 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 30 Nov 2020 23:41:42 -0300 Subject: [PATCH 11/42] split ValidationContext into a shared class ValidateExecutableVisitor was split into a lookup data structure (ValidationContext) and the actual visitor. The lookup data structure is shared across requests, saving queries and processing. --- include/Validation.h | 125 +++++++++++++-------- src/Validation.cpp | 252 +++++++++++++++++++++++++++++-------------- 2 files changed, 250 insertions(+), 127 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index 652fee44..a9651115 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -158,71 +158,55 @@ class ValidateVariableTypeVisitor ValidateType _variableType; }; -// ValidateExecutableVisitor visits the AST and validates that it is executable against the service -// schema. -class ValidateExecutableVisitor +class ValidationContext { public: - ValidateExecutableVisitor(const Request& service); - - void visit(const peg::ast_node& root); - - std::vector getStructuredErrors(); - -private: - static ValidateTypeFieldArguments getArguments(const response::ListType& argumentsMember); + ValidationContext(const Request& service); + ValidationContext(const response::Value& introspectionQuery); using FieldTypes = std::map; - using TypeFields = std::map; using InputFieldTypes = ValidateTypeFieldArguments; - using InputTypeFields = std::map; - using EnumValues = std::map>; std::optional getTypeKind(const std::string& name) const; - std::optional getScopedTypeKind() const; - constexpr bool isScalarType(introspection::TypeKind kind); + const ValidateTypeKinds& getTypeKinds() const; + std::optional>> getMatchingTypes( + const std::string& name) const; + std::optional> getTypeFields( + const std::string& name) const; + std::optional> getInputTypeFields( + const std::string& name) const; + std::optional>> getEnumValues( + const std::string& name) const; + std::optional> getDirective( + const std::string& name) const; + std::optional> getOperationType( + const std::string& name) const; + + bool isKnownScalar(const std::string& name) const; - bool matchesScopedType(const std::string& name) const; - - TypeFields::const_iterator getScopedTypeFields() const; - InputTypeFields::const_iterator getInputTypeFields(const std::string& name) const; - static const ValidateType& getValidateFieldType(const FieldTypes::mapped_type& value); - static const ValidateType& getValidateFieldType(const InputFieldTypes::mapped_type& value); template static std::string getFieldType(const _FieldTypes& fields, const std::string& name); template static std::string getWrappedFieldType(const _FieldTypes& fields, const std::string& name); static std::string getWrappedFieldType(const ValidateType& returnType); - void visitFragmentDefinition(const peg::ast_node& fragmentDefinition); - void visitOperationDefinition(const peg::ast_node& operationDefinition); - - void visitSelection(const peg::ast_node& selection); - - void visitField(const peg::ast_node& field); - void visitFragmentSpread(const peg::ast_node& fragmentSpread); - void visitInlineFragment(const peg::ast_node& inlineFragment); + static constexpr bool isScalarType(introspection::TypeKind kind); - void visitDirectives( - introspection::DirectiveLocation location, const peg::ast_node& directives); +private: + void populate(const response::Value& introspectionQuery); - bool validateInputValue(bool hasNonNullDefaultValue, const ValidateArgumentValuePtr& argument, - const ValidateType& type); - bool validateVariableType(bool isNonNull, const ValidateType& variableType, - const schema_location& position, const ValidateType& inputType); + static ValidateTypeFieldArguments getArguments(const response::ListType& argumentsMember); + static const ValidateType& getValidateFieldType(const FieldTypes::mapped_type& value); + static const ValidateType& getValidateFieldType(const InputFieldTypes::mapped_type& value); - std::vector _errors; + using TypeFields = std::map; + using InputTypeFields = std::map; + using EnumValues = std::map>; using OperationTypes = std::map; using Directives = std::map; - using ExecutableNodes = std::map; - using FragmentSet = std::unordered_set; using MatchingTypes = std::map>; using ScalarTypes = std::set; - using VariableDefinitions = std::map; - using VariableTypes = std::map; - using OperationVariables = std::optional; - using VariableSet = std::set; // These members store Introspection schema information which does not change between queries. OperationTypes _operationTypes; @@ -232,6 +216,9 @@ class ValidateExecutableVisitor EnumValues _enumValues; ScalarTypes _scalarTypes; + TypeFields _typeFields; + InputTypeFields _inputTypeFields; + // builds the validation context (lookup maps) void addScalar(const std::string& scalarName); void addEnum(const std::string& enumName, const response::Value& enumDescriptionMap); @@ -242,6 +229,56 @@ class ValidateExecutableVisitor const response::Value& descriptionMap); void addTypeFields(const std::string& typeName, const response::Value& typeDescriptionMap); void addInputTypeFields(const std::string& typeName, const response::Value& typeDescriptionMap); +}; + +// ValidateExecutableVisitor visits the AST and validates that it is executable against the service +// schema. +class ValidateExecutableVisitor +{ +public: + // Legacy, left for compatibility reasons. Services should create a ValidationContext and pass + // it + ValidateExecutableVisitor(const Request& service); + ValidateExecutableVisitor(std::shared_ptr validationContext); + + void visit(const peg::ast_node& root); + + std::vector getStructuredErrors(); + +private: + std::optional getScopedTypeKind() const; + bool matchesScopedType(const std::string& name) const; + + std::optional> getScopedTypeFields() + const; + + void visitFragmentDefinition(const peg::ast_node& fragmentDefinition); + void visitOperationDefinition(const peg::ast_node& operationDefinition); + + void visitSelection(const peg::ast_node& selection); + + void visitField(const peg::ast_node& field); + void visitFragmentSpread(const peg::ast_node& fragmentSpread); + void visitInlineFragment(const peg::ast_node& inlineFragment); + + void visitDirectives( + introspection::DirectiveLocation location, const peg::ast_node& directives); + + bool validateInputValue(bool hasNonNullDefaultValue, const ValidateArgumentValuePtr& argument, + const ValidateType& type); + bool validateVariableType(bool isNonNull, const ValidateType& variableType, + const schema_location& position, const ValidateType& inputType); + + std::shared_ptr _validationContext; + + std::vector _errors; + + using ExecutableNodes = std::map; + using FragmentSet = std::unordered_set; + using VariableDefinitions = std::map; + using VariableTypes = std::map; + using OperationVariables = std::optional; + using VariableSet = std::set; // These members store information that's specific to a single query and changes every time we // visit a new one. They must be reset in between queries. @@ -256,8 +293,6 @@ class ValidateExecutableVisitor VariableSet _referencedVariables; FragmentSet _fragmentStack; size_t _fieldCount = 0; - TypeFields _typeFields; - InputTypeFields _inputTypeFields; std::string _scopedType; std::map _selectionFields; }; diff --git a/src/Validation.cpp b/src/Validation.cpp index 7edce4a6..0e3fc27e 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -420,7 +420,7 @@ ValidateType ValidateVariableTypeVisitor::getType() return result; } -ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) +ValidationContext::ValidationContext(const Request& service) { // TODO: we should execute this query only once per schema, // maybe it can be done and cached inside the Request itself to allow @@ -437,8 +437,19 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) const std::string operationName; response::Value variables(response::Type::Map); auto result = service.resolve(state, ast, operationName, std::move(variables)).get(); - const auto& itrData = result.find(std::string { strData }); - if (itrData == result.end()) + + populate(result); +} + +ValidationContext::ValidationContext(const response::Value& introspectionQuery) +{ + populate(introspectionQuery); +} + +void ValidationContext::populate(const response::Value& introspectionQuery) +{ + const auto& itrData = introspectionQuery.find(std::string { strData }); + if (itrData == introspectionQuery.end()) { return; } @@ -551,6 +562,17 @@ ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) } } +ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) + : _validationContext(std::make_shared(service)) +{ +} + +ValidateExecutableVisitor::ValidateExecutableVisitor( + std::shared_ptr validationContext) + : _validationContext(validationContext) +{ +} + void ValidateExecutableVisitor::visit(const peg::ast_node& root) { // Visit all of the fragment definitions and check for duplicates. @@ -687,17 +709,17 @@ void ValidateExecutableVisitor::visitFragmentDefinition(const peg::ast_node& fra const auto& typeCondition = fragmentDefinition.children[1]; auto innerType = typeCondition->children.front()->string(); - auto itrKind = _typeKinds.find(innerType); + auto kind = _validationContext->getTypeKind(innerType); - if (itrKind == _typeKinds.end() || isScalarType(itrKind->second)) + if (!kind || _validationContext->isScalarType(*kind)) { // http://spec.graphql.org/June2018/#sec-Fragment-Spread-Type-Existence // http://spec.graphql.org/June2018/#sec-Fragments-On-Composite-Types auto position = typeCondition->begin(); std::ostringstream message; - message << (itrKind == _typeKinds.end() ? "Undefined target type on fragment definition: " - : "Scalar target type on fragment definition: ") + message << (!kind ? "Undefined target type on fragment definition: " + : "Scalar target type on fragment definition: ") << name << " name: " << innerType; _errors.push_back({ message.str(), { position.line, position.column } }); @@ -766,7 +788,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op else if (child->is_type() || child->is_type() || child->is_type()) { - ValidateVariableTypeVisitor visitor(_typeKinds); + ValidateVariableTypeVisitor visitor(_validationContext->getTypeKinds()); visitor.visit(*child); @@ -843,9 +865,8 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op visitDirectives(location, child); }); - auto itrType = _operationTypes.find(operationType); - - if (itrType == _operationTypes.cend()) + const auto& typeRef = _validationContext->getOperationType(std::string { operationType }); + if (!typeRef) { auto position = operationDefinition.begin(); std::ostringstream error; @@ -856,7 +877,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op return; } - _scopedType = itrType->second; + _scopedType = typeRef.value(); _fieldCount = 0; const auto& selection = *operationDefinition.children.back(); @@ -921,7 +942,7 @@ void ValidateExecutableVisitor::visitSelection(const peg::ast_node& selection) } } -ValidateTypeFieldArguments ValidateExecutableVisitor::getArguments(const response::ListType& args) +ValidateTypeFieldArguments ValidationContext::getArguments(const response::ListType& args) { ValidateTypeFieldArguments result; @@ -954,20 +975,68 @@ ValidateTypeFieldArguments ValidateExecutableVisitor::getArguments(const respons return result; } -std::optional ValidateExecutableVisitor::getTypeKind( - const std::string& name) const +std::optional ValidationContext::getTypeKind(const std::string& name) const { auto itrKind = _typeKinds.find(name); return (itrKind == _typeKinds.cend() ? std::nullopt : std::make_optional(itrKind->second)); } +const ValidateTypeKinds& ValidationContext::getTypeKinds() const +{ + return _typeKinds; +} + +std::optional>> ValidationContext:: + getMatchingTypes(const std::string& name) const +{ + const auto& itr = _matchingTypes.find(name); + if (itr == _matchingTypes.cend()) + { + return std::nullopt; + } + return std::optional>>(itr->second); +} + +std::optional> ValidationContext:: + getTypeFields(const std::string& name) const +{ + const auto& itr = _typeFields.find(name); + if (itr == _typeFields.cend()) + { + return std::nullopt; + } + return std::optional>(itr->second); +} + +std::optional> ValidationContext::getDirective( + const std::string& name) const +{ + const auto& itr = _directives.find(name); + if (itr == _directives.cend()) + { + return std::nullopt; + } + return std::optional>(itr->second); +} + +std::optional> ValidationContext::getOperationType( + const std::string& name) const +{ + const auto& itr = _operationTypes.find(name); + if (itr == _operationTypes.cend()) + { + return std::nullopt; + } + return std::optional>(itr->second); +} + std::optional ValidateExecutableVisitor::getScopedTypeKind() const { - return getTypeKind(_scopedType); + return _validationContext->getTypeKind(_scopedType); } -constexpr bool ValidateExecutableVisitor::isScalarType(introspection::TypeKind kind) +constexpr bool ValidationContext::isScalarType(introspection::TypeKind kind) { switch (kind) { @@ -982,6 +1051,12 @@ constexpr bool ValidateExecutableVisitor::isScalarType(introspection::TypeKind k } } +bool ValidationContext::isKnownScalar(const std::string& name) const +{ + const auto& itr = _scalarTypes.find(name); + return itr != _scalarTypes.cend(); +} + bool ValidateExecutableVisitor::matchesScopedType(const std::string& name) const { if (name == _scopedType) @@ -989,18 +1064,20 @@ bool ValidateExecutableVisitor::matchesScopedType(const std::string& name) const return true; } - auto itrScoped = _matchingTypes.find(_scopedType); - auto itrNamed = _matchingTypes.find(name); + const auto& scoped = _validationContext->getMatchingTypes(_scopedType); + const auto& named = _validationContext->getMatchingTypes(name); - if (itrScoped != _matchingTypes.end() && itrNamed != _matchingTypes.end()) + if (scoped && named) { - auto itrMatch = std::find_if(itrScoped->second.begin(), - itrScoped->second.end(), - [this, itrNamed](const std::string& matchingType) noexcept { - return itrNamed->second.find(matchingType) != itrNamed->second.end(); + const std::set& scopedSet = scoped.value().get(); + const std::set& namedSet = named.value().get(); + auto itrMatch = std::find_if(scopedSet.cbegin(), + scopedSet.cend(), + [this, namedSet](const std::string& matchingType) noexcept { + return namedSet.find(matchingType) != namedSet.cend(); }); - return itrMatch != itrScoped->second.end(); + return itrMatch != scopedSet.cend(); } return false; @@ -1140,9 +1217,8 @@ bool ValidateExecutableVisitor::validateInputValue( return false; } - auto itrFields = getInputTypeFields(name); - - if (itrFields == _inputTypeFields.end()) + const auto& fieldsRef = _validationContext->getInputTypeFields(name); + if (!fieldsRef) { std::ostringstream message; @@ -1152,15 +1228,15 @@ bool ValidateExecutableVisitor::validateInputValue( return false; } + const auto& fields = fieldsRef.value().get(); const auto& values = std::get(argument.value->data).values; std::set subFields; // Check every value against the target type. for (const auto& entry : values) { - auto itrField = itrFields->second.find(entry.first); - - if (itrField == itrFields->second.end()) + const auto& itrField = fields.find(entry.first); + if (itrField == fields.cend()) { // http://spec.graphql.org/June2018/#sec-Input-Object-Field-Names std::ostringstream message; @@ -1188,7 +1264,7 @@ bool ValidateExecutableVisitor::validateInputValue( } // See if all required fields were specified. - for (const auto& entry : itrFields->second) + for (const auto& entry : fields) { if (entry.second.defaultValue || subFields.find(entry.first) != subFields.end()) { @@ -1251,10 +1327,10 @@ bool ValidateExecutableVisitor::validateInputValue( } const auto& value = std::get(argument.value->data).value; - auto itrEnumValues = _enumValues.find(name); + const auto& enumValuesRef = _validationContext->getEnumValues(name); - if (itrEnumValues == _enumValues.end() - || itrEnumValues->second.find(value) == itrEnumValues->second.end()) + if (!enumValuesRef + || enumValuesRef.value().get().find(value) == enumValuesRef.value().get().end()) { std::ostringstream message; @@ -1333,7 +1409,7 @@ bool ValidateExecutableVisitor::validateInputValue( } } - if (_scalarTypes.find(name) == _scalarTypes.end()) + if (!_validationContext->isKnownScalar(name)) { std::ostringstream message; @@ -1542,13 +1618,13 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, return true; } -ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor:: - getScopedTypeFields() const +std::optional> +ValidateExecutableVisitor::getScopedTypeFields() const { - return _typeFields.find(_scopedType); + return _validationContext->getTypeFields(_scopedType); } -void ValidateExecutableVisitor::addTypeFields( +void ValidationContext::addTypeFields( const std::string& typeName, const response::Value& typeDescriptionMap) { std::map fields; @@ -1646,13 +1722,19 @@ void ValidateExecutableVisitor::addTypeFields( _typeFields.insert({ typeName, std::move(fields) }); } -ValidateExecutableVisitor::InputTypeFields::const_iterator ValidateExecutableVisitor:: +std::optional> ValidationContext:: getInputTypeFields(const std::string& name) const { - return _inputTypeFields.find(name); + const auto& itr = _inputTypeFields.find(name); + if (itr == _inputTypeFields.cend()) + { + return std::nullopt; + } + return std::optional>( + itr->second); } -void ValidateExecutableVisitor::addInputTypeFields( +void ValidationContext::addInputTypeFields( const std::string& typeName, const response::Value& typeDescriptionMap) { const auto& itrFields = typeDescriptionMap.find(R"gql(inputFields)gql"); @@ -1663,7 +1745,18 @@ void ValidateExecutableVisitor::addInputTypeFields( } } -void ValidateExecutableVisitor::addEnum( +std::optional>> ValidationContext::getEnumValues( + const std::string& name) const +{ + const auto& itr = _enumValues.find(name); + if (itr == _enumValues.cend()) + { + return std::nullopt; + } + return std::optional>>(itr->second); +} + +void ValidationContext::addEnum( const std::string& enumName, const response::Value& enumDescriptionMap) { const auto& itrEnumValues = enumDescriptionMap.find(R"gql(enumValues)gql"); @@ -1695,7 +1788,7 @@ void ValidateExecutableVisitor::addEnum( } } -void ValidateExecutableVisitor::addObject( +void ValidationContext::addObject( const std::string& name, const response::Value& typeDescriptionMap) { auto itr = _matchingTypes.find(name); @@ -1711,7 +1804,7 @@ void ValidateExecutableVisitor::addObject( addTypeFields(name, typeDescriptionMap); } -void ValidateExecutableVisitor::addInputObject( +void ValidationContext::addInputObject( const std::string& name, const response::Value& typeDescriptionMap) { auto itr = _matchingTypes.find(name); @@ -1727,7 +1820,7 @@ void ValidateExecutableVisitor::addInputObject( addInputTypeFields(name, typeDescriptionMap); } -void ValidateExecutableVisitor::addInterfaceOrUnion( +void ValidationContext::addInterfaceOrUnion( const std::string& name, const response::Value& typeDescriptionMap) { const auto& itrPossibleTypes = typeDescriptionMap.find(R"gql(possibleTypes)gql"); @@ -1765,13 +1858,13 @@ void ValidateExecutableVisitor::addInterfaceOrUnion( addTypeFields(name, typeDescriptionMap); } -void ValidateExecutableVisitor::addScalar(const std::string& scalarName) +void ValidationContext::addScalar(const std::string& scalarName) { _scalarTypes.insert(scalarName); } -void ValidateExecutableVisitor::addDirective(const std::string& name, - const response::ListType& locations, const response::Value& descriptionMap) +void ValidationContext::addDirective(const std::string& name, const response::ListType& locations, + const response::Value& descriptionMap) { ValidateDirective directive; @@ -1796,8 +1889,7 @@ void ValidateExecutableVisitor::addDirective(const std::string& name, } template -std::string ValidateExecutableVisitor::getFieldType( - const _FieldTypes& fields, const std::string& name) +std::string ValidationContext::getFieldType(const _FieldTypes& fields, const std::string& name) { std::string result; auto itrType = fields.find(name); @@ -1835,20 +1927,19 @@ std::string ValidateExecutableVisitor::getFieldType( return result; } -const ValidateType& ValidateExecutableVisitor::getValidateFieldType( - const FieldTypes::mapped_type& value) +const ValidateType& ValidationContext::getValidateFieldType(const FieldTypes::mapped_type& value) { return value.returnType; } -const ValidateType& ValidateExecutableVisitor::getValidateFieldType( +const ValidateType& ValidationContext::getValidateFieldType( const InputFieldTypes::mapped_type& value) { return value.type; } template -std::string ValidateExecutableVisitor::getWrappedFieldType( +std::string ValidationContext::getWrappedFieldType( const _FieldTypes& fields, const std::string& name) { std::string result; @@ -1864,7 +1955,7 @@ std::string ValidateExecutableVisitor::getWrappedFieldType( return result; } -std::string ValidateExecutableVisitor::getWrappedFieldType(const ValidateType& returnType) +std::string ValidationContext::getWrappedFieldType(const ValidateType& returnType) { // Recursively expand nested types till we get the underlying field type. const std::string nameMember { R"gql(name)gql" }; @@ -1931,9 +2022,9 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) std::string innerType; std::string wrappedType; - auto itrType = getScopedTypeFields(); + const auto& typeRef = getScopedTypeFields(); - if (itrType == _typeFields.cend()) + if (!typeRef) { // http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections auto position = field.begin(); @@ -1945,14 +2036,16 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) return; } + const auto& type = typeRef.value().get(); + switch (*kind) { case introspection::TypeKind::OBJECT: case introspection::TypeKind::INTERFACE: { // http://spec.graphql.org/June2018/#sec-Field-Selections-on-Objects-Interfaces-and-Unions-Types - innerType = getFieldType(itrType->second, name); - wrappedType = getWrappedFieldType(itrType->second, name); + innerType = _validationContext->getFieldType(type, name); + wrappedType = _validationContext->getWrappedFieldType(type, name); break; } @@ -2063,9 +2156,8 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) } } - auto itrField = itrType->second.find(name); - - if (itrField != itrType->second.end()) + const auto& itrField = type.find(name); + if (itrField != type.end()) { while (!argumentNames.empty()) { @@ -2168,9 +2260,8 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) if (subFieldCount == 0) { - auto itrInnerKind = _typeKinds.find(innerType); - - if (itrInnerKind != _typeKinds.end() && !isScalarType(itrInnerKind->second)) + const auto& innerKind = _validationContext->getTypeKind(innerType); + if (innerKind && !_validationContext->isScalarType(innerKind.value())) { // http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections auto position = field.begin(); @@ -2275,17 +2366,15 @@ void ValidateExecutableVisitor::visitInlineFragment(const peg::ast_node& inlineF } else { - auto itrKind = _typeKinds.find(innerType); - - if (itrKind == _typeKinds.end() || isScalarType(itrKind->second)) + auto kind = _validationContext->getTypeKind(innerType); + if (!kind || _validationContext->isScalarType(kind.value())) { // http://spec.graphql.org/June2018/#sec-Fragment-Spread-Type-Existence // http://spec.graphql.org/June2018/#sec-Fragments-On-Composite-Types std::ostringstream message; - message << (itrKind == _typeKinds.end() - ? "Undefined target type on inline fragment name: " - : "Scalar target type on inline fragment name: ") + message << (!kind ? "Undefined target type on inline fragment name: " + : "Scalar target type on inline fragment name: ") << innerType; _errors.push_back({ message.str(), std::move(typeConditionLocation) }); @@ -2342,9 +2431,8 @@ void ValidateExecutableVisitor::visitDirectives( continue; } - auto itrDirective = _directives.find(directiveName); - - if (itrDirective == _directives.end()) + const auto& validateDirectiveRef = _validationContext->getDirective(directiveName); + if (!validateDirectiveRef) { // http://spec.graphql.org/June2018/#sec-Directives-Are-Defined auto position = directive->begin(); @@ -2356,7 +2444,8 @@ void ValidateExecutableVisitor::visitDirectives( continue; } - if (itrDirective->second.locations.find(location) == itrDirective->second.locations.end()) + const auto& validateDirective = validateDirectiveRef.value().get(); + if (validateDirective.locations.find(location) == validateDirective.locations.end()) { // http://spec.graphql.org/June2018/#sec-Directives-Are-In-Valid-Locations auto position = directive->begin(); @@ -2403,7 +2492,7 @@ void ValidateExecutableVisitor::visitDirectives( } peg::on_first_child(*directive, - [this, &directive, &directiveName, itrDirective](const peg::ast_node& child) { + [this, &directive, &directiveName, &validateDirective](const peg::ast_node& child) { ValidateFieldArguments validateArguments; std::map argumentLocations; std::queue argumentNames; @@ -2439,9 +2528,8 @@ void ValidateExecutableVisitor::visitDirectives( argumentNames.pop(); - auto itrArgument = itrDirective->second.arguments.find(argumentName); - - if (itrArgument == itrDirective->second.arguments.end()) + const auto& itrArgument = validateDirective.arguments.find(argumentName); + if (itrArgument == validateDirective.arguments.cend()) { // http://spec.graphql.org/June2018/#sec-Argument-Names std::ostringstream message; @@ -2453,7 +2541,7 @@ void ValidateExecutableVisitor::visitDirectives( } } - for (auto& argument : itrDirective->second.arguments) + for (const auto& argument : validateDirective.arguments) { auto itrArgument = validateArguments.find(argument.first); const bool missing = itrArgument == validateArguments.end(); From e85f36008fc042cc4ec0c608b9a7c56ac13842b6 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 30 Nov 2020 23:55:50 -0300 Subject: [PATCH 12/42] ValidationContext change operationTypes to a struct We do not need a map, there are only 3 well defined names --- include/Validation.h | 10 ++++++++-- src/Validation.cpp | 29 +++++++++++++++++++---------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index a9651115..b33cc729 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -167,6 +167,13 @@ class ValidationContext using FieldTypes = std::map; using InputFieldTypes = ValidateTypeFieldArguments; + struct OperationTypes + { + std::string queryType; + std::string mutationType; + std::string subscriptionType; + }; + std::optional getTypeKind(const std::string& name) const; const ValidateTypeKinds& getTypeKinds() const; std::optional>> getMatchingTypes( @@ -180,7 +187,7 @@ class ValidationContext std::optional> getDirective( const std::string& name) const; std::optional> getOperationType( - const std::string& name) const; + const std::string_view& name) const; bool isKnownScalar(const std::string& name) const; @@ -203,7 +210,6 @@ class ValidationContext using InputTypeFields = std::map; using EnumValues = std::map>; - using OperationTypes = std::map; using Directives = std::map; using MatchingTypes = std::map>; using ScalarTypes = std::set; diff --git a/src/Validation.cpp b/src/Validation.cpp index 0e3fc27e..08a0100c 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -469,15 +469,15 @@ void ValidationContext::populate(const response::Value& introspectionQuery) { if (member.first == R"gql(queryType)gql") { - _operationTypes[strQuery] = itrType->second.get(); + _operationTypes.queryType = itrType->second.get(); } else if (member.first == R"gql(mutationType)gql") { - _operationTypes[strMutation] = itrType->second.get(); + _operationTypes.mutationType = itrType->second.get(); } else if (member.first == R"gql(subscriptionType)gql") { - _operationTypes[strSubscription] = + _operationTypes.subscriptionType = itrType->second.get(); } } @@ -865,7 +865,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op visitDirectives(location, child); }); - const auto& typeRef = _validationContext->getOperationType(std::string { operationType }); + const auto& typeRef = _validationContext->getOperationType(operationType); if (!typeRef) { auto position = operationDefinition.begin(); @@ -1021,14 +1021,23 @@ std::optional> ValidationContext } std::optional> ValidationContext::getOperationType( - const std::string& name) const + const std::string_view& name) const { - const auto& itr = _operationTypes.find(name); - if (itr == _operationTypes.cend()) + if (name == strQuery) { - return std::nullopt; + return std::optional>(_operationTypes.queryType); + } + if (name == strMutation) + { + return std::optional>( + _operationTypes.mutationType); + } + if (name == strSubscription) + { + return std::optional>( + _operationTypes.subscriptionType); } - return std::optional>(itr->second); + return std::nullopt; } std::optional ValidateExecutableVisitor::getScopedTypeKind() const @@ -1668,7 +1677,7 @@ void ValidationContext::addTypeFields( } } - if (typeName == _operationTypes[strQuery]) + if (typeName == _operationTypes.queryType) { response::Value objectKind(response::Type::EnumValue); From 4c6cc75ac3c49b7d9a8fb429a28be358057a2156 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Tue, 1 Dec 2020 18:04:46 -0300 Subject: [PATCH 13/42] add specific ValidateType Instead of using a map with properties `name`, `kind` (string) and `ofType` (another map), use a set of custom classes with kind as enumeration and ofType as shared pointer. This allows much simpler handling and comparison, no need to serialize to string to make it simpler to compare. We can also store reference to types, know which kind (ie: isInputType?) and save memory by using references, in particular to common types such as Int, Float, String... The matchingTypes and fields are stored as part of each ValidateType specialization. --- include/Validation.h | 489 +++++++++++++++++++--- src/Validation.cpp | 957 ++++++++++++++++--------------------------- 2 files changed, 778 insertions(+), 668 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index b33cc729..b9263910 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -11,24 +11,359 @@ namespace graphql::service { -using ValidateType = response::Value; +class ValidateType +{ +public: + virtual introspection::TypeKind kind() const = 0; + virtual const std::string_view name() const = 0; + virtual bool isInputType() const = 0; + virtual bool isValid() const = 0; + virtual std::shared_ptr getInnerType() const = 0; + virtual bool operator==(const ValidateType& other) const = 0; + + static constexpr bool isKindInput(introspection::TypeKind typeKind) + { + switch (typeKind) + { + case introspection::TypeKind::SCALAR: + case introspection::TypeKind::ENUM: + case introspection::TypeKind::INPUT_OBJECT: + return true; + default: + return false; + } + } +}; + +class NamedValidateType + : public ValidateType + , public std::enable_shared_from_this +{ +public: + std::string _name; + + NamedValidateType(const std::string_view& name) + : _name(std::string(name)) + { + } + + virtual const std::string_view name() const + { + return _name; + } + + virtual bool isValid() const + { + return !name().empty(); + } + + virtual std::shared_ptr getInnerType() const + { + return shared_from_this(); + } + + virtual bool operator==(const ValidateType& other) const + { + if (this == &other) + { + return true; + } + + if (kind() != other.kind()) + { + return false; + } + + return _name == other.name(); + } +}; + +template +class NamedType : public NamedValidateType +{ +public: + NamedType(const std::string_view& name) + : NamedValidateType(name) + { + } + + virtual introspection::TypeKind kind() const + { + return typeKind; + } + + virtual bool isInputType() const + { + return ValidateType::isKindInput(typeKind); + } +}; + +using ScalarType = NamedType; + +class EnumType : public NamedType +{ +public: + EnumType(const std::string_view& name, std::set&& values) + : NamedType(name) + , _values(std::move(values)) + { + } + + const std::set& values() const + { + return _values; + } + +private: + std::set _values; +}; + +template +class WrapperOfType : public ValidateType +{ +public: + WrapperOfType(std::shared_ptr ofType) + : _ofType(std::move(ofType)) + { + } + + std::shared_ptr ofType() const + { + return _ofType; + } + + virtual introspection::TypeKind kind() const + { + return typeKind; + } + + virtual const std::string_view name() const + { + return _name; + } + + virtual bool isInputType() const + { + return _ofType ? _ofType->isInputType() : false; + } + + virtual bool isValid() const + { + return _ofType ? _ofType->isValid() : false; + } + + virtual std::shared_ptr getInnerType() const + { + return _ofType ? _ofType->getInnerType() : nullptr; + } + + virtual bool operator==(const ValidateType& otherType) const + { + if (this == &otherType) + { + return true; + } + + if (typeKind != otherType.kind()) + { + return false; + } + + const auto& other = static_cast&>(otherType); + if (_ofType == other.ofType()) + { + return true; + } + if (_ofType && other.ofType()) + { + return *_ofType == *other.ofType(); + } + return false; + } + +private: + std::shared_ptr _ofType; + static inline const std::string_view _name = ""sv; +}; + +using ListOfType = WrapperOfType; +using NonNullOfType = WrapperOfType; struct ValidateArgument { + std::shared_ptr type; bool defaultValue = false; bool nonNullDefaultValue = false; - ValidateType type; }; -using ValidateTypeFieldArguments = std::map; +using ValidateTypeFieldArguments = std::unordered_map; struct ValidateTypeField { - ValidateType returnType; + std::shared_ptr returnType; ValidateTypeFieldArguments arguments; }; -using ValidateDirectiveArguments = std::map; +using ValidateDirectiveArguments = std::unordered_map; + +template +class ContainerValidateType : public NamedValidateType +{ +public: + ContainerValidateType(const std::string_view& name) + : NamedValidateType(name) + { + } + + using FieldsContainer = std::unordered_map; + + std::optional> getField(const std::string& name) const + { + const auto& itr = _fields.find(name); + if (itr == _fields.cend()) + { + return std::nullopt; + } + + return std::optional>(itr->second); + } + + void setFields(FieldsContainer&& fields) + { + _fields = std::move(fields); + } + + typename FieldsContainer::const_iterator begin() const + { + return _fields.cbegin(); + } + + typename FieldsContainer::const_iterator end() const + { + return _fields.cend(); + } + + virtual bool matchesType(const ValidateType& other) const + { + if (*this == other) + { + return true; + } + + switch (other.kind()) + { + case introspection::TypeKind::INTERFACE: + case introspection::TypeKind::UNION: + return static_cast(other).matchesType(*this); + + default: + return false; + } + } + +private: + FieldsContainer _fields; +}; + +template +class ContainerType : public ContainerValidateType +{ +public: + ContainerType(const std::string_view& name) + : ContainerValidateType(name) + { + } + + virtual introspection::TypeKind kind() const + { + return typeKind; + } + + virtual bool isInputType() const + { + return ValidateType::isKindInput(typeKind); + } +}; + +using ObjectType = ContainerType; +using InputObjectType = ContainerType; + +class PossibleTypesContainerValidateType : public ContainerValidateType +{ +public: + PossibleTypesContainerValidateType(const std::string_view& name) + : ContainerValidateType(name) + { + } + + const std::set& possibleTypes() const + { + return _possibleTypes; + } + + virtual bool matchesType(const ValidateType& other) const + { + if (*this == other) + { + return true; + } + + switch (other.kind()) + { + case introspection::TypeKind::OBJECT: + return _possibleTypes.find(&other) != _possibleTypes.cend(); + + case introspection::TypeKind::INTERFACE: + case introspection::TypeKind::UNION: + { + const auto& types = + static_cast(other).possibleTypes(); + for (const auto& itr : _possibleTypes) + { + if (types.find(itr) != types.cend()) + { + return true; + } + } + return false; + } + + default: + return false; + } + } + + void setPossibleTypes(std::set&& possibleTypes) + { + _possibleTypes = std::move(possibleTypes); + } + +private: + std::set _possibleTypes; +}; + +template +class PossibleTypesContainer : public PossibleTypesContainerValidateType +{ +public: + PossibleTypesContainer(const std::string_view& name) + : PossibleTypesContainerValidateType(name) + { + } + + virtual introspection::TypeKind kind() const + { + return typeKind; + } + + virtual bool isInputType() const + { + return ValidateType::isKindInput(typeKind); + } +}; + +using InterfaceType = PossibleTypesContainer; +using UnionType = PossibleTypesContainer; struct ValidateDirective { @@ -122,40 +457,40 @@ using ValidateFieldArguments = std::map; struct ValidateField { - ValidateField(std::string&& returnType, std::optional&& objectType, - const std::string& fieldName, ValidateFieldArguments&& arguments); + ValidateField(std::shared_ptr returnType, + std::shared_ptr&& objectType, const std::string& fieldName, + ValidateFieldArguments&& arguments); bool operator==(const ValidateField& other) const; - std::string returnType; - std::optional objectType; + std::shared_ptr returnType; + std::shared_ptr objectType; std::string fieldName; ValidateFieldArguments arguments; }; -using ValidateTypeKinds = std::map; +class ValidationContext; // ValidateVariableTypeVisitor visits the AST and builds a ValidateType structure representing // a variable type in an operation definition as if it came from an Introspection query. class ValidateVariableTypeVisitor { public: - ValidateVariableTypeVisitor(const ValidateTypeKinds& typeKinds); + ValidateVariableTypeVisitor(const ValidationContext& validationContext); void visit(const peg::ast_node& typeName); bool isInputType() const; - ValidateType getType(); + std::shared_ptr getType(); private: void visitNamedType(const peg::ast_node& namedType); void visitListType(const peg::ast_node& listType); void visitNonNullType(const peg::ast_node& nonNullType); - const ValidateTypeKinds& _typeKinds; + const ValidationContext& _validationContext; - bool _isInputType = false; - ValidateType _variableType; + std::shared_ptr _variableType; }; class ValidationContext @@ -164,9 +499,6 @@ class ValidationContext ValidationContext(const Request& service); ValidationContext(const response::Value& introspectionQuery); - using FieldTypes = std::map; - using InputFieldTypes = ValidateTypeFieldArguments; - struct OperationTypes { std::string queryType; @@ -174,67 +506,96 @@ class ValidationContext std::string subscriptionType; }; - std::optional getTypeKind(const std::string& name) const; - const ValidateTypeKinds& getTypeKinds() const; - std::optional>> getMatchingTypes( - const std::string& name) const; - std::optional> getTypeFields( - const std::string& name) const; - std::optional> getInputTypeFields( - const std::string& name) const; - std::optional>> getEnumValues( - const std::string& name) const; std::optional> getDirective( const std::string& name) const; std::optional> getOperationType( const std::string_view& name) const; - bool isKnownScalar(const std::string& name) const; - - template - static std::string getFieldType(const _FieldTypes& fields, const std::string& name); - template - static std::string getWrappedFieldType(const _FieldTypes& fields, const std::string& name); - static std::string getWrappedFieldType(const ValidateType& returnType); - - static constexpr bool isScalarType(introspection::TypeKind kind); + template ::value>::type* = nullptr> + std::shared_ptr getNamedValidateType(const std::string_view& name) const; + template ::value>::type* = nullptr> + std::shared_ptr getListOfType(std::shared_ptr&& ofType) const; + template ::value>::type* = nullptr> + std::shared_ptr getNonNullOfType(std::shared_ptr&& ofType) const; private: void populate(const response::Value& introspectionQuery); - static ValidateTypeFieldArguments getArguments(const response::ListType& argumentsMember); - static const ValidateType& getValidateFieldType(const FieldTypes::mapped_type& value); - static const ValidateType& getValidateFieldType(const InputFieldTypes::mapped_type& value); + struct + { + std::shared_ptr string; + std::shared_ptr nonNullString; + } commonTypes; - using TypeFields = std::map; - using InputTypeFields = std::map; - using EnumValues = std::map>; + ValidateTypeFieldArguments getArguments(const response::ListType& argumentsMember); using Directives = std::map; - using MatchingTypes = std::map>; - using ScalarTypes = std::set; // These members store Introspection schema information which does not change between queries. OperationTypes _operationTypes; - ValidateTypeKinds _typeKinds; - MatchingTypes _matchingTypes; Directives _directives; - EnumValues _enumValues; - ScalarTypes _scalarTypes; - TypeFields _typeFields; - InputTypeFields _inputTypeFields; + std::unordered_map> _listOfCache; + std::unordered_map> _nonNullCache; + std::unordered_map> _namedCache; + + template ::value>::type* = nullptr> + std::shared_ptr makeNamedValidateType(T&& typeDef); + + template ::value>::type* = nullptr> + std::shared_ptr makeListOfType(std::shared_ptr&& ofType); + + template ::value>::type* = nullptr> + std::shared_ptr makeListOfType(std::shared_ptr& ofType) + { + return makeListOfType(std::shared_ptr(ofType)); + } + + template ::value>::type* = nullptr> + std::shared_ptr makeNonNullOfType(std::shared_ptr&& ofType); + + template ::value>::type* = nullptr> + std::shared_ptr makeNonNullOfType(std::shared_ptr& ofType) + { + return makeNonNullOfType(std::shared_ptr(ofType)); + } + + std::shared_ptr makeScalarType(const std::string_view& name) + { + return makeNamedValidateType(ScalarType { name }); + } + + std::shared_ptr makeObjectType(const std::string_view& name) + { + return makeNamedValidateType(ObjectType { name }); + } + + std::shared_ptr getTypeFromMap(const response::Value& typeMap); // builds the validation context (lookup maps) - void addScalar(const std::string& scalarName); - void addEnum(const std::string& enumName, const response::Value& enumDescriptionMap); - void addObject(const std::string& name, const response::Value& typeDescriptionMap); - void addInputObject(const std::string& name, const response::Value& typeDescriptionMap); - void addInterfaceOrUnion(const std::string& name, const response::Value& typeDescriptionMap); + void addScalar(const std::string_view& scalarName); + void addEnum(const std::string_view& enumName, const response::Value& enumDescriptionMap); + void addObject(const std::string_view& name); + void addInputObject(const std::string_view& name); + void addInterface(const std::string_view& name, const response::Value& typeDescriptionMap); + void addUnion(const std::string_view& name, const response::Value& typeDescriptionMap); void addDirective(const std::string& name, const response::ListType& locations, const response::Value& descriptionMap); - void addTypeFields(const std::string& typeName, const response::Value& typeDescriptionMap); - void addInputTypeFields(const std::string& typeName, const response::Value& typeDescriptionMap); + + void addTypeFields(std::shared_ptr> type, + const response::Value& typeDescriptionMap); + void addPossibleTypes(std::shared_ptr type, + const response::Value& typeDescriptionMap); + void addInputTypeFields( + std::shared_ptr type, const response::Value& typeDescriptionMap); }; // ValidateExecutableVisitor visits the AST and validates that it is executable against the service @@ -252,11 +613,7 @@ class ValidateExecutableVisitor std::vector getStructuredErrors(); private: - std::optional getScopedTypeKind() const; - bool matchesScopedType(const std::string& name) const; - - std::optional> getScopedTypeFields() - const; + bool matchesScopedType(const ValidateType& name) const; void visitFragmentDefinition(const peg::ast_node& fragmentDefinition); void visitOperationDefinition(const peg::ast_node& operationDefinition); @@ -299,8 +656,12 @@ class ValidateExecutableVisitor VariableSet _referencedVariables; FragmentSet _fragmentStack; size_t _fieldCount = 0; - std::string _scopedType; + std::shared_ptr _scopedType; std::map _selectionFields; + struct + { + std::shared_ptr nonNullString; + } commonTypes; }; } /* namespace graphql::service */ diff --git a/src/Validation.cpp b/src/Validation.cpp index 08a0100c..ea3b019b 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -309,9 +309,10 @@ void ValidateArgumentValueVisitor::visitObjectValue(const peg::ast_node& objectV _argumentValue.position = { position.line, position.column }; } -ValidateField::ValidateField(std::string&& returnType, std::optional&& objectType, - const std::string& fieldName, ValidateFieldArguments&& arguments) - : returnType(std::move(returnType)) +ValidateField::ValidateField(std::shared_ptr returnType, + std::shared_ptr&& objectType, const std::string& fieldName, + ValidateFieldArguments&& arguments) + : returnType(returnType) , objectType(std::move(objectType)) , fieldName(fieldName) , arguments(std::move(arguments)) @@ -320,17 +321,14 @@ ValidateField::ValidateField(std::string&& returnType, std::optionalsecond) - { - case introspection::TypeKind::SCALAR: - kind.set(R"gql(SCALAR)gql"); - break; - - case introspection::TypeKind::ENUM: - kind.set(R"gql(ENUM)gql"); - break; - - case introspection::TypeKind::INPUT_OBJECT: - kind.set(R"gql(INPUT_OBJECT)gql"); - break; - - default: - return; - } - - _isInputType = true; - _variableType.emplace_back(R"gql(kind)gql", std::move(kind)); - _variableType.emplace_back(R"gql(name)gql", response::Value(std::move(name))); + _variableType = _validationContext.getNamedValidateType(namedType.string_view()); } void ValidateVariableTypeVisitor::visitListType(const peg::ast_node& listType) { - response::Value kind(response::Type::EnumValue); - ValidateVariableTypeVisitor visitor(_typeKinds); + ValidateVariableTypeVisitor visitor(_validationContext); - kind.set(R"gql(LIST)gql"); visitor.visit(*listType.children.front()); - _isInputType = visitor.isInputType(); - _variableType.emplace_back(R"gql(kind)gql", std::move(kind)); - _variableType.emplace_back(R"gql(ofType)gql", visitor.getType()); + + _variableType = _validationContext.getListOfType(visitor.getType()); } void ValidateVariableTypeVisitor::visitNonNullType(const peg::ast_node& nonNullType) { - response::Value kind(response::Type::EnumValue); - ValidateVariableTypeVisitor visitor(_typeKinds); + ValidateVariableTypeVisitor visitor(_validationContext); - kind.set(R"gql(NON_NULL)gql"); visitor.visit(*nonNullType.children.front()); - _isInputType = visitor.isInputType(); - _variableType.emplace_back(R"gql(kind)gql", std::move(kind)); - _variableType.emplace_back(R"gql(ofType)gql", visitor.getType()); + + _variableType = _validationContext.getNonNullOfType(visitor.getType()); } bool ValidateVariableTypeVisitor::isInputType() const { - return _isInputType; + return _variableType && _variableType->isInputType(); } -ValidateType ValidateVariableTypeVisitor::getType() +std::shared_ptr ValidateVariableTypeVisitor::getType() { auto result = std::move(_variableType); @@ -448,6 +410,9 @@ ValidationContext::ValidationContext(const response::Value& introspectionQuery) void ValidationContext::populate(const response::Value& introspectionQuery) { + commonTypes.string = makeScalarType("String"); + commonTypes.nonNullString = makeNonNullOfType(commonTypes.string); + const auto& itrData = introspectionQuery.find(std::string { strData }); if (itrData == introspectionQuery.end()) { @@ -486,6 +451,8 @@ void ValidationContext::populate(const response::Value& introspectionQuery) && member.first == R"gql(types)gql") { const auto& entries = member.second.get(); + + // first iteration add the named types for (const auto& entry : entries) { if (entry.type() != response::Type::Map) @@ -504,20 +471,21 @@ void ValidationContext::populate(const response::Value& introspectionQuery) const auto& kind = ModifiedArgument::convert(itrKind->second); - if (!isScalarType(kind)) + if (kind == introspection::TypeKind::OBJECT) + { + addObject(name); + } + else if (kind == introspection::TypeKind::INPUT_OBJECT) + { + addInputObject(name); + } + else if (kind == introspection::TypeKind::INTERFACE) + { + addInterface(name, entry); + } + else if (kind == introspection::TypeKind::UNION) { - if (kind == introspection::TypeKind::OBJECT) - { - addObject(name, entry); - } - else if (kind == introspection::TypeKind::INPUT_OBJECT) - { - addInputObject(name, entry); - } - else - { - addInterfaceOrUnion(name, entry); - } + addUnion(name, entry); } else if (kind == introspection::TypeKind::ENUM) { @@ -527,8 +495,46 @@ void ValidationContext::populate(const response::Value& introspectionQuery) { addScalar(name); } + } + } - _typeKinds[std::move(name)] = kind; + // second iteration add the fields that refer to given types + for (const auto& entry : entries) + { + if (entry.type() != response::Type::Map) + { + continue; + } + + const auto& itrName = entry.find(R"gql(name)gql"); + const auto& itrKind = entry.find(R"gql(kind)gql"); + + if (itrName != entry.end() && itrName->second.type() == response::Type::String + && itrKind != entry.end() + && itrKind->second.type() == response::Type::EnumValue) + { + const auto& name = itrName->second.get(); + const auto& kind = + ModifiedArgument::convert(itrKind->second); + + if (kind == introspection::TypeKind::OBJECT) + { + auto type = getNamedValidateType(name); + addTypeFields(type, entry); + } + else if (kind == introspection::TypeKind::INTERFACE + || kind == introspection::TypeKind::UNION) + { + auto type = + getNamedValidateType(name); + addTypeFields(type, entry); + addPossibleTypes(type, entry); + } + else if (kind == introspection::TypeKind::INPUT_OBJECT) + { + auto type = getNamedValidateType(name); + addInputTypeFields(type, entry); + } } } } @@ -563,7 +569,7 @@ void ValidationContext::populate(const response::Value& introspectionQuery) } ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) - : _validationContext(std::make_shared(service)) + : ValidateExecutableVisitor(std::make_shared(service)) { } @@ -571,6 +577,8 @@ ValidateExecutableVisitor::ValidateExecutableVisitor( std::shared_ptr validationContext) : _validationContext(validationContext) { + commonTypes.nonNullString = _validationContext->getNonNullOfType( + _validationContext->getNamedValidateType("String")); } void ValidateExecutableVisitor::visit(const peg::ast_node& root) @@ -707,20 +715,19 @@ void ValidateExecutableVisitor::visitFragmentDefinition(const peg::ast_node& fra const auto name = fragmentDefinition.children.front()->string(); const auto& selection = *fragmentDefinition.children.back(); const auto& typeCondition = fragmentDefinition.children[1]; - auto innerType = typeCondition->children.front()->string(); - - auto kind = _validationContext->getTypeKind(innerType); + const auto& innerTypeName = typeCondition->children.front()->string_view(); + const auto& innerType = _validationContext->getNamedValidateType(innerTypeName); - if (!kind || _validationContext->isScalarType(*kind)) + if (!innerType || innerType->isInputType()) { // http://spec.graphql.org/June2018/#sec-Fragment-Spread-Type-Existence // http://spec.graphql.org/June2018/#sec-Fragments-On-Composite-Types auto position = typeCondition->begin(); std::ostringstream message; - message << (!kind ? "Undefined target type on fragment definition: " - : "Scalar target type on fragment definition: ") - << name << " name: " << innerType; + message << (!innerType ? "Undefined target type on fragment definition: " + : "Scalar target type on fragment definition: ") + << name << " name: " << innerTypeName; _errors.push_back({ message.str(), { position.line, position.column } }); return; @@ -731,7 +738,7 @@ void ValidateExecutableVisitor::visitFragmentDefinition(const peg::ast_node& fra visitSelection(selection); - _scopedType.clear(); + _scopedType.reset(); _fragmentStack.clear(); _selectionFields.clear(); } @@ -788,7 +795,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op else if (child->is_type() || child->is_type() || child->is_type()) { - ValidateVariableTypeVisitor visitor(_validationContext->getTypeKinds()); + ValidateVariableTypeVisitor visitor(*_validationContext); visitor.visit(*child); @@ -821,7 +828,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op auto argument = visitor.getArgumentValue(); - if (!validateInputValue(false, argument, variableArgument.type)) + if (!validateInputValue(false, argument, *variableArgument.type)) { // http://spec.graphql.org/June2018/#sec-Values-of-Correct-Type auto position = child->begin(); @@ -877,7 +884,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op return; } - _scopedType = typeRef.value(); + _scopedType = _validationContext->getNamedValidateType(typeRef.value().get()); _fieldCount = 0; const auto& selection = *operationDefinition.children.back(); @@ -900,7 +907,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op _errors.push_back({ error.str(), { position.line, position.column } }); } - _scopedType.clear(); + _scopedType.reset(); _fragmentStack.clear(); _selectionFields.clear(); @@ -966,7 +973,7 @@ ValidateTypeFieldArguments ValidationContext::getArguments(const response::ListT && itrDefaultValue->second.type() == response::Type::String); argument.nonNullDefaultValue = argument.defaultValue && itrDefaultValue->second.get() != R"gql(null)gql"; - argument.type = ValidateType(itrType->second); + argument.type = getTypeFromMap(itrType->second); result[itrName->second.get()] = std::move(argument); } @@ -975,40 +982,6 @@ ValidateTypeFieldArguments ValidationContext::getArguments(const response::ListT return result; } -std::optional ValidationContext::getTypeKind(const std::string& name) const -{ - auto itrKind = _typeKinds.find(name); - - return (itrKind == _typeKinds.cend() ? std::nullopt : std::make_optional(itrKind->second)); -} - -const ValidateTypeKinds& ValidationContext::getTypeKinds() const -{ - return _typeKinds; -} - -std::optional>> ValidationContext:: - getMatchingTypes(const std::string& name) const -{ - const auto& itr = _matchingTypes.find(name); - if (itr == _matchingTypes.cend()) - { - return std::nullopt; - } - return std::optional>>(itr->second); -} - -std::optional> ValidationContext:: - getTypeFields(const std::string& name) const -{ - const auto& itr = _typeFields.find(name); - if (itr == _typeFields.cend()) - { - return std::nullopt; - } - return std::optional>(itr->second); -} - std::optional> ValidationContext::getDirective( const std::string& name) const { @@ -1040,62 +1013,61 @@ std::optional> ValidationContext::getO return std::nullopt; } -std::optional ValidateExecutableVisitor::getScopedTypeKind() const +template ::value>::type*> +std::shared_ptr ValidationContext::getNamedValidateType(const std::string_view& name) const { - return _validationContext->getTypeKind(_scopedType); + const auto& itr = _namedCache.find(name); + if (itr != _namedCache.cend()) + { + return std::dynamic_pointer_cast(itr->second); + } + + return nullptr; } -constexpr bool ValidationContext::isScalarType(introspection::TypeKind kind) +template ::value>::type*> +std::shared_ptr ValidationContext::getListOfType(std::shared_ptr&& ofType) const { - switch (kind) + const auto& itr = _listOfCache.find(ofType.get()); + if (itr != _listOfCache.cend()) { - case introspection::TypeKind::OBJECT: - case introspection::TypeKind::INTERFACE: - case introspection::TypeKind::UNION: - case introspection::TypeKind::INPUT_OBJECT: - return false; - - default: - return true; + return itr->second; } -} -bool ValidationContext::isKnownScalar(const std::string& name) const -{ - const auto& itr = _scalarTypes.find(name); - return itr != _scalarTypes.cend(); + return nullptr; } -bool ValidateExecutableVisitor::matchesScopedType(const std::string& name) const +template ::value>::type*> +std::shared_ptr ValidationContext::getNonNullOfType( + std::shared_ptr&& ofType) const { - if (name == _scopedType) + const auto& itr = _nonNullCache.find(ofType.get()); + if (itr != _nonNullCache.cend()) { - return true; + return itr->second; } - const auto& scoped = _validationContext->getMatchingTypes(_scopedType); - const auto& named = _validationContext->getMatchingTypes(name); + return nullptr; +} - if (scoped && named) +bool ValidateExecutableVisitor::matchesScopedType(const ValidateType& otherType) const +{ + switch (_scopedType->kind()) { - const std::set& scopedSet = scoped.value().get(); - const std::set& namedSet = named.value().get(); - auto itrMatch = std::find_if(scopedSet.cbegin(), - scopedSet.cend(), - [this, namedSet](const std::string& matchingType) noexcept { - return namedSet.find(matchingType) != namedSet.cend(); - }); - - return itrMatch != scopedSet.cend(); + case introspection::TypeKind::INTERFACE: + case introspection::TypeKind::OBJECT: + case introspection::TypeKind::UNION: + return static_cast&>(*_scopedType) + .matchesType(otherType); + default: + return false; } - - return false; } bool ValidateExecutableVisitor::validateInputValue( bool hasNonNullDefaultValue, const ValidateArgumentValuePtr& argument, const ValidateType& type) { - if (type.type() != response::Type::Map) + if (!type.isValid()) { _errors.push_back({ "Unknown input type", argument.position }); return false; @@ -1123,7 +1095,7 @@ bool ValidateExecutableVisitor::validateInputValue( return validateVariableType( hasNonNullDefaultValue || itrVariable->second.nonNullDefaultValue, - itrVariable->second.type, + *itrVariable->second.type, argument.position, type); } @@ -1137,20 +1109,10 @@ bool ValidateExecutableVisitor::validateInputValue( } } - auto itrKind = type.find(R"gql(kind)gql"); - - if (itrKind == type.end() || itrKind->second.type() != response::Type::EnumValue) - { - _errors.push_back({ "Unknown input type", argument.position }); - return false; - } - - auto kind = ModifiedArgument::convert(itrKind->second); - if (!argument.value) { // The null literal matches any nullable type and does not match a non-nullable type. - if (kind == introspection::TypeKind::NON_NULL && !hasNonNullDefaultValue) + if (type.kind() == introspection::TypeKind::NON_NULL && !hasNonNullDefaultValue) { _errors.push_back({ "Expected Non-Null value", argument.position }); return false; @@ -1159,20 +1121,18 @@ bool ValidateExecutableVisitor::validateInputValue( return true; } - switch (kind) + switch (type.kind()) { case introspection::TypeKind::NON_NULL: { - // Unwrap and check the next one. - auto itrOfType = type.find(R"gql(ofType)gql"); - - if (itrOfType == type.end() || itrOfType->second.type() != response::Type::Map) + const auto& ofType = static_cast(type).ofType(); + if (!ofType) { _errors.push_back({ "Unknown Non-Null type", argument.position }); return false; } - return validateInputValue(hasNonNullDefaultValue, argument, itrOfType->second); + return validateInputValue(hasNonNullDefaultValue, argument, *ofType); } case introspection::TypeKind::LIST: @@ -1183,9 +1143,8 @@ bool ValidateExecutableVisitor::validateInputValue( return false; } - auto itrOfType = type.find(R"gql(ofType)gql"); - - if (itrOfType == type.end() || itrOfType->second.type() != response::Type::Map) + const auto& ofType = static_cast(type).ofType(); + if (!ofType) { _errors.push_back({ "Unknown List type", argument.position }); return false; @@ -1194,7 +1153,7 @@ bool ValidateExecutableVisitor::validateInputValue( // Check every value against the target type. for (const auto& value : std::get(argument.value->data).values) { - if (!validateInputValue(false, value, itrOfType->second)) + if (!validateInputValue(false, value, *ofType)) { // Error messages are added in the recursive call, so just bubble up the result. return false; @@ -1206,16 +1165,13 @@ bool ValidateExecutableVisitor::validateInputValue( case introspection::TypeKind::INPUT_OBJECT: { - auto itrName = type.find(R"gql(name)gql"); - - if (itrName == type.end() || itrName->second.type() != response::Type::String) + if (!type.isValid()) { _errors.push_back({ "Unknown Input Object type", argument.position }); return false; } - const auto& name = itrName->second.get(); - + const auto& name = type.name(); if (!std::holds_alternative(argument.value->data)) { std::ostringstream message; @@ -1226,26 +1182,16 @@ bool ValidateExecutableVisitor::validateInputValue( return false; } - const auto& fieldsRef = _validationContext->getInputTypeFields(name); - if (!fieldsRef) - { - std::ostringstream message; - - message << "Expected Input Object fields name: " << name; + const auto& inputObj = static_cast(type); - _errors.push_back({ message.str(), argument.position }); - return false; - } - - const auto& fields = fieldsRef.value().get(); const auto& values = std::get(argument.value->data).values; std::set subFields; // Check every value against the target type. for (const auto& entry : values) { - const auto& itrField = fields.find(entry.first); - if (itrField == fields.cend()) + const auto& fieldOpt = inputObj.getField(entry.first); + if (!fieldOpt) { // http://spec.graphql.org/June2018/#sec-Input-Object-Field-Names std::ostringstream message; @@ -1257,11 +1203,11 @@ bool ValidateExecutableVisitor::validateInputValue( return false; } - if (entry.second.value || !itrField->second.defaultValue) + const auto& field = fieldOpt.value().get(); + + if (entry.second.value || !field.defaultValue) { - if (!validateInputValue(itrField->second.nonNullDefaultValue, - entry.second, - itrField->second.type)) + if (!validateInputValue(field.nonNullDefaultValue, entry.second, *field.type)) { // Error messages are added in the recursive call, so just bubble up the // result. @@ -1273,30 +1219,14 @@ bool ValidateExecutableVisitor::validateInputValue( } // See if all required fields were specified. - for (const auto& entry : fields) + for (const auto& entry : inputObj) { if (entry.second.defaultValue || subFields.find(entry.first) != subFields.end()) { continue; } - auto itrFieldKind = entry.second.type.find(R"gql(kind)gql"); - - if (itrFieldKind == entry.second.type.end() - || itrFieldKind->second.type() != response::Type::EnumValue) - { - std::ostringstream message; - - message << "Unknown Input Object field type: " << name - << " name: " << entry.first; - - _errors.push_back({ message.str(), argument.position }); - return false; - } - - auto fieldKind = - ModifiedArgument::convert(itrFieldKind->second); - + auto fieldKind = entry.second.type->kind(); if (fieldKind == introspection::TypeKind::NON_NULL) { // http://spec.graphql.org/June2018/#sec-Input-Object-Required-Fields @@ -1315,16 +1245,13 @@ bool ValidateExecutableVisitor::validateInputValue( case introspection::TypeKind::ENUM: { - auto itrName = type.find(R"gql(name)gql"); - - if (itrName == type.end() || itrName->second.type() != response::Type::String) + if (!type.isValid()) { _errors.push_back({ "Unknown Enum value", argument.position }); return false; } - const auto& name = itrName->second.get(); - + const auto& name = type.name(); if (!std::holds_alternative(argument.value->data)) { std::ostringstream message; @@ -1336,10 +1263,9 @@ bool ValidateExecutableVisitor::validateInputValue( } const auto& value = std::get(argument.value->data).value; - const auto& enumValuesRef = _validationContext->getEnumValues(name); + const auto& enumValues = static_cast(type).values(); - if (!enumValuesRef - || enumValuesRef.value().get().find(value) == enumValuesRef.value().get().end()) + if (enumValues.find(value) == enumValues.cend()) { std::ostringstream message; @@ -1354,16 +1280,13 @@ bool ValidateExecutableVisitor::validateInputValue( case introspection::TypeKind::SCALAR: { - auto itrName = type.find(R"gql(name)gql"); - - if (itrName == type.end() || itrName->second.type() != response::Type::String) + if (!type.isValid()) { _errors.push_back({ "Unknown Scalar value", argument.position }); return false; } - const auto& name = itrName->second.get(); - + const auto& name = type.name(); if (name == R"gql(Int)gql") { if (!std::holds_alternative(argument.value->data)) @@ -1418,16 +1341,6 @@ bool ValidateExecutableVisitor::validateInputValue( } } - if (!_validationContext->isKnownScalar(name)) - { - std::ostringstream message; - - message << "Undefined Scalar type name: " << name; - - _errors.push_back({ message.str(), argument.position }); - return false; - } - return true; } @@ -1443,53 +1356,20 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, const ValidateType& variableType, const schema_location& position, const ValidateType& inputType) { - if (variableType.type() != response::Type::Map) - { - _errors.push_back({ "Unknown variable type", position }); - return false; - } - - auto itrVariableKind = variableType.find(R"gql(kind)gql"); - - if (itrVariableKind == variableType.end() - || itrVariableKind->second.type() != response::Type::EnumValue) - { - _errors.push_back({ "Unknown variable type", position }); - return false; - } - - auto variableKind = ModifiedArgument::convert(itrVariableKind->second); - + auto variableKind = variableType.kind(); if (variableKind == introspection::TypeKind::NON_NULL) { - auto itrVariableOfType = variableType.find(R"gql(ofType)gql"); - - if (itrVariableOfType == variableType.end() - || itrVariableOfType->second.type() != response::Type::Map) + const auto variableOfType = static_cast(variableType).ofType(); + if (!variableOfType) { _errors.push_back({ "Unknown Non-Null variable type", position }); return false; } - return validateVariableType(true, itrVariableOfType->second, position, inputType); - } - - if (inputType.type() != response::Type::Map) - { - _errors.push_back({ "Unknown input type", position }); - return false; - } - - auto itrInputKind = inputType.find(R"gql(kind)gql"); - - if (itrInputKind == inputType.end() || itrInputKind->second.type() != response::Type::EnumValue) - { - _errors.push_back({ "Unknown input type", position }); - return false; + return validateVariableType(true, *variableOfType, position, inputType); } - auto inputKind = ModifiedArgument::convert(itrInputKind->second); - + auto inputKind = inputType.kind(); switch (inputKind) { case introspection::TypeKind::NON_NULL: @@ -1502,16 +1382,14 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, } // Unwrap and check the next one. - auto itrInputOfType = inputType.find(R"gql(ofType)gql"); - - if (itrInputOfType == inputType.end() - || itrInputOfType->second.type() != response::Type::Map) + const auto inputOfType = static_cast(inputType).ofType(); + if (!inputOfType) { _errors.push_back({ "Unknown Non-Null input type", position }); return false; } - return validateVariableType(false, variableType, position, itrInputOfType->second); + return validateVariableType(false, variableType, position, *inputOfType); } case introspection::TypeKind::LIST: @@ -1524,28 +1402,22 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, } // Unwrap and check the next one. - auto itrVariableOfType = variableType.find(R"gql(ofType)gql"); + const auto variableOfType = static_cast(variableType).ofType(); - if (itrVariableOfType == variableType.end() - || itrVariableOfType->second.type() != response::Type::Map) + if (!variableOfType) { _errors.push_back({ "Unknown List variable type", position }); return false; } - auto itrInputOfType = inputType.find(R"gql(ofType)gql"); - - if (itrInputOfType == inputType.end() - || itrInputOfType->second.type() != response::Type::Map) + const auto inputOfType = static_cast(inputType).ofType(); + if (!inputOfType) { _errors.push_back({ "Unknown List input type", position }); return false; } - return validateVariableType(false, - itrVariableOfType->second, - position, - itrInputOfType->second); + return validateVariableType(false, *variableOfType, position, *inputOfType); } case introspection::TypeKind::INPUT_OBJECT: @@ -1592,27 +1464,20 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, } } - auto itrVariableName = variableType.find(R"gql(name)gql"); - - if (itrVariableName == variableType.end() - || itrVariableName->second.type() != response::Type::String) + const auto& variableName = variableType.name(); + if (variableName.empty()) { _errors.push_back({ "Unknown variable type", position }); return false; } - const auto& variableName = itrVariableName->second.get(); - - auto itrInputName = inputType.find(R"gql(name)gql"); - - if (itrInputName == inputType.end() || itrInputName->second.type() != response::Type::String) + const auto& inputName = inputType.name(); + if (inputName.empty()) { _errors.push_back({ "Unknown input type", position }); return false; } - const auto& inputName = itrInputName->second.get(); - if (variableName != inputName) { // http://spec.graphql.org/June2018/#sec-All-Variable-Usages-are-Allowed @@ -1627,21 +1492,11 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, return true; } -std::optional> -ValidateExecutableVisitor::getScopedTypeFields() const -{ - return _validationContext->getTypeFields(_scopedType); -} - void ValidationContext::addTypeFields( - const std::string& typeName, const response::Value& typeDescriptionMap) + std::shared_ptr> type, + const response::Value& typeDescriptionMap) { - std::map fields; - response::Value scalarKind(response::Type::EnumValue); - response::Value nonNullKind(response::Type::EnumValue); - - scalarKind.set(R"gql(SCALAR)gql"); - nonNullKind.set(R"gql(NON_NULL)gql"); + std::unordered_map fields; const auto& itrFields = typeDescriptionMap.find(R"gql(fields)gql"); if (itrFields != typeDescriptionMap.end() && itrFields->second.type() == response::Type::List) @@ -1665,7 +1520,7 @@ void ValidationContext::addTypeFields( const auto& fieldName = itrFieldName->second.get(); ValidateTypeField subField; - subField.returnType = ValidateType(itrFieldType->second); + subField.returnType = getTypeFromMap(itrFieldType->second); const auto& itrArgs = entry.find(R"gql(args)gql"); if (itrArgs != entry.end() && itrArgs->second.type() == response::Type::List) @@ -1677,102 +1532,72 @@ void ValidationContext::addTypeFields( } } - if (typeName == _operationTypes.queryType) + if (type->name() == _operationTypes.queryType) { - response::Value objectKind(response::Type::EnumValue); - - objectKind.set(R"gql(OBJECT)gql"); - - ValidateTypeField schemaField; - response::Value schemaType(response::Type::Map); - response::Value notNullSchemaType(response::Type::Map); - - schemaType.emplace_back(R"gql(kind)gql", response::Value(objectKind)); - schemaType.emplace_back(R"gql(name)gql", response::Value(R"gql(__Schema)gql")); - notNullSchemaType.emplace_back(R"gql(kind)gql", response::Value(nonNullKind)); - notNullSchemaType.emplace_back(R"gql(ofType)gql", std::move(schemaType)); - schemaField.returnType = std::move(notNullSchemaType); - fields[R"gql(__schema)gql"] = std::move(schemaField); - - ValidateTypeField typeField; - response::Value typeType(response::Type::Map); + fields[R"gql(__schema)gql"] = + ValidateTypeField { makeNonNullOfType(makeObjectType(R"gql(__Schema)gql")) }; - typeType.emplace_back(R"gql(kind)gql", response::Value(objectKind)); - typeType.emplace_back(R"gql(name)gql", response::Value(R"gql(__Type)gql")); - typeField.returnType = std::move(typeType); - - ValidateArgument nameArgument; - response::Value typeNameArg(response::Type::Map); - response::Value nonNullTypeNameArg(response::Type::Map); - - typeNameArg.emplace_back(R"gql(kind)gql", response::Value(scalarKind)); - typeNameArg.emplace_back(R"gql(name)gql", response::Value(R"gql(String)gql")); - nonNullTypeNameArg.emplace_back(R"gql(kind)gql", response::Value(nonNullKind)); - nonNullTypeNameArg.emplace_back(R"gql(ofType)gql", std::move(typeNameArg)); - nameArgument.type = std::move(nonNullTypeNameArg); - - typeField.arguments[R"gql(name)gql"] = std::move(nameArgument); - - fields[R"gql(__type)gql"] = std::move(typeField); + fields[R"gql(__type)gql"] = ValidateTypeField { makeObjectType(R"gql(__Type)gql"), + ValidateTypeFieldArguments { + { R"gql(name)gql", ValidateArgument { commonTypes.nonNullString } } } }; } } - ValidateTypeField typenameField; - response::Value typenameType(response::Type::Map); - response::Value notNullTypenameType(response::Type::Map); - - typenameType.emplace_back(R"gql(kind)gql", response::Value(scalarKind)); - typenameType.emplace_back(R"gql(name)gql", response::Value(R"gql(String)gql")); - notNullTypenameType.emplace_back(R"gql(kind)gql", response::Value(nonNullKind)); - notNullTypenameType.emplace_back(R"gql(ofType)gql", std::move(typenameType)); - typenameField.returnType = std::move(notNullTypenameType); - fields[R"gql(__typename)gql"] = std::move(typenameField); + fields[R"gql(__typename)gql"] = ValidateTypeField { commonTypes.nonNullString }; - _typeFields.insert({ typeName, std::move(fields) }); + type->setFields(std::move(fields)); } -std::optional> ValidationContext:: - getInputTypeFields(const std::string& name) const +void ValidationContext::addPossibleTypes(std::shared_ptr type, + const response::Value& typeDescriptionMap) { - const auto& itr = _inputTypeFields.find(name); - if (itr == _inputTypeFields.cend()) + const auto& itrPossibleTypes = typeDescriptionMap.find(R"gql(possibleTypes)gql"); + std::set possibleTypes; + + if (itrPossibleTypes != typeDescriptionMap.end() + && itrPossibleTypes->second.type() == response::Type::List) { - return std::nullopt; + const auto& matchingTypeEntries = itrPossibleTypes->second.get(); + + for (const auto& matchingTypeEntry : matchingTypeEntries) + { + if (matchingTypeEntry.type() != response::Type::Map) + { + continue; + } + + const auto& itrMatchingTypeName = matchingTypeEntry.find(R"gql(name)gql"); + if (itrMatchingTypeName != matchingTypeEntry.end() + && itrMatchingTypeName->second.type() == response::Type::String) + { + const auto& possibleType = + getNamedValidateType(itrMatchingTypeName->second.get()); + possibleTypes.insert(possibleType.get()); + } + } } - return std::optional>( - itr->second); + + type->setPossibleTypes(std::move(possibleTypes)); } void ValidationContext::addInputTypeFields( - const std::string& typeName, const response::Value& typeDescriptionMap) + std::shared_ptr type, const response::Value& typeDescriptionMap) { const auto& itrFields = typeDescriptionMap.find(R"gql(inputFields)gql"); if (itrFields != typeDescriptionMap.end() && itrFields->second.type() == response::Type::List) { - _inputTypeFields.insert( - { typeName, getArguments(itrFields->second.get()) }); - } -} - -std::optional>> ValidationContext::getEnumValues( - const std::string& name) const -{ - const auto& itr = _enumValues.find(name); - if (itr == _enumValues.cend()) - { - return std::nullopt; + type->setFields(getArguments(itrFields->second.get())); } - return std::optional>>(itr->second); } void ValidationContext::addEnum( - const std::string& enumName, const response::Value& enumDescriptionMap) + const std::string_view& enumName, const response::Value& enumDescriptionMap) { const auto& itrEnumValues = enumDescriptionMap.find(R"gql(enumValues)gql"); if (itrEnumValues != enumDescriptionMap.end() && itrEnumValues->second.type() == response::Type::List) { - std::set enumValues; + std::set enumValues; const auto& enumValuesEntries = itrEnumValues->second.get(); for (const auto& enumValuesEntry : enumValuesEntries) @@ -1792,84 +1617,48 @@ void ValidationContext::addEnum( if (!enumValues.empty()) { - _enumValues[enumName] = std::move(enumValues); + makeNamedValidateType(EnumType { enumName, std::move(enumValues) }); } } } -void ValidationContext::addObject( - const std::string& name, const response::Value& typeDescriptionMap) +void ValidationContext::addObject(const std::string_view& name) { - auto itr = _matchingTypes.find(name); - if (itr != _matchingTypes.cend()) - { - itr->second.insert(name); - } - else - { - _matchingTypes.insert({ name, { name } }); - } - - addTypeFields(name, typeDescriptionMap); + makeNamedValidateType(ObjectType { name }); } -void ValidationContext::addInputObject( - const std::string& name, const response::Value& typeDescriptionMap) +void ValidationContext::addInputObject(const std::string_view& name) { - auto itr = _matchingTypes.find(name); - if (itr != _matchingTypes.cend()) - { - itr->second.insert(name); - } - else - { - _matchingTypes.insert({ name, { name } }); - } + makeNamedValidateType(InputObjectType { name }); +} - addInputTypeFields(name, typeDescriptionMap); +void ValidationContext::addInterface( + const std::string_view& name, const response::Value& typeDescriptionMap) +{ + makeNamedValidateType(InterfaceType { name }); } -void ValidationContext::addInterfaceOrUnion( - const std::string& name, const response::Value& typeDescriptionMap) +void ValidationContext::addUnion( + const std::string_view& name, const response::Value& typeDescriptionMap) { - const auto& itrPossibleTypes = typeDescriptionMap.find(R"gql(possibleTypes)gql"); - if (itrPossibleTypes != typeDescriptionMap.end() - && itrPossibleTypes->second.type() == response::Type::List) - { - const auto& matchingTypeEntries = itrPossibleTypes->second.get(); - auto itr = _matchingTypes.find(name); + makeNamedValidateType(UnionType { name }); +} - for (const auto& matchingTypeEntry : matchingTypeEntries) - { - if (matchingTypeEntry.type() != response::Type::Map) - { - continue; - } +template ::value>::type*> +std::shared_ptr ValidationContext::makeNamedValidateType(T&& typeDef) +{ + const std::string_view key(typeDef.name()); - const auto& itrMatchingTypeName = matchingTypeEntry.find(R"gql(name)gql"); - if (itrMatchingTypeName != matchingTypeEntry.end() - && itrMatchingTypeName->second.type() == response::Type::String) - { - const auto& matchingTypeName = - itrMatchingTypeName->second.get(); - if (itr != _matchingTypes.cend()) - { - itr->second.insert(matchingTypeName); - } - else - { - itr = _matchingTypes.insert({ name, { matchingTypeName } }).first; - } - } - } + const auto& itr = _namedCache.find(key); + if (itr != _namedCache.cend()) + { + return std::dynamic_pointer_cast(itr->second); } - addTypeFields(name, typeDescriptionMap); -} + auto type = std::make_shared(std::move(typeDef)); + _namedCache.insert({ type->name(), type }); -void ValidationContext::addScalar(const std::string& scalarName) -{ - _scalarTypes.insert(scalarName); + return type; } void ValidationContext::addDirective(const std::string& name, const response::ListType& locations, @@ -1897,110 +1686,83 @@ void ValidationContext::addDirective(const std::string& name, const response::Li _directives[name] = std::move(directive); } -template -std::string ValidationContext::getFieldType(const _FieldTypes& fields, const std::string& name) +template ::value>::type*> +std::shared_ptr ValidationContext::makeListOfType(std::shared_ptr&& ofType) { - std::string result; - auto itrType = fields.find(name); + const ValidateType* key = ofType.get(); - if (itrType == fields.end()) + const auto& itr = _listOfCache.find(key); + if (itr != _listOfCache.cend()) { - return result; + return itr->second; } - // Iteratively expand nested types till we get the underlying field type. - const std::string nameMember { R"gql(name)gql" }; - const std::string ofTypeMember { R"gql(ofType)gql" }; - auto itrName = getValidateFieldType(itrType->second).find(nameMember); - auto itrOfType = getValidateFieldType(itrType->second).find(ofTypeMember); - auto itrEnd = getValidateFieldType(itrType->second).end(); - - do - { - if (itrName != itrEnd && itrName->second.type() == response::Type::String) - { - result = itrName->second.template get(); - } - else if (itrOfType != itrEnd && itrOfType->second.type() == response::Type::Map) - { - itrEnd = itrOfType->second.end(); - itrName = itrOfType->second.find(nameMember); - itrOfType = itrOfType->second.find(ofTypeMember); - } - else - { - break; - } - } while (result.empty()); - - return result; -} - -const ValidateType& ValidationContext::getValidateFieldType(const FieldTypes::mapped_type& value) -{ - return value.returnType; -} - -const ValidateType& ValidationContext::getValidateFieldType( - const InputFieldTypes::mapped_type& value) -{ - return value.type; + return _listOfCache.insert({ key, std::make_shared(std::move(ofType)) }) + .first->second; } -template -std::string ValidationContext::getWrappedFieldType( - const _FieldTypes& fields, const std::string& name) +template ::value>::type*> +std::shared_ptr ValidationContext::makeNonNullOfType(std::shared_ptr&& ofType) { - std::string result; - auto itrType = fields.find(name); + const ValidateType* key = ofType.get(); - if (itrType == fields.end()) + const auto& itr = _nonNullCache.find(key); + if (itr != _nonNullCache.cend()) { - return result; + return itr->second; } - result = getWrappedFieldType(getValidateFieldType(itrType->second)); - - return result; + return _nonNullCache.insert({ key, std::make_shared(std::move(ofType)) }) + .first->second; } -std::string ValidationContext::getWrappedFieldType(const ValidateType& returnType) +std::shared_ptr ValidationContext::getTypeFromMap(const response::Value& typeMap) { - // Recursively expand nested types till we get the underlying field type. - const std::string nameMember { R"gql(name)gql" }; - auto itrName = returnType.find(nameMember); - auto itrEnd = returnType.end(); - - if (itrName != itrEnd && itrName->second.type() == response::Type::String) + const auto& itrKind = typeMap.find(R"gql(kind)gql"); + if (itrKind == typeMap.end() || itrKind->second.type() != response::Type::EnumValue) { - return itrName->second.get(); + return std::shared_ptr(); } - std::ostringstream oss; - const std::string kindMember { R"gql(kind)gql" }; - const std::string ofTypeMember { R"gql(ofType)gql" }; - auto itrKind = returnType.find(kindMember); - auto itrOfType = returnType.find(ofTypeMember); - - if (itrKind != itrEnd && itrKind->second.type() == response::Type::EnumValue - && itrOfType != itrEnd && itrOfType->second.type() == response::Type::Map) + introspection::TypeKind kind = + ModifiedArgument::convert(itrKind->second); + const auto& itrName = typeMap.find(R"gql(name)gql"); + if (itrName != typeMap.end() && itrName->second.type() == response::Type::String) { - switch (ModifiedArgument::convert(itrKind->second)) + const auto& name = itrName->second.get(); + if (!name.empty()) { - case introspection::TypeKind::LIST: - oss << '[' << getWrappedFieldType(itrOfType->second) << ']'; - break; - - case introspection::TypeKind::NON_NULL: - oss << getWrappedFieldType(itrOfType->second) << '!'; - break; + return getNamedValidateType(name); + } + } - default: - break; + const auto& itrOfType = typeMap.find(R"gql(ofType)gql"); + if (itrOfType != typeMap.end() && itrOfType->second.type() == response::Type::Map) + { + std::shared_ptr ofType = getTypeFromMap(itrOfType->second); + if (ofType) + { + if (kind == introspection::TypeKind::LIST) + { + return makeListOfType(std::move(ofType)); + } + else if (kind == introspection::TypeKind::NON_NULL) + { + return makeNonNullOfType(std::move(ofType)); + } } + + // should never reach + return nullptr; } - return oss.str(); + // should never reach + return nullptr; +} + +void ValidationContext::addScalar(const std::string_view& scalarName) +{ + makeNamedValidateType(ScalarType { scalarName }); } void ValidateExecutableVisitor::visitField(const peg::ast_node& field) @@ -2015,46 +1777,34 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) name = child.string_view(); }); - auto kind = getScopedTypeKind(); - - if (!kind) + if (!_scopedType) { // http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections auto position = field.begin(); std::ostringstream message; - message << "Field on unknown type: " << _scopedType << " name: " << name; + message << "Field on unknown type: " << _scopedType->name() << " name: " << name; _errors.push_back({ message.str(), { position.line, position.column } }); return; } - std::string innerType; - std::string wrappedType; - const auto& typeRef = getScopedTypeFields(); + std::shared_ptr wrappedType; + std::optional> objectFieldRef; - if (!typeRef) - { - // http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections - auto position = field.begin(); - std::ostringstream message; - - message << "Field on scalar type: " << _scopedType << " name: " << name; - - _errors.push_back({ message.str(), { position.line, position.column } }); - return; - } - - const auto& type = typeRef.value().get(); - - switch (*kind) + switch (_scopedType->kind()) { case introspection::TypeKind::OBJECT: case introspection::TypeKind::INTERFACE: { // http://spec.graphql.org/June2018/#sec-Field-Selections-on-Objects-Interfaces-and-Unions-Types - innerType = _validationContext->getFieldType(type, name); - wrappedType = _validationContext->getWrappedFieldType(type, name); + objectFieldRef = + static_cast&>(*_scopedType) + .getField(name); + if (objectFieldRef) + { + wrappedType = objectFieldRef.value().get().returnType; + } break; } @@ -2066,29 +1816,35 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) auto position = field.begin(); std::ostringstream message; - message << "Field on union type: " << _scopedType << " name: " << name; + message << "Field on union type: " << _scopedType->name() << " name: " << name; _errors.push_back({ message.str(), { position.line, position.column } }); return; } // http://spec.graphql.org/June2018/#sec-Field-Selections-on-Objects-Interfaces-and-Unions-Types - innerType = "String"; - wrappedType = "String!"; + wrappedType = commonTypes.nonNullString; break; } default: - break; + // http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections + auto position = field.begin(); + std::ostringstream message; + + message << "Field on scalar type: " << _scopedType->name() << " name: " << name; + + _errors.push_back({ message.str(), { position.line, position.column } }); + return; } - if (innerType.empty()) + if (!wrappedType) { // http://spec.graphql.org/June2018/#sec-Field-Selections-on-Objects-Interfaces-and-Unions-Types auto position = field.begin(); std::ostringstream message; - message << "Undefined field type: " << _scopedType << " name: " << name; + message << "Undefined field type: " << _scopedType->name() << " name: " << name; _errors.push_back({ message.str(), { position.line, position.column } }); return; @@ -2122,8 +1878,8 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) // http://spec.graphql.org/June2018/#sec-Argument-Uniqueness std::ostringstream message; - message << "Conflicting argument type: " << _scopedType << " field: " << name - << " name: " << argumentName; + message << "Conflicting argument type: " << _scopedType->name() + << " field: " << name << " name: " << argumentName; _errors.push_back({ message.str(), { position.line, position.column } }); continue; @@ -2138,9 +1894,9 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) } }); - std::optional objectType = - (*kind == introspection::TypeKind::OBJECT ? std::make_optional(_scopedType) : std::nullopt); - ValidateField validateField(std::move(wrappedType), + std::shared_ptr objectType = + (_scopedType->kind() == introspection::TypeKind::OBJECT ? _scopedType : nullptr); + ValidateField validateField(wrappedType, std::move(objectType), name, std::move(validateArguments)); @@ -2159,36 +1915,36 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) auto position = field.begin(); std::ostringstream message; - message << "Conflicting field type: " << _scopedType << " name: " << name; + message << "Conflicting field type: " << _scopedType->name() << " name: " << name; _errors.push_back({ message.str(), { position.line, position.column } }); } } - const auto& itrField = type.find(name); - if (itrField != type.end()) + if (objectFieldRef) { + const auto& objectField = objectFieldRef.value().get(); while (!argumentNames.empty()) { auto argumentName = std::move(argumentNames.front()); argumentNames.pop(); - auto itrArgument = itrField->second.arguments.find(argumentName); + auto itrArgument = objectField.arguments.find(argumentName); - if (itrArgument == itrField->second.arguments.end()) + if (itrArgument == objectField.arguments.end()) { // http://spec.graphql.org/June2018/#sec-Argument-Names std::ostringstream message; - message << "Undefined argument type: " << _scopedType << " field: " << name + message << "Undefined argument type: " << _scopedType->name() << " field: " << name << " name: " << argumentName; _errors.push_back({ message.str(), argumentLocations[argumentName] }); } } - for (auto& argument : itrField->second.arguments) + for (auto& argument : objectField.arguments) { auto itrArgument = validateField.arguments.find(argument.first); const bool missing = itrArgument == validateField.arguments.end(); @@ -2198,13 +1954,13 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) // The value was not null. if (!validateInputValue(argument.second.nonNullDefaultValue, itrArgument->second, - argument.second.type)) + *argument.second.type)) { // http://spec.graphql.org/June2018/#sec-Values-of-Correct-Type std::ostringstream message; - message << "Incompatible argument type: " << _scopedType << " field: " << name - << " name: " << argument.first; + message << "Incompatible argument type: " << _scopedType->name() + << " field: " << name << " name: " << argument.first; _errors.push_back({ message.str(), argumentLocations[argument.first] }); } @@ -2218,12 +1974,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) } // See if the argument is wrapped in NON_NULL - auto itrKind = argument.second.type.find(R"gql(kind)gql"); - - if (itrKind != argument.second.type.end() - && itrKind->second.type() == response::Type::EnumValue - && introspection::TypeKind::NON_NULL - == ModifiedArgument::convert(itrKind->second)) + if (argument.second.type->kind() == introspection::TypeKind::NON_NULL) { // http://spec.graphql.org/June2018/#sec-Required-Arguments auto position = field.begin(); @@ -2231,7 +1982,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) message << (missing ? "Missing argument type: " : "Required non-null argument type: ") - << _scopedType << " field: " << name << " name: " << argument.first; + << _scopedType->name() << " field: " << name << " name: " << argument.first; _errors.push_back({ message.str(), { position.line, position.column } }); } @@ -2256,11 +2007,10 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) _fieldCount = 0; _selectionFields.clear(); - _scopedType = std::move(innerType); + _scopedType = wrappedType->getInnerType(); visitSelection(*selection); - innerType = std::move(_scopedType); _scopedType = std::move(outerType); _selectionFields = std::move(outerFields); subFieldCount = _fieldCount; @@ -2269,14 +2019,14 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) if (subFieldCount == 0) { - const auto& innerKind = _validationContext->getTypeKind(innerType); - if (innerKind && !_validationContext->isScalarType(innerKind.value())) + const auto& innerType = wrappedType->getInnerType(); + if (!innerType->isInputType()) { // http://spec.graphql.org/June2018/#sec-Leaf-Field-Selections auto position = field.begin(); std::ostringstream message; - message << "Missing fields on non-scalar type: " << innerType; + message << "Missing fields on non-scalar type: " << innerType->name(); _errors.push_back({ message.str(), { position.line, position.column } }); return; @@ -2325,15 +2075,17 @@ void ValidateExecutableVisitor::visitFragmentSpread(const peg::ast_node& fragmen const auto& selection = *itr->second.children.back(); const auto& typeCondition = itr->second.children[1]; - std::string innerType { typeCondition->children.front()->string_view() }; + const auto& innerType = + _validationContext->getNamedValidateType(typeCondition->children.front()->string_view()); - if (!matchesScopedType(innerType)) + if (!matchesScopedType(*innerType)) { // http://spec.graphql.org/June2018/#sec-Fragment-spread-is-possible auto position = fragmentSpread.begin(); std::ostringstream message; - message << "Incompatible fragment spread target type: " << innerType << " name: " << name; + message << "Incompatible fragment spread target type: " << innerType->name() + << " name: " << name; _errors.push_back({ message.str(), { position.line, position.column } }); return; @@ -2358,44 +2110,46 @@ void ValidateExecutableVisitor::visitInlineFragment(const peg::ast_node& inlineF visitDirectives(introspection::DirectiveLocation::INLINE_FRAGMENT, child); }); - std::string innerType; + std::string_view innerTypeName; schema_location typeConditionLocation; peg::on_first_child(inlineFragment, - [&innerType, &typeConditionLocation](const peg::ast_node& child) { + [&innerTypeName, &typeConditionLocation](const peg::ast_node& child) { auto position = child.begin(); - innerType = child.children.front()->string(); + innerTypeName = child.children.front()->string_view(); typeConditionLocation = { position.line, position.column }; }); - if (innerType.empty()) + std::shared_ptr innerType; + + if (innerTypeName.empty()) { innerType = _scopedType; } else { - auto kind = _validationContext->getTypeKind(innerType); - if (!kind || _validationContext->isScalarType(kind.value())) + innerType = _validationContext->getNamedValidateType(innerTypeName); + if (!innerType || innerType->isInputType()) { // http://spec.graphql.org/June2018/#sec-Fragment-Spread-Type-Existence // http://spec.graphql.org/June2018/#sec-Fragments-On-Composite-Types std::ostringstream message; - message << (!kind ? "Undefined target type on inline fragment name: " - : "Scalar target type on inline fragment name: ") - << innerType; + message << (!innerType ? "Undefined target type on inline fragment name: " + : "Scalar target type on inline fragment name: ") + << innerTypeName; _errors.push_back({ message.str(), std::move(typeConditionLocation) }); return; } - if (!matchesScopedType(innerType)) + if (!matchesScopedType(*innerType)) { // http://spec.graphql.org/June2018/#sec-Fragment-spread-is-possible std::ostringstream message; - message << "Incompatible target type on inline fragment name: " << innerType; + message << "Incompatible target type on inline fragment name: " << innerType->name(); _errors.push_back({ message.str(), std::move(typeConditionLocation) }); return; @@ -2560,7 +2314,7 @@ void ValidateExecutableVisitor::visitDirectives( // The value was not null. if (!validateInputValue(argument.second.nonNullDefaultValue, itrArgument->second, - argument.second.type)) + *argument.second.type)) { // http://spec.graphql.org/June2018/#sec-Values-of-Correct-Type std::ostringstream message; @@ -2580,12 +2334,7 @@ void ValidateExecutableVisitor::visitDirectives( } // See if the argument is wrapped in NON_NULL - auto itrKind = argument.second.type.find(R"gql(kind)gql"); - - if (itrKind != argument.second.type.end() - && itrKind->second.type() == response::Type::EnumValue - && introspection::TypeKind::NON_NULL - == ModifiedArgument::convert(itrKind->second)) + if (argument.second.type->kind() == introspection::TypeKind::NON_NULL) { // http://spec.graphql.org/June2018/#sec-Required-Arguments auto position = directive->begin(); From b76dc2c12b06a38a7ece684024a88622fd6ccbde Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Tue, 8 Dec 2020 09:22:10 -0300 Subject: [PATCH 14/42] simplify variable processing Instead of 2 maps + set (both ordered), use one single unordered_map (string_view) + unordered_set (pointer to definition). The string_view is okay since the ast_node tree is valid during the processing, so the references are valid. The pointer to definition is also okay for _referencedVariables, since the defintitions are all created upfront and the map (thus references) won't change while visiting the fields. The 2->1 map was possible since we're now storing the definition location instead of using a second map just to store the ast_node to query the position on errors. --- include/Validation.h | 13 ++++++++----- src/Validation.cpp | 25 +++++++++++++------------ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index b9263910..80927542 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -198,6 +198,11 @@ struct ValidateArgument using ValidateTypeFieldArguments = std::unordered_map; +struct VariableDefinition : public ValidateArgument +{ + schema_location position; +}; + struct ValidateTypeField { std::shared_ptr returnType; @@ -375,7 +380,7 @@ struct ValidateArgumentVariable { bool operator==(const ValidateArgumentVariable& other) const; - std::string name; + const std::string_view name; }; struct ValidateArgumentEnumValue @@ -638,10 +643,9 @@ class ValidateExecutableVisitor using ExecutableNodes = std::map; using FragmentSet = std::unordered_set; - using VariableDefinitions = std::map; - using VariableTypes = std::map; + using VariableTypes = std::unordered_map; using OperationVariables = std::optional; - using VariableSet = std::set; + using VariableSet = std::unordered_set; // These members store information that's specific to a single query and changes every time we // visit a new one. They must be reset in between queries. @@ -652,7 +656,6 @@ class ValidateExecutableVisitor // These members store state for the visitor. They implicitly reset each time we call visit. OperationVariables _operationVariables; - VariableDefinitions _variableDefinitions; VariableSet _referencedVariables; FragmentSet _fragmentStack; size_t _fieldCount = 0; diff --git a/src/Validation.cpp b/src/Validation.cpp index ea3b019b..8198689f 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -199,7 +199,7 @@ void ValidateArgumentValueVisitor::visit(const peg::ast_node& value) void ValidateArgumentValueVisitor::visitVariable(const peg::ast_node& variable) { - ValidateArgumentVariable value { std::string { variable.string_view().substr(1) } }; + ValidateArgumentVariable value { variable.string_view().substr(1) }; auto position = variable.begin(); _argumentValue.value = std::make_unique(std::move(value)); @@ -763,8 +763,8 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op peg::for_each_child(operationDefinition, [this, &operationName](const peg::ast_node& variable) { - std::string variableName; - ValidateArgument variableArgument; + std::string_view variableName; + VariableDefinition variableArgument; for (const auto& child : variable.children) { @@ -852,8 +852,9 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op } } - _variableDefinitions.insert({ variableName, variable }); - _operationVariables->insert({ std::move(variableName), std::move(variableArgument) }); + const auto& position = variable.begin(); + variableArgument.position = { position.line, position.column }; + _operationVariables->insert({ variableName, std::move(variableArgument) }); }); peg::on_first_child(operationDefinition, @@ -911,12 +912,12 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op _fragmentStack.clear(); _selectionFields.clear(); - for (const auto& variable : _variableDefinitions) + for (const auto& variable : *_operationVariables) { - if (_referencedVariables.find(variable.first) == _referencedVariables.end()) + if (_referencedVariables.find(&variable.second) == _referencedVariables.end()) { // http://spec.graphql.org/June2018/#sec-All-Variables-Used - auto position = variable.second.begin(); + auto position = variable.second.position; std::ostringstream error; error << "Unused variable name: " << variable.first; @@ -926,7 +927,6 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op } _operationVariables.reset(); - _variableDefinitions.clear(); _referencedVariables.clear(); } @@ -1091,11 +1091,12 @@ bool ValidateExecutableVisitor::validateInputValue( return false; } - _referencedVariables.insert(variable.name); + const auto& variableDefinition = itrVariable->second; + _referencedVariables.insert(&variableDefinition); return validateVariableType( - hasNonNullDefaultValue || itrVariable->second.nonNullDefaultValue, - *itrVariable->second.type, + hasNonNullDefaultValue || variableDefinition.nonNullDefaultValue, + *variableDefinition.type, argument.position, type); } From 2c8954af5bdbfb8da045377d66704f5d490ecd6b Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Tue, 8 Dec 2020 09:42:57 -0300 Subject: [PATCH 15/42] enum validation now uses string_view There is one minor left over that the set keeps the strings, but this will be gone in the future. Moved to unordered_set as the order is not important. --- include/Validation.h | 11 ++++++----- src/Validation.cpp | 8 +++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index 80927542..acf750f2 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -103,19 +103,20 @@ using ScalarType = NamedType; class EnumType : public NamedType { public: - EnumType(const std::string_view& name, std::set&& values) + EnumType(const std::string_view& name, std::unordered_set&& values) : NamedType(name) , _values(std::move(values)) { } - const std::set& values() const + bool find(const std::string_view& key) const { - return _values; + // TODO: in the future the set will be of string_view + return _values.find(std::string { key }) != _values.end(); } private: - std::set _values; + std::unordered_set _values; }; template @@ -387,7 +388,7 @@ struct ValidateArgumentEnumValue { bool operator==(const ValidateArgumentEnumValue& other) const; - std::string value; + const std::string_view value; }; struct ValidateArgumentValue; diff --git a/src/Validation.cpp b/src/Validation.cpp index 8198689f..7046ca08 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -252,7 +252,7 @@ void ValidateArgumentValueVisitor::visitNullValue(const peg::ast_node& nullValue void ValidateArgumentValueVisitor::visitEnumValue(const peg::ast_node& enumValue) { - ValidateArgumentEnumValue value { enumValue.string() }; + ValidateArgumentEnumValue value { enumValue.string_view() }; auto position = enumValue.begin(); _argumentValue.value = std::make_unique(std::move(value)); @@ -1264,9 +1264,7 @@ bool ValidateExecutableVisitor::validateInputValue( } const auto& value = std::get(argument.value->data).value; - const auto& enumValues = static_cast(type).values(); - - if (enumValues.find(value) == enumValues.cend()) + if (!static_cast(type).find(value)) { std::ostringstream message; @@ -1598,7 +1596,7 @@ void ValidationContext::addEnum( if (itrEnumValues != enumDescriptionMap.end() && itrEnumValues->second.type() == response::Type::List) { - std::set enumValues; + std::unordered_set enumValues; const auto& enumValuesEntries = itrEnumValues->second.get(); for (const auto& enumValuesEntry : enumValuesEntries) From 20089d57d91cdaf47cc907ad31dcbe0addb7c216 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Tue, 8 Dec 2020 12:07:51 -0300 Subject: [PATCH 16/42] field validation now uses string_view All strings that are granted to be alive during the validation are handled as views. some maps and sets were converted to unordered. --- include/Validation.h | 16 +++++++++------- src/Validation.cpp | 32 ++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index acf750f2..f0716648 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -223,9 +223,11 @@ class ContainerValidateType : public NamedValidateType using FieldsContainer = std::unordered_map; - std::optional> getField(const std::string& name) const + std::optional> getField( + const std::string_view& name) const { - const auto& itr = _fields.find(name); + // TODO: string is a work around, the _fields set will be moved to string_view soon + const auto& itr = _fields.find(std::string { name }); if (itr == _fields.cend()) { return std::nullopt; @@ -412,7 +414,7 @@ struct ValidateArgumentMap { bool operator==(const ValidateArgumentMap& other) const; - std::map values; + std::unordered_map values; }; using ValidateArgumentVariant = std::variant& _errors; }; -using ValidateFieldArguments = std::map; +using ValidateFieldArguments = std::unordered_map; struct ValidateField { ValidateField(std::shared_ptr returnType, - std::shared_ptr&& objectType, const std::string& fieldName, + std::shared_ptr&& objectType, const std::string_view& fieldName, ValidateFieldArguments&& arguments); bool operator==(const ValidateField& other) const; std::shared_ptr returnType; std::shared_ptr objectType; - std::string fieldName; + std::string_view fieldName; ValidateFieldArguments arguments; }; @@ -661,7 +663,7 @@ class ValidateExecutableVisitor FragmentSet _fragmentStack; size_t _fieldCount = 0; std::shared_ptr _scopedType; - std::map _selectionFields; + std::unordered_map _selectionFields; struct { std::shared_ptr nonNullString; diff --git a/src/Validation.cpp b/src/Validation.cpp index 7046ca08..d8a9be7d 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -285,7 +285,7 @@ void ValidateArgumentValueVisitor::visitObjectValue(const peg::ast_node& objectV for (const auto& field : objectValue.children) { - auto name = field->children.front()->string(); + const auto& name = field->children.front()->string_view(); if (value.values.find(name) != value.values.end()) { @@ -310,7 +310,7 @@ void ValidateArgumentValueVisitor::visitObjectValue(const peg::ast_node& objectV } ValidateField::ValidateField(std::shared_ptr returnType, - std::shared_ptr&& objectType, const std::string& fieldName, + std::shared_ptr&& objectType, const std::string_view& fieldName, ValidateFieldArguments&& arguments) : returnType(returnType) , objectType(std::move(objectType)) @@ -1186,7 +1186,7 @@ bool ValidateExecutableVisitor::validateInputValue( const auto& inputObj = static_cast(type); const auto& values = std::get(argument.value->data).values; - std::set subFields; + std::unordered_set subFields; // Check every value against the target type. for (const auto& entry : values) @@ -1770,7 +1770,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) visitDirectives(introspection::DirectiveLocation::FIELD, child); }); - std::string name; + std::string_view name; peg::on_first_child(field, [&name](const peg::ast_node& child) { name = child.string_view(); @@ -1849,7 +1849,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) return; } - std::string alias; + std::string_view alias; peg::on_first_child(field, [&alias](const peg::ast_node& child) { alias = child.string_view(); @@ -1861,15 +1861,15 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) } ValidateFieldArguments validateArguments; - std::map argumentLocations; - std::queue argumentNames; + std::unordered_map argumentLocations; + std::queue argumentNames; peg::on_first_child(field, [this, &name, &validateArguments, &argumentLocations, &argumentNames]( const peg::ast_node& child) { for (auto& argument : child.children) { - auto argumentName = argument->children.front()->string(); + const auto& argumentName = argument->children.front()->string_view(); auto position = argument->begin(); if (validateArguments.find(argumentName) != validateArguments.end()) @@ -1929,7 +1929,8 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) argumentNames.pop(); - auto itrArgument = objectField.arguments.find(argumentName); + // TODO: string is a work around, the arguments set will be moved to string_view soon + auto itrArgument = objectField.arguments.find(std::string { argumentName }); if (itrArgument == objectField.arguments.end()) { @@ -2256,13 +2257,13 @@ void ValidateExecutableVisitor::visitDirectives( peg::on_first_child(*directive, [this, &directive, &directiveName, &validateDirective](const peg::ast_node& child) { ValidateFieldArguments validateArguments; - std::map argumentLocations; - std::queue argumentNames; + std::unordered_map argumentLocations; + std::queue argumentNames; for (auto& argument : child.children) { auto position = argument->begin(); - auto argumentName = argument->children.front()->string(); + const auto& argumentName = argument->children.front()->string_view(); if (validateArguments.find(argumentName) != validateArguments.end()) { @@ -2281,7 +2282,7 @@ void ValidateExecutableVisitor::visitDirectives( visitor.visit(*argument->children.back()); validateArguments[argumentName] = visitor.getArgumentValue(); argumentLocations[argumentName] = { position.line, position.column }; - argumentNames.push(std::move(argumentName)); + argumentNames.push(argumentName); } while (!argumentNames.empty()) @@ -2290,7 +2291,10 @@ void ValidateExecutableVisitor::visitDirectives( argumentNames.pop(); - const auto& itrArgument = validateDirective.arguments.find(argumentName); + // TODO: string is a work around, the arguments set will be moved to string_view + // soon + const auto& itrArgument = + validateDirective.arguments.find(std::string { argumentName }); if (itrArgument == validateDirective.arguments.cend()) { // http://spec.graphql.org/June2018/#sec-Argument-Names From 8a7bff844605a4f1f157d1b1bf9da8eaed2612d1 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Tue, 8 Dec 2020 12:19:38 -0300 Subject: [PATCH 17/42] directives validation now uses string_view Some temporary conversions left and will be fixed later. map was converted to unordered_map --- include/Validation.h | 6 +++--- src/Validation.cpp | 16 +++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index f0716648..88fb09b5 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -515,7 +515,7 @@ class ValidationContext }; std::optional> getDirective( - const std::string& name) const; + const std::string_view& name) const; std::optional> getOperationType( const std::string_view& name) const; @@ -540,7 +540,7 @@ class ValidationContext ValidateTypeFieldArguments getArguments(const response::ListType& argumentsMember); - using Directives = std::map; + using Directives = std::unordered_map; // These members store Introspection schema information which does not change between queries. OperationTypes _operationTypes; @@ -595,7 +595,7 @@ class ValidationContext void addInputObject(const std::string_view& name); void addInterface(const std::string_view& name, const response::Value& typeDescriptionMap); void addUnion(const std::string_view& name, const response::Value& typeDescriptionMap); - void addDirective(const std::string& name, const response::ListType& locations, + void addDirective(const std::string_view& name, const response::ListType& locations, const response::Value& descriptionMap); void addTypeFields(std::shared_ptr> type, diff --git a/src/Validation.cpp b/src/Validation.cpp index d8a9be7d..bc093040 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -983,9 +983,10 @@ ValidateTypeFieldArguments ValidationContext::getArguments(const response::ListT } std::optional> ValidationContext::getDirective( - const std::string& name) const + const std::string_view& name) const { - const auto& itr = _directives.find(name); + // TODO: string is a work around, the directives map will be moved to string_view soon + const auto& itr = _directives.find(std::string { name }); if (itr == _directives.cend()) { return std::nullopt; @@ -1660,8 +1661,8 @@ std::shared_ptr ValidationContext::makeNamedValidateType(T&& typeDef) return type; } -void ValidationContext::addDirective(const std::string& name, const response::ListType& locations, - const response::Value& descriptionMap) +void ValidationContext::addDirective(const std::string_view& name, + const response::ListType& locations, const response::Value& descriptionMap) { ValidateDirective directive; @@ -1682,7 +1683,8 @@ void ValidationContext::addDirective(const std::string& name, const response::Li directive.arguments = getArguments(itrArgs->second.get()); } - _directives[name] = std::move(directive); + // TODO: string is a work around, the directives will be moved to string_view soon + _directives[std::string { name }] = std::move(directive); } template ::value>::type*> @@ -2171,11 +2173,11 @@ void ValidateExecutableVisitor::visitInlineFragment(const peg::ast_node& inlineF void ValidateExecutableVisitor::visitDirectives( introspection::DirectiveLocation location, const peg::ast_node& directives) { - std::set uniqueDirectives; + std::unordered_set uniqueDirectives; for (const auto& directive : directives.children) { - std::string directiveName; + std::string_view directiveName; peg::on_first_child(*directive, [&directiveName](const peg::ast_node& child) { From faa53cc923759abaf5e271d606ebc8d0c3e86d27 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Tue, 8 Dec 2020 12:49:25 -0300 Subject: [PATCH 18/42] simplify operation validation, use string_view operations do not need to be stored in the visitor as it's only used in the ValidateExecutableVisitor::visit() (root) With some simple changes it can be a set instead of a map --- include/Validation.h | 5 ++--- src/Validation.cpp | 43 +++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index 88fb09b5..60983f2a 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -644,8 +644,8 @@ class ValidateExecutableVisitor std::vector _errors; - using ExecutableNodes = std::map; - using FragmentSet = std::unordered_set; + using ExecutableNodes = std::unordered_map; + using FragmentSet = std::unordered_set; using VariableTypes = std::unordered_map; using OperationVariables = std::optional; using VariableSet = std::unordered_set; @@ -653,7 +653,6 @@ class ValidateExecutableVisitor // These members store information that's specific to a single query and changes every time we // visit a new one. They must be reset in between queries. ExecutableNodes _fragmentDefinitions; - ExecutableNodes _operationDefinitions; FragmentSet _referencedFragments; FragmentSet _fragmentCycles; diff --git a/src/Validation.cpp b/src/Validation.cpp index bc093040..7716bf19 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -588,7 +588,7 @@ void ValidateExecutableVisitor::visit(const peg::ast_node& root) [this](const peg::ast_node& fragmentDefinition) { const auto& fragmentName = fragmentDefinition.children.front(); const auto inserted = - _fragmentDefinitions.insert({ fragmentName->string(), fragmentDefinition }); + _fragmentDefinitions.insert({ fragmentName->string_view(), fragmentDefinition }); if (!inserted.second) { @@ -602,18 +602,21 @@ void ValidateExecutableVisitor::visit(const peg::ast_node& root) } }); + std::unordered_set definedOperations; + schema_location unamedOperationLocation; + // Visit all of the operation definitions and check for duplicates. peg::for_each_child(root, - [this](const peg::ast_node& operationDefinition) { - std::string operationName; + [this, &definedOperations, &unamedOperationLocation]( + const peg::ast_node& operationDefinition) { + std::string_view operationName; peg::on_first_child(operationDefinition, [&operationName](const peg::ast_node& child) { operationName = child.string_view(); }); - const auto inserted = - _operationDefinitions.insert({ std::move(operationName), operationDefinition }); + const auto inserted = definedOperations.insert(operationName); if (!inserted.second) { @@ -621,28 +624,25 @@ void ValidateExecutableVisitor::visit(const peg::ast_node& root) auto position = operationDefinition.begin(); std::ostringstream error; - error << "Duplicate operation name: " << inserted.first->first; + error << "Duplicate operation name: " << operationName; _errors.push_back({ error.str(), { position.line, position.column } }); } + else if (operationName.empty()) + { + auto position = operationDefinition.begin(); + unamedOperationLocation = { position.line, position.column }; + } }); // Check for lone anonymous operations. - if (_operationDefinitions.size() > 1) + if (definedOperations.size() > 1) { - auto itr = std::find_if(_operationDefinitions.cbegin(), - _operationDefinitions.cend(), - [](const std::pair& entry) noexcept { - return entry.first.empty(); - }); - - if (itr != _operationDefinitions.cend()) + if (definedOperations.find("") != definedOperations.cend()) { // http://spec.graphql.org/June2018/#sec-Lone-Anonymous-Operation - auto position = itr->second.begin(); - - _errors.push_back( - { "Anonymous operation not alone", { position.line, position.column } }); + _errors.push_back({ "Anonymous operation not alone", + { unamedOperationLocation.line, unamedOperationLocation.column } }); } } @@ -681,7 +681,7 @@ void ValidateExecutableVisitor::visit(const peg::ast_node& root) std::transform(unreferencedFragments.cbegin(), unreferencedFragments.cend(), _errors.begin() + originalSize, - [](const std::pair& + [](const std::pair& fragmentDefinition) noexcept { auto position = fragmentDefinition.second.begin(); std::ostringstream message; @@ -699,7 +699,6 @@ std::vector ValidateExecutableVisitor::getStructuredErrors() // Reset all of the state for this query, but keep the Introspection schema information. _fragmentDefinitions.clear(); - _operationDefinitions.clear(); _referencedFragments.clear(); _fragmentCycles.clear(); @@ -712,7 +711,7 @@ void ValidateExecutableVisitor::visitFragmentDefinition(const peg::ast_node& fra visitDirectives(introspection::DirectiveLocation::FRAGMENT_DEFINITION, child); }); - const auto name = fragmentDefinition.children.front()->string(); + const auto name = fragmentDefinition.children.front()->string_view(); const auto& selection = *fragmentDefinition.children.back(); const auto& typeCondition = fragmentDefinition.children[1]; const auto& innerTypeName = typeCondition->children.front()->string_view(); @@ -2044,7 +2043,7 @@ void ValidateExecutableVisitor::visitFragmentSpread(const peg::ast_node& fragmen visitDirectives(introspection::DirectiveLocation::FRAGMENT_SPREAD, child); }); - const std::string name(fragmentSpread.children.front()->string_view()); + const std::string_view name(fragmentSpread.children.front()->string_view()); auto itr = _fragmentDefinitions.find(name); if (itr == _fragmentDefinitions.cend()) From 16754976cc5bd99732f0f0e9378716196020f950 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Tue, 1 Dec 2020 18:36:34 -0300 Subject: [PATCH 19/42] graphql::service::Request now accepts an introspection query results This allows the schema to be compiled without introspection fields and still works, usually loading a schema.json using parseJSON() --- include/graphqlservice/GraphQLService.h | 3 ++- src/GraphQLService.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index b8d2add7..dfc71eef 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -956,7 +956,8 @@ class ValidateExecutableVisitor; class Request : public std::enable_shared_from_this { protected: - GRAPHQLSERVICE_EXPORT explicit Request(TypeMap&& operationTypes); + GRAPHQLSERVICE_EXPORT explicit Request( + TypeMap&& operationTypes, const response::Value* introspectionQuery = nullptr); GRAPHQLSERVICE_EXPORT virtual ~Request(); public: diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index bf064b35..5cc93e93 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -1757,9 +1757,11 @@ void SubscriptionDefinitionVisitor::visitInlineFragment(const peg::ast_node& inl } } -Request::Request(TypeMap&& operationTypes) +Request::Request(TypeMap&& operationTypes, const response::Value* introspectionQuery) : _operations(std::move(operationTypes)) - , _validation(std::make_unique(*this)) + , _validation(std::make_unique(introspectionQuery + ? std::make_shared(*introspectionQuery) + : std::make_shared(*this))) { } From e7d158b2f35b8506681cbad35cb3bd22531872dc Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Fri, 4 Dec 2020 19:35:58 -0300 Subject: [PATCH 20/42] create GraphQLError.h move schema_exception to its own file and namespace, it's a base class that does not know about response::Value. GraphQLService.h still defines its own subclass that imlements getErrors() returning response::Value, as well as redeclaring the moved types (field_path, path_segment) to keep existing code working. what() was changed to use the structuredErrors rather than the response::Value, it's more generic and cheaper. --- include/graphqlservice/GraphQLError.h | 65 +++++++++++++++++++++++++ include/graphqlservice/GraphQLService.h | 30 +++--------- samples/CMakeLists.txt | 3 +- src/CMakeLists.txt | 19 +++++++- src/GraphQLError.cpp | 62 +++++++++++++++++++++++ src/GraphQLService.cpp | 50 ++----------------- test/CMakeLists.txt | 3 +- 7 files changed, 158 insertions(+), 74 deletions(-) create mode 100644 include/graphqlservice/GraphQLError.h create mode 100644 src/GraphQLError.cpp diff --git a/include/graphqlservice/GraphQLError.h b/include/graphqlservice/GraphQLError.h new file mode 100644 index 00000000..d8087cbc --- /dev/null +++ b/include/graphqlservice/GraphQLError.h @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#ifndef GRAPHQLERROR_H +#define GRAPHQLERROR_H + +// clang-format off +#ifdef GRAPHQL_DLLEXPORTS + #ifdef IMPL_GRAPHQLERROR_DLL + #define GRAPHQLERROR_EXPORT __declspec(dllexport) + #else // !IMPL_GRAPHQLERROR_DLL + #define GRAPHQLERROR_EXPORT __declspec(dllimport) + #endif // !IMPL_GRAPHQLERROR_DLL +#else // !GRAPHQL_DLLEXPORTS + #define GRAPHQLERROR_EXPORT +#endif // !GRAPHQL_DLLEXPORTS +// clang-format on + +#include +#include +#include + +namespace graphql::error { + +struct schema_location +{ + size_t line = 0; + size_t column = 1; +}; + +using path_segment = std::variant; +using field_path = std::queue; + +struct schema_error +{ + std::string message; + schema_location location; + field_path path; +}; + +// This exception bubbles up 1 or more error messages to the JSON results. +class schema_exception : public std::exception +{ +public: + GRAPHQLERROR_EXPORT explicit schema_exception(std::vector&& structuredErrors); + GRAPHQLERROR_EXPORT explicit schema_exception(std::vector&& messages); + + schema_exception() = delete; + + GRAPHQLERROR_EXPORT const char* what() const noexcept override; + + GRAPHQLERROR_EXPORT const std::vector& getStructuredErrors() const noexcept; + GRAPHQLERROR_EXPORT std::vector getStructuredErrors() noexcept; + +protected: + static std::vector convertMessages(std::vector&& messages) noexcept; + +private: + std::vector _structuredErrors; +}; +} // namespace graphql::error + +#endif // GRAPHQLERROR_H \ No newline at end of file diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index dfc71eef..39f505d8 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -18,6 +18,7 @@ #endif // !GRAPHQL_DLLEXPORTS // clang-format on +#include "graphqlservice/GraphQLError.h" #include "graphqlservice/GraphQLParse.h" #include "graphqlservice/GraphQLResponse.h" @@ -45,51 +46,32 @@ namespace graphql::service { // Errors should have a message string, and optional locations and a path. GRAPHQLSERVICE_EXPORT void addErrorMessage(std::string&& message, response::Value& error); -struct schema_location -{ - size_t line = 0; - size_t column = 1; -}; +using schema_location = graphql::error::schema_location; GRAPHQLSERVICE_EXPORT void addErrorLocation( const schema_location& location, response::Value& error); -using path_segment = std::variant; -using field_path = std::queue; +using path_segment = graphql::error::path_segment; +using field_path = graphql::error::field_path; GRAPHQLSERVICE_EXPORT void addErrorPath(field_path&& path, response::Value& error); -struct schema_error -{ - std::string message; - schema_location location; - field_path path; -}; +using schema_error = graphql::error::schema_error; GRAPHQLSERVICE_EXPORT response::Value buildErrorValues( const std::vector& structuredErrors); // This exception bubbles up 1 or more error messages to the JSON results. -class schema_exception : public std::exception +class schema_exception : public graphql::error::schema_exception { public: GRAPHQLSERVICE_EXPORT explicit schema_exception(std::vector&& structuredErrors); GRAPHQLSERVICE_EXPORT explicit schema_exception(std::vector&& messages); - schema_exception() = delete; - - GRAPHQLSERVICE_EXPORT const char* what() const noexcept override; - - GRAPHQLSERVICE_EXPORT const std::vector& getStructuredErrors() const noexcept; - GRAPHQLSERVICE_EXPORT std::vector getStructuredErrors() noexcept; - GRAPHQLSERVICE_EXPORT const response::Value& getErrors() const noexcept; GRAPHQLSERVICE_EXPORT response::Value getErrors() noexcept; private: - static std::vector convertMessages(std::vector&& messages) noexcept; - - std::vector _structuredErrors; response::Value _errors; }; diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 86428af9..6aac4ebe 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -115,8 +115,9 @@ if(WIN32 AND BUILD_SHARED_LIBS) add_custom_target(copy_sample_dlls ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS graphqlservice graphqljson graphqlpeg graphqlresponse) + DEPENDS graphqlservice graphqljson graphqlpeg graphqlerror graphqlresponse) add_dependencies(sample copy_sample_dlls) endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 23617111..3e8c19f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,8 +26,21 @@ if(WIN32 AND BUILD_SHARED_LIBS) PRIVATE IMPL_GRAPHQLPEG_DLL) endif() +# graphqlerror +add_library(graphqlerror GraphQLError.cpp) +add_library(cppgraphqlgen::graphqlerror ALIAS graphqlerror) +target_include_directories(graphqlerror PUBLIC + $ + $) + +if(WIN32 AND BUILD_SHARED_LIBS) + target_compile_definitions(graphqlerror + PUBLIC GRAPHQL_DLLEXPORTS + PRIVATE IMPL_GRAPHQLERROR_DLL) +endif() + # graphqlresponse -add_library(graphqlresponse GraphQLResponse.cpp) +add_library(graphqlresponse GraphQLResponse.cpp GraphQLError.cpp) add_library(cppgraphqlgen::graphqlresponse ALIAS graphqlresponse) target_include_directories(graphqlresponse PUBLIC $ @@ -45,6 +58,7 @@ if(GRAPHQL_BUILD_SCHEMAGEN) add_executable(cppgraphqlgen::schemagen ALIAS schemagen) target_link_libraries(schemagen PRIVATE graphqlpeg + graphqlerror graphqlresponse) add_bigobj_flag(schemagen) @@ -157,6 +171,7 @@ add_library(cppgraphqlgen::graphqlservice ALIAS graphqlservice) target_link_libraries(graphqlservice PUBLIC graphqlpeg Threads::Threads) +target_link_libraries(graphqlservice PUBLIC graphqlerror) target_link_libraries(graphqlservice PUBLIC graphqlresponse) target_include_directories(graphqlservice PUBLIC $) @@ -211,6 +226,7 @@ endif() install(TARGETS graphqlpeg + graphqlerror graphqlresponse graphqlservice EXPORT cppgraphqlgen-targets @@ -220,6 +236,7 @@ install(TARGETS install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../include/graphqlservice/GraphQLParse.h + ${CMAKE_CURRENT_SOURCE_DIR}/../include/graphqlservice/GraphQLError.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/graphqlservice/GraphQLResponse.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/graphqlservice/GraphQLService.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/graphqlservice/GraphQLGrammar.h diff --git a/src/GraphQLError.cpp b/src/GraphQLError.cpp new file mode 100644 index 00000000..d73e4100 --- /dev/null +++ b/src/GraphQLError.cpp @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "graphqlservice/GraphQLError.h" + +#include + +namespace graphql::error { + +schema_exception::schema_exception(std::vector&& structuredErrors) + : _structuredErrors(std::move(structuredErrors)) +{ +} + +schema_exception::schema_exception(std::vector&& messages) + : schema_exception(convertMessages(std::move(messages))) +{ +} + +std::vector schema_exception::convertMessages( + std::vector&& messages) noexcept +{ + std::vector errors(messages.size()); + + std::transform(messages.begin(), + messages.end(), + errors.begin(), + [](std::string& message) noexcept { + return schema_error { std::move(message) }; + }); + + return errors; +} + +const char* schema_exception::what() const noexcept +{ + const char* message = nullptr; + + if (_structuredErrors.size() > 0) + { + if (!_structuredErrors[0].message.empty()) + { + message = _structuredErrors[0].message.c_str(); + } + } + + return (message == nullptr) ? "Unknown schema error" : message; +} + +const std::vector& schema_exception::getStructuredErrors() const noexcept +{ + return _structuredErrors; +} + +std::vector schema_exception::getStructuredErrors() noexcept +{ + auto structuredErrors = std::move(_structuredErrors); + + return structuredErrors; +} + +} /* namespace graphql::error */ diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index 5cc93e93..cfe8534a 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -93,60 +93,16 @@ response::Value buildErrorValues(const std::vector& structuredErro } schema_exception::schema_exception(std::vector&& structuredErrors) - : _structuredErrors(std::move(structuredErrors)) - , _errors(buildErrorValues(_structuredErrors)) + : graphql::error::schema_exception(std::move(structuredErrors)) + , _errors(buildErrorValues(std::as_const(*this).getStructuredErrors())) { } schema_exception::schema_exception(std::vector&& messages) - : schema_exception(convertMessages(std::move(messages))) + : graphql::service::schema_exception(convertMessages(std::move(messages))) { } -std::vector schema_exception::convertMessages( - std::vector&& messages) noexcept -{ - std::vector errors(messages.size()); - - std::transform(messages.begin(), - messages.end(), - errors.begin(), - [](std::string& message) noexcept { - return schema_error { std::move(message) }; - }); - - return errors; -} - -const char* schema_exception::what() const noexcept -{ - const char* message = nullptr; - - if (_errors.size() > 0) - { - auto itr = _errors[0].find("message"); - - if (itr != _errors[0].end() && itr->second.type() == response::Type::String) - { - message = itr->second.get().c_str(); - } - } - - return (message == nullptr) ? "Unknown schema error" : message; -} - -const std::vector& schema_exception::getStructuredErrors() const noexcept -{ - return _structuredErrors; -} - -std::vector schema_exception::getStructuredErrors() noexcept -{ - auto structuredErrors = std::move(_structuredErrors); - - return structuredErrors; -} - const response::Value& schema_exception::getErrors() const noexcept { return _errors; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 40ebf705..346aafd1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -56,8 +56,9 @@ if(WIN32 AND BUILD_SHARED_LIBS) add_custom_target(copy_test_dlls ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS graphqlservice graphqljson graphqlpeg graphqlresponse + DEPENDS graphqlservice graphqljson graphqlpeg graphqlerror graphqlresponse WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../src) add_dependencies(validation_tests copy_test_dlls) From e90ab5a22dc20a44f6a34050e0ef3357d2cfa0d7 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Sat, 5 Dec 2020 10:55:41 -0300 Subject: [PATCH 21/42] add operator== to basic GraphQLError types These will be needed in order to compare error arrays. --- include/graphqlservice/GraphQLError.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/graphqlservice/GraphQLError.h b/include/graphqlservice/GraphQLError.h index d8087cbc..eaeb0d82 100644 --- a/include/graphqlservice/GraphQLError.h +++ b/include/graphqlservice/GraphQLError.h @@ -28,6 +28,11 @@ struct schema_location { size_t line = 0; size_t column = 1; + + GRAPHQLERROR_EXPORT bool operator==(const schema_location& rhs) const noexcept + { + return line == rhs.line && column == rhs.column; + } }; using path_segment = std::variant; @@ -38,6 +43,11 @@ struct schema_error std::string message; schema_location location; field_path path; + + GRAPHQLERROR_EXPORT bool operator==(const schema_error& rhs) const noexcept + { + return location == rhs.location && message == rhs.message && path == rhs.path; + } }; // This exception bubbles up 1 or more error messages to the JSON results. From 372710be2035da9f2eb06cdc885a2a786965bc3f Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Tue, 8 Dec 2020 23:55:36 -0300 Subject: [PATCH 22/42] refactor Response TypedData Keep _data allocated as unique_ptr, but make it an abstract base class with methods such as kind(), this way the code is more isolated and allows for specific handling whenever needed. Closes #128 --- include/graphqlservice/GraphQLResponse.h | 1 - src/GraphQLResponse.cpp | 750 ++++++++++++++++++----- 2 files changed, 592 insertions(+), 159 deletions(-) diff --git a/include/graphqlservice/GraphQLResponse.h b/include/graphqlservice/GraphQLResponse.h index 91ec497a..6f85fafa 100644 --- a/include/graphqlservice/GraphQLResponse.h +++ b/include/graphqlservice/GraphQLResponse.h @@ -194,7 +194,6 @@ struct Value } private: - const Type _type; std::unique_ptr _data; }; diff --git a/src/GraphQLResponse.cpp b/src/GraphQLResponse.cpp index 14748f84..0f96f8ab 100644 --- a/src/GraphQLResponse.cpp +++ b/src/GraphQLResponse.cpp @@ -9,91 +9,592 @@ namespace graphql::response { +struct TypedData +{ + TypedData& operator=(const TypedData&) = delete; + + virtual ~TypedData() + { + } + + virtual Type type() const noexcept = 0; + virtual bool operator==(const TypedData& rhs) const noexcept = 0; + + virtual std::unique_ptr copy() const + { + throw std::logic_error("Invalid copy of Value"); + } + + virtual void setString(StringType&& value) + { + throw std::logic_error("Invalid call to Value::set for StringType"); + } + + virtual void setBoolean(BooleanType value) + { + throw std::logic_error("Invalid call to Value::set for BooleanType"); + } + + virtual void setInt(IntType value) + { + throw std::logic_error("Invalid call to Value::set for IntType"); + } + + virtual void setFloat(FloatType value) + { + throw std::logic_error("Invalid call to Value::set for FloatType"); + } + + virtual void setScalar(ScalarType&& value) + { + throw std::logic_error("Invalid call to Value::set for ScalarType"); + } + + virtual const StringType& getString() const + { + throw std::logic_error("Invalid call to Value::get for StringType"); + } + + virtual BooleanType getBoolean() const + { + throw std::logic_error("Invalid call to Value::get for BooleanType"); + } + + virtual IntType getInt() const + { + throw std::logic_error("Invalid call to Value::get for IntType"); + } + + virtual FloatType getFloat() const + { + throw std::logic_error("Invalid call to Value::get for FloatType"); + } + + virtual const ScalarType& getScalar() const + { + throw std::logic_error("Invalid call to Value::get for ScalarType"); + } + + virtual const MapType& getMap() const + { + throw std::logic_error("Invalid call to Value::get for MapType"); + } + + virtual const ListType& getList() const + { + throw std::logic_error("Invalid call to Value::get for ListType"); + } + + virtual StringType releaseString() + { + throw std::logic_error("Invalid call to Value::release for StringType"); + } + + virtual ScalarType releaseScalar() + { + throw std::logic_error("Invalid call to Value::release for ScalarType"); + } + + virtual MapType releaseMap() + { + throw std::logic_error("Invalid call to Value::release for MapType"); + } + + virtual ListType releaseList() const + { + throw std::logic_error("Invalid call to Value::release for ListType"); + } + + // Valid for Type::Map or Type::List + virtual void reserve(size_t count) + { + throw std::logic_error("Invalid call to Value::reserve"); + } + + virtual size_t size() const + { + throw std::logic_error("Invalid call to Value::size"); + } + + // Valid for Type::Map + virtual void emplace_back(std::string&& name, Value&& value) + { + throw std::logic_error("Invalid call to Value::emplace_back for MapType"); + } + + virtual MapType::const_iterator find(const std::string& name) const + { + throw std::logic_error("Invalid call to Value::find for MapType"); + } + + virtual MapType::const_iterator begin() const + { + throw std::logic_error("Invalid call to Value::begin for MapType"); + } + + virtual MapType::const_iterator end() const + { + throw std::logic_error("Invalid call to Value::end for MapType"); + } + + // Valid for Type::List + virtual void emplace_back(Value&& value) + { + throw std::logic_error("Invalid call to Value::emplace_back for ListType"); + } + + virtual const Value& operator[](size_t index) const + { + throw std::logic_error("Invalid call to Value::operator[] for ListType"); + } +}; + +struct BooleanData final : public TypedData +{ + BooleanData(BooleanType value = false) + : _value(value) + { + } + + Type type() const noexcept final + { + return Type::Boolean; + } + + bool operator==(const TypedData& rhs) const noexcept final + { + if (typeid(*this).name() != typeid(rhs).name()) + { + return false; + } + + return _value == static_cast(rhs)._value; + } + + std::unique_ptr copy() const final + { + return std::make_unique(_value); + } + + BooleanType getBoolean() const final + { + return _value; + } + + void setBoolean(BooleanType value) final + { + _value = value; + } + +private: + BooleanType _value; +}; + +struct IntData final : public TypedData +{ + IntData(IntType value = 0) + : _value(value) + { + } + + Type type() const noexcept final + { + return Type::Int; + } + + bool operator==(const TypedData& rhs) const noexcept final + { + if (typeid(*this).name() != typeid(rhs).name()) + { + return false; + } + + return _value == static_cast(rhs)._value; + } + + std::unique_ptr copy() const final + { + return std::make_unique(*this); + } + + IntType getInt() const final + { + return _value; + } + + void setInt(IntType value) final + { + _value = value; + } + + FloatType getFloat() const final + { + return _value; + } + + void setFloat(FloatType value) final + { + _value = value; + } + +private: + IntType _value; +}; + +struct FloatData final : public TypedData +{ + FloatData(FloatType value = 0) + : _value(value) + { + } + + Type type() const noexcept final + { + return Type::Float; + } + + bool operator==(const TypedData& rhs) const noexcept final + { + if (typeid(*this).name() != typeid(rhs).name()) + { + return false; + } + + return _value == static_cast(rhs)._value; + } + + std::unique_ptr copy() const final + { + return std::make_unique(_value); + } + + IntType getInt() const final + { + return _value; + } + + void setInt(IntType value) final + { + _value = value; + } + + FloatType getFloat() const final + { + return _value; + } + + void setFloat(FloatType value) final + { + _value = value; + } + +private: + FloatType _value; +}; + // Type::Map -struct MapData +struct MapData final : public TypedData { - bool operator==(const MapData& rhs) const + Type type() const noexcept final { - return map == rhs.map; + return Type::Map; } + bool operator==(const TypedData& rhs) const noexcept final + { + if (typeid(*this).name() != typeid(rhs).name()) + { + return false; + } + return map == static_cast(rhs).map; + } + + std::unique_ptr copy() const final + { + return std::make_unique(*this); + } + + void reserve(size_t count) final + { + map.reserve(count); + members.reserve(count); + } + + size_t size() const final + { + return map.size(); + } + + void emplace_back(std::string&& name, Value&& value) final + { + if (members.find(name) != members.cend()) + { + throw std::runtime_error("Duplicate Map member"); + } + + members.insert({ name, map.size() }); + map.emplace_back(std::make_pair(std::move(name), std::move(value))); + } + + MapType::const_iterator find(const std::string& name) const final + { + const auto& itr = members.find(name); + + if (itr == members.cend()) + { + return map.cend(); + } + + return map.cbegin() + itr->second; + } + + MapType::const_iterator begin() const final + { + return map.cbegin(); + } + + MapType::const_iterator end() const final + { + return map.cend(); + } + + const MapType& getMap() const final + { + return map; + } + + MapType releaseMap() final + { + MapType result = std::move(map); + + members.clear(); + + return result; + } + +private: MapType map; std::unordered_map members; }; // Type::List -struct ListData +struct ListData final : public TypedData { - bool operator==(const ListData& rhs) const + Type type() const noexcept final + { + return Type::List; + } + + bool operator==(const TypedData& rhs) const noexcept final + { + if (typeid(*this).name() != typeid(rhs).name()) + { + return false; + } + return list == static_cast(rhs).list; + } + + std::unique_ptr copy() const final + { + return std::make_unique(*this); + } + + void reserve(size_t count) final { - return list == rhs.list; + list.reserve(count); } + size_t size() const final + { + return list.size(); + } + + void emplace_back(Value&& value) final + { + list.emplace_back(std::move(value)); + } + + const Value& operator[](size_t index) const final + { + return list.at(index); + } + + const ListType& getList() const final + { + return list; + } + + ListType releaseList() const final + { + auto result = std::move(list); + + return result; + } + +private: ListType list; }; -// Type::String or Type::EnumValue -struct StringOrEnumData +struct CommonStringData : public TypedData { - bool operator==(const StringOrEnumData& rhs) const + CommonStringData(std::string&& value = "") + : _value(std::move(value)) + { + } + + Type type() const noexcept + { + return Type::String; + } + + void setString(StringType&& value) final { - return string == rhs.string && from_json == rhs.from_json; + _value = std::move(value); } - StringType string; - bool from_json = false; + const StringType& getString() const final + { + return _value; + } + + StringType releaseString() final + { + StringType result = std::move(_value); + + return result; + } + + bool operator==(const TypedData& rhs) const noexcept final + { + if (typeid(*this).name() != typeid(rhs).name()) + { + return false; + } + + return _value == static_cast(rhs)._value; + } + +protected: + StringType _value; }; -// Type::Scalar -struct ScalarData +struct StringData final : public CommonStringData { - bool operator==(const ScalarData& rhs) const + StringData(std::string&& value = "") + : CommonStringData(std::move(value)) { - return scalar == rhs.scalar; } - ScalarType scalar; + std::unique_ptr copy() const final + { + return std::make_unique(std::string { _value }); + } }; -struct TypedData - : std::variant, std::optional, std::optional, - std::optional, BooleanType, IntType, FloatType> +struct JSONStringData final : public CommonStringData { + JSONStringData(std::string&& value = "") + : CommonStringData(std::move(value)) + { + } + + std::unique_ptr copy() const final + { + return std::make_unique(std::string { _value }); + } +}; + +struct EnumData final : public CommonStringData +{ + EnumData(std::string&& value = "") + : CommonStringData(std::move(value)) + { + } + + Type type() const noexcept final + { + return Type::EnumValue; + } + + std::unique_ptr copy() const final + { + return std::make_unique(std::string { _value }); + } +}; + +// Type::Scalar +struct ScalarData : public TypedData +{ + ScalarData() + : TypedData() + { + } + + Type type() const noexcept final + { + return Type::Scalar; + } + + bool operator==(const TypedData& rhs) const noexcept final + { + if (typeid(*this).name() != typeid(rhs).name()) + { + return false; + } + + return scalar == static_cast(rhs).scalar; + } + + const ScalarType& getScalar() const final + { + return scalar; + } + + ScalarType releaseScalar() final + { + ScalarType result = std::move(scalar); + + return result; + } + +private: + ScalarType scalar; }; Value::Value(Type type /*= Type::Null*/) - : _type(type) { switch (type) { case Type::Map: - _data = std::make_unique(TypedData { std::make_optional() }); + _data = std::make_unique(); break; case Type::List: - _data = std::make_unique(TypedData { std::make_optional() }); + _data = std::make_unique(); break; case Type::String: + _data = std::make_unique(); + break; + case Type::EnumValue: - _data = - std::make_unique(TypedData { std::make_optional() }); + _data = std::make_unique(); break; case Type::Scalar: - _data = std::make_unique(TypedData { std::make_optional() }); + _data = std::make_unique(); break; case Type::Boolean: - _data = std::make_unique(TypedData { BooleanType { false } }); + _data = std::make_unique(); break; case Type::Int: - _data = std::make_unique(TypedData { IntType { 0 } }); + _data = std::make_unique(); break; case Type::Float: - _data = std::make_unique(TypedData { FloatType { 0.0 } }); + _data = std::make_unique(); break; default: @@ -109,45 +610,37 @@ Value::~Value() } Value::Value(const char* value) - : _type(Type::String) - , _data(std::make_unique( - TypedData { StringOrEnumData { StringType { value }, false } })) + : _data(std::make_unique(value)) { } Value::Value(StringType&& value) - : _type(Type::String) - , _data(std::make_unique(TypedData { StringOrEnumData { std::move(value), false } })) + : _data(std::make_unique(std::move(value))) { } Value::Value(BooleanType value) - : _type(Type::Boolean) - , _data(std::make_unique(TypedData { value })) + : _data(std::make_unique(value)) { } Value::Value(IntType value) - : _type(Type::Int) - , _data(std::make_unique(TypedData { value })) + : _data(std::make_unique(value)) { } Value::Value(FloatType value) - : _type(Type::Float) - , _data(std::make_unique(TypedData { value })) + : _data(std::make_unique(value)) { } Value::Value(Value&& other) noexcept - : _type(other.type()) - , _data(std::move(other._data)) + : _data(std::move(other._data)) { } Value::Value(const Value& other) - : _type(other.type()) - , _data(std::make_unique(other._data ? *other._data : TypedData {})) + : _data(other._data ? other._data->copy() : nullptr) { } @@ -155,7 +648,6 @@ Value& Value::operator=(Value&& rhs) noexcept { if (&rhs != this) { - const_cast(_type) = rhs._type; _data = std::move(rhs._data); } @@ -169,7 +661,16 @@ bool Value::operator==(const Value& rhs) const noexcept return false; } - return !_data || *_data == *rhs._data; + if (_data == rhs._data) + { + return true; + } + else if (_data && rhs._data) + { + return *_data == *rhs._data; + } + + return false; } bool Value::operator!=(const Value& rhs) const noexcept @@ -179,69 +680,46 @@ bool Value::operator!=(const Value& rhs) const noexcept Type Value::type() const noexcept { - return _data ? _type : Type::Null; + return _data ? _data->type() : Type::Null; } Value&& Value::from_json() noexcept { - std::get>(*_data)->from_json = true; + _data = std::make_unique(_data->releaseString()); return std::move(*this); } bool Value::maybe_enum() const noexcept { - return type() == Type::EnumValue - || (type() == Type::String && std::get>(*_data)->from_json); + if (type() == Type::EnumValue) + { + return true; + } + else if (type() != Type::String) + { + return false; + } + + return !!dynamic_cast(_data.get()); } void Value::reserve(size_t count) { - switch (type()) + if (!_data) { - case Type::Map: - { - auto& mapData = std::get>(*_data); - - mapData->members.reserve(count); - mapData->map.reserve(count); - break; - } - - case Type::List: - { - auto& listData = std::get>(*_data); - - listData->list.reserve(count); - break; - } - - default: - throw std::logic_error("Invalid call to Value::reserve"); + throw std::logic_error("Invalid call to Value::reserve"); } + _data->reserve(count); } size_t Value::size() const { - switch (type()) + if (!_data) { - case Type::Map: - { - const auto& mapData = std::get>(*_data); - - return mapData->map.size(); - } - - case Type::List: - { - const auto& listData = std::get>(*_data); - - return listData->list.size(); - } - - default: - throw std::logic_error("Invalid call to Value::size"); + throw std::logic_error("Invalid call to Value::size"); } + return _data->size(); } void Value::emplace_back(std::string&& name, Value&& value) @@ -251,15 +729,7 @@ void Value::emplace_back(std::string&& name, Value&& value) throw std::logic_error("Invalid call to Value::emplace_back for MapType"); } - auto& mapData = std::get>(*_data); - - if (mapData->members.find(name) != mapData->members.cend()) - { - throw std::runtime_error("Duplicate Map member"); - } - - mapData->members.insert({ name, mapData->map.size() }); - mapData->map.emplace_back(std::make_pair(std::move(name), std::move(value))); + return _data->emplace_back(std::move(name), std::move(value)); } MapType::const_iterator Value::find(const std::string& name) const @@ -269,25 +739,17 @@ MapType::const_iterator Value::find(const std::string& name) const throw std::logic_error("Invalid call to Value::find for MapType"); } - const auto& mapData = std::get>(*_data); - const auto itr = mapData->members.find(name); - - if (itr == mapData->members.cend()) - { - return mapData->map.cend(); - } - - return mapData->map.cbegin() + itr->second; + return _data->find(name); } MapType::const_iterator Value::begin() const { if (type() != Type::Map) { - throw std::logic_error("Invalid call to Value::end for MapType"); + throw std::logic_error("Invalid call to Value::begin for MapType"); } - return std::get>(*_data)->map.cbegin(); + return _data->begin(); } MapType::const_iterator Value::end() const @@ -297,7 +759,7 @@ MapType::const_iterator Value::end() const throw std::logic_error("Invalid call to Value::end for MapType"); } - return std::get>(*_data)->map.cend(); + return _data->end(); } const Value& Value::operator[](const std::string& name) const @@ -319,17 +781,17 @@ void Value::emplace_back(Value&& value) throw std::logic_error("Invalid call to Value::emplace_back for ListType"); } - std::get>(*_data)->list.emplace_back(std::move(value)); + _data->emplace_back(std::move(value)); } const Value& Value::operator[](size_t index) const { if (type() != Type::List) { - throw std::logic_error("Invalid call to Value::emplace_back for ListType"); + throw std::logic_error("Invalid call to Value::operator[] for ListType"); } - return std::get>(*_data)->list.at(index); + return _data->operator[](index); } template <> @@ -340,7 +802,7 @@ void Value::set(StringType&& value) throw std::logic_error("Invalid call to Value::set for StringType"); } - std::get>(*_data)->string = std::move(value); + _data->setString(std::move(value)); } template <> @@ -351,26 +813,18 @@ void Value::set(BooleanType value) throw std::logic_error("Invalid call to Value::set for BooleanType"); } - *_data = { value }; + _data->setBoolean(value); } template <> void Value::set(IntType value) { - if (type() == Type::Float) + if (type() == Type::Float || type() == Type::Int) { - // Coerce IntType to FloatType - *_data = { static_cast(value) }; + throw std::logic_error("Invalid call to Value::set for IntType"); } - else - { - if (type() != Type::Int) - { - throw std::logic_error("Invalid call to Value::set for IntType"); - } - *_data = { value }; - } + _data->setInt(value); } template <> @@ -381,7 +835,7 @@ void Value::set(FloatType value) throw std::logic_error("Invalid call to Value::set for FloatType"); } - *_data = { value }; + _data->setFloat(value); } template <> @@ -392,7 +846,7 @@ void Value::set(ScalarType&& value) throw std::logic_error("Invalid call to Value::set for ScalarType"); } - *_data = { ScalarData { std::move(value) } }; + _data->setScalar(std::move(value)); } template <> @@ -403,7 +857,7 @@ const MapType& Value::get() const throw std::logic_error("Invalid call to Value::get for MapType"); } - return std::get>(*_data)->map; + return _data->getMap(); } template <> @@ -414,7 +868,7 @@ const ListType& Value::get() const throw std::logic_error("Invalid call to Value::get for ListType"); } - return std::get>(*_data)->list; + return _data->getList(); } template <> @@ -425,7 +879,7 @@ const StringType& Value::get() const throw std::logic_error("Invalid call to Value::get for StringType"); } - return std::get>(*_data)->string; + return _data->getString(); } template <> @@ -436,7 +890,7 @@ BooleanType Value::get() const throw std::logic_error("Invalid call to Value::get for BooleanType"); } - return std::get(*_data); + return _data->getBoolean(); } template <> @@ -447,24 +901,18 @@ IntType Value::get() const throw std::logic_error("Invalid call to Value::get for IntType"); } - return std::get(*_data); + return _data->getInt(); } template <> FloatType Value::get() const { - if (type() == Type::Int) - { - // Coerce IntType to FloatType - return static_cast(std::get(*_data)); - } - - if (type() != Type::Float) + if (type() != Type::Int && type() != Type::Float) { throw std::logic_error("Invalid call to Value::get for FloatType"); } - return std::get(*_data); + return _data->getFloat(); } template <> @@ -475,7 +923,7 @@ const ScalarType& Value::get() const throw std::logic_error("Invalid call to Value::get for ScalarType"); } - return std::get>(*_data)->scalar; + return _data->getScalar(); } template <> @@ -486,12 +934,7 @@ MapType Value::release() throw std::logic_error("Invalid call to Value::release for MapType"); } - auto& mapData = std::get>(*_data); - MapType result = std::move(mapData->map); - - mapData->members.clear(); - - return result; + return _data->releaseMap(); } template <> @@ -502,9 +945,7 @@ ListType Value::release() throw std::logic_error("Invalid call to Value::release for ListType"); } - ListType result = std::move(std::get>(*_data)->list); - - return result; + return _data->releaseList(); } template <> @@ -515,12 +956,7 @@ StringType Value::release() throw std::logic_error("Invalid call to Value::release for StringType"); } - auto& stringData = std::get>(*_data); - StringType result = std::move(stringData->string); - - stringData->from_json = false; - - return result; + return _data->releaseString(); } template <> @@ -531,9 +967,7 @@ ScalarType Value::release() throw std::logic_error("Invalid call to Value::release for ScalarType"); } - ScalarType result = std::move(std::get>(*_data)->scalar); - - return result; + return _data->releaseScalar(); } } /* namespace graphql::response */ From 7b79024be7360b359da7aac97709ae71587488b7 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 9 Dec 2020 01:07:20 -0300 Subject: [PATCH 23/42] response now uses copy-on-write for complex types map, list and strings will keep a shared_ptr that is passed along. It will copy the data prior to modifications when they are not the unique pointer user. --- src/GraphQLResponse.cpp | 166 +++++++++++++++++++++++++++++++--------- 1 file changed, 131 insertions(+), 35 deletions(-) diff --git a/src/GraphQLResponse.cpp b/src/GraphQLResponse.cpp index 0f96f8ab..37b4ecb4 100644 --- a/src/GraphQLResponse.cpp +++ b/src/GraphQLResponse.cpp @@ -295,6 +295,12 @@ struct FloatData final : public TypedData // Type::Map struct MapData final : public TypedData { + MapData() + : map(std::make_shared()) + , members(std::make_shared>()) + { + } + Type type() const noexcept final { return Type::Map; @@ -306,7 +312,17 @@ struct MapData final : public TypedData { return false; } - return map == static_cast(rhs).map; + + const auto& otherMap = static_cast(rhs).map; + if (map == otherMap) + { + return true; + } + else if (map && otherMap) + { + return *map == *otherMap; + } + return false; } std::unique_ptr copy() const final @@ -314,72 +330,100 @@ struct MapData final : public TypedData return std::make_unique(*this); } + void willModify() + { + if (map.unique()) + { + return; + } + + map = std::make_shared(*map); + members = std::make_shared>(*members); + } + void reserve(size_t count) final { - map.reserve(count); - members.reserve(count); + willModify(); + map->reserve(count); + members->reserve(count); } size_t size() const final { - return map.size(); + if (!map) + { + return 0; + } + return map->size(); } void emplace_back(std::string&& name, Value&& value) final { - if (members.find(name) != members.cend()) + willModify(); + + if (members->find(name) != members->cend()) { throw std::runtime_error("Duplicate Map member"); } - members.insert({ name, map.size() }); - map.emplace_back(std::make_pair(std::move(name), std::move(value))); + members->insert({ name, map->size() }); + map->emplace_back(std::make_pair(std::move(name), std::move(value))); } MapType::const_iterator find(const std::string& name) const final { - const auto& itr = members.find(name); + const auto& itr = members->find(name); - if (itr == members.cend()) + if (itr == members->cend()) { - return map.cend(); + return map->cend(); } - return map.cbegin() + itr->second; + return map->cbegin() + itr->second; } MapType::const_iterator begin() const final { - return map.cbegin(); + return map->cbegin(); } MapType::const_iterator end() const final { - return map.cend(); + return map->cend(); } const MapType& getMap() const final { - return map; + return *map; } MapType releaseMap() final { - MapType result = std::move(map); + if (!map.unique()) + { + return MapType(*map); + } + + MapType result = std::move(*map); - members.clear(); + members->clear(); return result; } private: - MapType map; - std::unordered_map members; + std::shared_ptr map; + std::shared_ptr> members; }; // Type::List struct ListData final : public TypedData { + ListData() + : list(std::make_shared()) + { + } + Type type() const noexcept final { return Type::List; @@ -391,7 +435,17 @@ struct ListData final : public TypedData { return false; } - return list == static_cast(rhs).list; + + const auto& otherList = static_cast(rhs).list; + if (list == otherList) + { + return true; + } + else if (list && otherList) + { + return *list == *otherList; + } + return false; } std::unique_ptr copy() const final @@ -399,46 +453,67 @@ struct ListData final : public TypedData return std::make_unique(*this); } + void willModify() + { + if (list.unique()) + { + return; + } + + list = std::make_shared(*list); + } + void reserve(size_t count) final { - list.reserve(count); + willModify(); + list->reserve(count); } size_t size() const final { - return list.size(); + if (!list) + { + return 0; + } + return list->size(); } void emplace_back(Value&& value) final { - list.emplace_back(std::move(value)); + willModify(); + list->emplace_back(std::move(value)); } const Value& operator[](size_t index) const final { - return list.at(index); + return list->at(index); } const ListType& getList() const final { - return list; + return *list; } ListType releaseList() const final { - auto result = std::move(list); + if (!list.unique()) + { + return ListType(*list); + } + + auto result = std::move(*list); return result; } private: - ListType list; + std::shared_ptr list; }; struct CommonStringData : public TypedData { CommonStringData(std::string&& value = "") - : _value(std::move(value)) + : _value(std::make_shared(std::move(value))) { } @@ -449,17 +524,29 @@ struct CommonStringData : public TypedData void setString(StringType&& value) final { - _value = std::move(value); + if (!_value.unique()) + { + _value = std::make_shared(std::move(value)); + } + else + { + _value->assign(std::move(value)); + } } const StringType& getString() const final { - return _value; + return *_value; } StringType releaseString() final { - StringType result = std::move(_value); + if (!_value.unique()) + { + return StringType(*_value); + } + + StringType result = std::move(*_value); return result; } @@ -471,11 +558,20 @@ struct CommonStringData : public TypedData return false; } - return _value == static_cast(rhs)._value; + const auto& otherValue = static_cast(rhs)._value; + if (_value == otherValue) + { + return true; + } + else if (_value && otherValue) + { + return *_value == *otherValue; + } + return false; } protected: - StringType _value; + std::shared_ptr _value; }; struct StringData final : public CommonStringData @@ -487,7 +583,7 @@ struct StringData final : public CommonStringData std::unique_ptr copy() const final { - return std::make_unique(std::string { _value }); + return std::make_unique(*this); } }; @@ -500,7 +596,7 @@ struct JSONStringData final : public CommonStringData std::unique_ptr copy() const final { - return std::make_unique(std::string { _value }); + return std::make_unique(*this); } }; @@ -518,7 +614,7 @@ struct EnumData final : public CommonStringData std::unique_ptr copy() const final { - return std::make_unique(std::string { _value }); + return std::make_unique(*this); } }; From d06d2b79d203a7fd2d546c5e7ec9d4a509185fa3 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 9 Dec 2020 12:14:20 -0300 Subject: [PATCH 24/42] optimize Request::findOperationDefinition() We don't need to validate this again, since the validation will already cover the specs bits. This way we save doing the whole loop, allocating usedNames and the queries that were already performed during validation. In our internal usages, there is no need to allocate the string, keep only the string_view to the operationType. Keep the old function, but mark as deprected. --- include/graphqlservice/GraphQLGrammar.h | 17 +++++ include/graphqlservice/GraphQLService.h | 12 ++- src/GraphQLService.cpp | 97 +++++++------------------ 3 files changed, 51 insertions(+), 75 deletions(-) diff --git a/include/graphqlservice/GraphQLGrammar.h b/include/graphqlservice/GraphQLGrammar.h index c40a56f0..56259c28 100644 --- a/include/graphqlservice/GraphQLGrammar.h +++ b/include/graphqlservice/GraphQLGrammar.h @@ -34,6 +34,23 @@ void for_each_child(const ast_node& n, std::function&& fu } } +template +const ast_node* find_child(const ast_node& n, std::function&& func) +{ + for (const auto& child : n.children) + { + if (child->is_type()) + { + if (func(*child)) + { + return child.get(); + } + } + } + + return nullptr; +} + template void on_first_child(const ast_node& n, std::function&& func) { diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index 39f505d8..be88b977 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -870,7 +870,7 @@ GRAPHQLSERVICE_EXPORT std::future ModifiedResult::conve FieldResult>&& result, ResolverParams&& params); #endif // GRAPHQL_DLLEXPORTS -using TypeMap = std::unordered_map>; +using TypeMap = std::unordered_map>; // 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 @@ -945,8 +945,14 @@ class Request : public std::enable_shared_from_this public: GRAPHQLSERVICE_EXPORT std::vector validate(peg::ast& query) const; - GRAPHQLSERVICE_EXPORT std::pair findOperationDefinition( - const peg::ast_node& root, const std::string& operationName) const; + [[deprecated( + "Use the Request::findOperationDefinition overload which takes a string_view reference " + "instead.")]] GRAPHQLSERVICE_EXPORT std::pair + findOperationDefinition( + const peg::ast_node& root, const std::string& operationName) const noexcept; + + GRAPHQLSERVICE_EXPORT std::pair findOperationDefinition( + const peg::ast_node& root, const std::string_view& operationName) const noexcept; GRAPHQLSERVICE_EXPORT std::future resolve( const std::shared_ptr& state, peg::ast& query, diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index cfe8534a..27734322 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -1369,7 +1369,7 @@ class OperationDefinitionVisitor std::future getValue(); - void visit(const std::string& operationType, const peg::ast_node& operationDefinition); + void visit(const std::string_view& operationType, const peg::ast_node& operationDefinition); private: const ResolverContext _resolverContext; @@ -1398,7 +1398,7 @@ std::future OperationDefinitionVisitor::getValue() } void OperationDefinitionVisitor::visit( - const std::string& operationType, const peg::ast_node& operationDefinition) + const std::string_view& operationType, const peg::ast_node& operationDefinition) { auto itr = _operations.find(operationType); @@ -1743,92 +1743,43 @@ std::vector Request::validate(peg::ast& query) const } std::pair Request::findOperationDefinition( - const peg::ast_node& root, const std::string& operationName) const + const peg::ast_node& root, const std::string& operationName) const noexcept +{ + auto result = findOperationDefinition(root, std::string_view { operationName }); + + return std::pair { std::string { result.first }, + result.second }; +} + +std::pair Request::findOperationDefinition( + const peg::ast_node& root, const std::string_view& operationName) const noexcept { bool hasAnonymous = false; - std::unordered_set usedNames; - std::pair result = { {}, nullptr }; + std::pair result = { {}, nullptr }; - peg::for_each_child(root, - [this, &hasAnonymous, &usedNames, &operationName, &result]( - const peg::ast_node& operationDefinition) { - std::string operationType(strQuery); + peg::find_child(root, + [this, &hasAnonymous, &operationName, &result](const peg::ast_node& operationDefinition) { + std::string_view operationType = strQuery; peg::on_first_child(operationDefinition, [&operationType](const peg::ast_node& child) { operationType = child.string_view(); }); - std::string name; + std::string_view name; peg::on_first_child(operationDefinition, [&name](const peg::ast_node& child) { name = child.string_view(); }); - std::vector errors; - auto position = operationDefinition.begin(); - - // http://spec.graphql.org/June2018/#sec-Operation-Name-Uniqueness - if (!usedNames.insert(name).second) + if (operationName.empty() || name == operationName) { - std::ostringstream message; - - if (name.empty()) - { - message << "Multiple anonymous operations"; - } - else - { - message << "Duplicate named operations name: " << name; - } - - errors.push_back({ message.str(), { position.line, position.column } }); + result = { operationType, &operationDefinition }; + return true; } - hasAnonymous = hasAnonymous || name.empty(); - - // http://spec.graphql.org/June2018/#sec-Lone-Anonymous-Operation - if (name.empty() ? usedNames.size() > 1 : hasAnonymous) - { - std::ostringstream message; - - if (name.empty()) - { - message << "Unexpected anonymous operation"; - } - else - { - message << "Unexpected named operation name: " << name; - } - - errors.push_back({ message.str(), { position.line, position.column } }); - } - - auto itr = _operations.find(operationType); - - if (itr == _operations.cend()) - { - std::ostringstream message; - - message << "Unsupported operation type: " << operationType; - - if (!name.empty()) - { - message << " name: " << name; - } - - errors.push_back({ message.str(), { position.line, position.column } }); - } - - if (!errors.empty()) - { - throw schema_exception(std::move(errors)); - } - else if (operationName.empty() || name == operationName) - { - result = { std::move(operationType), &operationDefinition }; - } + return false; }); return result; @@ -1909,7 +1860,8 @@ std::future Request::resolveValidated(std::launch launch, }); auto fragments = fragmentVisitor.getFragments(); - auto operationDefinition = findOperationDefinition(root, operationName); + auto operationDefinition = + findOperationDefinition(root, std::string_view { operationName }); if (!operationDefinition.second) { @@ -1994,7 +1946,8 @@ SubscriptionKey Request::subscribe(SubscriptionParams&& params, SubscriptionCall }); auto fragments = fragmentVisitor.getFragments(); - auto operationDefinition = findOperationDefinition(*params.query.root, params.operationName); + auto operationDefinition = + findOperationDefinition(*params.query.root, std::string_view { params.operationName }); if (!operationDefinition.second) { From c677d4515e2e698d22f09d229834296931c86191 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 9 Dec 2020 12:40:07 -0300 Subject: [PATCH 25/42] Validation: replace queue with list Our pattern to quickly push and pop some items to be processed is not that good with std::queue, which uses deque (double-ended queue). This is because it will keep an internal array to allow O(1) access. Since we do not use direct field access, let's stick with a double-linked list --- src/Validation.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Validation.cpp b/src/Validation.cpp index 7716bf19..9b1ba100 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace graphql::service { @@ -1863,7 +1864,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) ValidateFieldArguments validateArguments; std::unordered_map argumentLocations; - std::queue argumentNames; + std::list argumentNames; peg::on_first_child(field, [this, &name, &validateArguments, &argumentLocations, &argumentNames]( @@ -1890,7 +1891,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) visitor.visit(*argument->children.back()); validateArguments[argumentName] = visitor.getArgumentValue(); argumentLocations[argumentName] = { position.line, position.column }; - argumentNames.push(std::move(argumentName)); + argumentNames.push_back(std::move(argumentName)); } }); @@ -1928,7 +1929,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) { auto argumentName = std::move(argumentNames.front()); - argumentNames.pop(); + argumentNames.pop_front(); // TODO: string is a work around, the arguments set will be moved to string_view soon auto itrArgument = objectField.arguments.find(std::string { argumentName }); @@ -2259,7 +2260,7 @@ void ValidateExecutableVisitor::visitDirectives( [this, &directive, &directiveName, &validateDirective](const peg::ast_node& child) { ValidateFieldArguments validateArguments; std::unordered_map argumentLocations; - std::queue argumentNames; + std::list argumentNames; for (auto& argument : child.children) { @@ -2283,14 +2284,14 @@ void ValidateExecutableVisitor::visitDirectives( visitor.visit(*argument->children.back()); validateArguments[argumentName] = visitor.getArgumentValue(); argumentLocations[argumentName] = { position.line, position.column }; - argumentNames.push(argumentName); + argumentNames.push_back(argumentName); } while (!argumentNames.empty()) { auto argumentName = std::move(argumentNames.front()); - argumentNames.pop(); + argumentNames.pop_front(); // TODO: string is a work around, the arguments set will be moved to string_view // soon From 7d746b594f2696e9b215990c7c486e2475272f84 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 9 Dec 2020 13:02:15 -0300 Subject: [PATCH 26/42] Result conversions should all be launched as deferred The conversions are lightweigh helpers to do the conversions, keep them in the same thread. If the user is using expensive resolvers, then the actual resolver will be async. --- include/graphqlservice/GraphQLService.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index be88b977..62fbb38d 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -536,7 +536,7 @@ struct ModifiedResult static_assert(std::is_same_v, typename ResultTraits::type>, "this is the derived object type"); auto resultFuture = std::async( - params.launch, + std::launch::deferred, [](auto&& objectType) { return std::static_pointer_cast(objectType.get()); }, @@ -565,7 +565,7 @@ struct ModifiedResult ResolverParams&& params) { return std::async( - params.launch, + std::launch::deferred, [](auto&& wrappedFuture, ResolverParams&& wrappedParams) { auto wrappedResult = wrappedFuture.get(); @@ -603,7 +603,7 @@ struct ModifiedResult "this is the optional version"); return std::async( - params.launch, + std::launch::deferred, [](auto&& wrappedFuture, ResolverParams&& wrappedParams) { auto wrappedResult = wrappedFuture.get(); @@ -635,7 +635,7 @@ struct ModifiedResult ResolverParams&& params) { return std::async( - params.launch, + std::launch::deferred, [](auto&& wrappedFuture, ResolverParams&& wrappedParams) { auto wrappedResult = wrappedFuture.get(); std::queue> children; From b72fe3c83430638a23d5b414cd9f9a58a2a4c20e Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 7 Dec 2020 09:13:27 -0300 Subject: [PATCH 27/42] add response::Type::Result This new type is focused on returning a Value and errors. It's bit more efficient than the Map with only 2 keys, in particular given that it's bubbled to the top. In order to keep the public interface working, it's automatically converted `toMap()` in such places --- include/graphqlservice/GraphQLError.h | 2 + include/graphqlservice/GraphQLResponse.h | 60 +++++++ include/graphqlservice/GraphQLService.h | 128 ++++---------- src/GraphQLResponse.cpp | 202 +++++++++++++++++++++++ src/GraphQLService.cpp | 151 ++++------------- src/SchemaGenerator.cpp | 4 + 6 files changed, 337 insertions(+), 210 deletions(-) diff --git a/include/graphqlservice/GraphQLError.h b/include/graphqlservice/GraphQLError.h index eaeb0d82..0703af5f 100644 --- a/include/graphqlservice/GraphQLError.h +++ b/include/graphqlservice/GraphQLError.h @@ -35,6 +35,8 @@ struct schema_location } }; +constexpr schema_location emptyLocation {}; + using path_segment = std::variant; using field_path = std::queue; diff --git a/include/graphqlservice/GraphQLResponse.h b/include/graphqlservice/GraphQLResponse.h index 6f85fafa..a6374b44 100644 --- a/include/graphqlservice/GraphQLResponse.h +++ b/include/graphqlservice/GraphQLResponse.h @@ -18,6 +18,8 @@ #endif // !GRAPHQL_DLLEXPORTS // clang-format on +#include "graphqlservice/GraphQLError.h" + #include #include #include @@ -39,6 +41,7 @@ enum class Type : uint8_t Float, // JSON Number EnumValue, // JSON String Scalar, // JSON any type + Result, // pair of data=Value, errors=vector }; struct Value; @@ -51,6 +54,7 @@ using IntType = int; using FloatType = double; using ScalarType = Value; using IdType = std::vector; +struct ResultType; template struct ValueTypeTraits @@ -103,6 +107,14 @@ struct ValueTypeTraits using get_type = FloatType; }; +template <> +struct ValueTypeTraits +{ + // Get by const reference and release by value. + using get_type = const ResultType&; + using release_type = ResultType; +}; + template <> struct ValueTypeTraits { @@ -122,6 +134,7 @@ struct Value GRAPHQLRESPONSE_EXPORT explicit Value(BooleanType value); GRAPHQLRESPONSE_EXPORT explicit Value(IntType value); GRAPHQLRESPONSE_EXPORT explicit Value(FloatType value); + GRAPHQLRESPONSE_EXPORT explicit Value(ResultType&& value); GRAPHQLRESPONSE_EXPORT Value(Value&& other) noexcept; GRAPHQLRESPONSE_EXPORT explicit Value(const Value& other); @@ -156,6 +169,9 @@ struct Value GRAPHQLRESPONSE_EXPORT void emplace_back(Value&& value); GRAPHQLRESPONSE_EXPORT const Value& operator[](size_t index) const; + // Valid for Type::Result + GRAPHQLRESPONSE_EXPORT Value toMap(); + // Specialized for all single-value Types. template void set(typename std::enable_if_t, ValueType>, @@ -197,6 +213,46 @@ struct Value std::unique_ptr _data; }; +struct ResultType +{ + Value data = Value(Type::Null); + std::vector errors = {}; + + ResultType& operator=(const ResultType& other) = delete; + + GRAPHQLRESPONSE_EXPORT bool operator==(const ResultType& rhs) const noexcept + { + return data == rhs.data && errors == rhs.errors; + } + + GRAPHQLRESPONSE_EXPORT size_t size() const; +}; + +namespace { + +using namespace std::literals; + +constexpr std::string_view strData { "data"sv }; +constexpr std::string_view strErrors { "errors"sv }; +constexpr std::string_view strMessage { "message"sv }; +constexpr std::string_view strLocations { "locations"sv }; +constexpr std::string_view strLine { "line"sv }; +constexpr std::string_view strColumn { "column"sv }; +constexpr std::string_view strPath { "path"sv }; + +} // namespace + +// Errors should have a message string, and optional locations and a path. +GRAPHQLRESPONSE_EXPORT void addErrorMessage(std::string&& message, Value& error); + +GRAPHQLRESPONSE_EXPORT void addErrorLocation( + const graphql::error::schema_location& location, Value& error); + +GRAPHQLRESPONSE_EXPORT void addErrorPath(graphql::error::field_path&& path, Value& error); + +GRAPHQLRESPONSE_EXPORT Value buildErrorValues( + const std::vector& structuredErrors); + #ifdef GRAPHQL_DLLEXPORTS // Export all of the specialized template methods template <> @@ -224,6 +280,8 @@ GRAPHQLRESPONSE_EXPORT FloatType Value::get() const; template <> GRAPHQLRESPONSE_EXPORT const ScalarType& Value::get() const; template <> +GRAPHQLRESPONSE_EXPORT const ResultType& Value::get() const; +template <> GRAPHQLRESPONSE_EXPORT MapType Value::release(); template <> GRAPHQLRESPONSE_EXPORT ListType Value::release(); @@ -231,6 +289,8 @@ template <> GRAPHQLRESPONSE_EXPORT StringType Value::release(); template <> GRAPHQLRESPONSE_EXPORT ScalarType Value::release(); +template <> +GRAPHQLRESPONSE_EXPORT ResultType Value::release(); #endif // GRAPHQL_DLLEXPORTS } /* namespace graphql::response */ diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index 62fbb38d..593a5f58 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -87,13 +87,13 @@ namespace { using namespace std::literals; -constexpr std::string_view strData { "data"sv }; -constexpr std::string_view strErrors { "errors"sv }; -constexpr std::string_view strMessage { "message"sv }; -constexpr std::string_view strLocations { "locations"sv }; -constexpr std::string_view strLine { "line"sv }; -constexpr std::string_view strColumn { "column"sv }; -constexpr std::string_view strPath { "path"sv }; +constexpr std::string_view strData = response::strData; +constexpr std::string_view strErrors = response::strErrors; +constexpr std::string_view strMessage = response::strMessage; +constexpr std::string_view strLocations = response::strLocations; +constexpr std::string_view strLine = response::strLine; +constexpr std::string_view strColumn = response::strColumn; +constexpr std::string_view strPath = response::strPath; constexpr std::string_view strQuery { "query"sv }; constexpr std::string_view strMutation { "mutation"sv }; constexpr std::string_view strSubscription { "subscription"sv }; @@ -571,11 +571,7 @@ struct ModifiedResult if (!wrappedResult) { - response::Value document(response::Type::Map); - - document.emplace_back(std::string { strData }, response::Value()); - - return document; + return response::Value(response::ResultType()); } std::promise::type> promise; @@ -609,11 +605,7 @@ struct ModifiedResult if (!wrappedResult) { - response::Value document(response::Type::Map); - - document.emplace_back(std::string { strData }, response::Value()); - - return document; + return response::Value(response::ResultType()); } std::promise::type> promise; @@ -668,7 +660,7 @@ struct ModifiedResult } response::Value data(response::Type::List); - response::Value errors(response::Type::List); + std::vector errors; wrappedParams.errorPath.back() = size_t { 0 }; @@ -677,45 +669,32 @@ struct ModifiedResult try { auto value = children.front().get(); - auto members = value.release(); - - for (auto& entry : members) + auto result = value.release(); + if (result.errors.size()) { - if (entry.second.type() == response::Type::List - && entry.first == strErrors) - { - auto errorEntries = entry.second.release(); - - for (auto& errorEntry : errorEntries) - { - errors.emplace_back(std::move(errorEntry)); - } - } - else if (entry.first == strData) + errors.reserve(errors.size() + result.errors.size()); + for (auto& error : result.errors) { - data.emplace_back(std::move(entry.second)); + errors.emplace_back(std::move(error)); } } + data.emplace_back(std::move(result.data)); } catch (schema_exception& scx) { auto messages = scx.getStructuredErrors(); errors.reserve(errors.size() + messages.size()); - for (auto& message : messages) + for (auto& error : messages) { - response::Value error(response::Type::Map); - - error.reserve(3); - addErrorMessage(std::move(message.message), error); - addErrorLocation(message.location.line > 0 - ? message.location - : wrappedParams.getLocation(), - error); - addErrorPath(field_path { message.path.empty() ? wrappedParams.errorPath - : message.path }, - error); - + if (error.location == graphql::error::emptyLocation) + { + error.location = wrappedParams.getLocation(); + } + if (error.path.empty()) + { + error.path = field_path { wrappedParams.errorPath }; + } errors.emplace_back(std::move(error)); } } @@ -727,12 +706,8 @@ struct ModifiedResult << " unknown error: " << ex.what(); schema_location location = wrappedParams.getLocation(); - response::Value error(response::Type::Map); - - error.reserve(3); - addErrorMessage(message.str(), error); - addErrorLocation(location, error); - addErrorPath(field_path { wrappedParams.errorPath }, error); + field_path path { wrappedParams.errorPath }; + schema_error error { message.str(), std::move(location), std::move(path) }; errors.emplace_back(std::move(error)); } @@ -741,17 +716,7 @@ struct ModifiedResult ++std::get(wrappedParams.errorPath.back()); } - response::Value document(response::Type::Map); - - document.reserve(2); - document.emplace_back(std::string { strData }, std::move(data)); - - if (errors.size() > 0) - { - document.emplace_back(std::string { strErrors }, std::move(errors)); - } - - return document; + return response::Value(response::ResultType { std::move(data), std::move(errors) }); }, std::move(result), std::move(params)); @@ -772,7 +737,7 @@ struct ModifiedResult ResolverParams&& paramsFuture, ResolverCallback&& resolverFuture) noexcept { response::Value data; - response::Value errors(response::Type::List); + std::vector errors; try { @@ -783,19 +748,8 @@ struct ModifiedResult auto messages = scx.getStructuredErrors(); errors.reserve(errors.size() + messages.size()); - for (auto& message : messages) + for (auto& error : messages) { - response::Value error(response::Type::Map); - - error.reserve(3); - addErrorMessage(std::move(message.message), error); - addErrorLocation(message.location.line > 0 ? message.location - : paramsFuture.getLocation(), - error); - addErrorPath(field_path { message.path.empty() ? paramsFuture.errorPath - : message.path }, - error); - errors.emplace_back(std::move(error)); } } @@ -806,27 +760,13 @@ struct ModifiedResult message << "Field name: " << paramsFuture.fieldName << " unknown error: " << ex.what(); - response::Value error(response::Type::Map); - - error.reserve(3); - addErrorMessage(message.str(), error); - addErrorLocation(paramsFuture.getLocation(), error); - addErrorPath(std::move(paramsFuture.errorPath), error); - + schema_error error { message.str(), + paramsFuture.getLocation(), + paramsFuture.errorPath }; errors.emplace_back(std::move(error)); } - response::Value document(response::Type::Map); - - document.reserve(2); - document.emplace_back(std::string { strData }, std::move(data)); - - if (errors.size() > 0) - { - document.emplace_back(std::string { strErrors }, std::move(errors)); - } - - return document; + return response::Value(response::ResultType { std::move(data), std::move(errors) }); }, std::move(result), std::move(params), diff --git a/src/GraphQLResponse.cpp b/src/GraphQLResponse.cpp index 37b4ecb4..48de29e4 100644 --- a/src/GraphQLResponse.cpp +++ b/src/GraphQLResponse.cpp @@ -85,6 +85,11 @@ struct TypedData throw std::logic_error("Invalid call to Value::get for ListType"); } + virtual const ResultType& getResult() const + { + throw std::logic_error("Invalid call to Value::get for ResultType"); + } + virtual StringType releaseString() { throw std::logic_error("Invalid call to Value::release for StringType"); @@ -105,6 +110,11 @@ struct TypedData throw std::logic_error("Invalid call to Value::release for ListType"); } + virtual ResultType releaseResult() const + { + throw std::logic_error("Invalid call to Value::release for ResultType"); + } + // Valid for Type::Map or Type::List virtual void reserve(size_t count) { @@ -657,6 +667,64 @@ struct ScalarData : public TypedData ScalarType scalar; }; +// Type::Result +struct ResultData final : public TypedData +{ + ResultData(ResultType&& value = {}) + : _value(std::make_shared(std::move(value))) + { + } + + Type type() const noexcept final + { + return Type::Result; + } + + bool operator==(const TypedData& rhs) const noexcept final + { + if (typeid(*this).name() != typeid(rhs).name()) + { + return false; + } + + const auto& otherValue = static_cast(rhs)._value; + if (_value == otherValue) + { + return true; + } + else if (_value && otherValue) + { + return *_value == *otherValue; + } + return false; + } + + std::unique_ptr copy() const final + { + return std::make_unique(*this); + } + + const ResultType& getResult() const final + { + return *_value; + } + + ResultType releaseResult() const final + { + if (!_value.unique()) + { + return ResultType(*_value); + } + + ResultType result = std::move(*_value); + + return result; + } + +private: + std::shared_ptr _value; +}; + Value::Value(Type type /*= Type::Null*/) { switch (type) @@ -693,6 +761,9 @@ Value::Value(Type type /*= Type::Null*/) _data = std::make_unique(); break; + case Type::Result: + _data = std::make_unique(); + default: break; } @@ -730,6 +801,11 @@ Value::Value(FloatType value) { } +Value::Value(ResultType&& value) + : _data(std::make_unique(std::move(value))) +{ +} + Value::Value(Value&& other) noexcept : _data(std::move(other._data)) { @@ -1022,6 +1098,17 @@ const ScalarType& Value::get() const return _data->getScalar(); } +template <> +const ResultType& Value::get() const +{ + if (type() != Type::Result) + { + throw std::logic_error("Invalid call to Value::get for ResultType"); + } + + return _data->getResult(); +} + template <> MapType Value::release() { @@ -1066,4 +1153,119 @@ ScalarType Value::release() return _data->releaseScalar(); } +template <> +ResultType Value::release() +{ + if (type() != Type::Result) + { + throw std::logic_error("Invalid call to Value::release for ResultType"); + } + + return _data->releaseResult(); +} + +Value Value::toMap() +{ + if (type() != Type::Result) + { + throw std::logic_error("Invalid call to Value::toMap for ResultType"); + } + + auto resultData = release(); + + Value map(Type::Map); + map.reserve(resultData.size()); + map.emplace_back(std::string { strData }, std::move(resultData.data)); + if (resultData.errors.size() > 0) + { + map.emplace_back(std::string { strErrors }, buildErrorValues(resultData.errors)); + } + + return map; +} + +size_t ResultType::size() const +{ + return 1 + (errors.size() > 0); +} + +void addErrorMessage(std::string&& message, Value& error) +{ + error.emplace_back(std::string { strMessage }, response::Value(std::move(message))); +} + +void addErrorLocation(const graphql::error::schema_location& location, response::Value& error) +{ + if (location == graphql::error::emptyLocation) + { + return; + } + + response::Value errorLocation(response::Type::Map); + + errorLocation.reserve(2); + errorLocation.emplace_back(std::string { strLine }, + response::Value(static_cast(location.line))); + errorLocation.emplace_back(std::string { strColumn }, + response::Value(static_cast(location.column))); + + response::Value errorLocations(response::Type::List); + + errorLocations.reserve(1); + errorLocations.emplace_back(std::move(errorLocation)); + + error.emplace_back(std::string { strLocations }, std::move(errorLocations)); +} + +void addErrorPath(graphql::error::field_path&& path, Value& error) +{ + if (path.empty()) + { + return; + } + + response::Value errorPath(response::Type::List); + + errorPath.reserve(path.size()); + while (!path.empty()) + { + auto& segment = path.front(); + + if (std::holds_alternative(segment)) + { + errorPath.emplace_back(response::Value(std::move(std::get(segment)))); + } + else if (std::holds_alternative(segment)) + { + errorPath.emplace_back( + response::Value(static_cast(std::get(segment)))); + } + + path.pop(); + } + + error.emplace_back(std::string { strPath }, std::move(errorPath)); +} + +response::Value buildErrorValues(const std::vector& structuredErrors) +{ + response::Value errors(response::Type::List); + + errors.reserve(structuredErrors.size()); + + for (auto error : structuredErrors) + { + response::Value entry(response::Type::Map); + + entry.reserve(3); + addErrorMessage(std::move(error.message), entry); + addErrorLocation(error.location, entry); + addErrorPath(std::move(error.path), entry); + + errors.emplace_back(std::move(entry)); + } + + return errors; +} + } /* namespace graphql::response */ diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index 27734322..b45ab776 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -15,81 +15,22 @@ namespace graphql::service { void addErrorMessage(std::string&& message, response::Value& error) { - error.emplace_back(std::string { strMessage }, response::Value(std::move(message))); + graphql::response::addErrorMessage(std::move(message), error); } void addErrorLocation(const schema_location& location, response::Value& error) { - if (location.line == 0) - { - return; - } - - response::Value errorLocation(response::Type::Map); - - errorLocation.reserve(2); - errorLocation.emplace_back(std::string { strLine }, - response::Value(static_cast(location.line))); - errorLocation.emplace_back(std::string { strColumn }, - response::Value(static_cast(location.column))); - - response::Value errorLocations(response::Type::List); - - errorLocations.reserve(1); - errorLocations.emplace_back(std::move(errorLocation)); - - error.emplace_back(std::string { strLocations }, std::move(errorLocations)); + graphql::response::addErrorLocation(location, error); } void addErrorPath(field_path&& path, response::Value& error) { - if (path.empty()) - { - return; - } - - response::Value errorPath(response::Type::List); - - errorPath.reserve(path.size()); - while (!path.empty()) - { - auto& segment = path.front(); - - if (std::holds_alternative(segment)) - { - errorPath.emplace_back(response::Value(std::move(std::get(segment)))); - } - else if (std::holds_alternative(segment)) - { - errorPath.emplace_back( - response::Value(static_cast(std::get(segment)))); - } - - path.pop(); - } - - error.emplace_back(std::string { strPath }, std::move(errorPath)); + graphql::response::addErrorPath(std::move(path), error); } response::Value buildErrorValues(const std::vector& structuredErrors) { - response::Value errors(response::Type::List); - - errors.reserve(structuredErrors.size()); - - for (auto error : structuredErrors) - { - response::Value entry(response::Type::Map); - - entry.reserve(3); - addErrorMessage(std::move(error.message), entry); - addErrorLocation(error.location, entry); - addErrorPath(std::move(error.path), entry); - - errors.emplace_back(std::move(entry)); - } - - return errors; + return graphql::response::buildErrorValues(structuredErrors); } schema_exception::schema_exception(std::vector&& structuredErrors) @@ -801,12 +742,7 @@ std::future ModifiedResult::convert( if (!wrappedResult) { - response::Value document(response::Type::Map); - - document.emplace_back(std::string { strData }, - response::Value(response::Type::Null)); - - return document; + return response::Value(response::ResultType()); } return wrappedResult @@ -1020,7 +956,7 @@ void SelectionVisitor::visitField(const peg::ast_node& field) for (auto& message : messages) { - if (message.location.line == 0) + if (message.location == graphql::error::emptyLocation) { message.location = { position.line, position.column }; } @@ -1211,7 +1147,7 @@ std::future Object::resolve(const SelectionSetParams& selection selectionSetParams.launch, [](std::queue>>&& children) { response::Value data(response::Type::Map); - response::Value errors(response::Type::List); + std::vector errors; while (!children.empty()) { @@ -1220,42 +1156,32 @@ std::future Object::resolve(const SelectionSetParams& selection try { auto value = children.front().second.get(); - auto members = value.release(); - - for (auto& entry : members) + auto result = value.release(); + if (result.errors.size()) { - if (entry.second.type() == response::Type::List && entry.first == strErrors) + errors.reserve(errors.size() + result.errors.size()); + for (auto& error : result.errors) { - auto errorEntries = entry.second.release(); - - for (auto& errorEntry : errorEntries) - { - errors.emplace_back(std::move(errorEntry)); - } - } - else if (entry.first == strData) - { - auto itrData = data.find(name); - - if (itrData == data.end()) - { - data.emplace_back(std::move(name), std::move(entry.second)); - } - else if (itrData->second != entry.second) - { - std::ostringstream message; - response::Value error(response::Type::Map); - - message << "Ambiguous field error name: " << name; - addErrorMessage(message.str(), error); - errors.emplace_back(std::move(error)); - } + errors.emplace_back(std::move(error)); } } + auto itrData = data.find(name); + + if (itrData == data.end()) + { + data.emplace_back(std::move(name), std::move(result.data)); + } + else if (itrData->second != result.data) + { + std::ostringstream message; + + message << "Ambiguous field error name: " << name; + errors.emplace_back(schema_error { message.str() }); + } } catch (schema_exception& scx) { - auto messages = scx.getErrors().release(); + auto messages = scx.getStructuredErrors(); errors.reserve(errors.size() + messages.size()); for (auto& error : messages) @@ -1274,10 +1200,7 @@ std::future Object::resolve(const SelectionSetParams& selection message << "Field error name: " << name << " unknown error: " << ex.what(); - response::Value error(response::Type::Map); - - addErrorMessage(message.str(), error); - errors.emplace_back(std::move(error)); + errors.emplace_back(schema_error { message.str() }); if (data.find(name) == data.end()) { @@ -1288,16 +1211,7 @@ std::future Object::resolve(const SelectionSetParams& selection children.pop(); } - response::Value result(response::Type::Map); - - result.emplace_back(std::string { strData }, std::move(data)); - - if (errors.size() > 0) - { - result.emplace_back(std::string { strErrors }, std::move(errors)); - } - - return result; + return response::Value(response::ResultType { std::move(data), std::move(errors) }); }, std::move(selections)); } @@ -1826,7 +1740,12 @@ std::future Request::resolve(std::launch launch, return promise.get_future(); } - return resolveValidated(launch, state, *query.root, operationName, std::move(variables)); + return std::async( + launch, + [](std::future result) { + return result.get().toMap(); + }, + resolveValidated(launch, state, *query.root, operationName, std::move(variables))); } std::future Request::resolveValidated(std::launch launch, @@ -2269,7 +2188,7 @@ void Request::deliver(std::launch launch, const SubscriptionName& name, result = std::async( launch, [registration](std::future document) { - return document.get(); + return document.get().toMap(); }, optionalOrDefaultSubscription->resolve(selectionSetParams, registration->selection, diff --git a/src/SchemaGenerator.cpp b/src/SchemaGenerator.cpp index b9ab1db1..c6619a96 100644 --- a/src/SchemaGenerator.cpp +++ b/src/SchemaGenerator.cpp @@ -3229,6 +3229,10 @@ std::string Generator::getArgumentDefaultValue( )cpp"; break; } + + default: + // should never reach + std::cerr << "Unexpected reponse type: " << (int)defaultValue.type() << std::endl; } return argumentDefaultValue.str(); From a0af0e1c01e560d6d7abb8719f3f27a17880456f Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 9 Dec 2020 11:14:57 -0300 Subject: [PATCH 28/42] optimize SelectionVisitor::visitField() Instead of using a queue, use a list so we can simply concatenate them using splice. Since Value.emplace_back() will already check for duplicates, avoid checking again, use a try-catch block instead --- src/GraphQLService.cpp | 66 +++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index b45ab776..8b1bd254 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace graphql::service { @@ -778,7 +779,7 @@ class SelectionVisitor void visit(const peg::ast_node& selection); - std::queue>> getValues(); + std::list>> getValues(); private: void visitField(const peg::ast_node& field); @@ -796,8 +797,8 @@ class SelectionVisitor const ResolverMap& _resolvers; std::stack _fragmentDirectives; - std::unordered_set _names; - std::queue>> _values; + std::unordered_set _names; + std::list>> _values; }; SelectionVisitor::SelectionVisitor(const SelectionSetParams& selectionSetParams, @@ -818,7 +819,7 @@ SelectionVisitor::SelectionVisitor(const SelectionSetParams& selectionSetParams, response::Value(response::Type::Map) }); } -std::queue>> SelectionVisitor::getValues() +std::list>> SelectionVisitor::getValues() { auto values = std::move(_values); @@ -843,24 +844,19 @@ void SelectionVisitor::visit(const peg::ast_node& selection) void SelectionVisitor::visitField(const peg::ast_node& field) { - std::string name; + std::string_view name; peg::on_first_child(field, [&name](const peg::ast_node& child) { name = child.string_view(); }); - std::string alias; + std::string_view aliasView = name; - peg::on_first_child(field, [&alias](const peg::ast_node& child) { - alias = child.string_view(); + peg::on_first_child(field, [&aliasView](const peg::ast_node& child) { + aliasView = child.string_view(); }); - if (alias.empty()) - { - alias = name; - } - - if (!_names.insert(alias).second) + if (!_names.insert(aliasView).second) { // Skip resolving fields which map to the same response name as a field we've already // resolved. Validation should handle merging multiple references to the same field or @@ -868,6 +864,8 @@ void SelectionVisitor::visitField(const peg::ast_node& field) return; } + std::string alias(aliasView); + const auto [itr, itrEnd] = std::equal_range(_resolvers.cbegin(), _resolvers.cend(), std::make_pair(std::string_view { name }, Resolver {}), @@ -886,7 +884,7 @@ void SelectionVisitor::visitField(const peg::ast_node& field) promise.set_exception(std::make_exception_ptr(schema_exception { { schema_error { error.str(), { position.line, position.column }, { _path } } } })); - _values.push({ std::move(alias), promise.get_future() }); + _values.push_back({ std::move(alias), promise.get_future() }); return; } @@ -946,7 +944,7 @@ void SelectionVisitor::visitField(const peg::ast_node& field) _fragments, _variables)); - _values.push({ std::move(alias), std::move(result) }); + _values.push_back({ std::move(alias), std::move(result) }); } catch (schema_exception& scx) { @@ -969,7 +967,7 @@ void SelectionVisitor::visitField(const peg::ast_node& field) promise.set_exception(std::make_exception_ptr(schema_exception { std::move(messages) })); - _values.push({ std::move(alias), promise.get_future() }); + _values.push_back({ std::move(alias), promise.get_future() }); } catch (const std::exception& ex) { @@ -984,7 +982,7 @@ void SelectionVisitor::visitField(const peg::ast_node& field) { position.line, position.column }, std::move(selectionSetParams.errorPath) } } })); - _values.push({ std::move(alias), promise.get_future() }); + _values.push_back({ std::move(alias), promise.get_future() }); } } @@ -1122,7 +1120,7 @@ std::future Object::resolve(const SelectionSetParams& selection const peg::ast_node& selection, const FragmentMap& fragments, const response::Value& variables) const { - std::queue>> selections; + std::list>> selections; beginSelectionSet(selectionSetParams); @@ -1132,20 +1130,14 @@ std::future Object::resolve(const SelectionSetParams& selection visitor.visit(*child); - auto values = visitor.getValues(); - - while (!values.empty()) - { - selections.push(std::move(values.front())); - values.pop(); - } + selections.splice(selections.end(), visitor.getValues()); } endSelectionSet(selectionSetParams); return std::async( selectionSetParams.launch, - [](std::queue>>&& children) { + [](std::list>>&& children) { response::Value data(response::Type::Map); std::vector errors; @@ -1165,14 +1157,14 @@ std::future Object::resolve(const SelectionSetParams& selection errors.emplace_back(std::move(error)); } } - auto itrData = data.find(name); - if (itrData == data.end()) + try { data.emplace_back(std::move(name), std::move(result.data)); } - else if (itrData->second != result.data) + catch (std::runtime_error& e) { + // Duplicate Map member std::ostringstream message; message << "Ambiguous field error name: " << name; @@ -1189,10 +1181,14 @@ std::future Object::resolve(const SelectionSetParams& selection errors.emplace_back(std::move(error)); } - if (data.find(name) == data.end()) + try { data.emplace_back(std::move(name), {}); } + catch (std::runtime_error& e) + { + // Duplicate Map member + } } catch (const std::exception& ex) { @@ -1202,13 +1198,17 @@ std::future Object::resolve(const SelectionSetParams& selection errors.emplace_back(schema_error { message.str() }); - if (data.find(name) == data.end()) + try { data.emplace_back(std::move(name), {}); } + catch (std::runtime_error& e) + { + // Duplicate Map member + } } - children.pop(); + children.pop_front(); } return response::Value(response::ResultType { std::move(data), std::move(errors) }); From d8fdda70eca90de3cf9948b81fc29dc20edd0430 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 9 Dec 2020 14:49:39 -0300 Subject: [PATCH 29/42] optimize list result conversion pre-allocate a vector and populate it, then iterate directly instead of using a queue --- include/graphqlservice/GraphQLService.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index 593a5f58..34d5a58e 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -630,7 +630,8 @@ struct ModifiedResult std::launch::deferred, [](auto&& wrappedFuture, ResolverParams&& wrappedParams) { auto wrappedResult = wrappedFuture.get(); - std::queue> children; + std::vector> children(wrappedResult.size()); + size_t idx = 0; wrappedParams.errorPath.push(size_t { 0 }); @@ -644,8 +645,8 @@ struct ModifiedResult // Copy the values from the std::vector<> rather than moving them. for (typename vector_type::value_type entry : wrappedResult) { - children.push(ModifiedResult::convert(std::move(entry), - ResolverParams(wrappedParams))); + children[idx++] = ModifiedResult::convert(std::move(entry), + ResolverParams(wrappedParams)); ++std::get(wrappedParams.errorPath.back()); } } @@ -653,8 +654,8 @@ struct ModifiedResult { for (auto& entry : wrappedResult) { - children.push(ModifiedResult::convert(std::move(entry), - ResolverParams(wrappedParams))); + children[idx++] = ModifiedResult::convert(std::move(entry), + ResolverParams(wrappedParams)); ++std::get(wrappedParams.errorPath.back()); } } @@ -663,13 +664,13 @@ struct ModifiedResult std::vector errors; wrappedParams.errorPath.back() = size_t { 0 }; + data.reserve(children.size()); - while (!children.empty()) + for (auto& future : children) { try { - auto value = children.front().get(); - auto result = value.release(); + auto result = future.get().release(); if (result.errors.size()) { errors.reserve(errors.size() + result.errors.size()); @@ -712,7 +713,6 @@ struct ModifiedResult errors.emplace_back(std::move(error)); } - children.pop(); ++std::get(wrappedParams.errorPath.back()); } From cb52a63bfcb7c73bdc000eed4e063d803e3a83f7 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 9 Dec 2020 15:22:03 -0300 Subject: [PATCH 30/42] optimize converters for FieldResult Instead of always creating a recursive resolver, which in turn may call `std::async()`, only do that if the result is not readily available. --- include/graphqlservice/GraphQLService.h | 38 +++++++++++++++++++++++++ src/GraphQLService.cpp | 38 ++++++------------------- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index 34d5a58e..a0132d35 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -182,6 +182,44 @@ class FieldResult return std::get(std::move(_value)); } + std::future get_future_result() + { + if (std::holds_alternative>(_value)) + { + return std::async( + std::launch::deferred, + [](auto&& future) { + return response::Value(response::ResultType { response::Value(future.get()) }); + }, + std::move(std::get>(std::move(_value)))); + } + + std::promise promise; + promise.set_value(response::Value( + response::ResultType { response::Value(std::get(std::move(_value))) })); + return promise.get_future(); + } + + template + std::future get_future_result(C converter) + { + if (std::holds_alternative>(_value)) + { + return std::async( + std::launch::deferred, + [&converter](auto&& future) { + return response::Value( + response::ResultType { response::Value(converter(future.get())) }); + }, + std::move(std::get>(std::move(_value)))); + } + + std::promise promise; + promise.set_value(response::Value( + response::ResultType { response::Value(converter(std::get(std::move(_value)))) })); + return promise.get_future(); + } + private: std::variant> _value; }; diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index 8b1bd254..1a4dd651 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -642,11 +642,7 @@ std::future ModifiedResult::convert( { blockSubFields(params); - return resolve(std::move(result), - std::move(params), - [](response::IntType&& value, const ResolverParams&) { - return response::Value(value); - }); + return result.get_future_result(); } template <> @@ -655,11 +651,7 @@ std::future ModifiedResult::convert( { blockSubFields(params); - return resolve(std::move(result), - std::move(params), - [](response::FloatType&& value, const ResolverParams&) { - return response::Value(value); - }); + return result.get_future_result(); } template <> @@ -668,11 +660,7 @@ std::future ModifiedResult::convert( { blockSubFields(params); - return resolve(std::move(result), - std::move(params), - [](response::StringType&& value, const ResolverParams&) { - return response::Value(std::move(value)); - }); + return result.get_future_result(); } template <> @@ -681,11 +669,7 @@ std::future ModifiedResult::convert( { blockSubFields(params); - return resolve(std::move(result), - std::move(params), - [](response::BooleanType&& value, const ResolverParams&) { - return response::Value(value); - }); + return result.get_future_result(); } template <> @@ -694,11 +678,7 @@ std::future ModifiedResult::convert( { blockSubFields(params); - return resolve(std::move(result), - std::move(params), - [](response::Value&& value, const ResolverParams&) { - return response::Value(std::move(value)); - }); + return result.get_future_result(); } template <> @@ -707,11 +687,9 @@ std::future ModifiedResult::convert( { blockSubFields(params); - return resolve(std::move(result), - std::move(params), - [](response::IdType&& value, const ResolverParams&) { - return response::Value(Base64::toBase64(value)); - }); + std::string (*converter)(const response::IdType&) = &Base64::toBase64; + + return result.get_future_result(converter); } void requireSubFields(const ResolverParams& params) From 6cbf8c81fb0bbf0e053ad7999abe5b20c9e57907 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 9 Dec 2020 16:34:54 -0300 Subject: [PATCH 31/42] do not use ResultType unless really needed The wrap is not for free and is, more often than not, useless. --- include/graphqlservice/GraphQLService.h | 61 ++++++++++++------------- src/GraphQLService.cpp | 43 +++++++++++++---- 2 files changed, 63 insertions(+), 41 deletions(-) diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index a0132d35..1810b775 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -189,14 +189,13 @@ class FieldResult return std::async( std::launch::deferred, [](auto&& future) { - return response::Value(response::ResultType { response::Value(future.get()) }); + return response::Value(future.get()); }, std::move(std::get>(std::move(_value)))); } std::promise promise; - promise.set_value(response::Value( - response::ResultType { response::Value(std::get(std::move(_value))) })); + promise.set_value(response::Value(std::get(std::move(_value)))); return promise.get_future(); } @@ -208,15 +207,13 @@ class FieldResult return std::async( std::launch::deferred, [&converter](auto&& future) { - return response::Value( - response::ResultType { response::Value(converter(future.get())) }); + return response::Value(converter(future.get())); }, std::move(std::get>(std::move(_value)))); } std::promise promise; - promise.set_value(response::Value( - response::ResultType { response::Value(converter(std::get(std::move(_value)))) })); + promise.set_value(response::Value(converter(std::get(std::move(_value))))); return promise.get_future(); } @@ -609,7 +606,7 @@ struct ModifiedResult if (!wrappedResult) { - return response::Value(response::ResultType()); + return response::Value(); } std::promise::type> promise; @@ -643,7 +640,7 @@ struct ModifiedResult if (!wrappedResult) { - return response::Value(response::ResultType()); + return response::Value(); } std::promise::type> promise; @@ -708,16 +705,22 @@ struct ModifiedResult { try { - auto result = future.get().release(); - if (result.errors.size()) + auto value = future.get(); + if (value.type() == response::Type::Result) { - errors.reserve(errors.size() + result.errors.size()); - for (auto& error : result.errors) + auto result = value.release(); + if (result.errors.size()) { - errors.emplace_back(std::move(error)); + errors.reserve(errors.size() + result.errors.size()); + for (auto& error : result.errors) + { + errors.emplace_back(std::move(error)); + } } + value = std::move(result.data); } - data.emplace_back(std::move(result.data)); + + data.emplace_back(std::move(value)); } catch (schema_exception& scx) { @@ -754,6 +757,11 @@ struct ModifiedResult ++std::get(wrappedParams.errorPath.back()); } + if (errors.size() == 0) + { + return std::move(data); + } + return response::Value(response::ResultType { std::move(data), std::move(errors) }); }, std::move(result), @@ -774,22 +782,13 @@ struct ModifiedResult [](auto&& resultFuture, ResolverParams&& paramsFuture, ResolverCallback&& resolverFuture) noexcept { - response::Value data; - std::vector errors; - try { - data = resolverFuture(resultFuture.get(), paramsFuture); + return resolverFuture(resultFuture.get(), paramsFuture); } catch (schema_exception& scx) { - auto messages = scx.getStructuredErrors(); - - errors.reserve(errors.size() + messages.size()); - for (auto& error : messages) - { - errors.emplace_back(std::move(error)); - } + return response::Value(response::ResultType { {}, scx.getStructuredErrors() }); } catch (const std::exception& ex) { @@ -798,13 +797,11 @@ struct ModifiedResult message << "Field name: " << paramsFuture.fieldName << " unknown error: " << ex.what(); - schema_error error { message.str(), - paramsFuture.getLocation(), - paramsFuture.errorPath }; - errors.emplace_back(std::move(error)); + return response::Value(response::ResultType { {}, + { { message.str(), + paramsFuture.getLocation(), + paramsFuture.errorPath } } }); } - - return response::Value(response::ResultType { std::move(data), std::move(errors) }); }, std::move(result), std::move(params), diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index 1a4dd651..8896e0a5 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -721,7 +721,7 @@ std::future ModifiedResult::convert( if (!wrappedResult) { - return response::Value(response::ResultType()); + return response::Value(); } return wrappedResult @@ -1126,19 +1126,23 @@ std::future Object::resolve(const SelectionSetParams& selection try { auto value = children.front().second.get(); - auto result = value.release(); - if (result.errors.size()) + if (value.type() == response::Type::Result) { - errors.reserve(errors.size() + result.errors.size()); - for (auto& error : result.errors) + auto result = value.release(); + if (result.errors.size()) { - errors.emplace_back(std::move(error)); + errors.reserve(errors.size() + result.errors.size()); + for (auto& error : result.errors) + { + errors.emplace_back(std::move(error)); + } } + value = std::move(result.data); } try { - data.emplace_back(std::move(name), std::move(result.data)); + data.emplace_back(std::move(name), std::move(value)); } catch (std::runtime_error& e) { @@ -1189,6 +1193,11 @@ std::future Object::resolve(const SelectionSetParams& selection children.pop_front(); } + if (errors.size() == 0) + { + return std::move(data); + } + return response::Value(response::ResultType { std::move(data), std::move(errors) }); }, std::move(selections)); @@ -1721,7 +1730,15 @@ std::future Request::resolve(std::launch launch, return std::async( launch, [](std::future result) { - return result.get().toMap(); + auto value = result.get(); + if (value.type() == response::Type::Result) + { + return value.toMap(); + } + + response::Value document(response::Type::Map); + document.emplace_back(std::string { strData }, std::move(value)); + return std::move(document); }, resolveValidated(launch, state, *query.root, operationName, std::move(variables))); } @@ -2166,7 +2183,15 @@ void Request::deliver(std::launch launch, const SubscriptionName& name, result = std::async( launch, [registration](std::future document) { - return document.get().toMap(); + auto value = document.get(); + if (value.type() == response::Type::Result) + { + return value.toMap(); + } + + response::Value result(response::Type::Map); + result.emplace_back(std::string { strData }, std::move(value)); + return std::move(result); }, optionalOrDefaultSubscription->resolve(selectionSetParams, registration->selection, From 89448cdf103c3409cc858fc583fc51c31d6a6924 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 9 Dec 2020 18:32:28 -0300 Subject: [PATCH 32/42] split ValidationContext into a public file Just minor tweaks to make it compile, moving the template functions to the header and also marking virtuals as final. In the next commits it will be moved to more public usage, including the generator. --- include/Validation.h | 471 +--------------- include/graphqlservice/GraphQLValidation.h | 529 +++++++++++++++++ src/CMakeLists.txt | 2 + src/GraphQLValidation.cpp | 602 ++++++++++++++++++++ src/Validation.cpp | 628 --------------------- 5 files changed, 1134 insertions(+), 1098 deletions(-) create mode 100644 include/graphqlservice/GraphQLValidation.h create mode 100644 src/GraphQLValidation.cpp diff --git a/include/Validation.h b/include/Validation.h index 60983f2a..ab30f03e 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -6,379 +6,15 @@ #ifndef VALIDATION_H #define VALIDATION_H -#include "graphqlservice/GraphQLService.h" -#include "graphqlservice/IntrospectionSchema.h" +#include "graphqlservice/GraphQLValidation.h" namespace graphql::service { -class ValidateType -{ -public: - virtual introspection::TypeKind kind() const = 0; - virtual const std::string_view name() const = 0; - virtual bool isInputType() const = 0; - virtual bool isValid() const = 0; - virtual std::shared_ptr getInnerType() const = 0; - virtual bool operator==(const ValidateType& other) const = 0; - - static constexpr bool isKindInput(introspection::TypeKind typeKind) - { - switch (typeKind) - { - case introspection::TypeKind::SCALAR: - case introspection::TypeKind::ENUM: - case introspection::TypeKind::INPUT_OBJECT: - return true; - default: - return false; - } - } -}; - -class NamedValidateType - : public ValidateType - , public std::enable_shared_from_this -{ -public: - std::string _name; - - NamedValidateType(const std::string_view& name) - : _name(std::string(name)) - { - } - - virtual const std::string_view name() const - { - return _name; - } - - virtual bool isValid() const - { - return !name().empty(); - } - - virtual std::shared_ptr getInnerType() const - { - return shared_from_this(); - } - - virtual bool operator==(const ValidateType& other) const - { - if (this == &other) - { - return true; - } - - if (kind() != other.kind()) - { - return false; - } - - return _name == other.name(); - } -}; - -template -class NamedType : public NamedValidateType -{ -public: - NamedType(const std::string_view& name) - : NamedValidateType(name) - { - } - - virtual introspection::TypeKind kind() const - { - return typeKind; - } - - virtual bool isInputType() const - { - return ValidateType::isKindInput(typeKind); - } -}; - -using ScalarType = NamedType; - -class EnumType : public NamedType -{ -public: - EnumType(const std::string_view& name, std::unordered_set&& values) - : NamedType(name) - , _values(std::move(values)) - { - } - - bool find(const std::string_view& key) const - { - // TODO: in the future the set will be of string_view - return _values.find(std::string { key }) != _values.end(); - } - -private: - std::unordered_set _values; -}; - -template -class WrapperOfType : public ValidateType -{ -public: - WrapperOfType(std::shared_ptr ofType) - : _ofType(std::move(ofType)) - { - } - - std::shared_ptr ofType() const - { - return _ofType; - } - - virtual introspection::TypeKind kind() const - { - return typeKind; - } - - virtual const std::string_view name() const - { - return _name; - } - - virtual bool isInputType() const - { - return _ofType ? _ofType->isInputType() : false; - } - - virtual bool isValid() const - { - return _ofType ? _ofType->isValid() : false; - } - - virtual std::shared_ptr getInnerType() const - { - return _ofType ? _ofType->getInnerType() : nullptr; - } - - virtual bool operator==(const ValidateType& otherType) const - { - if (this == &otherType) - { - return true; - } - - if (typeKind != otherType.kind()) - { - return false; - } - - const auto& other = static_cast&>(otherType); - if (_ofType == other.ofType()) - { - return true; - } - if (_ofType && other.ofType()) - { - return *_ofType == *other.ofType(); - } - return false; - } - -private: - std::shared_ptr _ofType; - static inline const std::string_view _name = ""sv; -}; - -using ListOfType = WrapperOfType; -using NonNullOfType = WrapperOfType; - -struct ValidateArgument -{ - std::shared_ptr type; - bool defaultValue = false; - bool nonNullDefaultValue = false; -}; - -using ValidateTypeFieldArguments = std::unordered_map; - struct VariableDefinition : public ValidateArgument { schema_location position; }; -struct ValidateTypeField -{ - std::shared_ptr returnType; - ValidateTypeFieldArguments arguments; -}; - -using ValidateDirectiveArguments = std::unordered_map; - -template -class ContainerValidateType : public NamedValidateType -{ -public: - ContainerValidateType(const std::string_view& name) - : NamedValidateType(name) - { - } - - using FieldsContainer = std::unordered_map; - - std::optional> getField( - const std::string_view& name) const - { - // TODO: string is a work around, the _fields set will be moved to string_view soon - const auto& itr = _fields.find(std::string { name }); - if (itr == _fields.cend()) - { - return std::nullopt; - } - - return std::optional>(itr->second); - } - - void setFields(FieldsContainer&& fields) - { - _fields = std::move(fields); - } - - typename FieldsContainer::const_iterator begin() const - { - return _fields.cbegin(); - } - - typename FieldsContainer::const_iterator end() const - { - return _fields.cend(); - } - - virtual bool matchesType(const ValidateType& other) const - { - if (*this == other) - { - return true; - } - - switch (other.kind()) - { - case introspection::TypeKind::INTERFACE: - case introspection::TypeKind::UNION: - return static_cast(other).matchesType(*this); - - default: - return false; - } - } - -private: - FieldsContainer _fields; -}; - -template -class ContainerType : public ContainerValidateType -{ -public: - ContainerType(const std::string_view& name) - : ContainerValidateType(name) - { - } - - virtual introspection::TypeKind kind() const - { - return typeKind; - } - - virtual bool isInputType() const - { - return ValidateType::isKindInput(typeKind); - } -}; - -using ObjectType = ContainerType; -using InputObjectType = ContainerType; - -class PossibleTypesContainerValidateType : public ContainerValidateType -{ -public: - PossibleTypesContainerValidateType(const std::string_view& name) - : ContainerValidateType(name) - { - } - - const std::set& possibleTypes() const - { - return _possibleTypes; - } - - virtual bool matchesType(const ValidateType& other) const - { - if (*this == other) - { - return true; - } - - switch (other.kind()) - { - case introspection::TypeKind::OBJECT: - return _possibleTypes.find(&other) != _possibleTypes.cend(); - - case introspection::TypeKind::INTERFACE: - case introspection::TypeKind::UNION: - { - const auto& types = - static_cast(other).possibleTypes(); - for (const auto& itr : _possibleTypes) - { - if (types.find(itr) != types.cend()) - { - return true; - } - } - return false; - } - - default: - return false; - } - } - - void setPossibleTypes(std::set&& possibleTypes) - { - _possibleTypes = std::move(possibleTypes); - } - -private: - std::set _possibleTypes; -}; - -template -class PossibleTypesContainer : public PossibleTypesContainerValidateType -{ -public: - PossibleTypesContainer(const std::string_view& name) - : PossibleTypesContainerValidateType(name) - { - } - - virtual introspection::TypeKind kind() const - { - return typeKind; - } - - virtual bool isInputType() const - { - return ValidateType::isKindInput(typeKind); - } -}; - -using InterfaceType = PossibleTypesContainer; -using UnionType = PossibleTypesContainer; - -struct ValidateDirective -{ - std::set locations; - ValidateDirectiveArguments arguments; -}; - struct ValidateArgumentVariable { bool operator==(const ValidateArgumentVariable& other) const; @@ -501,111 +137,6 @@ class ValidateVariableTypeVisitor std::shared_ptr _variableType; }; -class ValidationContext -{ -public: - ValidationContext(const Request& service); - ValidationContext(const response::Value& introspectionQuery); - - struct OperationTypes - { - std::string queryType; - std::string mutationType; - std::string subscriptionType; - }; - - std::optional> getDirective( - const std::string_view& name) const; - std::optional> getOperationType( - const std::string_view& name) const; - - template ::value>::type* = nullptr> - std::shared_ptr getNamedValidateType(const std::string_view& name) const; - template ::value>::type* = nullptr> - std::shared_ptr getListOfType(std::shared_ptr&& ofType) const; - template ::value>::type* = nullptr> - std::shared_ptr getNonNullOfType(std::shared_ptr&& ofType) const; - -private: - void populate(const response::Value& introspectionQuery); - - struct - { - std::shared_ptr string; - std::shared_ptr nonNullString; - } commonTypes; - - ValidateTypeFieldArguments getArguments(const response::ListType& argumentsMember); - - using Directives = std::unordered_map; - - // These members store Introspection schema information which does not change between queries. - OperationTypes _operationTypes; - Directives _directives; - - std::unordered_map> _listOfCache; - std::unordered_map> _nonNullCache; - std::unordered_map> _namedCache; - - template ::value>::type* = nullptr> - std::shared_ptr makeNamedValidateType(T&& typeDef); - - template ::value>::type* = nullptr> - std::shared_ptr makeListOfType(std::shared_ptr&& ofType); - - template ::value>::type* = nullptr> - std::shared_ptr makeListOfType(std::shared_ptr& ofType) - { - return makeListOfType(std::shared_ptr(ofType)); - } - - template ::value>::type* = nullptr> - std::shared_ptr makeNonNullOfType(std::shared_ptr&& ofType); - - template ::value>::type* = nullptr> - std::shared_ptr makeNonNullOfType(std::shared_ptr& ofType) - { - return makeNonNullOfType(std::shared_ptr(ofType)); - } - - std::shared_ptr makeScalarType(const std::string_view& name) - { - return makeNamedValidateType(ScalarType { name }); - } - - std::shared_ptr makeObjectType(const std::string_view& name) - { - return makeNamedValidateType(ObjectType { name }); - } - - std::shared_ptr getTypeFromMap(const response::Value& typeMap); - - // builds the validation context (lookup maps) - void addScalar(const std::string_view& scalarName); - void addEnum(const std::string_view& enumName, const response::Value& enumDescriptionMap); - void addObject(const std::string_view& name); - void addInputObject(const std::string_view& name); - void addInterface(const std::string_view& name, const response::Value& typeDescriptionMap); - void addUnion(const std::string_view& name, const response::Value& typeDescriptionMap); - void addDirective(const std::string_view& name, const response::ListType& locations, - const response::Value& descriptionMap); - - void addTypeFields(std::shared_ptr> type, - const response::Value& typeDescriptionMap); - void addPossibleTypes(std::shared_ptr type, - const response::Value& typeDescriptionMap); - void addInputTypeFields( - std::shared_ptr type, const response::Value& typeDescriptionMap); -}; - // ValidateExecutableVisitor visits the AST and validates that it is executable against the service // schema. class ValidateExecutableVisitor diff --git a/include/graphqlservice/GraphQLValidation.h b/include/graphqlservice/GraphQLValidation.h new file mode 100644 index 00000000..3c73717a --- /dev/null +++ b/include/graphqlservice/GraphQLValidation.h @@ -0,0 +1,529 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#ifndef GRAPHQLVALIDATION_H +#define GRAPHQLVALIDATION_H + +// clang-format off +#ifdef GRAPHQL_DLLEXPORTS + #ifdef IMPL_GRAPHQLSERVICE_DLL + #define GRAPHQLVALIDATION_EXPORT __declspec(dllexport) + #else // !IMPL_GRAPHQLSERVICE_DLL + #define GRAPHQLVALIDATION_EXPORT __declspec(dllimport) + #endif // !IMPL_GRAPHQLSERVICE_DLL +#else // !GRAPHQL_DLLEXPORTS + #define GRAPHQLVALIDATION_EXPORT +#endif // !GRAPHQL_DLLEXPORTS +// clang-format on + +#include "graphqlservice/GraphQLService.h" +#include "graphqlservice/IntrospectionSchema.h" + +#include +#include +#include + +namespace graphql::service { + +class ValidateType +{ +public: + virtual introspection::TypeKind kind() const = 0; + virtual const std::string_view name() const = 0; + virtual bool isInputType() const = 0; + virtual bool isValid() const = 0; + virtual std::shared_ptr getInnerType() const = 0; + virtual bool operator==(const ValidateType& other) const = 0; + + static constexpr bool isKindInput(introspection::TypeKind typeKind) + { + switch (typeKind) + { + case introspection::TypeKind::SCALAR: + case introspection::TypeKind::ENUM: + case introspection::TypeKind::INPUT_OBJECT: + return true; + default: + return false; + } + } +}; + +class NamedValidateType + : public ValidateType + , public std::enable_shared_from_this +{ +public: + std::string _name; + + NamedValidateType(const std::string_view& name) + : _name(std::string(name)) + { + } + + const std::string_view name() const final + { + return _name; + } + + bool isValid() const final + { + return !name().empty(); + } + + std::shared_ptr getInnerType() const final + { + return shared_from_this(); + } + + bool operator==(const ValidateType& other) const final + { + if (this == &other) + { + return true; + } + + if (kind() != other.kind()) + { + return false; + } + + return _name == other.name(); + } +}; + +template +class NamedType : public NamedValidateType +{ +public: + NamedType(const std::string_view& name) + : NamedValidateType(name) + { + } + + introspection::TypeKind kind() const final + { + return typeKind; + } + + bool isInputType() const final + { + return ValidateType::isKindInput(typeKind); + } +}; + +using ScalarType = NamedType; + +class EnumType final : public NamedType +{ +public: + EnumType(const std::string_view& name, std::unordered_set&& values) + : NamedType(name) + , _values(std::move(values)) + { + } + + bool find(const std::string_view& key) const + { + // TODO: in the future the set will be of string_view + return _values.find(std::string { key }) != _values.end(); + } + +private: + std::unordered_set _values; +}; + +template +class WrapperOfType final : public ValidateType +{ +public: + WrapperOfType(std::shared_ptr ofType) + : _ofType(std::move(ofType)) + { + } + + std::shared_ptr ofType() const + { + return _ofType; + } + + introspection::TypeKind kind() const final + { + return typeKind; + } + + const std::string_view name() const final + { + return _name; + } + + bool isInputType() const final + { + return _ofType ? _ofType->isInputType() : false; + } + + bool isValid() const final + { + return _ofType ? _ofType->isValid() : false; + } + + std::shared_ptr getInnerType() const final + { + return _ofType ? _ofType->getInnerType() : nullptr; + } + + bool operator==(const ValidateType& otherType) const final + { + if (this == &otherType) + { + return true; + } + + if (typeKind != otherType.kind()) + { + return false; + } + + const auto& other = static_cast&>(otherType); + if (_ofType == other.ofType()) + { + return true; + } + if (_ofType && other.ofType()) + { + return *_ofType == *other.ofType(); + } + return false; + } + +private: + std::shared_ptr _ofType; + static inline const std::string_view _name = ""sv; +}; + +using ListOfType = WrapperOfType; +using NonNullOfType = WrapperOfType; + +struct ValidateArgument +{ + std::shared_ptr type; + bool defaultValue = false; + bool nonNullDefaultValue = false; +}; + +using ValidateTypeFieldArguments = std::unordered_map; + +struct ValidateTypeField +{ + std::shared_ptr returnType; + ValidateTypeFieldArguments arguments; +}; + +using ValidateDirectiveArguments = std::unordered_map; + +template +class ContainerValidateType : public NamedValidateType +{ +public: + ContainerValidateType(const std::string_view& name) + : NamedValidateType(name) + { + } + + using FieldsContainer = std::unordered_map; + + std::optional> getField( + const std::string_view& name) const + { + // TODO: string is a work around, the _fields set will be moved to string_view soon + const auto& itr = _fields.find(std::string { name }); + if (itr == _fields.cend()) + { + return std::nullopt; + } + + return std::optional>(itr->second); + } + + void setFields(FieldsContainer&& fields) + { + _fields = std::move(fields); + } + + typename FieldsContainer::const_iterator begin() const + { + return _fields.cbegin(); + } + + typename FieldsContainer::const_iterator end() const + { + return _fields.cend(); + } + + virtual bool matchesType(const ValidateType& other) const + { + if (*this == other) + { + return true; + } + + switch (other.kind()) + { + case introspection::TypeKind::INTERFACE: + case introspection::TypeKind::UNION: + return static_cast(other).matchesType(*this); + + default: + return false; + } + } + +private: + FieldsContainer _fields; +}; + +template +class ContainerType : public ContainerValidateType +{ +public: + ContainerType(const std::string_view& name) + : ContainerValidateType(name) + { + } + + introspection::TypeKind kind() const final + { + return typeKind; + } + + bool isInputType() const final + { + return ValidateType::isKindInput(typeKind); + } +}; + +using ObjectType = ContainerType; +using InputObjectType = ContainerType; + +class PossibleTypesContainerValidateType : public ContainerValidateType +{ +public: + PossibleTypesContainerValidateType(const std::string_view& name) + : ContainerValidateType(name) + { + } + + const std::set& possibleTypes() const + { + return _possibleTypes; + } + + bool matchesType(const ValidateType& other) const final + { + if (*this == other) + { + return true; + } + + switch (other.kind()) + { + case introspection::TypeKind::OBJECT: + return _possibleTypes.find(&other) != _possibleTypes.cend(); + + case introspection::TypeKind::INTERFACE: + case introspection::TypeKind::UNION: + { + const auto& types = + static_cast(other).possibleTypes(); + for (const auto& itr : _possibleTypes) + { + if (types.find(itr) != types.cend()) + { + return true; + } + } + return false; + } + + default: + return false; + } + } + + void setPossibleTypes(std::set&& possibleTypes) + { + _possibleTypes = std::move(possibleTypes); + } + +private: + std::set _possibleTypes; +}; + +template +class PossibleTypesContainer final : public PossibleTypesContainerValidateType +{ +public: + PossibleTypesContainer(const std::string_view& name) + : PossibleTypesContainerValidateType(name) + { + } + + introspection::TypeKind kind() const final + { + return typeKind; + } + + bool isInputType() const final + { + return ValidateType::isKindInput(typeKind); + } +}; + +using InterfaceType = PossibleTypesContainer; +using UnionType = PossibleTypesContainer; + +struct ValidateDirective +{ + std::set locations; + ValidateDirectiveArguments arguments; +}; + +class ValidationContext +{ +public: + ValidationContext(const Request& service); + ValidationContext(const response::Value& introspectionQuery); + + struct OperationTypes + { + std::string queryType; + std::string mutationType; + std::string subscriptionType; + }; + + GRAPHQLVALIDATION_EXPORT std::optional> + getDirective(const std::string_view& name) const; + GRAPHQLVALIDATION_EXPORT std::optional> + getOperationType(const std::string_view& name) const; + + template ::value>::type* = nullptr> + std::shared_ptr getNamedValidateType(const std::string_view& name) const + { + const auto& itr = _namedCache.find(name); + if (itr != _namedCache.cend()) + { + return std::dynamic_pointer_cast(itr->second); + } + + return nullptr; + } + + template ::value>::type* = nullptr> + std::shared_ptr getListOfType(std::shared_ptr&& ofType) const + { + const auto& itr = _listOfCache.find(ofType.get()); + if (itr != _listOfCache.cend()) + { + return itr->second; + } + + return nullptr; + } + + template ::value>::type* = nullptr> + std::shared_ptr getNonNullOfType(std::shared_ptr&& ofType) const + { + const auto& itr = _nonNullCache.find(ofType.get()); + if (itr != _nonNullCache.cend()) + { + return itr->second; + } + + return nullptr; + } + +private: + void populate(const response::Value& introspectionQuery); + + struct + { + std::shared_ptr string; + std::shared_ptr nonNullString; + } commonTypes; + + ValidateTypeFieldArguments getArguments(const response::ListType& argumentsMember); + + using Directives = std::unordered_map; + + // These members store Introspection schema information which does not change between queries. + OperationTypes _operationTypes; + Directives _directives; + + std::unordered_map> _listOfCache; + std::unordered_map> _nonNullCache; + std::unordered_map> _namedCache; + + template ::value>::type* = nullptr> + std::shared_ptr makeNamedValidateType(T&& typeDef); + + template ::value>::type* = nullptr> + std::shared_ptr makeListOfType(std::shared_ptr&& ofType); + + template ::value>::type* = nullptr> + std::shared_ptr makeListOfType(std::shared_ptr& ofType) + { + return makeListOfType(std::shared_ptr(ofType)); + } + + template ::value>::type* = nullptr> + std::shared_ptr makeNonNullOfType(std::shared_ptr&& ofType); + + template ::value>::type* = nullptr> + std::shared_ptr makeNonNullOfType(std::shared_ptr& ofType) + { + return makeNonNullOfType(std::shared_ptr(ofType)); + } + + std::shared_ptr makeScalarType(const std::string_view& name) + { + return makeNamedValidateType(ScalarType { name }); + } + + std::shared_ptr makeObjectType(const std::string_view& name) + { + return makeNamedValidateType(ObjectType { name }); + } + + std::shared_ptr getTypeFromMap(const response::Value& typeMap); + + // builds the validation context (lookup maps) + void addScalar(const std::string_view& scalarName); + void addEnum(const std::string_view& enumName, const response::Value& enumDescriptionMap); + void addObject(const std::string_view& name); + void addInputObject(const std::string_view& name); + void addInterface(const std::string_view& name, const response::Value& typeDescriptionMap); + void addUnion(const std::string_view& name, const response::Value& typeDescriptionMap); + void addDirective(const std::string_view& name, const response::ListType& locations, + const response::Value& descriptionMap); + + void addTypeFields(std::shared_ptr> type, + const response::Value& typeDescriptionMap); + void addPossibleTypes(std::shared_ptr type, + const response::Value& typeDescriptionMap); + void addInputTypeFields( + std::shared_ptr type, const response::Value& typeDescriptionMap); +}; + +} /* namespace graphql::service */ + +#endif // GRAPHQLVALIDATION_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3e8c19f1..50604153 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -164,6 +164,7 @@ endif() # graphqlservice add_library(graphqlservice GraphQLService.cpp + GraphQLValidation.cpp Introspection.cpp Validation.cpp ${CMAKE_CURRENT_BINARY_DIR}/../IntrospectionSchema.cpp) @@ -239,6 +240,7 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../include/graphqlservice/GraphQLError.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/graphqlservice/GraphQLResponse.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/graphqlservice/GraphQLService.h + ${CMAKE_CURRENT_SOURCE_DIR}/../include/graphqlservice/GraphQLValidation.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/graphqlservice/GraphQLGrammar.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/graphqlservice/GraphQLTree.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/graphqlservice/Introspection.h diff --git a/src/GraphQLValidation.cpp b/src/GraphQLValidation.cpp new file mode 100644 index 00000000..7705c356 --- /dev/null +++ b/src/GraphQLValidation.cpp @@ -0,0 +1,602 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "graphqlservice/GraphQLValidation.h" + +namespace graphql::service { + +constexpr std::string_view introspectionQuery = R"gql( +query IntrospectionQuery { + __schema { + queryType { name } + mutationType { name } + subscriptionType { name } + types { ...FullType } + directives { + name + locations + args { ...InputValue } + } + } +} + +fragment FullType on __Type { + kind + name + fields(includeDeprecated: true) { + name + args { ...InputValue } + type { ...TypeRef } + } + inputFields { ...InputValue } + interfaces { ...TypeRef } + enumValues(includeDeprecated: true) { name } + possibleTypes { ...TypeRef } +} + +fragment InputValue on __InputValue { + name + type { ...TypeRef } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } +} +)gql"; + +ValidationContext::ValidationContext(const Request& service) +{ + // TODO: we should execute this query only once per schema, + // maybe it can be done and cached inside the Request itself to allow + // this. Alternatively it could be provided at compile-time such as schema.json + // that is parsed, this would allow us to drop introspection from the Request + // and still have it to work + auto ast = peg::parseString(introspectionQuery); + // This is taking advantage of the fact that during validation we can choose to execute + // unvalidated queries against the Introspection schema. This way we can use fragment + // cycles to expand an arbitrary number of wrapper types. + ast.validated = true; + + std::shared_ptr state; + const std::string operationName; + response::Value variables(response::Type::Map); + auto result = service.resolve(state, ast, operationName, std::move(variables)).get(); + + populate(result); +} + +ValidationContext::ValidationContext(const response::Value& introspectionQuery) +{ + populate(introspectionQuery); +} + +void ValidationContext::populate(const response::Value& introspectionQuery) +{ + commonTypes.string = makeScalarType("String"); + commonTypes.nonNullString = makeNonNullOfType(commonTypes.string); + + const auto& itrData = introspectionQuery.find(std::string { strData }); + if (itrData == introspectionQuery.end()) + { + return; + } + + const auto& data = itrData->second; + const auto& itrSchema = data.find(R"gql(__schema)gql"); + if (itrSchema != data.end() && itrSchema->second.type() == response::Type::Map) + { + for (auto itr = itrSchema->second.begin(); itr < itrSchema->second.end(); itr++) + { + const auto& member = *itr; + if (member.second.type() == response::Type::Map) + { + const auto& itrType = member.second.find(R"gql(name)gql"); + if (itrType != member.second.end() + && itrType->second.type() == response::Type::String) + { + if (member.first == R"gql(queryType)gql") + { + _operationTypes.queryType = itrType->second.get(); + } + else if (member.first == R"gql(mutationType)gql") + { + _operationTypes.mutationType = itrType->second.get(); + } + else if (member.first == R"gql(subscriptionType)gql") + { + _operationTypes.subscriptionType = + itrType->second.get(); + } + } + } + else if (member.second.type() == response::Type::List + && member.first == R"gql(types)gql") + { + const auto& entries = member.second.get(); + + // first iteration add the named types + for (const auto& entry : entries) + { + if (entry.type() != response::Type::Map) + { + continue; + } + + const auto& itrName = entry.find(R"gql(name)gql"); + const auto& itrKind = entry.find(R"gql(kind)gql"); + + if (itrName != entry.end() && itrName->second.type() == response::Type::String + && itrKind != entry.end() + && itrKind->second.type() == response::Type::EnumValue) + { + const auto& name = itrName->second.get(); + const auto& kind = + ModifiedArgument::convert(itrKind->second); + + if (kind == introspection::TypeKind::OBJECT) + { + addObject(name); + } + else if (kind == introspection::TypeKind::INPUT_OBJECT) + { + addInputObject(name); + } + else if (kind == introspection::TypeKind::INTERFACE) + { + addInterface(name, entry); + } + else if (kind == introspection::TypeKind::UNION) + { + addUnion(name, entry); + } + else if (kind == introspection::TypeKind::ENUM) + { + addEnum(name, entry); + } + else if (kind == introspection::TypeKind::SCALAR) + { + addScalar(name); + } + } + } + + // second iteration add the fields that refer to given types + for (const auto& entry : entries) + { + if (entry.type() != response::Type::Map) + { + continue; + } + + const auto& itrName = entry.find(R"gql(name)gql"); + const auto& itrKind = entry.find(R"gql(kind)gql"); + + if (itrName != entry.end() && itrName->second.type() == response::Type::String + && itrKind != entry.end() + && itrKind->second.type() == response::Type::EnumValue) + { + const auto& name = itrName->second.get(); + const auto& kind = + ModifiedArgument::convert(itrKind->second); + + if (kind == introspection::TypeKind::OBJECT) + { + auto type = getNamedValidateType(name); + addTypeFields(type, entry); + } + else if (kind == introspection::TypeKind::INTERFACE + || kind == introspection::TypeKind::UNION) + { + auto type = + getNamedValidateType(name); + addTypeFields(type, entry); + addPossibleTypes(type, entry); + } + else if (kind == introspection::TypeKind::INPUT_OBJECT) + { + auto type = getNamedValidateType(name); + addInputTypeFields(type, entry); + } + } + } + } + else if (member.second.type() == response::Type::List + && member.first == R"gql(directives)gql") + { + const auto& entries = member.second.get(); + + for (const auto& entry : entries) + { + if (entry.type() != response::Type::Map) + { + continue; + } + + const auto& itrName = entry.find(R"gql(name)gql"); + const auto& itrLocations = entry.find(R"gql(locations)gql"); + + if (itrName != entry.end() && itrName->second.type() == response::Type::String + && itrLocations != entry.end() + && itrLocations->second.type() == response::Type::List) + { + const auto& name = itrName->second.get(); + const auto& locations = itrLocations->second.get(); + + addDirective(name, locations, entry); + } + } + } + } + } +} + +ValidateTypeFieldArguments ValidationContext::getArguments(const response::ListType& args) +{ + ValidateTypeFieldArguments result; + + for (const auto& arg : args) + { + if (arg.type() != response::Type::Map) + { + continue; + } + + const auto& itrName = arg.find(R"gql(name)gql"); + const auto& itrType = arg.find(R"gql(type)gql"); + const auto& itrDefaultValue = arg.find(R"gql(defaultValue)gql"); + + if (itrName != arg.end() && itrName->second.type() == response::Type::String + && itrType != arg.end() && itrType->second.type() == response::Type::Map) + { + ValidateArgument argument; + + argument.defaultValue = (itrDefaultValue != arg.end() + && itrDefaultValue->second.type() == response::Type::String); + argument.nonNullDefaultValue = argument.defaultValue + && itrDefaultValue->second.get() != R"gql(null)gql"; + argument.type = getTypeFromMap(itrType->second); + + result[itrName->second.get()] = std::move(argument); + } + } + + return result; +} + +std::optional> ValidationContext::getDirective( + const std::string_view& name) const +{ + // TODO: string is a work around, the directives map will be moved to string_view soon + const auto& itr = _directives.find(std::string { name }); + if (itr == _directives.cend()) + { + return std::nullopt; + } + return std::optional>(itr->second); +} + +std::optional> ValidationContext::getOperationType( + const std::string_view& name) const +{ + if (name == strQuery) + { + return std::optional>(_operationTypes.queryType); + } + if (name == strMutation) + { + return std::optional>( + _operationTypes.mutationType); + } + if (name == strSubscription) + { + return std::optional>( + _operationTypes.subscriptionType); + } + return std::nullopt; +} + +void ValidationContext::addTypeFields( + std::shared_ptr> type, + const response::Value& typeDescriptionMap) +{ + std::unordered_map fields; + + const auto& itrFields = typeDescriptionMap.find(R"gql(fields)gql"); + if (itrFields != typeDescriptionMap.end() && itrFields->second.type() == response::Type::List) + { + const auto& entries = itrFields->second.get(); + + for (const auto& entry : entries) + { + if (entry.type() != response::Type::Map) + { + continue; + } + + const auto& itrFieldName = entry.find(R"gql(name)gql"); + const auto& itrFieldType = entry.find(R"gql(type)gql"); + + if (itrFieldName != entry.end() && itrFieldName->second.type() == response::Type::String + && itrFieldType != entry.end() + && itrFieldType->second.type() == response::Type::Map) + { + const auto& fieldName = itrFieldName->second.get(); + ValidateTypeField subField; + + subField.returnType = getTypeFromMap(itrFieldType->second); + + const auto& itrArgs = entry.find(R"gql(args)gql"); + if (itrArgs != entry.end() && itrArgs->second.type() == response::Type::List) + { + subField.arguments = getArguments(itrArgs->second.get()); + } + + fields[std::move(fieldName)] = std::move(subField); + } + } + + if (type->name() == _operationTypes.queryType) + { + fields[R"gql(__schema)gql"] = + ValidateTypeField { makeNonNullOfType(makeObjectType(R"gql(__Schema)gql")) }; + + fields[R"gql(__type)gql"] = ValidateTypeField { makeObjectType(R"gql(__Type)gql"), + ValidateTypeFieldArguments { + { R"gql(name)gql", ValidateArgument { commonTypes.nonNullString } } } }; + } + } + + fields[R"gql(__typename)gql"] = ValidateTypeField { commonTypes.nonNullString }; + + type->setFields(std::move(fields)); +} + +void ValidationContext::addPossibleTypes(std::shared_ptr type, + const response::Value& typeDescriptionMap) +{ + const auto& itrPossibleTypes = typeDescriptionMap.find(R"gql(possibleTypes)gql"); + std::set possibleTypes; + + if (itrPossibleTypes != typeDescriptionMap.end() + && itrPossibleTypes->second.type() == response::Type::List) + { + const auto& matchingTypeEntries = itrPossibleTypes->second.get(); + + for (const auto& matchingTypeEntry : matchingTypeEntries) + { + if (matchingTypeEntry.type() != response::Type::Map) + { + continue; + } + + const auto& itrMatchingTypeName = matchingTypeEntry.find(R"gql(name)gql"); + if (itrMatchingTypeName != matchingTypeEntry.end() + && itrMatchingTypeName->second.type() == response::Type::String) + { + const auto& possibleType = + getNamedValidateType(itrMatchingTypeName->second.get()); + possibleTypes.insert(possibleType.get()); + } + } + } + + type->setPossibleTypes(std::move(possibleTypes)); +} + +void ValidationContext::addInputTypeFields( + std::shared_ptr type, const response::Value& typeDescriptionMap) +{ + const auto& itrFields = typeDescriptionMap.find(R"gql(inputFields)gql"); + if (itrFields != typeDescriptionMap.end() && itrFields->second.type() == response::Type::List) + { + type->setFields(getArguments(itrFields->second.get())); + } +} + +void ValidationContext::addEnum( + const std::string_view& enumName, const response::Value& enumDescriptionMap) +{ + const auto& itrEnumValues = enumDescriptionMap.find(R"gql(enumValues)gql"); + if (itrEnumValues != enumDescriptionMap.end() + && itrEnumValues->second.type() == response::Type::List) + { + std::unordered_set enumValues; + const auto& enumValuesEntries = itrEnumValues->second.get(); + + for (const auto& enumValuesEntry : enumValuesEntries) + { + if (enumValuesEntry.type() != response::Type::Map) + { + continue; + } + + const auto& itrEnumValuesName = enumValuesEntry.find(R"gql(name)gql"); + if (itrEnumValuesName != enumValuesEntry.end() + && itrEnumValuesName->second.type() == response::Type::String) + { + enumValues.insert(itrEnumValuesName->second.get()); + } + } + + if (!enumValues.empty()) + { + makeNamedValidateType(EnumType { enumName, std::move(enumValues) }); + } + } +} + +void ValidationContext::addObject(const std::string_view& name) +{ + makeNamedValidateType(ObjectType { name }); +} + +void ValidationContext::addInputObject(const std::string_view& name) +{ + makeNamedValidateType(InputObjectType { name }); +} + +void ValidationContext::addInterface( + const std::string_view& name, const response::Value& typeDescriptionMap) +{ + makeNamedValidateType(InterfaceType { name }); +} + +void ValidationContext::addUnion( + const std::string_view& name, const response::Value& typeDescriptionMap) +{ + makeNamedValidateType(UnionType { name }); +} + +template ::value>::type*> +std::shared_ptr ValidationContext::makeNamedValidateType(T&& typeDef) +{ + const std::string_view key(typeDef.name()); + + const auto& itr = _namedCache.find(key); + if (itr != _namedCache.cend()) + { + return std::dynamic_pointer_cast(itr->second); + } + + auto type = std::make_shared(std::move(typeDef)); + _namedCache.insert({ type->name(), type }); + + return type; +} + +void ValidationContext::addDirective(const std::string_view& name, + const response::ListType& locations, const response::Value& descriptionMap) +{ + ValidateDirective directive; + + for (const auto& location : locations) + { + if (location.type() != response::Type::EnumValue) + { + continue; + } + + directive.locations.insert( + ModifiedArgument::convert(location)); + } + + const auto& itrArgs = descriptionMap.find(R"gql(args)gql"); + if (itrArgs != descriptionMap.end() && itrArgs->second.type() == response::Type::List) + { + directive.arguments = getArguments(itrArgs->second.get()); + } + + // TODO: string is a work around, the directives will be moved to string_view soon + _directives[std::string { name }] = std::move(directive); +} + +template ::value>::type*> +std::shared_ptr ValidationContext::makeListOfType(std::shared_ptr&& ofType) +{ + const ValidateType* key = ofType.get(); + + const auto& itr = _listOfCache.find(key); + if (itr != _listOfCache.cend()) + { + return itr->second; + } + + return _listOfCache.insert({ key, std::make_shared(std::move(ofType)) }) + .first->second; +} + +template ::value>::type*> +std::shared_ptr ValidationContext::makeNonNullOfType(std::shared_ptr&& ofType) +{ + const ValidateType* key = ofType.get(); + + const auto& itr = _nonNullCache.find(key); + if (itr != _nonNullCache.cend()) + { + return itr->second; + } + + return _nonNullCache.insert({ key, std::make_shared(std::move(ofType)) }) + .first->second; +} + +std::shared_ptr ValidationContext::getTypeFromMap(const response::Value& typeMap) +{ + const auto& itrKind = typeMap.find(R"gql(kind)gql"); + if (itrKind == typeMap.end() || itrKind->second.type() != response::Type::EnumValue) + { + return std::shared_ptr(); + } + + introspection::TypeKind kind = + ModifiedArgument::convert(itrKind->second); + const auto& itrName = typeMap.find(R"gql(name)gql"); + if (itrName != typeMap.end() && itrName->second.type() == response::Type::String) + { + const auto& name = itrName->second.get(); + if (!name.empty()) + { + return getNamedValidateType(name); + } + } + + const auto& itrOfType = typeMap.find(R"gql(ofType)gql"); + if (itrOfType != typeMap.end() && itrOfType->second.type() == response::Type::Map) + { + std::shared_ptr ofType = getTypeFromMap(itrOfType->second); + if (ofType) + { + if (kind == introspection::TypeKind::LIST) + { + return makeListOfType(std::move(ofType)); + } + else if (kind == introspection::TypeKind::NON_NULL) + { + return makeNonNullOfType(std::move(ofType)); + } + } + + // should never reach + return nullptr; + } + + // should never reach + return nullptr; +} + +void ValidationContext::addScalar(const std::string_view& scalarName) +{ + makeNamedValidateType(ScalarType { scalarName }); +} + +} /* namespace graphql::service */ diff --git a/src/Validation.cpp b/src/Validation.cpp index 9b1ba100..b7b079f1 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -12,74 +12,6 @@ namespace graphql::service { -constexpr std::string_view introspectionQuery = R"gql( -query IntrospectionQuery { - __schema { - queryType { name } - mutationType { name } - subscriptionType { name } - types { ...FullType } - directives { - name - locations - args { ...InputValue } - } - } -} - -fragment FullType on __Type { - kind - name - fields(includeDeprecated: true) { - name - args { ...InputValue } - type { ...TypeRef } - } - inputFields { ...InputValue } - interfaces { ...TypeRef } - enumValues(includeDeprecated: true) { name } - possibleTypes { ...TypeRef } -} - -fragment InputValue on __InputValue { - name - type { ...TypeRef } - defaultValue -} - -fragment TypeRef on __Type { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - } - } - } - } - } - } - } -} -)gql"; bool ValidateArgumentVariable::operator==(const ValidateArgumentVariable& other) const { @@ -383,192 +315,6 @@ std::shared_ptr ValidateVariableTypeVisitor::getType() return result; } -ValidationContext::ValidationContext(const Request& service) -{ - // TODO: we should execute this query only once per schema, - // maybe it can be done and cached inside the Request itself to allow - // this. Alternatively it could be provided at compile-time such as schema.json - // that is parsed, this would allow us to drop introspection from the Request - // and still have it to work - auto ast = peg::parseString(introspectionQuery); - // This is taking advantage of the fact that during validation we can choose to execute - // unvalidated queries against the Introspection schema. This way we can use fragment - // cycles to expand an arbitrary number of wrapper types. - ast.validated = true; - - std::shared_ptr state; - const std::string operationName; - response::Value variables(response::Type::Map); - auto result = service.resolve(state, ast, operationName, std::move(variables)).get(); - - populate(result); -} - -ValidationContext::ValidationContext(const response::Value& introspectionQuery) -{ - populate(introspectionQuery); -} - -void ValidationContext::populate(const response::Value& introspectionQuery) -{ - commonTypes.string = makeScalarType("String"); - commonTypes.nonNullString = makeNonNullOfType(commonTypes.string); - - const auto& itrData = introspectionQuery.find(std::string { strData }); - if (itrData == introspectionQuery.end()) - { - return; - } - - const auto& data = itrData->second; - const auto& itrSchema = data.find(R"gql(__schema)gql"); - if (itrSchema != data.end() && itrSchema->second.type() == response::Type::Map) - { - for (auto itr = itrSchema->second.begin(); itr < itrSchema->second.end(); itr++) - { - const auto& member = *itr; - if (member.second.type() == response::Type::Map) - { - const auto& itrType = member.second.find(R"gql(name)gql"); - if (itrType != member.second.end() - && itrType->second.type() == response::Type::String) - { - if (member.first == R"gql(queryType)gql") - { - _operationTypes.queryType = itrType->second.get(); - } - else if (member.first == R"gql(mutationType)gql") - { - _operationTypes.mutationType = itrType->second.get(); - } - else if (member.first == R"gql(subscriptionType)gql") - { - _operationTypes.subscriptionType = - itrType->second.get(); - } - } - } - else if (member.second.type() == response::Type::List - && member.first == R"gql(types)gql") - { - const auto& entries = member.second.get(); - - // first iteration add the named types - for (const auto& entry : entries) - { - if (entry.type() != response::Type::Map) - { - continue; - } - - const auto& itrName = entry.find(R"gql(name)gql"); - const auto& itrKind = entry.find(R"gql(kind)gql"); - - if (itrName != entry.end() && itrName->second.type() == response::Type::String - && itrKind != entry.end() - && itrKind->second.type() == response::Type::EnumValue) - { - const auto& name = itrName->second.get(); - const auto& kind = - ModifiedArgument::convert(itrKind->second); - - if (kind == introspection::TypeKind::OBJECT) - { - addObject(name); - } - else if (kind == introspection::TypeKind::INPUT_OBJECT) - { - addInputObject(name); - } - else if (kind == introspection::TypeKind::INTERFACE) - { - addInterface(name, entry); - } - else if (kind == introspection::TypeKind::UNION) - { - addUnion(name, entry); - } - else if (kind == introspection::TypeKind::ENUM) - { - addEnum(name, entry); - } - else if (kind == introspection::TypeKind::SCALAR) - { - addScalar(name); - } - } - } - - // second iteration add the fields that refer to given types - for (const auto& entry : entries) - { - if (entry.type() != response::Type::Map) - { - continue; - } - - const auto& itrName = entry.find(R"gql(name)gql"); - const auto& itrKind = entry.find(R"gql(kind)gql"); - - if (itrName != entry.end() && itrName->second.type() == response::Type::String - && itrKind != entry.end() - && itrKind->second.type() == response::Type::EnumValue) - { - const auto& name = itrName->second.get(); - const auto& kind = - ModifiedArgument::convert(itrKind->second); - - if (kind == introspection::TypeKind::OBJECT) - { - auto type = getNamedValidateType(name); - addTypeFields(type, entry); - } - else if (kind == introspection::TypeKind::INTERFACE - || kind == introspection::TypeKind::UNION) - { - auto type = - getNamedValidateType(name); - addTypeFields(type, entry); - addPossibleTypes(type, entry); - } - else if (kind == introspection::TypeKind::INPUT_OBJECT) - { - auto type = getNamedValidateType(name); - addInputTypeFields(type, entry); - } - } - } - } - else if (member.second.type() == response::Type::List - && member.first == R"gql(directives)gql") - { - const auto& entries = member.second.get(); - - for (const auto& entry : entries) - { - if (entry.type() != response::Type::Map) - { - continue; - } - - const auto& itrName = entry.find(R"gql(name)gql"); - const auto& itrLocations = entry.find(R"gql(locations)gql"); - - if (itrName != entry.end() && itrName->second.type() == response::Type::String - && itrLocations != entry.end() - && itrLocations->second.type() == response::Type::List) - { - const auto& name = itrName->second.get(); - const auto& locations = itrLocations->second.get(); - - addDirective(name, locations, entry); - } - } - } - } - } -} - ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) : ValidateExecutableVisitor(std::make_shared(service)) { @@ -949,107 +695,7 @@ void ValidateExecutableVisitor::visitSelection(const peg::ast_node& selection) } } -ValidateTypeFieldArguments ValidationContext::getArguments(const response::ListType& args) -{ - ValidateTypeFieldArguments result; - - for (const auto& arg : args) - { - if (arg.type() != response::Type::Map) - { - continue; - } - - const auto& itrName = arg.find(R"gql(name)gql"); - const auto& itrType = arg.find(R"gql(type)gql"); - const auto& itrDefaultValue = arg.find(R"gql(defaultValue)gql"); - - if (itrName != arg.end() && itrName->second.type() == response::Type::String - && itrType != arg.end() && itrType->second.type() == response::Type::Map) - { - ValidateArgument argument; - - argument.defaultValue = (itrDefaultValue != arg.end() - && itrDefaultValue->second.type() == response::Type::String); - argument.nonNullDefaultValue = argument.defaultValue - && itrDefaultValue->second.get() != R"gql(null)gql"; - argument.type = getTypeFromMap(itrType->second); - - result[itrName->second.get()] = std::move(argument); - } - } - - return result; -} - -std::optional> ValidationContext::getDirective( - const std::string_view& name) const -{ - // TODO: string is a work around, the directives map will be moved to string_view soon - const auto& itr = _directives.find(std::string { name }); - if (itr == _directives.cend()) - { - return std::nullopt; - } - return std::optional>(itr->second); -} - -std::optional> ValidationContext::getOperationType( - const std::string_view& name) const -{ - if (name == strQuery) - { - return std::optional>(_operationTypes.queryType); - } - if (name == strMutation) - { - return std::optional>( - _operationTypes.mutationType); - } - if (name == strSubscription) - { - return std::optional>( - _operationTypes.subscriptionType); - } - return std::nullopt; -} - -template ::value>::type*> -std::shared_ptr ValidationContext::getNamedValidateType(const std::string_view& name) const -{ - const auto& itr = _namedCache.find(name); - if (itr != _namedCache.cend()) - { - return std::dynamic_pointer_cast(itr->second); - } - return nullptr; -} - -template ::value>::type*> -std::shared_ptr ValidationContext::getListOfType(std::shared_ptr&& ofType) const -{ - const auto& itr = _listOfCache.find(ofType.get()); - if (itr != _listOfCache.cend()) - { - return itr->second; - } - - return nullptr; -} - -template ::value>::type*> -std::shared_ptr ValidationContext::getNonNullOfType( - std::shared_ptr&& ofType) const -{ - const auto& itr = _nonNullCache.find(ofType.get()); - if (itr != _nonNullCache.cend()) - { - return itr->second; - } - - return nullptr; -} bool ValidateExecutableVisitor::matchesScopedType(const ValidateType& otherType) const { @@ -1492,280 +1138,6 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, return true; } -void ValidationContext::addTypeFields( - std::shared_ptr> type, - const response::Value& typeDescriptionMap) -{ - std::unordered_map fields; - - const auto& itrFields = typeDescriptionMap.find(R"gql(fields)gql"); - if (itrFields != typeDescriptionMap.end() && itrFields->second.type() == response::Type::List) - { - const auto& entries = itrFields->second.get(); - - for (const auto& entry : entries) - { - if (entry.type() != response::Type::Map) - { - continue; - } - - const auto& itrFieldName = entry.find(R"gql(name)gql"); - const auto& itrFieldType = entry.find(R"gql(type)gql"); - - if (itrFieldName != entry.end() && itrFieldName->second.type() == response::Type::String - && itrFieldType != entry.end() - && itrFieldType->second.type() == response::Type::Map) - { - const auto& fieldName = itrFieldName->second.get(); - ValidateTypeField subField; - - subField.returnType = getTypeFromMap(itrFieldType->second); - - const auto& itrArgs = entry.find(R"gql(args)gql"); - if (itrArgs != entry.end() && itrArgs->second.type() == response::Type::List) - { - subField.arguments = getArguments(itrArgs->second.get()); - } - - fields[std::move(fieldName)] = std::move(subField); - } - } - - if (type->name() == _operationTypes.queryType) - { - fields[R"gql(__schema)gql"] = - ValidateTypeField { makeNonNullOfType(makeObjectType(R"gql(__Schema)gql")) }; - - fields[R"gql(__type)gql"] = ValidateTypeField { makeObjectType(R"gql(__Type)gql"), - ValidateTypeFieldArguments { - { R"gql(name)gql", ValidateArgument { commonTypes.nonNullString } } } }; - } - } - - fields[R"gql(__typename)gql"] = ValidateTypeField { commonTypes.nonNullString }; - - type->setFields(std::move(fields)); -} - -void ValidationContext::addPossibleTypes(std::shared_ptr type, - const response::Value& typeDescriptionMap) -{ - const auto& itrPossibleTypes = typeDescriptionMap.find(R"gql(possibleTypes)gql"); - std::set possibleTypes; - - if (itrPossibleTypes != typeDescriptionMap.end() - && itrPossibleTypes->second.type() == response::Type::List) - { - const auto& matchingTypeEntries = itrPossibleTypes->second.get(); - - for (const auto& matchingTypeEntry : matchingTypeEntries) - { - if (matchingTypeEntry.type() != response::Type::Map) - { - continue; - } - - const auto& itrMatchingTypeName = matchingTypeEntry.find(R"gql(name)gql"); - if (itrMatchingTypeName != matchingTypeEntry.end() - && itrMatchingTypeName->second.type() == response::Type::String) - { - const auto& possibleType = - getNamedValidateType(itrMatchingTypeName->second.get()); - possibleTypes.insert(possibleType.get()); - } - } - } - - type->setPossibleTypes(std::move(possibleTypes)); -} - -void ValidationContext::addInputTypeFields( - std::shared_ptr type, const response::Value& typeDescriptionMap) -{ - const auto& itrFields = typeDescriptionMap.find(R"gql(inputFields)gql"); - if (itrFields != typeDescriptionMap.end() && itrFields->second.type() == response::Type::List) - { - type->setFields(getArguments(itrFields->second.get())); - } -} - -void ValidationContext::addEnum( - const std::string_view& enumName, const response::Value& enumDescriptionMap) -{ - const auto& itrEnumValues = enumDescriptionMap.find(R"gql(enumValues)gql"); - if (itrEnumValues != enumDescriptionMap.end() - && itrEnumValues->second.type() == response::Type::List) - { - std::unordered_set enumValues; - const auto& enumValuesEntries = itrEnumValues->second.get(); - - for (const auto& enumValuesEntry : enumValuesEntries) - { - if (enumValuesEntry.type() != response::Type::Map) - { - continue; - } - - const auto& itrEnumValuesName = enumValuesEntry.find(R"gql(name)gql"); - if (itrEnumValuesName != enumValuesEntry.end() - && itrEnumValuesName->second.type() == response::Type::String) - { - enumValues.insert(itrEnumValuesName->second.get()); - } - } - - if (!enumValues.empty()) - { - makeNamedValidateType(EnumType { enumName, std::move(enumValues) }); - } - } -} - -void ValidationContext::addObject(const std::string_view& name) -{ - makeNamedValidateType(ObjectType { name }); -} - -void ValidationContext::addInputObject(const std::string_view& name) -{ - makeNamedValidateType(InputObjectType { name }); -} - -void ValidationContext::addInterface( - const std::string_view& name, const response::Value& typeDescriptionMap) -{ - makeNamedValidateType(InterfaceType { name }); -} - -void ValidationContext::addUnion( - const std::string_view& name, const response::Value& typeDescriptionMap) -{ - makeNamedValidateType(UnionType { name }); -} - -template ::value>::type*> -std::shared_ptr ValidationContext::makeNamedValidateType(T&& typeDef) -{ - const std::string_view key(typeDef.name()); - - const auto& itr = _namedCache.find(key); - if (itr != _namedCache.cend()) - { - return std::dynamic_pointer_cast(itr->second); - } - - auto type = std::make_shared(std::move(typeDef)); - _namedCache.insert({ type->name(), type }); - - return type; -} - -void ValidationContext::addDirective(const std::string_view& name, - const response::ListType& locations, const response::Value& descriptionMap) -{ - ValidateDirective directive; - - for (const auto& location : locations) - { - if (location.type() != response::Type::EnumValue) - { - continue; - } - - directive.locations.insert( - ModifiedArgument::convert(location)); - } - - const auto& itrArgs = descriptionMap.find(R"gql(args)gql"); - if (itrArgs != descriptionMap.end() && itrArgs->second.type() == response::Type::List) - { - directive.arguments = getArguments(itrArgs->second.get()); - } - - // TODO: string is a work around, the directives will be moved to string_view soon - _directives[std::string { name }] = std::move(directive); -} - -template ::value>::type*> -std::shared_ptr ValidationContext::makeListOfType(std::shared_ptr&& ofType) -{ - const ValidateType* key = ofType.get(); - - const auto& itr = _listOfCache.find(key); - if (itr != _listOfCache.cend()) - { - return itr->second; - } - - return _listOfCache.insert({ key, std::make_shared(std::move(ofType)) }) - .first->second; -} - -template ::value>::type*> -std::shared_ptr ValidationContext::makeNonNullOfType(std::shared_ptr&& ofType) -{ - const ValidateType* key = ofType.get(); - - const auto& itr = _nonNullCache.find(key); - if (itr != _nonNullCache.cend()) - { - return itr->second; - } - - return _nonNullCache.insert({ key, std::make_shared(std::move(ofType)) }) - .first->second; -} - -std::shared_ptr ValidationContext::getTypeFromMap(const response::Value& typeMap) -{ - const auto& itrKind = typeMap.find(R"gql(kind)gql"); - if (itrKind == typeMap.end() || itrKind->second.type() != response::Type::EnumValue) - { - return std::shared_ptr(); - } - - introspection::TypeKind kind = - ModifiedArgument::convert(itrKind->second); - const auto& itrName = typeMap.find(R"gql(name)gql"); - if (itrName != typeMap.end() && itrName->second.type() == response::Type::String) - { - const auto& name = itrName->second.get(); - if (!name.empty()) - { - return getNamedValidateType(name); - } - } - - const auto& itrOfType = typeMap.find(R"gql(ofType)gql"); - if (itrOfType != typeMap.end() && itrOfType->second.type() == response::Type::Map) - { - std::shared_ptr ofType = getTypeFromMap(itrOfType->second); - if (ofType) - { - if (kind == introspection::TypeKind::LIST) - { - return makeListOfType(std::move(ofType)); - } - else if (kind == introspection::TypeKind::NON_NULL) - { - return makeNonNullOfType(std::move(ofType)); - } - } - - // should never reach - return nullptr; - } - - // should never reach - return nullptr; -} - -void ValidationContext::addScalar(const std::string_view& scalarName) -{ - makeNamedValidateType(ScalarType { scalarName }); -} - void ValidateExecutableVisitor::visitField(const peg::ast_node& field) { peg::on_first_child(field, [this](const peg::ast_node& child) { From f1033e4cf061cf786487ec4a0e6f89c164fbe5b1 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 9 Dec 2020 18:53:47 -0300 Subject: [PATCH 33/42] Split ValidationContext base class and IntrospectionValidationContext Soon there will be a generated ValidationContext, thus we don't need to carry any of the introspection bits. --- include/graphqlservice/GraphQLValidation.h | 96 ++++++++++---- src/GraphQLService.cpp | 4 +- src/GraphQLValidation.cpp | 145 ++++++++------------- src/Validation.cpp | 2 +- 4 files changed, 124 insertions(+), 123 deletions(-) diff --git a/include/graphqlservice/GraphQLValidation.h b/include/graphqlservice/GraphQLValidation.h index 3c73717a..f9011739 100644 --- a/include/graphqlservice/GraphQLValidation.h +++ b/include/graphqlservice/GraphQLValidation.h @@ -393,9 +393,6 @@ struct ValidateDirective class ValidationContext { public: - ValidationContext(const Request& service); - ValidationContext(const response::Value& introspectionQuery); - struct OperationTypes { std::string queryType; @@ -447,34 +444,40 @@ class ValidationContext return nullptr; } -private: - void populate(const response::Value& introspectionQuery); - - struct +protected: + template ::value>::type* = nullptr> + std::shared_ptr makeNamedValidateType(T&& typeDef) { - std::shared_ptr string; - std::shared_ptr nonNullString; - } commonTypes; - - ValidateTypeFieldArguments getArguments(const response::ListType& argumentsMember); - - using Directives = std::unordered_map; + const std::string_view key(typeDef.name()); - // These members store Introspection schema information which does not change between queries. - OperationTypes _operationTypes; - Directives _directives; + const auto& itr = _namedCache.find(key); + if (itr != _namedCache.cend()) + { + return std::dynamic_pointer_cast(itr->second); + } - std::unordered_map> _listOfCache; - std::unordered_map> _nonNullCache; - std::unordered_map> _namedCache; + auto type = std::make_shared(std::move(typeDef)); + _namedCache.insert({ type->name(), type }); - template ::value>::type* = nullptr> - std::shared_ptr makeNamedValidateType(T&& typeDef); + return type; + } template ::value>::type* = nullptr> - std::shared_ptr makeListOfType(std::shared_ptr&& ofType); + std::shared_ptr makeListOfType(std::shared_ptr&& ofType) + { + const ValidateType* key = ofType.get(); + + const auto& itr = _listOfCache.find(key); + if (itr != _listOfCache.cend()) + { + return itr->second; + } + + return _listOfCache.insert({ key, std::make_shared(std::move(ofType)) }) + .first->second; + } template ::value>::type* = nullptr> @@ -485,7 +488,19 @@ class ValidationContext template ::value>::type* = nullptr> - std::shared_ptr makeNonNullOfType(std::shared_ptr&& ofType); + std::shared_ptr makeNonNullOfType(std::shared_ptr&& ofType) + { + const ValidateType* key = ofType.get(); + + const auto& itr = _nonNullCache.find(key); + if (itr != _nonNullCache.cend()) + { + return itr->second; + } + + return _nonNullCache.insert({ key, std::make_shared(std::move(ofType)) }) + .first->second; + } template ::value>::type* = nullptr> @@ -504,9 +519,38 @@ class ValidationContext return makeNamedValidateType(ObjectType { name }); } + using Directives = std::unordered_map; + + OperationTypes _operationTypes; + Directives _directives; + +private: + // These members store Introspection schema information which does not change between queries. + + std::unordered_map> _listOfCache; + std::unordered_map> _nonNullCache; + std::unordered_map> _namedCache; +}; + +class IntrospectionValidationContext : public ValidationContext +{ +public: + IntrospectionValidationContext(const Request& service); + IntrospectionValidationContext(const response::Value& introspectionQuery); + +private: + void populate(const response::Value& introspectionQuery); + + struct + { + std::shared_ptr string; + std::shared_ptr nonNullString; + } commonTypes; + + ValidateTypeFieldArguments getArguments(const response::ListType& argumentsMember); + std::shared_ptr getTypeFromMap(const response::Value& typeMap); - // builds the validation context (lookup maps) void addScalar(const std::string_view& scalarName); void addEnum(const std::string_view& enumName, const response::Value& enumDescriptionMap); void addObject(const std::string_view& name); diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index 8896e0a5..d31a1ffc 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -1617,8 +1617,8 @@ void SubscriptionDefinitionVisitor::visitInlineFragment(const peg::ast_node& inl Request::Request(TypeMap&& operationTypes, const response::Value* introspectionQuery) : _operations(std::move(operationTypes)) , _validation(std::make_unique(introspectionQuery - ? std::make_shared(*introspectionQuery) - : std::make_shared(*this))) + ? std::make_shared(*introspectionQuery) + : std::make_shared(*this))) { } diff --git a/src/GraphQLValidation.cpp b/src/GraphQLValidation.cpp index 7705c356..b3f3fdfb 100644 --- a/src/GraphQLValidation.cpp +++ b/src/GraphQLValidation.cpp @@ -5,6 +5,38 @@ namespace graphql::service { +std::optional> ValidationContext::getDirective( + const std::string_view& name) const +{ + // TODO: string is a work around, the directives map will be moved to string_view soon + const auto& itr = _directives.find(std::string { name }); + if (itr == _directives.cend()) + { + return std::nullopt; + } + return std::optional>(itr->second); +} + +std::optional> ValidationContext::getOperationType( + const std::string_view& name) const +{ + if (name == strQuery) + { + return std::optional>(_operationTypes.queryType); + } + if (name == strMutation) + { + return std::optional>( + _operationTypes.mutationType); + } + if (name == strSubscription) + { + return std::optional>( + _operationTypes.subscriptionType); + } + return std::nullopt; +} + constexpr std::string_view introspectionQuery = R"gql( query IntrospectionQuery { __schema { @@ -74,7 +106,7 @@ fragment TypeRef on __Type { } )gql"; -ValidationContext::ValidationContext(const Request& service) +IntrospectionValidationContext::IntrospectionValidationContext(const Request& service) { // TODO: we should execute this query only once per schema, // maybe it can be done and cached inside the Request itself to allow @@ -95,12 +127,13 @@ ValidationContext::ValidationContext(const Request& service) populate(result); } -ValidationContext::ValidationContext(const response::Value& introspectionQuery) +IntrospectionValidationContext::IntrospectionValidationContext( + const response::Value& introspectionQuery) { populate(introspectionQuery); } -void ValidationContext::populate(const response::Value& introspectionQuery) +void IntrospectionValidationContext::populate(const response::Value& introspectionQuery) { commonTypes.string = makeScalarType("String"); commonTypes.nonNullString = makeNonNullOfType(commonTypes.string); @@ -260,7 +293,8 @@ void ValidationContext::populate(const response::Value& introspectionQuery) } } -ValidateTypeFieldArguments ValidationContext::getArguments(const response::ListType& args) +ValidateTypeFieldArguments IntrospectionValidationContext::getArguments( + const response::ListType& args) { ValidateTypeFieldArguments result; @@ -293,39 +327,7 @@ ValidateTypeFieldArguments ValidationContext::getArguments(const response::ListT return result; } -std::optional> ValidationContext::getDirective( - const std::string_view& name) const -{ - // TODO: string is a work around, the directives map will be moved to string_view soon - const auto& itr = _directives.find(std::string { name }); - if (itr == _directives.cend()) - { - return std::nullopt; - } - return std::optional>(itr->second); -} - -std::optional> ValidationContext::getOperationType( - const std::string_view& name) const -{ - if (name == strQuery) - { - return std::optional>(_operationTypes.queryType); - } - if (name == strMutation) - { - return std::optional>( - _operationTypes.mutationType); - } - if (name == strSubscription) - { - return std::optional>( - _operationTypes.subscriptionType); - } - return std::nullopt; -} - -void ValidationContext::addTypeFields( +void IntrospectionValidationContext::addTypeFields( std::shared_ptr> type, const response::Value& typeDescriptionMap) { @@ -381,7 +383,8 @@ void ValidationContext::addTypeFields( type->setFields(std::move(fields)); } -void ValidationContext::addPossibleTypes(std::shared_ptr type, +void IntrospectionValidationContext::addPossibleTypes( + std::shared_ptr type, const response::Value& typeDescriptionMap) { const auto& itrPossibleTypes = typeDescriptionMap.find(R"gql(possibleTypes)gql"); @@ -413,7 +416,7 @@ void ValidationContext::addPossibleTypes(std::shared_ptrsetPossibleTypes(std::move(possibleTypes)); } -void ValidationContext::addInputTypeFields( +void IntrospectionValidationContext::addInputTypeFields( std::shared_ptr type, const response::Value& typeDescriptionMap) { const auto& itrFields = typeDescriptionMap.find(R"gql(inputFields)gql"); @@ -423,7 +426,7 @@ void ValidationContext::addInputTypeFields( } } -void ValidationContext::addEnum( +void IntrospectionValidationContext::addEnum( const std::string_view& enumName, const response::Value& enumDescriptionMap) { const auto& itrEnumValues = enumDescriptionMap.find(R"gql(enumValues)gql"); @@ -455,46 +458,29 @@ void ValidationContext::addEnum( } } -void ValidationContext::addObject(const std::string_view& name) +void IntrospectionValidationContext::addObject(const std::string_view& name) { makeNamedValidateType(ObjectType { name }); } -void ValidationContext::addInputObject(const std::string_view& name) +void IntrospectionValidationContext::addInputObject(const std::string_view& name) { makeNamedValidateType(InputObjectType { name }); } -void ValidationContext::addInterface( +void IntrospectionValidationContext::addInterface( const std::string_view& name, const response::Value& typeDescriptionMap) { makeNamedValidateType(InterfaceType { name }); } -void ValidationContext::addUnion( +void IntrospectionValidationContext::addUnion( const std::string_view& name, const response::Value& typeDescriptionMap) { makeNamedValidateType(UnionType { name }); } -template ::value>::type*> -std::shared_ptr ValidationContext::makeNamedValidateType(T&& typeDef) -{ - const std::string_view key(typeDef.name()); - - const auto& itr = _namedCache.find(key); - if (itr != _namedCache.cend()) - { - return std::dynamic_pointer_cast(itr->second); - } - - auto type = std::make_shared(std::move(typeDef)); - _namedCache.insert({ type->name(), type }); - - return type; -} - -void ValidationContext::addDirective(const std::string_view& name, +void IntrospectionValidationContext::addDirective(const std::string_view& name, const response::ListType& locations, const response::Value& descriptionMap) { ValidateDirective directive; @@ -520,37 +506,8 @@ void ValidationContext::addDirective(const std::string_view& name, _directives[std::string { name }] = std::move(directive); } -template ::value>::type*> -std::shared_ptr ValidationContext::makeListOfType(std::shared_ptr&& ofType) -{ - const ValidateType* key = ofType.get(); - - const auto& itr = _listOfCache.find(key); - if (itr != _listOfCache.cend()) - { - return itr->second; - } - - return _listOfCache.insert({ key, std::make_shared(std::move(ofType)) }) - .first->second; -} - -template ::value>::type*> -std::shared_ptr ValidationContext::makeNonNullOfType(std::shared_ptr&& ofType) -{ - const ValidateType* key = ofType.get(); - - const auto& itr = _nonNullCache.find(key); - if (itr != _nonNullCache.cend()) - { - return itr->second; - } - - return _nonNullCache.insert({ key, std::make_shared(std::move(ofType)) }) - .first->second; -} - -std::shared_ptr ValidationContext::getTypeFromMap(const response::Value& typeMap) +std::shared_ptr IntrospectionValidationContext::getTypeFromMap( + const response::Value& typeMap) { const auto& itrKind = typeMap.find(R"gql(kind)gql"); if (itrKind == typeMap.end() || itrKind->second.type() != response::Type::EnumValue) @@ -594,7 +551,7 @@ std::shared_ptr ValidationContext::getTypeFromMap(const response:: return nullptr; } -void ValidationContext::addScalar(const std::string_view& scalarName) +void IntrospectionValidationContext::addScalar(const std::string_view& scalarName) { makeNamedValidateType(ScalarType { scalarName }); } diff --git a/src/Validation.cpp b/src/Validation.cpp index b7b079f1..28e9ba0a 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -316,7 +316,7 @@ std::shared_ptr ValidateVariableTypeVisitor::getType() } ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) - : ValidateExecutableVisitor(std::make_shared(service)) + : ValidateExecutableVisitor(std::make_shared(service)) { } From c98f4469753d614b18280480aea062cb6c7d0b7f Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 9 Dec 2020 20:44:41 -0300 Subject: [PATCH 34/42] Prepare Request to receive a ValidationContext This is basically moving code around, change the parameters to allow the request to receive the ValidationContext. Bring back the original Request() constructor so it will not break dynamically linked binaries. The introspection results are kept around, in the future the validation context will only use pointers to strings (string_view) and everything must be alive in order to work. --- include/Validation.h | 7 ++---- include/graphqlservice/GraphQLService.h | 5 +++- include/graphqlservice/GraphQLValidation.h | 9 ++++--- src/GraphQLService.cpp | 16 +++++++++--- src/GraphQLValidation.cpp | 21 ++++++---------- src/Validation.cpp | 29 ++++++++-------------- 6 files changed, 42 insertions(+), 45 deletions(-) diff --git a/include/Validation.h b/include/Validation.h index ab30f03e..cea1ab23 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -142,10 +142,7 @@ class ValidateVariableTypeVisitor class ValidateExecutableVisitor { public: - // Legacy, left for compatibility reasons. Services should create a ValidationContext and pass - // it - ValidateExecutableVisitor(const Request& service); - ValidateExecutableVisitor(std::shared_ptr validationContext); + ValidateExecutableVisitor(const ValidationContext& validationContext); void visit(const peg::ast_node& root); @@ -171,7 +168,7 @@ class ValidateExecutableVisitor bool validateVariableType(bool isNonNull, const ValidateType& variableType, const schema_location& position, const ValidateType& inputType); - std::shared_ptr _validationContext; + const ValidationContext& _validationContext; std::vector _errors; diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index 1810b775..97bed6a7 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -906,6 +906,7 @@ struct SubscriptionData : std::enable_shared_from_this // Forward declare just the class type so we can reference it in the Request::_validation member. class ValidateExecutableVisitor; +class ValidationContext; // Request scans the fragment definitions and finds the right operation definition to interpret // depending on the operation name (which might be empty for a single-operation document). It @@ -913,8 +914,9 @@ class ValidateExecutableVisitor; class Request : public std::enable_shared_from_this { protected: + GRAPHQLSERVICE_EXPORT explicit Request(TypeMap&& operationTypes); GRAPHQLSERVICE_EXPORT explicit Request( - TypeMap&& operationTypes, const response::Value* introspectionQuery = nullptr); + TypeMap&& operationTypes, std::unique_ptr&& validationContext); GRAPHQLSERVICE_EXPORT virtual ~Request(); public: @@ -992,6 +994,7 @@ class Request : public std::enable_shared_from_this const std::string& operationName, response::Value&& variables) const; TypeMap _operations; + std::unique_ptr _validationContext; std::unique_ptr _validation; std::map> _subscriptions; std::unordered_map> _listeners; diff --git a/include/graphqlservice/GraphQLValidation.h b/include/graphqlservice/GraphQLValidation.h index f9011739..0071dcd5 100644 --- a/include/graphqlservice/GraphQLValidation.h +++ b/include/graphqlservice/GraphQLValidation.h @@ -18,7 +18,7 @@ #endif // !GRAPHQL_DLLEXPORTS // clang-format on -#include "graphqlservice/GraphQLService.h" +#include "graphqlservice/GraphQLResponse.h" #include "graphqlservice/IntrospectionSchema.h" #include @@ -532,14 +532,17 @@ class ValidationContext std::unordered_map> _namedCache; }; +class Request; + class IntrospectionValidationContext : public ValidationContext { public: IntrospectionValidationContext(const Request& service); - IntrospectionValidationContext(const response::Value& introspectionQuery); + IntrospectionValidationContext(response::Value&& introspectionQuery); private: - void populate(const response::Value& introspectionQuery); + response::Value _introspectionQuery; + void populate(); struct { diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index d31a1ffc..1c1b93ee 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -1614,11 +1614,19 @@ void SubscriptionDefinitionVisitor::visitInlineFragment(const peg::ast_node& inl } } -Request::Request(TypeMap&& operationTypes, const response::Value* introspectionQuery) +Request::Request(TypeMap&& operationTypes) : _operations(std::move(operationTypes)) - , _validation(std::make_unique(introspectionQuery - ? std::make_shared(*introspectionQuery) - : std::make_shared(*this))) + , _validationContext(std::make_unique(*this)) + , _validation(std::make_unique(*_validationContext)) +{ +} + +Request::Request(TypeMap&& operationTypes, std::unique_ptr&& validationContext) + : _operations(std::move(operationTypes)) + , _validationContext(validationContext + ? std::move(validationContext) + : std::make_unique(*this)) + , _validation(std::make_unique(*_validationContext)) { } diff --git a/src/GraphQLValidation.cpp b/src/GraphQLValidation.cpp index b3f3fdfb..86f0f188 100644 --- a/src/GraphQLValidation.cpp +++ b/src/GraphQLValidation.cpp @@ -108,11 +108,6 @@ fragment TypeRef on __Type { IntrospectionValidationContext::IntrospectionValidationContext(const Request& service) { - // TODO: we should execute this query only once per schema, - // maybe it can be done and cached inside the Request itself to allow - // this. Alternatively it could be provided at compile-time such as schema.json - // that is parsed, this would allow us to drop introspection from the Request - // and still have it to work auto ast = peg::parseString(introspectionQuery); // This is taking advantage of the fact that during validation we can choose to execute // unvalidated queries against the Introspection schema. This way we can use fragment @@ -122,24 +117,24 @@ IntrospectionValidationContext::IntrospectionValidationContext(const Request& se std::shared_ptr state; const std::string operationName; response::Value variables(response::Type::Map); - auto result = service.resolve(state, ast, operationName, std::move(variables)).get(); + _introspectionQuery = service.resolve(state, ast, operationName, std::move(variables)).get(); - populate(result); + populate(); } -IntrospectionValidationContext::IntrospectionValidationContext( - const response::Value& introspectionQuery) +IntrospectionValidationContext::IntrospectionValidationContext(response::Value&& introspectionQuery) + : _introspectionQuery(std::move(introspectionQuery)) { - populate(introspectionQuery); + populate(); } -void IntrospectionValidationContext::populate(const response::Value& introspectionQuery) +void IntrospectionValidationContext::populate() { commonTypes.string = makeScalarType("String"); commonTypes.nonNullString = makeNonNullOfType(commonTypes.string); - const auto& itrData = introspectionQuery.find(std::string { strData }); - if (itrData == introspectionQuery.end()) + const auto& itrData = _introspectionQuery.find(std::string { strData }); + if (itrData == _introspectionQuery.end()) { return; } diff --git a/src/Validation.cpp b/src/Validation.cpp index 28e9ba0a..88713045 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -12,7 +12,6 @@ namespace graphql::service { - bool ValidateArgumentVariable::operator==(const ValidateArgumentVariable& other) const { return name == other.name; @@ -315,17 +314,11 @@ std::shared_ptr ValidateVariableTypeVisitor::getType() return result; } -ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) - : ValidateExecutableVisitor(std::make_shared(service)) -{ -} - -ValidateExecutableVisitor::ValidateExecutableVisitor( - std::shared_ptr validationContext) +ValidateExecutableVisitor::ValidateExecutableVisitor(const ValidationContext& validationContext) : _validationContext(validationContext) { - commonTypes.nonNullString = _validationContext->getNonNullOfType( - _validationContext->getNamedValidateType("String")); + commonTypes.nonNullString = _validationContext.getNonNullOfType( + _validationContext.getNamedValidateType("String")); } void ValidateExecutableVisitor::visit(const peg::ast_node& root) @@ -462,7 +455,7 @@ void ValidateExecutableVisitor::visitFragmentDefinition(const peg::ast_node& fra const auto& selection = *fragmentDefinition.children.back(); const auto& typeCondition = fragmentDefinition.children[1]; const auto& innerTypeName = typeCondition->children.front()->string_view(); - const auto& innerType = _validationContext->getNamedValidateType(innerTypeName); + const auto& innerType = _validationContext.getNamedValidateType(innerTypeName); if (!innerType || innerType->isInputType()) { @@ -541,7 +534,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op else if (child->is_type() || child->is_type() || child->is_type()) { - ValidateVariableTypeVisitor visitor(*_validationContext); + ValidateVariableTypeVisitor visitor(_validationContext); visitor.visit(*child); @@ -619,7 +612,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op visitDirectives(location, child); }); - const auto& typeRef = _validationContext->getOperationType(operationType); + const auto& typeRef = _validationContext.getOperationType(operationType); if (!typeRef) { auto position = operationDefinition.begin(); @@ -631,7 +624,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op return; } - _scopedType = _validationContext->getNamedValidateType(typeRef.value().get()); + _scopedType = _validationContext.getNamedValidateType(typeRef.value().get()); _fieldCount = 0; const auto& selection = *operationDefinition.children.back(); @@ -695,8 +688,6 @@ void ValidateExecutableVisitor::visitSelection(const peg::ast_node& selection) } } - - bool ValidateExecutableVisitor::matchesScopedType(const ValidateType& otherType) const { switch (_scopedType->kind()) @@ -1450,7 +1441,7 @@ void ValidateExecutableVisitor::visitFragmentSpread(const peg::ast_node& fragmen const auto& selection = *itr->second.children.back(); const auto& typeCondition = itr->second.children[1]; const auto& innerType = - _validationContext->getNamedValidateType(typeCondition->children.front()->string_view()); + _validationContext.getNamedValidateType(typeCondition->children.front()->string_view()); if (!matchesScopedType(*innerType)) { @@ -1503,7 +1494,7 @@ void ValidateExecutableVisitor::visitInlineFragment(const peg::ast_node& inlineF } else { - innerType = _validationContext->getNamedValidateType(innerTypeName); + innerType = _validationContext.getNamedValidateType(innerTypeName); if (!innerType || innerType->isInputType()) { // http://spec.graphql.org/June2018/#sec-Fragment-Spread-Type-Existence @@ -1568,7 +1559,7 @@ void ValidateExecutableVisitor::visitDirectives( continue; } - const auto& validateDirectiveRef = _validationContext->getDirective(directiveName); + const auto& validateDirectiveRef = _validationContext.getDirective(directiveName); if (!validateDirectiveRef) { // http://spec.graphql.org/June2018/#sec-Directives-Are-Defined From 499a0d385171bf920c546dc51b2ce3d8a8d7654e Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Wed, 9 Dec 2020 21:00:02 -0300 Subject: [PATCH 35/42] Validation is now fully string_view --- include/graphqlservice/GraphQLValidation.h | 32 ++++++++++------------ src/GraphQLValidation.cpp | 26 +++++++----------- src/Validation.cpp | 16 ++++------- 3 files changed, 31 insertions(+), 43 deletions(-) diff --git a/include/graphqlservice/GraphQLValidation.h b/include/graphqlservice/GraphQLValidation.h index 0071dcd5..1950de7a 100644 --- a/include/graphqlservice/GraphQLValidation.h +++ b/include/graphqlservice/GraphQLValidation.h @@ -56,10 +56,10 @@ class NamedValidateType , public std::enable_shared_from_this { public: - std::string _name; + std::string_view _name; NamedValidateType(const std::string_view& name) - : _name(std::string(name)) + : _name(name) { } @@ -119,7 +119,7 @@ using ScalarType = NamedType; class EnumType final : public NamedType { public: - EnumType(const std::string_view& name, std::unordered_set&& values) + EnumType(const std::string_view& name, std::unordered_set&& values) : NamedType(name) , _values(std::move(values)) { @@ -127,12 +127,11 @@ class EnumType final : public NamedType bool find(const std::string_view& key) const { - // TODO: in the future the set will be of string_view - return _values.find(std::string { key }) != _values.end(); + return _values.find(key) != _values.end(); } private: - std::unordered_set _values; + std::unordered_set _values; }; template @@ -213,7 +212,7 @@ struct ValidateArgument bool nonNullDefaultValue = false; }; -using ValidateTypeFieldArguments = std::unordered_map; +using ValidateTypeFieldArguments = std::unordered_map; struct ValidateTypeField { @@ -221,7 +220,7 @@ struct ValidateTypeField ValidateTypeFieldArguments arguments; }; -using ValidateDirectiveArguments = std::unordered_map; +using ValidateDirectiveArguments = std::unordered_map; template class ContainerValidateType : public NamedValidateType @@ -232,13 +231,12 @@ class ContainerValidateType : public NamedValidateType { } - using FieldsContainer = std::unordered_map; + using FieldsContainer = std::unordered_map; std::optional> getField( const std::string_view& name) const { - // TODO: string is a work around, the _fields set will be moved to string_view soon - const auto& itr = _fields.find(std::string { name }); + const auto& itr = _fields.find(name); if (itr == _fields.cend()) { return std::nullopt; @@ -395,15 +393,15 @@ class ValidationContext public: struct OperationTypes { - std::string queryType; - std::string mutationType; - std::string subscriptionType; + std::string_view queryType; + std::string_view mutationType; + std::string_view subscriptionType; }; GRAPHQLVALIDATION_EXPORT std::optional> getDirective(const std::string_view& name) const; - GRAPHQLVALIDATION_EXPORT std::optional> - getOperationType(const std::string_view& name) const; + GRAPHQLVALIDATION_EXPORT const std::string_view getOperationType( + const std::string_view& name) const; template ::value>::type* = nullptr> @@ -519,7 +517,7 @@ class ValidationContext return makeNamedValidateType(ObjectType { name }); } - using Directives = std::unordered_map; + using Directives = std::unordered_map; OperationTypes _operationTypes; Directives _directives; diff --git a/src/GraphQLValidation.cpp b/src/GraphQLValidation.cpp index 86f0f188..adb69eda 100644 --- a/src/GraphQLValidation.cpp +++ b/src/GraphQLValidation.cpp @@ -8,8 +8,7 @@ namespace graphql::service { std::optional> ValidationContext::getDirective( const std::string_view& name) const { - // TODO: string is a work around, the directives map will be moved to string_view soon - const auto& itr = _directives.find(std::string { name }); + const auto& itr = _directives.find(name); if (itr == _directives.cend()) { return std::nullopt; @@ -17,24 +16,21 @@ std::optional> ValidationContext return std::optional>(itr->second); } -std::optional> ValidationContext::getOperationType( - const std::string_view& name) const +const std::string_view ValidationContext::getOperationType(const std::string_view& name) const { if (name == strQuery) { - return std::optional>(_operationTypes.queryType); + return _operationTypes.queryType; } if (name == strMutation) { - return std::optional>( - _operationTypes.mutationType); + return _operationTypes.mutationType; } if (name == strSubscription) { - return std::optional>( - _operationTypes.subscriptionType); + return _operationTypes.subscriptionType; } - return std::nullopt; + return ""; } constexpr std::string_view introspectionQuery = R"gql( @@ -115,9 +111,8 @@ IntrospectionValidationContext::IntrospectionValidationContext(const Request& se ast.validated = true; std::shared_ptr state; - const std::string operationName; response::Value variables(response::Type::Map); - _introspectionQuery = service.resolve(state, ast, operationName, std::move(variables)).get(); + _introspectionQuery = service.resolve(state, ast, "", std::move(variables)).get(); populate(); } @@ -326,7 +321,7 @@ void IntrospectionValidationContext::addTypeFields( std::shared_ptr> type, const response::Value& typeDescriptionMap) { - std::unordered_map fields; + std::unordered_map fields; const auto& itrFields = typeDescriptionMap.find(R"gql(fields)gql"); if (itrFields != typeDescriptionMap.end() && itrFields->second.type() == response::Type::List) @@ -428,7 +423,7 @@ void IntrospectionValidationContext::addEnum( if (itrEnumValues != enumDescriptionMap.end() && itrEnumValues->second.type() == response::Type::List) { - std::unordered_set enumValues; + std::unordered_set enumValues; const auto& enumValuesEntries = itrEnumValues->second.get(); for (const auto& enumValuesEntry : enumValuesEntries) @@ -497,8 +492,7 @@ void IntrospectionValidationContext::addDirective(const std::string_view& name, directive.arguments = getArguments(itrArgs->second.get()); } - // TODO: string is a work around, the directives will be moved to string_view soon - _directives[std::string { name }] = std::move(directive); + _directives[name] = std::move(directive); } std::shared_ptr IntrospectionValidationContext::getTypeFromMap( diff --git a/src/Validation.cpp b/src/Validation.cpp index 88713045..19eaa6da 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -491,7 +491,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op operationType = child.string_view(); }); - std::string operationName; + std::string_view operationName; peg::on_first_child(operationDefinition, [&operationName](const peg::ast_node& child) { @@ -612,8 +612,8 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op visitDirectives(location, child); }); - const auto& typeRef = _validationContext.getOperationType(operationType); - if (!typeRef) + const auto& type = _validationContext.getOperationType(operationType); + if (type.empty()) { auto position = operationDefinition.begin(); std::ostringstream error; @@ -624,7 +624,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op return; } - _scopedType = _validationContext.getNamedValidateType(typeRef.value().get()); + _scopedType = _validationContext.getNamedValidateType(type); _fieldCount = 0; const auto& selection = *operationDefinition.children.back(); @@ -1294,8 +1294,7 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) argumentNames.pop_front(); - // TODO: string is a work around, the arguments set will be moved to string_view soon - auto itrArgument = objectField.arguments.find(std::string { argumentName }); + auto itrArgument = objectField.arguments.find(argumentName); if (itrArgument == objectField.arguments.end()) { @@ -1656,10 +1655,7 @@ void ValidateExecutableVisitor::visitDirectives( argumentNames.pop_front(); - // TODO: string is a work around, the arguments set will be moved to string_view - // soon - const auto& itrArgument = - validateDirective.arguments.find(std::string { argumentName }); + const auto& itrArgument = validateDirective.arguments.find(argumentName); if (itrArgument == validateDirective.arguments.cend()) { // http://spec.graphql.org/June2018/#sec-Argument-Names From 801ac3eb49f6b4300f17cd604f9c2b983cc40726 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Thu, 10 Dec 2020 01:20:07 -0300 Subject: [PATCH 36/42] schemagen now outputs the ValidationContext This avoids the introspection query and simplifies the build of lookup maps --- include/SchemaGenerator.h | 84 ++- samples/introspection/IntrospectionSchema.cpp | 1 + samples/separate/TodaySchema.cpp | 272 +++++++- samples/unified/TodaySchema.cpp | 272 +++++++- samples/validation/ValidationSchema.cpp | 255 ++++++- src/SchemaGenerator.cpp | 626 +++++++++++++++++- 6 files changed, 1503 insertions(+), 7 deletions(-) diff --git a/include/SchemaGenerator.h b/include/SchemaGenerator.h index 02dd608d..9c536a28 100644 --- a/include/SchemaGenerator.h +++ b/include/SchemaGenerator.h @@ -293,7 +293,7 @@ class Generator // Run the generator and return a list of filenames that were output. std::vector Build() const noexcept; -private: +protected: std::string getHeaderDir() const noexcept; std::string getSourceDir() const noexcept; std::string getHeaderPath() const noexcept; @@ -381,6 +381,42 @@ class Generator std::string getResolverDeclaration(const OutputField& outputField) const noexcept; bool outputSource() const noexcept; + + static void outputValidationScalarsList( + std::ostream& sourceFile, const ScalarTypeList& scalars); + static void outputValidationEnumsList(std::ostream& sourceFile, const EnumTypeList& enums); + static void outputValidationInputTypeList( + std::ostream& sourceFile, const InputTypeList& inputTypes); + static void outputValidationInputTypeListSetFields( + std::ostream& sourceFile, const InputTypeList& inputTypes); + static void outputValidationUnionTypeList( + std::ostream& sourceFile, const UnionTypeList& unionTypes); + static void outputValidationUnionTypeListSetFieldsAndPossibleTypes( + std::ostream& sourceFile, const UnionTypeList& unionTypes); + static void outputValidationInterfaceTypeList( + std::ostream& sourceFile, const InterfaceTypeList& interfaceTypes); + static void outputValidationInterfaceTypeListSetFieldsAndPossibleTypes(std::ostream& sourceFile, + const InterfaceTypeList& interfaceTypes, + const std::unordered_map>& interfacePossibleTypes); + static void outputValidationObjectTypeList(std::ostream& sourceFile, + const ObjectTypeList& objectTypes, + std::unordered_map>& interfacePossibleTypes); + static void outputValidationObjectTypeListSetFields( + std::ostream& sourceFile, const ObjectTypeList& objectTypes, const std::string& queryType); + static void outputValidationDirectiveList( + std::ostream& sourceFile, const DirectiveList& directives, bool& firstDirective); + + static void outputValidationInputField(std::ostream& sourceFile, const InputField& inputField); + static void outputValidationInputFieldListArrayBody(std::ostream& sourceFile, + const InputFieldList& list, const std::string indent, const std::string separator); + static void outputValidationOutputField( + std::ostream& sourceFile, const OutputField& outputField); + static void outputValidationSetFields( + std::ostream& sourceFile, const std::string& cppType, const OutputFieldList& list); + static void outputValidationSetPossibleTypes(std::ostream& sourceFile, + const std::string& cppType, const std::vector& options); + + void outputValidationContext(std::ostream& sourceFile) const; void outputObjectImplementation( std::ostream& sourceFile, const ObjectType& objectType, bool isQueryType) const; void outputObjectIntrospection(std::ostream& sourceFile, const ObjectType& objectType) const; @@ -393,6 +429,8 @@ class Generator std::string getTypeModifiers(const TypeModifierStack& modifiers) const noexcept; std::string getIntrospectionType( const std::string& type, const TypeModifierStack& modifiers) const noexcept; + static std::string getValidationType( + const std::string& type, const TypeModifierStack& modifiers) noexcept; std::vector outputSeparateFiles() const noexcept; @@ -430,6 +468,50 @@ class Generator OperationTypeList _operationTypes; }; +class IntrospectionValidationContextGenerator : Generator +{ +public: + IntrospectionValidationContextGenerator() + : Generator({ std::nullopt, std::nullopt, false, false, false }) + { + } + + const ScalarTypeList& GetScalarTypes() const + { + return _scalarTypes; + } + + const EnumTypeList& GetEnumTypes() const + { + return _enumTypes; + } + + const InputTypeList& GetInputTypes() const + { + return _inputTypes; + } + + const UnionTypeList& GetUnionTypes() const + { + return _unionTypes; + } + + const InterfaceTypeList& GetInterfaceTypes() const + { + return _interfaceTypes; + } + + const ObjectTypeList& GetObjectTypes() const + { + return _objectTypes; + } + + const DirectiveList& GetDirectives() const + { + return _directives; + } +}; + } /* namespace graphql::schema */ #endif // SCHEMAGENERATOR_H diff --git a/samples/introspection/IntrospectionSchema.cpp b/samples/introspection/IntrospectionSchema.cpp index 5a578bb4..59f1a2b7 100644 --- a/samples/introspection/IntrospectionSchema.cpp +++ b/samples/introspection/IntrospectionSchema.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "graphqlservice/Introspection.h" +#include "graphqlservice/GraphQLValidation.h" #include #include diff --git a/samples/separate/TodaySchema.cpp b/samples/separate/TodaySchema.cpp index 4eddca98..95a93737 100644 --- a/samples/separate/TodaySchema.cpp +++ b/samples/separate/TodaySchema.cpp @@ -4,6 +4,7 @@ #include "TodayObjects.h" #include "graphqlservice/Introspection.h" +#include "graphqlservice/GraphQLValidation.h" #include #include @@ -90,12 +91,281 @@ today::CompleteTaskInput ModifiedArgument::convert(con namespace today { +class ValidationContext : public service::ValidationContext +{ +public: + ValidationContext() + { + auto typeBoolean = makeNamedValidateType(service::ScalarType { "Boolean" }); + auto typeFloat = makeNamedValidateType(service::ScalarType { "Float" }); + auto typeID = makeNamedValidateType(service::ScalarType { "ID" }); + auto typeInt = makeNamedValidateType(service::ScalarType { "Int" }); + auto typeString = makeNamedValidateType(service::ScalarType { "String" }); + + auto typeItemCursor = makeNamedValidateType(service::ScalarType { "ItemCursor" }); + auto typeDateTime = makeNamedValidateType(service::ScalarType { "DateTime" }); + + auto type__TypeKind = makeNamedValidateType(service::EnumType { "__TypeKind", { + "SCALAR", + "OBJECT", + "INTERFACE", + "UNION", + "ENUM", + "INPUT_OBJECT", + "LIST", + "NON_NULL" + } }); + auto type__DirectiveLocation = makeNamedValidateType(service::EnumType { "__DirectiveLocation", { + "QUERY", + "MUTATION", + "SUBSCRIPTION", + "FIELD", + "FRAGMENT_DEFINITION", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT", + "SCHEMA", + "SCALAR", + "OBJECT", + "FIELD_DEFINITION", + "ARGUMENT_DEFINITION", + "INTERFACE", + "UNION", + "ENUM", + "ENUM_VALUE", + "INPUT_OBJECT", + "INPUT_FIELD_DEFINITION" + } }); + auto typeTaskState = makeNamedValidateType(service::EnumType { "TaskState", { + "New", + "Started", + "Complete", + "Unassigned" + } }); + + auto typeCompleteTaskInput = makeNamedValidateType(service::InputObjectType { "CompleteTaskInput" }); + + auto typeUnionType = makeNamedValidateType(service::UnionType { "UnionType" }); + + auto typeNode = makeNamedValidateType(service::InterfaceType { "Node" }); + + auto type__Schema = makeNamedValidateType(service::ObjectType { "__Schema" }); + auto type__Type = makeNamedValidateType(service::ObjectType { "__Type" }); + auto type__Field = makeNamedValidateType(service::ObjectType { "__Field" }); + auto type__InputValue = makeNamedValidateType(service::ObjectType { "__InputValue" }); + auto type__EnumValue = makeNamedValidateType(service::ObjectType { "__EnumValue" }); + auto type__Directive = makeNamedValidateType(service::ObjectType { "__Directive" }); + auto typeQuery = makeNamedValidateType(service::ObjectType { "Query" }); + auto typePageInfo = makeNamedValidateType(service::ObjectType { "PageInfo" }); + auto typeAppointmentEdge = makeNamedValidateType(service::ObjectType { "AppointmentEdge" }); + auto typeAppointmentConnection = makeNamedValidateType(service::ObjectType { "AppointmentConnection" }); + auto typeTaskEdge = makeNamedValidateType(service::ObjectType { "TaskEdge" }); + auto typeTaskConnection = makeNamedValidateType(service::ObjectType { "TaskConnection" }); + auto typeFolderEdge = makeNamedValidateType(service::ObjectType { "FolderEdge" }); + auto typeFolderConnection = makeNamedValidateType(service::ObjectType { "FolderConnection" }); + auto typeCompleteTaskPayload = makeNamedValidateType(service::ObjectType { "CompleteTaskPayload" }); + auto typeMutation = makeNamedValidateType(service::ObjectType { "Mutation" }); + auto typeSubscription = makeNamedValidateType(service::ObjectType { "Subscription" }); + auto typeAppointment = makeNamedValidateType(service::ObjectType { "Appointment" }); + auto typeTask = makeNamedValidateType(service::ObjectType { "Task" }); + auto typeFolder = makeNamedValidateType(service::ObjectType { "Folder" }); + auto typeNestedType = makeNamedValidateType(service::ObjectType { "NestedType" }); + auto typeExpensive = makeNamedValidateType(service::ObjectType { "Expensive" }); + + typeCompleteTaskInput->setFields({ + { "id", { makeNonNullOfType(typeID), 0, 0 } }, + { "isComplete", { typeBoolean, 1, 1 } }, + { "clientMutationId", { typeString, 0, 0 } } + }); + + typeUnionType->setPossibleTypes({ + typeAppointment.get(), + typeTask.get(), + typeFolder.get() + }); + typeUnionType->setFields({ + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + typeNode->setPossibleTypes({ + typeAppointment.get(), + typeTask.get(), + typeFolder.get() + }); + typeNode->setFields({ + { "id", { makeNonNullOfType(typeID), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + type__Schema->setFields({ + { "types", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__Type))), { } } }, + { "queryType", { makeNonNullOfType(type__Type), { } } }, + { "mutationType", { type__Type, { } } }, + { "subscriptionType", { type__Type, { } } }, + { "directives", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__Directive))), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__Type->setFields({ + { "kind", { makeNonNullOfType(type__TypeKind), { } } }, + { "name", { typeString, { } } }, + { "description", { typeString, { } } }, + { "fields", { makeListOfType(makeNonNullOfType(type__Field)), { { "includeDeprecated", { typeBoolean, 1, 1 } } } } }, + { "interfaces", { makeListOfType(makeNonNullOfType(type__Type)), { } } }, + { "possibleTypes", { makeListOfType(makeNonNullOfType(type__Type)), { } } }, + { "enumValues", { makeListOfType(makeNonNullOfType(type__EnumValue)), { { "includeDeprecated", { typeBoolean, 1, 1 } } } } }, + { "inputFields", { makeListOfType(makeNonNullOfType(type__InputValue)), { } } }, + { "ofType", { type__Type, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__Field->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "description", { typeString, { } } }, + { "args", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__InputValue))), { } } }, + { "type", { makeNonNullOfType(type__Type), { } } }, + { "isDeprecated", { makeNonNullOfType(typeBoolean), { } } }, + { "deprecationReason", { typeString, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__InputValue->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "description", { typeString, { } } }, + { "type", { makeNonNullOfType(type__Type), { } } }, + { "defaultValue", { typeString, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__EnumValue->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "description", { typeString, { } } }, + { "isDeprecated", { makeNonNullOfType(typeBoolean), { } } }, + { "deprecationReason", { typeString, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__Directive->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "description", { typeString, { } } }, + { "locations", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__DirectiveLocation))), { } } }, + { "args", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__InputValue))), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeQuery->setFields({ + { "node", { typeNode, { { "id", { makeNonNullOfType(typeID), 0, 0 } } } } }, + { "appointments", { makeNonNullOfType(typeAppointmentConnection), { { "first", { typeInt, 0, 0 } }, { "after", { typeItemCursor, 0, 0 } }, { "last", { typeInt, 0, 0 } }, { "before", { typeItemCursor, 0, 0 } } } } }, + { "tasks", { makeNonNullOfType(typeTaskConnection), { { "first", { typeInt, 0, 0 } }, { "after", { typeItemCursor, 0, 0 } }, { "last", { typeInt, 0, 0 } }, { "before", { typeItemCursor, 0, 0 } } } } }, + { "unreadCounts", { makeNonNullOfType(typeFolderConnection), { { "first", { typeInt, 0, 0 } }, { "after", { typeItemCursor, 0, 0 } }, { "last", { typeInt, 0, 0 } }, { "before", { typeItemCursor, 0, 0 } } } } }, + { "appointmentsById", { makeNonNullOfType(makeListOfType(typeAppointment)), { { "ids", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeID))), 1, 1 } } } } }, + { "tasksById", { makeNonNullOfType(makeListOfType(typeTask)), { { "ids", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeID))), 0, 0 } } } } }, + { "unreadCountsById", { makeNonNullOfType(makeListOfType(typeFolder)), { { "ids", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeID))), 0, 0 } } } } }, + { "nested", { makeNonNullOfType(typeNestedType), { } } }, + { "unimplemented", { makeNonNullOfType(typeString), { } } }, + { "expensive", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeExpensive))), { } } }, + { "__schema", { makeNonNullOfType(type__Schema), { } } }, + { "__type", { type__Type, { { "name", { makeNonNullOfType(typeString), 0, 0 } } } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typePageInfo->setFields({ + { "hasNextPage", { makeNonNullOfType(typeBoolean), { } } }, + { "hasPreviousPage", { makeNonNullOfType(typeBoolean), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeAppointmentEdge->setFields({ + { "node", { typeAppointment, { } } }, + { "cursor", { makeNonNullOfType(typeItemCursor), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeAppointmentConnection->setFields({ + { "pageInfo", { makeNonNullOfType(typePageInfo), { } } }, + { "edges", { makeListOfType(typeAppointmentEdge), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeTaskEdge->setFields({ + { "node", { typeTask, { } } }, + { "cursor", { makeNonNullOfType(typeItemCursor), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeTaskConnection->setFields({ + { "pageInfo", { makeNonNullOfType(typePageInfo), { } } }, + { "edges", { makeListOfType(typeTaskEdge), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeFolderEdge->setFields({ + { "node", { typeFolder, { } } }, + { "cursor", { makeNonNullOfType(typeItemCursor), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeFolderConnection->setFields({ + { "pageInfo", { makeNonNullOfType(typePageInfo), { } } }, + { "edges", { makeListOfType(typeFolderEdge), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeCompleteTaskPayload->setFields({ + { "task", { typeTask, { } } }, + { "clientMutationId", { typeString, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeMutation->setFields({ + { "completeTask", { makeNonNullOfType(typeCompleteTaskPayload), { { "input", { makeNonNullOfType(typeCompleteTaskInput), 0, 0 } } } } }, + { "setFloat", { makeNonNullOfType(typeFloat), { { "value", { makeNonNullOfType(typeFloat), 0, 0 } } } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeSubscription->setFields({ + { "nextAppointmentChange", { typeAppointment, { } } }, + { "nodeChange", { makeNonNullOfType(typeNode), { { "id", { makeNonNullOfType(typeID), 0, 0 } } } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeAppointment->setFields({ + { "id", { makeNonNullOfType(typeID), { } } }, + { "when", { typeDateTime, { } } }, + { "subject", { typeString, { } } }, + { "isNow", { makeNonNullOfType(typeBoolean), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeTask->setFields({ + { "id", { makeNonNullOfType(typeID), { } } }, + { "title", { typeString, { } } }, + { "isComplete", { makeNonNullOfType(typeBoolean), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeFolder->setFields({ + { "id", { makeNonNullOfType(typeID), { } } }, + { "name", { typeString, { } } }, + { "unreadCount", { makeNonNullOfType(typeInt), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeNestedType->setFields({ + { "depth", { makeNonNullOfType(typeInt), { } } }, + { "nested", { makeNonNullOfType(typeNestedType), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeExpensive->setFields({ + { "order", { makeNonNullOfType(typeInt), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + _directives = { + { "skip", { { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "if", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, + { "include", { { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "if", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, + { "deprecated", { { introspection::DirectiveLocation::FIELD_DEFINITION, introspection::DirectiveLocation::ENUM_VALUE }, { { "reason", { typeString, 1, 1 } } } } }, + { "id", { { introspection::DirectiveLocation::FIELD_DEFINITION }, { } } }, + { "subscriptionTag", { { introspection::DirectiveLocation::SUBSCRIPTION }, { { "field", { typeString, 0, 0 } } } } }, + { "queryTag", { { introspection::DirectiveLocation::QUERY }, { { "query", { makeNonNullOfType(typeString), 0, 0 } } } } }, + { "fieldTag", { { introspection::DirectiveLocation::FIELD }, { { "field", { makeNonNullOfType(typeString), 0, 0 } } } } }, + { "fragmentDefinitionTag", { { introspection::DirectiveLocation::FRAGMENT_DEFINITION }, { { "fragmentDefinition", { makeNonNullOfType(typeString), 0, 0 } } } } }, + { "fragmentSpreadTag", { { introspection::DirectiveLocation::FRAGMENT_SPREAD }, { { "fragmentSpread", { makeNonNullOfType(typeString), 0, 0 } } } } }, + { "inlineFragmentTag", { { introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "inlineFragment", { makeNonNullOfType(typeString), 0, 0 } } } } } + }; + + _operationTypes.queryType = "Query"; + _operationTypes.mutationType = "Mutation"; + _operationTypes.subscriptionType = "Subscription"; + } +}; + + Operations::Operations(std::shared_ptr query, std::shared_ptr mutation, std::shared_ptr subscription) : service::Request({ { "query", query }, { "mutation", mutation }, { "subscription", subscription } - }) + }, std::make_unique()) , _query(std::move(query)) , _mutation(std::move(mutation)) , _subscription(std::move(subscription)) diff --git a/samples/unified/TodaySchema.cpp b/samples/unified/TodaySchema.cpp index 325b73ae..aa2b2c07 100644 --- a/samples/unified/TodaySchema.cpp +++ b/samples/unified/TodaySchema.cpp @@ -4,6 +4,7 @@ #include "TodaySchema.h" #include "graphqlservice/Introspection.h" +#include "graphqlservice/GraphQLValidation.h" #include #include @@ -1026,12 +1027,281 @@ std::future Expensive::resolve_typename(service::ResolverParams } /* namespace object */ +class ValidationContext : public service::ValidationContext +{ +public: + ValidationContext() + { + auto typeBoolean = makeNamedValidateType(service::ScalarType { "Boolean" }); + auto typeFloat = makeNamedValidateType(service::ScalarType { "Float" }); + auto typeID = makeNamedValidateType(service::ScalarType { "ID" }); + auto typeInt = makeNamedValidateType(service::ScalarType { "Int" }); + auto typeString = makeNamedValidateType(service::ScalarType { "String" }); + + auto typeItemCursor = makeNamedValidateType(service::ScalarType { "ItemCursor" }); + auto typeDateTime = makeNamedValidateType(service::ScalarType { "DateTime" }); + + auto type__TypeKind = makeNamedValidateType(service::EnumType { "__TypeKind", { + "SCALAR", + "OBJECT", + "INTERFACE", + "UNION", + "ENUM", + "INPUT_OBJECT", + "LIST", + "NON_NULL" + } }); + auto type__DirectiveLocation = makeNamedValidateType(service::EnumType { "__DirectiveLocation", { + "QUERY", + "MUTATION", + "SUBSCRIPTION", + "FIELD", + "FRAGMENT_DEFINITION", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT", + "SCHEMA", + "SCALAR", + "OBJECT", + "FIELD_DEFINITION", + "ARGUMENT_DEFINITION", + "INTERFACE", + "UNION", + "ENUM", + "ENUM_VALUE", + "INPUT_OBJECT", + "INPUT_FIELD_DEFINITION" + } }); + auto typeTaskState = makeNamedValidateType(service::EnumType { "TaskState", { + "New", + "Started", + "Complete", + "Unassigned" + } }); + + auto typeCompleteTaskInput = makeNamedValidateType(service::InputObjectType { "CompleteTaskInput" }); + + auto typeUnionType = makeNamedValidateType(service::UnionType { "UnionType" }); + + auto typeNode = makeNamedValidateType(service::InterfaceType { "Node" }); + + auto type__Schema = makeNamedValidateType(service::ObjectType { "__Schema" }); + auto type__Type = makeNamedValidateType(service::ObjectType { "__Type" }); + auto type__Field = makeNamedValidateType(service::ObjectType { "__Field" }); + auto type__InputValue = makeNamedValidateType(service::ObjectType { "__InputValue" }); + auto type__EnumValue = makeNamedValidateType(service::ObjectType { "__EnumValue" }); + auto type__Directive = makeNamedValidateType(service::ObjectType { "__Directive" }); + auto typeQuery = makeNamedValidateType(service::ObjectType { "Query" }); + auto typePageInfo = makeNamedValidateType(service::ObjectType { "PageInfo" }); + auto typeAppointmentEdge = makeNamedValidateType(service::ObjectType { "AppointmentEdge" }); + auto typeAppointmentConnection = makeNamedValidateType(service::ObjectType { "AppointmentConnection" }); + auto typeTaskEdge = makeNamedValidateType(service::ObjectType { "TaskEdge" }); + auto typeTaskConnection = makeNamedValidateType(service::ObjectType { "TaskConnection" }); + auto typeFolderEdge = makeNamedValidateType(service::ObjectType { "FolderEdge" }); + auto typeFolderConnection = makeNamedValidateType(service::ObjectType { "FolderConnection" }); + auto typeCompleteTaskPayload = makeNamedValidateType(service::ObjectType { "CompleteTaskPayload" }); + auto typeMutation = makeNamedValidateType(service::ObjectType { "Mutation" }); + auto typeSubscription = makeNamedValidateType(service::ObjectType { "Subscription" }); + auto typeAppointment = makeNamedValidateType(service::ObjectType { "Appointment" }); + auto typeTask = makeNamedValidateType(service::ObjectType { "Task" }); + auto typeFolder = makeNamedValidateType(service::ObjectType { "Folder" }); + auto typeNestedType = makeNamedValidateType(service::ObjectType { "NestedType" }); + auto typeExpensive = makeNamedValidateType(service::ObjectType { "Expensive" }); + + typeCompleteTaskInput->setFields({ + { "id", { makeNonNullOfType(typeID), 0, 0 } }, + { "isComplete", { typeBoolean, 1, 1 } }, + { "clientMutationId", { typeString, 0, 0 } } + }); + + typeUnionType->setPossibleTypes({ + typeAppointment.get(), + typeTask.get(), + typeFolder.get() + }); + typeUnionType->setFields({ + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + typeNode->setPossibleTypes({ + typeAppointment.get(), + typeTask.get(), + typeFolder.get() + }); + typeNode->setFields({ + { "id", { makeNonNullOfType(typeID), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + type__Schema->setFields({ + { "types", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__Type))), { } } }, + { "queryType", { makeNonNullOfType(type__Type), { } } }, + { "mutationType", { type__Type, { } } }, + { "subscriptionType", { type__Type, { } } }, + { "directives", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__Directive))), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__Type->setFields({ + { "kind", { makeNonNullOfType(type__TypeKind), { } } }, + { "name", { typeString, { } } }, + { "description", { typeString, { } } }, + { "fields", { makeListOfType(makeNonNullOfType(type__Field)), { { "includeDeprecated", { typeBoolean, 1, 1 } } } } }, + { "interfaces", { makeListOfType(makeNonNullOfType(type__Type)), { } } }, + { "possibleTypes", { makeListOfType(makeNonNullOfType(type__Type)), { } } }, + { "enumValues", { makeListOfType(makeNonNullOfType(type__EnumValue)), { { "includeDeprecated", { typeBoolean, 1, 1 } } } } }, + { "inputFields", { makeListOfType(makeNonNullOfType(type__InputValue)), { } } }, + { "ofType", { type__Type, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__Field->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "description", { typeString, { } } }, + { "args", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__InputValue))), { } } }, + { "type", { makeNonNullOfType(type__Type), { } } }, + { "isDeprecated", { makeNonNullOfType(typeBoolean), { } } }, + { "deprecationReason", { typeString, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__InputValue->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "description", { typeString, { } } }, + { "type", { makeNonNullOfType(type__Type), { } } }, + { "defaultValue", { typeString, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__EnumValue->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "description", { typeString, { } } }, + { "isDeprecated", { makeNonNullOfType(typeBoolean), { } } }, + { "deprecationReason", { typeString, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__Directive->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "description", { typeString, { } } }, + { "locations", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__DirectiveLocation))), { } } }, + { "args", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__InputValue))), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeQuery->setFields({ + { "node", { typeNode, { { "id", { makeNonNullOfType(typeID), 0, 0 } } } } }, + { "appointments", { makeNonNullOfType(typeAppointmentConnection), { { "first", { typeInt, 0, 0 } }, { "after", { typeItemCursor, 0, 0 } }, { "last", { typeInt, 0, 0 } }, { "before", { typeItemCursor, 0, 0 } } } } }, + { "tasks", { makeNonNullOfType(typeTaskConnection), { { "first", { typeInt, 0, 0 } }, { "after", { typeItemCursor, 0, 0 } }, { "last", { typeInt, 0, 0 } }, { "before", { typeItemCursor, 0, 0 } } } } }, + { "unreadCounts", { makeNonNullOfType(typeFolderConnection), { { "first", { typeInt, 0, 0 } }, { "after", { typeItemCursor, 0, 0 } }, { "last", { typeInt, 0, 0 } }, { "before", { typeItemCursor, 0, 0 } } } } }, + { "appointmentsById", { makeNonNullOfType(makeListOfType(typeAppointment)), { { "ids", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeID))), 1, 1 } } } } }, + { "tasksById", { makeNonNullOfType(makeListOfType(typeTask)), { { "ids", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeID))), 0, 0 } } } } }, + { "unreadCountsById", { makeNonNullOfType(makeListOfType(typeFolder)), { { "ids", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeID))), 0, 0 } } } } }, + { "nested", { makeNonNullOfType(typeNestedType), { } } }, + { "unimplemented", { makeNonNullOfType(typeString), { } } }, + { "expensive", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeExpensive))), { } } }, + { "__schema", { makeNonNullOfType(type__Schema), { } } }, + { "__type", { type__Type, { { "name", { makeNonNullOfType(typeString), 0, 0 } } } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typePageInfo->setFields({ + { "hasNextPage", { makeNonNullOfType(typeBoolean), { } } }, + { "hasPreviousPage", { makeNonNullOfType(typeBoolean), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeAppointmentEdge->setFields({ + { "node", { typeAppointment, { } } }, + { "cursor", { makeNonNullOfType(typeItemCursor), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeAppointmentConnection->setFields({ + { "pageInfo", { makeNonNullOfType(typePageInfo), { } } }, + { "edges", { makeListOfType(typeAppointmentEdge), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeTaskEdge->setFields({ + { "node", { typeTask, { } } }, + { "cursor", { makeNonNullOfType(typeItemCursor), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeTaskConnection->setFields({ + { "pageInfo", { makeNonNullOfType(typePageInfo), { } } }, + { "edges", { makeListOfType(typeTaskEdge), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeFolderEdge->setFields({ + { "node", { typeFolder, { } } }, + { "cursor", { makeNonNullOfType(typeItemCursor), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeFolderConnection->setFields({ + { "pageInfo", { makeNonNullOfType(typePageInfo), { } } }, + { "edges", { makeListOfType(typeFolderEdge), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeCompleteTaskPayload->setFields({ + { "task", { typeTask, { } } }, + { "clientMutationId", { typeString, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeMutation->setFields({ + { "completeTask", { makeNonNullOfType(typeCompleteTaskPayload), { { "input", { makeNonNullOfType(typeCompleteTaskInput), 0, 0 } } } } }, + { "setFloat", { makeNonNullOfType(typeFloat), { { "value", { makeNonNullOfType(typeFloat), 0, 0 } } } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeSubscription->setFields({ + { "nextAppointmentChange", { typeAppointment, { } } }, + { "nodeChange", { makeNonNullOfType(typeNode), { { "id", { makeNonNullOfType(typeID), 0, 0 } } } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeAppointment->setFields({ + { "id", { makeNonNullOfType(typeID), { } } }, + { "when", { typeDateTime, { } } }, + { "subject", { typeString, { } } }, + { "isNow", { makeNonNullOfType(typeBoolean), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeTask->setFields({ + { "id", { makeNonNullOfType(typeID), { } } }, + { "title", { typeString, { } } }, + { "isComplete", { makeNonNullOfType(typeBoolean), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeFolder->setFields({ + { "id", { makeNonNullOfType(typeID), { } } }, + { "name", { typeString, { } } }, + { "unreadCount", { makeNonNullOfType(typeInt), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeNestedType->setFields({ + { "depth", { makeNonNullOfType(typeInt), { } } }, + { "nested", { makeNonNullOfType(typeNestedType), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeExpensive->setFields({ + { "order", { makeNonNullOfType(typeInt), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + _directives = { + { "skip", { { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "if", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, + { "include", { { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "if", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, + { "deprecated", { { introspection::DirectiveLocation::FIELD_DEFINITION, introspection::DirectiveLocation::ENUM_VALUE }, { { "reason", { typeString, 1, 1 } } } } }, + { "id", { { introspection::DirectiveLocation::FIELD_DEFINITION }, { } } }, + { "subscriptionTag", { { introspection::DirectiveLocation::SUBSCRIPTION }, { { "field", { typeString, 0, 0 } } } } }, + { "queryTag", { { introspection::DirectiveLocation::QUERY }, { { "query", { makeNonNullOfType(typeString), 0, 0 } } } } }, + { "fieldTag", { { introspection::DirectiveLocation::FIELD }, { { "field", { makeNonNullOfType(typeString), 0, 0 } } } } }, + { "fragmentDefinitionTag", { { introspection::DirectiveLocation::FRAGMENT_DEFINITION }, { { "fragmentDefinition", { makeNonNullOfType(typeString), 0, 0 } } } } }, + { "fragmentSpreadTag", { { introspection::DirectiveLocation::FRAGMENT_SPREAD }, { { "fragmentSpread", { makeNonNullOfType(typeString), 0, 0 } } } } }, + { "inlineFragmentTag", { { introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "inlineFragment", { makeNonNullOfType(typeString), 0, 0 } } } } } + }; + + _operationTypes.queryType = "Query"; + _operationTypes.mutationType = "Mutation"; + _operationTypes.subscriptionType = "Subscription"; + } +}; + + Operations::Operations(std::shared_ptr query, std::shared_ptr mutation, std::shared_ptr subscription) : service::Request({ { "query", query }, { "mutation", mutation }, { "subscription", subscription } - }) + }, std::make_unique()) , _query(std::move(query)) , _mutation(std::move(mutation)) , _subscription(std::move(subscription)) diff --git a/samples/validation/ValidationSchema.cpp b/samples/validation/ValidationSchema.cpp index 19f5157f..c72afe7a 100644 --- a/samples/validation/ValidationSchema.cpp +++ b/samples/validation/ValidationSchema.cpp @@ -4,6 +4,7 @@ #include "ValidationSchema.h" #include "graphqlservice/Introspection.h" +#include "graphqlservice/GraphQLValidation.h" #include #include @@ -832,12 +833,264 @@ std::future Arguments::resolve_typename(service::ResolverParams } /* namespace object */ +class ValidationContext : public service::ValidationContext +{ +public: + ValidationContext() + { + auto typeBoolean = makeNamedValidateType(service::ScalarType { "Boolean" }); + auto typeFloat = makeNamedValidateType(service::ScalarType { "Float" }); + auto typeID = makeNamedValidateType(service::ScalarType { "ID" }); + auto typeInt = makeNamedValidateType(service::ScalarType { "Int" }); + auto typeString = makeNamedValidateType(service::ScalarType { "String" }); + + + auto type__TypeKind = makeNamedValidateType(service::EnumType { "__TypeKind", { + "SCALAR", + "OBJECT", + "INTERFACE", + "UNION", + "ENUM", + "INPUT_OBJECT", + "LIST", + "NON_NULL" + } }); + auto type__DirectiveLocation = makeNamedValidateType(service::EnumType { "__DirectiveLocation", { + "QUERY", + "MUTATION", + "SUBSCRIPTION", + "FIELD", + "FRAGMENT_DEFINITION", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT", + "SCHEMA", + "SCALAR", + "OBJECT", + "FIELD_DEFINITION", + "ARGUMENT_DEFINITION", + "INTERFACE", + "UNION", + "ENUM", + "ENUM_VALUE", + "INPUT_OBJECT", + "INPUT_FIELD_DEFINITION" + } }); + auto typeDogCommand = makeNamedValidateType(service::EnumType { "DogCommand", { + "SIT", + "DOWN", + "HEEL" + } }); + auto typeCatCommand = makeNamedValidateType(service::EnumType { "CatCommand", { + "JUMP" + } }); + + auto typeComplexInput = makeNamedValidateType(service::InputObjectType { "ComplexInput" }); + + auto typeCatOrDog = makeNamedValidateType(service::UnionType { "CatOrDog" }); + auto typeDogOrHuman = makeNamedValidateType(service::UnionType { "DogOrHuman" }); + auto typeHumanOrAlien = makeNamedValidateType(service::UnionType { "HumanOrAlien" }); + + auto typeSentient = makeNamedValidateType(service::InterfaceType { "Sentient" }); + auto typePet = makeNamedValidateType(service::InterfaceType { "Pet" }); + + auto type__Schema = makeNamedValidateType(service::ObjectType { "__Schema" }); + auto type__Type = makeNamedValidateType(service::ObjectType { "__Type" }); + auto type__Field = makeNamedValidateType(service::ObjectType { "__Field" }); + auto type__InputValue = makeNamedValidateType(service::ObjectType { "__InputValue" }); + auto type__EnumValue = makeNamedValidateType(service::ObjectType { "__EnumValue" }); + auto type__Directive = makeNamedValidateType(service::ObjectType { "__Directive" }); + auto typeQuery = makeNamedValidateType(service::ObjectType { "Query" }); + auto typeDog = makeNamedValidateType(service::ObjectType { "Dog" }); + auto typeAlien = makeNamedValidateType(service::ObjectType { "Alien" }); + auto typeHuman = makeNamedValidateType(service::ObjectType { "Human" }); + auto typeCat = makeNamedValidateType(service::ObjectType { "Cat" }); + auto typeMutation = makeNamedValidateType(service::ObjectType { "Mutation" }); + auto typeMutateDogResult = makeNamedValidateType(service::ObjectType { "MutateDogResult" }); + auto typeSubscription = makeNamedValidateType(service::ObjectType { "Subscription" }); + auto typeMessage = makeNamedValidateType(service::ObjectType { "Message" }); + auto typeArguments = makeNamedValidateType(service::ObjectType { "Arguments" }); + + typeComplexInput->setFields({ + { "name", { typeString, 0, 0 } }, + { "owner", { typeString, 0, 0 } } + }); + + typeCatOrDog->setPossibleTypes({ + typeCat.get(), + typeDog.get() + }); + typeCatOrDog->setFields({ + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeDogOrHuman->setPossibleTypes({ + typeDog.get(), + typeHuman.get() + }); + typeDogOrHuman->setFields({ + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeHumanOrAlien->setPossibleTypes({ + typeHuman.get(), + typeAlien.get() + }); + typeHumanOrAlien->setFields({ + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + typeSentient->setPossibleTypes({ + typeAlien.get(), + typeHuman.get() + }); + typeSentient->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typePet->setPossibleTypes({ + typeDog.get(), + typeCat.get() + }); + typePet->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + type__Schema->setFields({ + { "types", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__Type))), { } } }, + { "queryType", { makeNonNullOfType(type__Type), { } } }, + { "mutationType", { type__Type, { } } }, + { "subscriptionType", { type__Type, { } } }, + { "directives", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__Directive))), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__Type->setFields({ + { "kind", { makeNonNullOfType(type__TypeKind), { } } }, + { "name", { typeString, { } } }, + { "description", { typeString, { } } }, + { "fields", { makeListOfType(makeNonNullOfType(type__Field)), { { "includeDeprecated", { typeBoolean, 1, 1 } } } } }, + { "interfaces", { makeListOfType(makeNonNullOfType(type__Type)), { } } }, + { "possibleTypes", { makeListOfType(makeNonNullOfType(type__Type)), { } } }, + { "enumValues", { makeListOfType(makeNonNullOfType(type__EnumValue)), { { "includeDeprecated", { typeBoolean, 1, 1 } } } } }, + { "inputFields", { makeListOfType(makeNonNullOfType(type__InputValue)), { } } }, + { "ofType", { type__Type, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__Field->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "description", { typeString, { } } }, + { "args", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__InputValue))), { } } }, + { "type", { makeNonNullOfType(type__Type), { } } }, + { "isDeprecated", { makeNonNullOfType(typeBoolean), { } } }, + { "deprecationReason", { typeString, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__InputValue->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "description", { typeString, { } } }, + { "type", { makeNonNullOfType(type__Type), { } } }, + { "defaultValue", { typeString, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__EnumValue->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "description", { typeString, { } } }, + { "isDeprecated", { makeNonNullOfType(typeBoolean), { } } }, + { "deprecationReason", { typeString, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + type__Directive->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "description", { typeString, { } } }, + { "locations", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__DirectiveLocation))), { } } }, + { "args", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__InputValue))), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeQuery->setFields({ + { "dog", { typeDog, { } } }, + { "human", { typeHuman, { } } }, + { "pet", { typePet, { } } }, + { "catOrDog", { typeCatOrDog, { } } }, + { "arguments", { typeArguments, { } } }, + { "findDog", { typeDog, { { "complex", { typeComplexInput, 0, 0 } } } } }, + { "booleanList", { typeBoolean, { { "booleanListArg", { makeListOfType(makeNonNullOfType(typeBoolean)), 0, 0 } } } } }, + { "__schema", { makeNonNullOfType(type__Schema), { } } }, + { "__type", { type__Type, { { "name", { makeNonNullOfType(typeString), 0, 0 } } } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeDog->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "nickname", { typeString, { } } }, + { "barkVolume", { typeInt, { } } }, + { "doesKnowCommand", { makeNonNullOfType(typeBoolean), { { "dogCommand", { makeNonNullOfType(typeDogCommand), 0, 0 } } } } }, + { "isHousetrained", { makeNonNullOfType(typeBoolean), { { "atOtherHomes", { typeBoolean, 0, 0 } } } } }, + { "owner", { typeHuman, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeAlien->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "homePlanet", { typeString, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeHuman->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "pets", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typePet))), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeCat->setFields({ + { "name", { makeNonNullOfType(typeString), { } } }, + { "nickname", { typeString, { } } }, + { "doesKnowCommand", { makeNonNullOfType(typeBoolean), { { "catCommand", { makeNonNullOfType(typeCatCommand), 0, 0 } } } } }, + { "meowVolume", { typeInt, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeMutation->setFields({ + { "mutateDog", { typeMutateDogResult, { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeMutateDogResult->setFields({ + { "id", { makeNonNullOfType(typeID), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeSubscription->setFields({ + { "newMessage", { makeNonNullOfType(typeMessage), { } } }, + { "disallowedSecondRootField", { makeNonNullOfType(typeBoolean), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeMessage->setFields({ + { "body", { typeString, { } } }, + { "sender", { makeNonNullOfType(typeID), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + typeArguments->setFields({ + { "multipleReqs", { makeNonNullOfType(typeInt), { { "x", { makeNonNullOfType(typeInt), 0, 0 } }, { "y", { makeNonNullOfType(typeInt), 0, 0 } } } } }, + { "booleanArgField", { typeBoolean, { { "booleanArg", { typeBoolean, 0, 0 } } } } }, + { "floatArgField", { typeFloat, { { "floatArg", { typeFloat, 0, 0 } } } } }, + { "intArgField", { typeInt, { { "intArg", { typeInt, 0, 0 } } } } }, + { "nonNullBooleanArgField", { makeNonNullOfType(typeBoolean), { { "nonNullBooleanArg", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, + { "nonNullBooleanListField", { makeListOfType(makeNonNullOfType(typeBoolean)), { { "nonNullBooleanListArg", { makeListOfType(makeNonNullOfType(typeBoolean)), 0, 0 } } } } }, + { "booleanListArgField", { makeListOfType(typeBoolean), { { "booleanListArg", { makeNonNullOfType(makeListOfType(typeBoolean)), 0, 0 } } } } }, + { "optionalNonNullBooleanArgField", { makeNonNullOfType(typeBoolean), { { "optionalBooleanArg", { makeNonNullOfType(typeBoolean), 1, 1 } } } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + _directives = { + { "skip", { { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "if", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, + { "include", { { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "if", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, + { "deprecated", { { introspection::DirectiveLocation::FIELD_DEFINITION, introspection::DirectiveLocation::ENUM_VALUE }, { { "reason", { typeString, 1, 1 } } } } } + }; + + _operationTypes.queryType = "Query"; + _operationTypes.mutationType = "Mutation"; + _operationTypes.subscriptionType = "Subscription"; + } +}; + + Operations::Operations(std::shared_ptr query, std::shared_ptr mutation, std::shared_ptr subscription) : service::Request({ { "query", query }, { "mutation", mutation }, { "subscription", subscription } - }) + }, std::make_unique()) , _query(std::move(query)) , _mutation(std::move(mutation)) , _subscription(std::move(subscription)) diff --git a/src/SchemaGenerator.cpp b/src/SchemaGenerator.cpp index c6619a96..7b9d57fb 100644 --- a/src/SchemaGenerator.cpp +++ b/src/SchemaGenerator.cpp @@ -2066,6 +2066,7 @@ bool Generator::outputSource() const noexcept } sourceFile << R"cpp(#include "graphqlservice/Introspection.h" +#include "graphqlservice/GraphQLValidation.h" #include #include @@ -2281,6 +2282,8 @@ std::future ModifiedResult<)cpp" { bool firstOperation = true; + outputValidationContext(sourceFile); + sourceFile << R"cpp( Operations::Operations()cpp"; @@ -2316,7 +2319,7 @@ Operations::Operations()cpp"; } sourceFile << R"cpp( - }) + }, std::make_unique()) )cpp"; for (const auto& operation : _operationTypes) @@ -2532,8 +2535,7 @@ Operations::Operations()cpp"; { bool firstValue = true; - sourceFile << R"cpp( type)cpp" << unionType.cppType - << R"cpp(->AddPossibleTypes({ + sourceFile << R"cpp( type)cpp" << unionType.cppType << R"cpp(->AddPossibleTypes({ )cpp"; for (const auto& unionOption : unionType.options) @@ -2749,6 +2751,557 @@ Operations::Operations()cpp"; return true; } +void Generator::outputValidationScalarsList(std::ostream& sourceFile, const ScalarTypeList& scalars) +{ + if (scalars.empty()) + { + return; + } + + for (const auto& scalarType : scalars) + { + sourceFile << R"cpp( auto type)cpp" << scalarType.type + << R"cpp( = makeNamedValidateType(service::ScalarType { ")cpp" << scalarType.type + << R"cpp(" }); +)cpp"; + } +} + +void Generator::outputValidationEnumsList(std::ostream& sourceFile, const EnumTypeList& enums) +{ + if (enums.empty()) + { + return; + } + + for (const auto& enumType : enums) + { + sourceFile << R"cpp( auto type)cpp" << enumType.type + << R"cpp( = makeNamedValidateType(service::EnumType { ")cpp" << enumType.type + << R"cpp(", { +)cpp"; + + bool firstValue = true; + for (const auto& value : enumType.values) + { + if (!firstValue) + { + sourceFile << R"cpp(, +)cpp"; + } + firstValue = false; + sourceFile << R"cpp( ")cpp" << value.value << R"cpp(")cpp"; + } + sourceFile << R"cpp( + } }); +)cpp"; + } +} + +void Generator::outputValidationInputTypeList( + std::ostream& sourceFile, const InputTypeList& inputTypes) +{ + if (inputTypes.empty()) + { + return; + } + + for (const auto& inputType : inputTypes) + { + sourceFile << R"cpp( auto type)cpp" << inputType.type + << R"cpp( = makeNamedValidateType(service::InputObjectType { ")cpp" + << inputType.type << R"cpp(" }); +)cpp"; + } +} + +void Generator::outputValidationInputTypeListSetFields( + std::ostream& sourceFile, const InputTypeList& inputTypes) +{ + if (inputTypes.empty()) + { + return; + } + + for (const auto& inputType : inputTypes) + { + if (!inputType.fields.empty()) + { + bool firstValue = true; + + sourceFile << R"cpp( type)cpp" << inputType.type << R"cpp(->setFields({ +)cpp"; + + outputValidationInputFieldListArrayBody(sourceFile, + inputType.fields, + R"cpp( )cpp", + R"cpp(, +)cpp"); + + sourceFile << R"cpp( + }); +)cpp"; + } + } +} + +void Generator::outputValidationUnionTypeList( + std::ostream& sourceFile, const UnionTypeList& unionTypes) +{ + if (unionTypes.empty()) + { + return; + } + + for (const auto& unionType : unionTypes) + { + sourceFile << R"cpp( auto type)cpp" << unionType.type + << R"cpp( = makeNamedValidateType(service::UnionType { ")cpp" << unionType.type + << R"cpp(" }); +)cpp"; + } +} + +void Generator::outputValidationUnionTypeListSetFieldsAndPossibleTypes( + std::ostream& sourceFile, const UnionTypeList& unionTypes) +{ + if (unionTypes.empty()) + { + return; + } + + for (const auto& unionType : unionTypes) + { + outputValidationSetPossibleTypes(sourceFile, unionType.type, unionType.options); + outputValidationSetFields(sourceFile, unionType.type, {}); + } +} + +void Generator::outputValidationInterfaceTypeList( + std::ostream& sourceFile, const InterfaceTypeList& interfaceTypes) +{ + if (interfaceTypes.empty()) + { + return; + } + + for (const auto& interfaceType : interfaceTypes) + { + sourceFile << R"cpp( auto type)cpp" << interfaceType.type + << R"cpp( = makeNamedValidateType(service::InterfaceType { ")cpp" + << interfaceType.type << R"cpp(" }); +)cpp"; + } +} + +void Generator::outputValidationInterfaceTypeListSetFieldsAndPossibleTypes(std::ostream& sourceFile, + const InterfaceTypeList& interfaceTypes, + const std::unordered_map>& interfacePossibleTypes) +{ + if (interfaceTypes.empty()) + { + return; + } + + for (const auto& interfaceType : interfaceTypes) + { + const auto& itr = interfacePossibleTypes.find(interfaceType.type); + if (itr != interfacePossibleTypes.cend()) + { + outputValidationSetPossibleTypes(sourceFile, interfaceType.type, itr->second); + } + + outputValidationSetFields(sourceFile, interfaceType.type, interfaceType.fields); + } +} + +void Generator::outputValidationObjectTypeList(std::ostream& sourceFile, + const ObjectTypeList& objectTypes, + std::unordered_map>& interfacePossibleTypes) +{ + if (objectTypes.empty()) + { + return; + } + + for (const auto& objectType : objectTypes) + { + sourceFile << R"cpp( auto type)cpp" << objectType.type + << R"cpp( = makeNamedValidateType(service::ObjectType { ")cpp" << objectType.type + << R"cpp(" }); +)cpp"; + + if (!objectType.interfaces.empty()) + { + for (const auto& interfaceName : objectType.interfaces) + { + interfacePossibleTypes.try_emplace(interfaceName, std::vector {}) + .first->second.push_back(objectType.type); + } + } + } +} + +void Generator::outputValidationObjectTypeListSetFields( + std::ostream& sourceFile, const ObjectTypeList& objectTypes, const std::string& queryType) +{ + if (objectTypes.empty()) + { + return; + } + + for (const auto& objectType : objectTypes) + { + if (queryType != objectType.type) + { + outputValidationSetFields(sourceFile, objectType.type, objectType.fields); + } + else + { + bool foundSchema = false, foundType = false; + for (const auto& field : objectType.fields) + { + if (field.name == "__schema") + { + foundSchema = true; + } + if (field.name == "__type") + { + foundType = true; + } + if (foundSchema && foundType) + { + break; + } + } + if (foundSchema && foundType) + { + outputValidationSetFields(sourceFile, objectType.type, objectType.fields); + } + + auto fields = objectType.fields; + if (!foundSchema) + { + fields.push_back({ "__Schema", + "__schema", + "__Schema", + {}, + OutputFieldType::Builtin, + {}, + "", + std::nullopt, + std::nullopt, + false, + false, + { strGet } }); + } + if (!foundType) + { + fields.push_back({ "__Type", + "__type", + "__Type", + { { "String", + "name", + "name", + "", + response::Value(), + InputFieldType::Builtin, + {}, + "", + std::nullopt } }, + OutputFieldType::Builtin, + { service::TypeModifier::Nullable }, + "", + std::nullopt, + std::nullopt, + false, + false, + { strGet } }); + } + outputValidationSetFields(sourceFile, objectType.type, fields); + } + } +} + +void Generator::outputValidationDirectiveList( + std::ostream& sourceFile, const DirectiveList& directives, bool& firstDirective) +{ + if (directives.empty()) + { + return; + } + + for (const auto& directive : directives) + { + if (!firstDirective) + { + sourceFile << R"cpp(, +)cpp"; + } + firstDirective = false; + + sourceFile << R"cpp( { ")cpp" << directive.name << R"cpp(", { { )cpp"; + + if (!directive.locations.empty()) + { + bool firstLocation = true; + + for (const auto& location : directive.locations) + { + if (!firstLocation) + { + sourceFile << R"cpp(, )cpp"; + } + + firstLocation = false; + sourceFile << s_introspectionNamespace << "::DirectiveLocation::" << location; + } + } + sourceFile << R"cpp( }, { )cpp"; + + outputValidationInputFieldListArrayBody(sourceFile, directive.arguments, "", ", "); + sourceFile << R"cpp( } } })cpp"; + } +} + +void Generator::outputValidationInputField(std::ostream& sourceFile, const InputField& inputField) +{ + auto hasDefaultValue = !inputField.defaultValueString.empty(); + auto hasNonNullDefaultValue = hasDefaultValue && inputField.defaultValueString != "null"; + sourceFile << R"cpp({ ")cpp" << inputField.name << R"cpp(", { )cpp" + << getValidationType(inputField.type, inputField.modifiers) << R"cpp(, )cpp" + << hasDefaultValue << R"cpp(, )cpp" << hasNonNullDefaultValue << R"cpp( } })cpp"; +} + +void Generator::outputValidationInputFieldListArrayBody(std::ostream& sourceFile, + const InputFieldList& list, const std::string indent, const std::string separator) +{ + if (list.empty()) + { + return; + } + + bool firstField = true; + + for (const auto& field : list) + { + if (!firstField) + { + sourceFile << separator; + } + + firstField = false; + sourceFile << indent; + outputValidationInputField(sourceFile, field); + } +} + +void Generator::outputValidationOutputField( + std::ostream& sourceFile, const OutputField& outputField) +{ + sourceFile << R"cpp({ ")cpp" << outputField.name << R"cpp(", { )cpp" + << getValidationType(outputField.type, outputField.modifiers) << R"cpp(, { )cpp"; + + outputValidationInputFieldListArrayBody(sourceFile, outputField.arguments, "", ", "); + + sourceFile << R"cpp( } } })cpp"; +} + +void Generator::outputValidationSetFields( + std::ostream& sourceFile, const std::string& type, const OutputFieldList& list) +{ + bool firstField = true; + + sourceFile << R"cpp( type)cpp" << type << R"cpp(->setFields({ +)cpp"; + + bool foundTypename = false; + + for (const auto& field : list) + { + if (field.name == "__typename") + { + foundTypename = true; + } + + if (!firstField) + { + sourceFile << R"cpp(, +)cpp"; + } + + firstField = false; + sourceFile << R"cpp( )cpp"; + outputValidationOutputField(sourceFile, field); + } + + if (!foundTypename) + { + if (!firstField) + { + sourceFile << R"cpp(, +)cpp"; + } + + sourceFile << R"cpp( )cpp"; + outputValidationOutputField(sourceFile, + { "String", + "__typename", + "String", + {}, + OutputFieldType::Builtin, + {}, + "", + std::nullopt, + std::nullopt, + false, + false, + { strGet } }); + } + + sourceFile << R"cpp( + }); +)cpp"; +} + +void Generator::outputValidationSetPossibleTypes( + std::ostream& sourceFile, const std::string& type, const std::vector& options) +{ + if (options.empty()) + { + return; + } + bool firstValue = true; + + sourceFile << R"cpp( type)cpp" << type << R"cpp(->setPossibleTypes({ +)cpp"; + + for (const auto& option : options) + { + if (!firstValue) + { + sourceFile << R"cpp(, +)cpp"; + } + + firstValue = false; + sourceFile << R"cpp( type)cpp" << option << R"cpp(.get())cpp"; + } + + sourceFile << R"cpp( + }); +)cpp"; +} + +void Generator::outputValidationContext(std::ostream& sourceFile) const +{ + sourceFile << R"cpp( +class ValidationContext : public service::ValidationContext +{ +public: + ValidationContext() + { +)cpp"; + + std::string queryType; + for (const auto& operationType : _operationTypes) + { + if (operationType.operation == service::strQuery) + { + queryType = operationType.type; + break; + } + } + + const auto introspectionGenerator = IntrospectionValidationContextGenerator(); + + // Add SCALAR types for each of the built-in types + for (const auto& builtinType : s_builtinTypes) + { + sourceFile << R"cpp( auto type)cpp" << builtinType.first + << R"cpp( = makeNamedValidateType(service::ScalarType { ")cpp" + << builtinType.first << R"cpp(" }); +)cpp"; + } + sourceFile << std::endl; + + outputValidationScalarsList(sourceFile, introspectionGenerator.GetScalarTypes()); + outputValidationScalarsList(sourceFile, _scalarTypes); + sourceFile << std::endl; + + outputValidationEnumsList(sourceFile, introspectionGenerator.GetEnumTypes()); + outputValidationEnumsList(sourceFile, _enumTypes); + sourceFile << std::endl; + + outputValidationInputTypeList(sourceFile, introspectionGenerator.GetInputTypes()); + outputValidationInputTypeList(sourceFile, _inputTypes); + sourceFile << std::endl; + + outputValidationUnionTypeList(sourceFile, introspectionGenerator.GetUnionTypes()); + outputValidationUnionTypeList(sourceFile, _unionTypes); + sourceFile << std::endl; + + outputValidationInterfaceTypeList(sourceFile, introspectionGenerator.GetInterfaceTypes()); + outputValidationInterfaceTypeList(sourceFile, _interfaceTypes); + sourceFile << std::endl; + + std::unordered_map> interfacePossibleTypes; + + outputValidationObjectTypeList(sourceFile, + introspectionGenerator.GetObjectTypes(), + interfacePossibleTypes); + outputValidationObjectTypeList(sourceFile, _objectTypes, interfacePossibleTypes); + sourceFile << std::endl; + + outputValidationInputTypeListSetFields(sourceFile, introspectionGenerator.GetInputTypes()); + outputValidationInputTypeListSetFields(sourceFile, _inputTypes); + sourceFile << std::endl; + + outputValidationUnionTypeListSetFieldsAndPossibleTypes(sourceFile, + introspectionGenerator.GetUnionTypes()); + outputValidationUnionTypeListSetFieldsAndPossibleTypes(sourceFile, _unionTypes); + sourceFile << std::endl; + + outputValidationInterfaceTypeListSetFieldsAndPossibleTypes(sourceFile, + introspectionGenerator.GetInterfaceTypes(), + interfacePossibleTypes); + outputValidationInterfaceTypeListSetFieldsAndPossibleTypes(sourceFile, + _interfaceTypes, + interfacePossibleTypes); + sourceFile << std::endl; + + outputValidationObjectTypeListSetFields(sourceFile, + introspectionGenerator.GetObjectTypes(), + queryType); + outputValidationObjectTypeListSetFields(sourceFile, _objectTypes, queryType); + + sourceFile << R"cpp( + _directives = { +)cpp"; + bool firstDirective = true; + outputValidationDirectiveList(sourceFile, + introspectionGenerator.GetDirectives(), + firstDirective); + outputValidationDirectiveList(sourceFile, _directives, firstDirective); + + sourceFile << R"cpp( + }; + +)cpp"; + + for (const auto& operationType : _operationTypes) + { + sourceFile << R"cpp( _operationTypes.)cpp" << operationType.operation + << R"cpp(Type = ")cpp" << operationType.type << R"cpp("; +)cpp"; + } + + sourceFile << R"cpp( } +}; + +)cpp"; +} + void Generator::outputObjectImplementation( std::ostream& sourceFile, const ObjectType& objectType, bool isQueryType) const { @@ -3439,6 +3992,73 @@ std::string Generator::getIntrospectionType( return introspectionType.str(); } +std::string Generator::getValidationType( + const std::string& type, const TypeModifierStack& modifiers) noexcept +{ + size_t wrapperCount = 0; + bool nonNull = true; + std::ostringstream validationType; + + for (auto modifier : modifiers) + { + if (nonNull) + { + switch (modifier) + { + case service::TypeModifier::None: + case service::TypeModifier::List: + { + validationType << R"cpp(makeNonNullOfType()cpp"; + ++wrapperCount; + break; + } + + case service::TypeModifier::Nullable: + { + // If the next modifier is Nullable that cancels the non-nullable state. + nonNull = false; + break; + } + } + } + + switch (modifier) + { + case service::TypeModifier::None: + { + nonNull = true; + break; + } + + case service::TypeModifier::List: + { + nonNull = true; + validationType << R"cpp(makeListOfType()cpp"; + ++wrapperCount; + break; + } + + case service::TypeModifier::Nullable: + break; + } + } + + if (nonNull) + { + validationType << R"cpp(makeNonNullOfType()cpp"; + ++wrapperCount; + } + + validationType << R"cpp(type)cpp" << type; + + for (size_t i = 0; i < wrapperCount; ++i) + { + validationType << R"cpp())cpp"; + } + + return validationType.str(); +} + std::vector Generator::outputSeparateFiles() const noexcept { std::vector files; From 5dcd12ec8e0d827c3d144491b132abc246bb1e1b Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Thu, 10 Dec 2020 12:52:23 -0300 Subject: [PATCH 37/42] schemagen allow introspection to be disabled The generated file contains "#ifdef SCHEMAGEN_DISABLE_INTROSPECTION", if that is set then the introspection blocks will be disabled: - no __schema and __type resolvers - no AddTypesToSchema - no _schema field --- .gitignore | 1 + include/SchemaGenerator.h | 2 +- samples/CMakeLists.txt | 29 ++ samples/introspection/IntrospectionSchema.cpp | 2 + samples/introspection/IntrospectionSchema.h | 2 + samples/separate/QueryObject.cpp | 8 + samples/separate/QueryObject.h | 2 + samples/separate/TodaySchema.cpp | 46 +++- samples/separate/TodaySchema.h | 2 + samples/unified/TodaySchema.cpp | 54 +++- samples/unified/TodaySchema.h | 4 + samples/validation/ValidationSchema.cpp | 57 +++- samples/validation/ValidationSchema.h | 4 + src/SchemaGenerator.cpp | 247 ++++++++++++++---- test/CMakeLists.txt | 10 + test/NoIntrospectionTests.cpp | 243 +++++++++++++++++ 16 files changed, 642 insertions(+), 71 deletions(-) create mode 100644 test/NoIntrospectionTests.cpp diff --git a/.gitignore b/.gitignore index d67ed041..f611cee1 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ settings.json /test/pegtl_tests /test/response_tests /test/today_tests +/test/nointrospection_tests build/ install/ isenseconfig/ diff --git a/include/SchemaGenerator.h b/include/SchemaGenerator.h index 9c536a28..cd6fd367 100644 --- a/include/SchemaGenerator.h +++ b/include/SchemaGenerator.h @@ -404,7 +404,7 @@ class Generator static void outputValidationObjectTypeListSetFields( std::ostream& sourceFile, const ObjectTypeList& objectTypes, const std::string& queryType); static void outputValidationDirectiveList( - std::ostream& sourceFile, const DirectiveList& directives, bool& firstDirective); + std::ostream& sourceFile, const DirectiveList& directives); static void outputValidationInputField(std::ostream& sourceFile, const InputField& inputField); static void outputValidationInputFieldListArrayBody(std::ostream& sourceFile, diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 6aac4ebe..2f6a1110 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -111,6 +111,15 @@ target_include_directories(sample PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include ${CMAKE_CURRENT_SOURCE_DIR}/../PEGTL/include) +add_executable(sample_nointrospection today/sample.cpp) +target_link_libraries(sample_nointrospection PRIVATE + unifiednointrospectiongraphql + graphqljson) +target_include_directories(sample_nointrospection PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/../include + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${CMAKE_CURRENT_SOURCE_DIR}/../PEGTL/include) + if(WIN32 AND BUILD_SHARED_LIBS) add_custom_target(copy_sample_dlls ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR} @@ -142,6 +151,26 @@ if(GRAPHQL_BUILD_TESTS) target_include_directories(unifiedgraphql PUBLIC today) add_bigobj_flag(unifiedgraphql) + add_library(unifiednointrospectionschema STATIC unified/TodaySchema.cpp) + target_compile_definitions(unifiednointrospectionschema PUBLIC SCHEMAGEN_DISABLE_INTROSPECTION=1) + target_link_libraries(unifiednointrospectionschema PUBLIC graphqlservice) + target_include_directories(unifiednointrospectionschema PUBLIC + ${CMAKE_CURRENT_BINARY_DIR}/../include + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${CMAKE_CURRENT_SOURCE_DIR}/../PEGTL/include + unified) + + if(GRAPHQL_UPDATE_SAMPLES) + # wait for the sample update to complete + add_dependencies(unifiednointrospectionschema update_samples) + endif() + + add_library(unifiednointrospectiongraphql STATIC today/TodayMock.cpp) + target_compile_definitions(unifiednointrospectiongraphql PUBLIC SCHEMAGEN_DISABLE_INTROSPECTION=1) + target_link_libraries(unifiednointrospectiongraphql PUBLIC unifiednointrospectionschema) + target_include_directories(unifiednointrospectiongraphql PUBLIC today) + add_bigobj_flag(unifiednointrospectiongraphql) + add_library(validationgraphql STATIC validation/ValidationMock.cpp validation/ValidationSchema.cpp) diff --git a/samples/introspection/IntrospectionSchema.cpp b/samples/introspection/IntrospectionSchema.cpp index 59f1a2b7..791305ff 100644 --- a/samples/introspection/IntrospectionSchema.cpp +++ b/samples/introspection/IntrospectionSchema.cpp @@ -555,6 +555,7 @@ std::future Directive::resolve_typename(service::ResolverParams } /* namespace object */ +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION void AddTypesToSchema(const std::shared_ptr& schema) { schema->AddType("Boolean", std::make_shared("Boolean", R"md(Built-in type)md")); @@ -680,6 +681,7 @@ void AddTypesToSchema(const std::shared_ptr& schema) std::make_shared("reason", R"md()md", schema->LookupType("String"), R"gql("No longer supported")gql") }))); } +#endif } /* namespace introspection */ } /* namespace graphql */ diff --git a/samples/introspection/IntrospectionSchema.h b/samples/introspection/IntrospectionSchema.h index 075af537..520fe321 100644 --- a/samples/introspection/IntrospectionSchema.h +++ b/samples/introspection/IntrospectionSchema.h @@ -204,7 +204,9 @@ class Directive } /* namespace object */ +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION GRAPHQLSERVICE_EXPORT void AddTypesToSchema(const std::shared_ptr& schema); +#endif } /* namespace introspection */ } /* namespace graphql */ diff --git a/samples/separate/QueryObject.cpp b/samples/separate/QueryObject.cpp index 0a7f88bb..d2105ca4 100644 --- a/samples/separate/QueryObject.cpp +++ b/samples/separate/QueryObject.cpp @@ -20,8 +20,10 @@ Query::Query() : service::Object({ "Query" }, { +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION { R"gql(__schema)gql"sv, [this](service::ResolverParams&& params) { return resolve_schema(std::move(params)); } }, { R"gql(__type)gql"sv, [this](service::ResolverParams&& params) { return resolve_type(std::move(params)); } }, +#endif { R"gql(__typename)gql"sv, [this](service::ResolverParams&& params) { return resolve_typename(std::move(params)); } }, { R"gql(appointments)gql"sv, [this](service::ResolverParams&& params) { return resolveAppointments(std::move(params)); } }, { R"gql(appointmentsById)gql"sv, [this](service::ResolverParams&& params) { return resolveAppointmentsById(std::move(params)); } }, @@ -34,10 +36,14 @@ Query::Query() { R"gql(unreadCounts)gql"sv, [this](service::ResolverParams&& params) { return resolveUnreadCounts(std::move(params)); } }, { R"gql(unreadCountsById)gql"sv, [this](service::ResolverParams&& params) { return resolveUnreadCountsById(std::move(params)); } } }) +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION , _schema(std::make_shared()) +#endif { +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION introspection::AddTypesToSchema(_schema); today::AddTypesToSchema(_schema); +#endif } service::FieldResult> Query::getNode(service::FieldParams&&, response::IdType&&) const @@ -223,6 +229,7 @@ std::future Query::resolve_typename(service::ResolverParams&& p return service::ModifiedResult::convert(response::StringType{ R"gql(Query)gql" }, std::move(params)); } +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION std::future Query::resolve_schema(service::ResolverParams&& params) { return service::ModifiedResult::convert(std::static_pointer_cast(_schema), std::move(params)); @@ -234,6 +241,7 @@ std::future Query::resolve_type(service::ResolverParams&& param return service::ModifiedResult::convert(_schema->LookupType(argName), std::move(params)); } +#endif } /* namespace object */ diff --git a/samples/separate/QueryObject.h b/samples/separate/QueryObject.h index f24bb5da..80bb25a8 100644 --- a/samples/separate/QueryObject.h +++ b/samples/separate/QueryObject.h @@ -41,10 +41,12 @@ class Query std::future resolveExpensive(service::ResolverParams&& params); std::future resolve_typename(service::ResolverParams&& params); +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION std::future resolve_schema(service::ResolverParams&& params); std::future resolve_type(service::ResolverParams&& params); std::shared_ptr _schema; +#endif }; } /* namespace graphql::today::object */ diff --git a/samples/separate/TodaySchema.cpp b/samples/separate/TodaySchema.cpp index 95a93737..0260de76 100644 --- a/samples/separate/TodaySchema.cpp +++ b/samples/separate/TodaySchema.cpp @@ -102,9 +102,14 @@ class ValidationContext : public service::ValidationContext auto typeInt = makeNamedValidateType(service::ScalarType { "Int" }); auto typeString = makeNamedValidateType(service::ScalarType { "String" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif auto typeItemCursor = makeNamedValidateType(service::ScalarType { "ItemCursor" }); auto typeDateTime = makeNamedValidateType(service::ScalarType { "DateTime" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION auto type__TypeKind = makeNamedValidateType(service::EnumType { "__TypeKind", { "SCALAR", "OBJECT", @@ -135,6 +140,7 @@ class ValidationContext : public service::ValidationContext "INPUT_OBJECT", "INPUT_FIELD_DEFINITION" } }); +#endif auto typeTaskState = makeNamedValidateType(service::EnumType { "TaskState", { "New", "Started", @@ -142,18 +148,30 @@ class ValidationContext : public service::ValidationContext "Unassigned" } }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif auto typeCompleteTaskInput = makeNamedValidateType(service::InputObjectType { "CompleteTaskInput" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif auto typeUnionType = makeNamedValidateType(service::UnionType { "UnionType" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif auto typeNode = makeNamedValidateType(service::InterfaceType { "Node" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION auto type__Schema = makeNamedValidateType(service::ObjectType { "__Schema" }); auto type__Type = makeNamedValidateType(service::ObjectType { "__Type" }); auto type__Field = makeNamedValidateType(service::ObjectType { "__Field" }); auto type__InputValue = makeNamedValidateType(service::ObjectType { "__InputValue" }); auto type__EnumValue = makeNamedValidateType(service::ObjectType { "__EnumValue" }); auto type__Directive = makeNamedValidateType(service::ObjectType { "__Directive" }); +#endif auto typeQuery = makeNamedValidateType(service::ObjectType { "Query" }); auto typePageInfo = makeNamedValidateType(service::ObjectType { "PageInfo" }); auto typeAppointmentEdge = makeNamedValidateType(service::ObjectType { "AppointmentEdge" }); @@ -171,12 +189,18 @@ class ValidationContext : public service::ValidationContext auto typeNestedType = makeNamedValidateType(service::ObjectType { "NestedType" }); auto typeExpensive = makeNamedValidateType(service::ObjectType { "Expensive" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif typeCompleteTaskInput->setFields({ { "id", { makeNonNullOfType(typeID), 0, 0 } }, { "isComplete", { typeBoolean, 1, 1 } }, { "clientMutationId", { typeString, 0, 0 } } }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif typeUnionType->setPossibleTypes({ typeAppointment.get(), typeTask.get(), @@ -186,6 +210,9 @@ class ValidationContext : public service::ValidationContext { "__typename", { makeNonNullOfType(typeString), { } } } }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif typeNode->setPossibleTypes({ typeAppointment.get(), typeTask.get(), @@ -196,6 +223,8 @@ class ValidationContext : public service::ValidationContext { "__typename", { makeNonNullOfType(typeString), { } } } }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION type__Schema->setFields({ { "types", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__Type))), { } } }, { "queryType", { makeNonNullOfType(type__Type), { } } }, @@ -246,6 +275,7 @@ class ValidationContext : public service::ValidationContext { "args", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__InputValue))), { } } }, { "__typename", { makeNonNullOfType(typeString), { } } } }); +#endif typeQuery->setFields({ { "node", { typeNode, { { "id", { makeNonNullOfType(typeID), 0, 0 } } } } }, { "appointments", { makeNonNullOfType(typeAppointmentConnection), { { "first", { typeInt, 0, 0 } }, { "after", { typeItemCursor, 0, 0 } }, { "last", { typeInt, 0, 0 } }, { "before", { typeItemCursor, 0, 0 } } } } }, @@ -256,10 +286,14 @@ class ValidationContext : public service::ValidationContext { "unreadCountsById", { makeNonNullOfType(makeListOfType(typeFolder)), { { "ids", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeID))), 0, 0 } } } } }, { "nested", { makeNonNullOfType(typeNestedType), { } } }, { "unimplemented", { makeNonNullOfType(typeString), { } } }, - { "expensive", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeExpensive))), { } } }, - { "__schema", { makeNonNullOfType(type__Schema), { } } }, - { "__type", { type__Type, { { "name", { makeNonNullOfType(typeString), 0, 0 } } } } }, - { "__typename", { makeNonNullOfType(typeString), { } } } + { "expensive", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeExpensive))), { } } } +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + , { "__schema", { makeNonNullOfType(type__Schema), { } } } +#endif +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + , { "__type", { type__Type, { { "name", { makeNonNullOfType(typeString), 0, 0 } } } } } +#endif + , { "__typename", { makeNonNullOfType(typeString), { } } } }); typePageInfo->setFields({ { "hasNextPage", { makeNonNullOfType(typeBoolean), { } } }, @@ -341,9 +375,11 @@ class ValidationContext : public service::ValidationContext }); _directives = { +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION { "skip", { { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "if", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, { "include", { { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "if", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, { "deprecated", { { introspection::DirectiveLocation::FIELD_DEFINITION, introspection::DirectiveLocation::ENUM_VALUE }, { { "reason", { typeString, 1, 1 } } } } }, +#endif { "id", { { introspection::DirectiveLocation::FIELD_DEFINITION }, { } } }, { "subscriptionTag", { { introspection::DirectiveLocation::SUBSCRIPTION }, { { "field", { typeString, 0, 0 } } } } }, { "queryTag", { { introspection::DirectiveLocation::QUERY }, { { "query", { makeNonNullOfType(typeString), 0, 0 } } } } }, @@ -372,6 +408,7 @@ Operations::Operations(std::shared_ptr query, std::shared_ptr& schema) { schema->AddType("ItemCursor", std::make_shared("ItemCursor", R"md()md")); @@ -495,6 +532,7 @@ void AddTypesToSchema(const std::shared_ptr& schema) schema->AddMutationType(typeMutation); schema->AddSubscriptionType(typeSubscription); } +#endif } /* namespace today */ } /* namespace graphql */ diff --git a/samples/separate/TodaySchema.h b/samples/separate/TodaySchema.h index 5a948be1..a58a800d 100644 --- a/samples/separate/TodaySchema.h +++ b/samples/separate/TodaySchema.h @@ -92,7 +92,9 @@ void AddFolderDetails(std::shared_ptr typeFolder, con void AddNestedTypeDetails(std::shared_ptr typeNestedType, const std::shared_ptr& schema); void AddExpensiveDetails(std::shared_ptr typeExpensive, const std::shared_ptr& schema); +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION void AddTypesToSchema(const std::shared_ptr& schema); +#endif } /* namespace today */ } /* namespace graphql */ diff --git a/samples/unified/TodaySchema.cpp b/samples/unified/TodaySchema.cpp index aa2b2c07..1036d0c9 100644 --- a/samples/unified/TodaySchema.cpp +++ b/samples/unified/TodaySchema.cpp @@ -96,8 +96,10 @@ Query::Query() : service::Object({ "Query" }, { +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION { R"gql(__schema)gql"sv, [this](service::ResolverParams&& params) { return resolve_schema(std::move(params)); } }, { R"gql(__type)gql"sv, [this](service::ResolverParams&& params) { return resolve_type(std::move(params)); } }, +#endif { R"gql(__typename)gql"sv, [this](service::ResolverParams&& params) { return resolve_typename(std::move(params)); } }, { R"gql(appointments)gql"sv, [this](service::ResolverParams&& params) { return resolveAppointments(std::move(params)); } }, { R"gql(appointmentsById)gql"sv, [this](service::ResolverParams&& params) { return resolveAppointmentsById(std::move(params)); } }, @@ -110,10 +112,14 @@ Query::Query() { R"gql(unreadCounts)gql"sv, [this](service::ResolverParams&& params) { return resolveUnreadCounts(std::move(params)); } }, { R"gql(unreadCountsById)gql"sv, [this](service::ResolverParams&& params) { return resolveUnreadCountsById(std::move(params)); } } }) +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION , _schema(std::make_shared()) +#endif { +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION introspection::AddTypesToSchema(_schema); today::AddTypesToSchema(_schema); +#endif } service::FieldResult> Query::getNode(service::FieldParams&&, response::IdType&&) const @@ -299,6 +305,7 @@ std::future Query::resolve_typename(service::ResolverParams&& p return service::ModifiedResult::convert(response::StringType{ R"gql(Query)gql" }, std::move(params)); } +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION std::future Query::resolve_schema(service::ResolverParams&& params) { return service::ModifiedResult::convert(std::static_pointer_cast(_schema), std::move(params)); @@ -310,6 +317,7 @@ std::future Query::resolve_type(service::ResolverParams&& param return service::ModifiedResult::convert(_schema->LookupType(argName), std::move(params)); } +#endif PageInfo::PageInfo() : service::Object({ @@ -1038,9 +1046,14 @@ class ValidationContext : public service::ValidationContext auto typeInt = makeNamedValidateType(service::ScalarType { "Int" }); auto typeString = makeNamedValidateType(service::ScalarType { "String" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif auto typeItemCursor = makeNamedValidateType(service::ScalarType { "ItemCursor" }); auto typeDateTime = makeNamedValidateType(service::ScalarType { "DateTime" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION auto type__TypeKind = makeNamedValidateType(service::EnumType { "__TypeKind", { "SCALAR", "OBJECT", @@ -1071,6 +1084,7 @@ class ValidationContext : public service::ValidationContext "INPUT_OBJECT", "INPUT_FIELD_DEFINITION" } }); +#endif auto typeTaskState = makeNamedValidateType(service::EnumType { "TaskState", { "New", "Started", @@ -1078,18 +1092,30 @@ class ValidationContext : public service::ValidationContext "Unassigned" } }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif auto typeCompleteTaskInput = makeNamedValidateType(service::InputObjectType { "CompleteTaskInput" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif auto typeUnionType = makeNamedValidateType(service::UnionType { "UnionType" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif auto typeNode = makeNamedValidateType(service::InterfaceType { "Node" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION auto type__Schema = makeNamedValidateType(service::ObjectType { "__Schema" }); auto type__Type = makeNamedValidateType(service::ObjectType { "__Type" }); auto type__Field = makeNamedValidateType(service::ObjectType { "__Field" }); auto type__InputValue = makeNamedValidateType(service::ObjectType { "__InputValue" }); auto type__EnumValue = makeNamedValidateType(service::ObjectType { "__EnumValue" }); auto type__Directive = makeNamedValidateType(service::ObjectType { "__Directive" }); +#endif auto typeQuery = makeNamedValidateType(service::ObjectType { "Query" }); auto typePageInfo = makeNamedValidateType(service::ObjectType { "PageInfo" }); auto typeAppointmentEdge = makeNamedValidateType(service::ObjectType { "AppointmentEdge" }); @@ -1107,12 +1133,18 @@ class ValidationContext : public service::ValidationContext auto typeNestedType = makeNamedValidateType(service::ObjectType { "NestedType" }); auto typeExpensive = makeNamedValidateType(service::ObjectType { "Expensive" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif typeCompleteTaskInput->setFields({ { "id", { makeNonNullOfType(typeID), 0, 0 } }, { "isComplete", { typeBoolean, 1, 1 } }, { "clientMutationId", { typeString, 0, 0 } } }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif typeUnionType->setPossibleTypes({ typeAppointment.get(), typeTask.get(), @@ -1122,6 +1154,9 @@ class ValidationContext : public service::ValidationContext { "__typename", { makeNonNullOfType(typeString), { } } } }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif typeNode->setPossibleTypes({ typeAppointment.get(), typeTask.get(), @@ -1132,6 +1167,8 @@ class ValidationContext : public service::ValidationContext { "__typename", { makeNonNullOfType(typeString), { } } } }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION type__Schema->setFields({ { "types", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__Type))), { } } }, { "queryType", { makeNonNullOfType(type__Type), { } } }, @@ -1182,6 +1219,7 @@ class ValidationContext : public service::ValidationContext { "args", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__InputValue))), { } } }, { "__typename", { makeNonNullOfType(typeString), { } } } }); +#endif typeQuery->setFields({ { "node", { typeNode, { { "id", { makeNonNullOfType(typeID), 0, 0 } } } } }, { "appointments", { makeNonNullOfType(typeAppointmentConnection), { { "first", { typeInt, 0, 0 } }, { "after", { typeItemCursor, 0, 0 } }, { "last", { typeInt, 0, 0 } }, { "before", { typeItemCursor, 0, 0 } } } } }, @@ -1192,10 +1230,14 @@ class ValidationContext : public service::ValidationContext { "unreadCountsById", { makeNonNullOfType(makeListOfType(typeFolder)), { { "ids", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeID))), 0, 0 } } } } }, { "nested", { makeNonNullOfType(typeNestedType), { } } }, { "unimplemented", { makeNonNullOfType(typeString), { } } }, - { "expensive", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeExpensive))), { } } }, - { "__schema", { makeNonNullOfType(type__Schema), { } } }, - { "__type", { type__Type, { { "name", { makeNonNullOfType(typeString), 0, 0 } } } } }, - { "__typename", { makeNonNullOfType(typeString), { } } } + { "expensive", { makeNonNullOfType(makeListOfType(makeNonNullOfType(typeExpensive))), { } } } +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + , { "__schema", { makeNonNullOfType(type__Schema), { } } } +#endif +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + , { "__type", { type__Type, { { "name", { makeNonNullOfType(typeString), 0, 0 } } } } } +#endif + , { "__typename", { makeNonNullOfType(typeString), { } } } }); typePageInfo->setFields({ { "hasNextPage", { makeNonNullOfType(typeBoolean), { } } }, @@ -1277,9 +1319,11 @@ class ValidationContext : public service::ValidationContext }); _directives = { +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION { "skip", { { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "if", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, { "include", { { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "if", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, { "deprecated", { { introspection::DirectiveLocation::FIELD_DEFINITION, introspection::DirectiveLocation::ENUM_VALUE }, { { "reason", { typeString, 1, 1 } } } } }, +#endif { "id", { { introspection::DirectiveLocation::FIELD_DEFINITION }, { } } }, { "subscriptionTag", { { introspection::DirectiveLocation::SUBSCRIPTION }, { { "field", { typeString, 0, 0 } } } } }, { "queryTag", { { introspection::DirectiveLocation::QUERY }, { { "query", { makeNonNullOfType(typeString), 0, 0 } } } } }, @@ -1308,6 +1352,7 @@ Operations::Operations(std::shared_ptr query, std::shared_ptr& schema) { schema->AddType("ItemCursor", std::make_shared("ItemCursor", R"md()md")); @@ -1528,6 +1573,7 @@ void AddTypesToSchema(const std::shared_ptr& schema) schema->AddMutationType(typeMutation); schema->AddSubscriptionType(typeSubscription); } +#endif } /* namespace today */ } /* namespace graphql */ diff --git a/samples/unified/TodaySchema.h b/samples/unified/TodaySchema.h index e1e6ff3b..0c25e791 100644 --- a/samples/unified/TodaySchema.h +++ b/samples/unified/TodaySchema.h @@ -95,10 +95,12 @@ class Query std::future resolveExpensive(service::ResolverParams&& params); std::future resolve_typename(service::ResolverParams&& params); +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION std::future resolve_schema(service::ResolverParams&& params); std::future resolve_type(service::ResolverParams&& params); std::shared_ptr _schema; +#endif }; class PageInfo @@ -379,7 +381,9 @@ class Operations std::shared_ptr _subscription; }; +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION void AddTypesToSchema(const std::shared_ptr& schema); +#endif } /* namespace today */ } /* namespace graphql */ diff --git a/samples/validation/ValidationSchema.cpp b/samples/validation/ValidationSchema.cpp index c72afe7a..0eeb05d6 100644 --- a/samples/validation/ValidationSchema.cpp +++ b/samples/validation/ValidationSchema.cpp @@ -115,8 +115,10 @@ Query::Query() : service::Object({ "Query" }, { +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION { R"gql(__schema)gql"sv, [this](service::ResolverParams&& params) { return resolve_schema(std::move(params)); } }, { R"gql(__type)gql"sv, [this](service::ResolverParams&& params) { return resolve_type(std::move(params)); } }, +#endif { R"gql(__typename)gql"sv, [this](service::ResolverParams&& params) { return resolve_typename(std::move(params)); } }, { R"gql(arguments)gql"sv, [this](service::ResolverParams&& params) { return resolveArguments(std::move(params)); } }, { R"gql(booleanList)gql"sv, [this](service::ResolverParams&& params) { return resolveBooleanList(std::move(params)); } }, @@ -126,10 +128,14 @@ Query::Query() { R"gql(human)gql"sv, [this](service::ResolverParams&& params) { return resolveHuman(std::move(params)); } }, { R"gql(pet)gql"sv, [this](service::ResolverParams&& params) { return resolvePet(std::move(params)); } } }) +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION , _schema(std::make_shared()) +#endif { +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION introspection::AddTypesToSchema(_schema); validation::AddTypesToSchema(_schema); +#endif } service::FieldResult> Query::getDog(service::FieldParams&&) const @@ -237,6 +243,7 @@ std::future Query::resolve_typename(service::ResolverParams&& p return service::ModifiedResult::convert(response::StringType{ R"gql(Query)gql" }, std::move(params)); } +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION std::future Query::resolve_schema(service::ResolverParams&& params) { return service::ModifiedResult::convert(std::static_pointer_cast(_schema), std::move(params)); @@ -248,6 +255,7 @@ std::future Query::resolve_type(service::ResolverParams&& param return service::ModifiedResult::convert(_schema->LookupType(argName), std::move(params)); } +#endif Dog::Dog() : service::Object({ @@ -845,6 +853,11 @@ class ValidationContext : public service::ValidationContext auto typeString = makeNamedValidateType(service::ScalarType { "String" }); +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif + + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION auto type__TypeKind = makeNamedValidateType(service::EnumType { "__TypeKind", { "SCALAR", "OBJECT", @@ -875,6 +888,7 @@ class ValidationContext : public service::ValidationContext "INPUT_OBJECT", "INPUT_FIELD_DEFINITION" } }); +#endif auto typeDogCommand = makeNamedValidateType(service::EnumType { "DogCommand", { "SIT", "DOWN", @@ -884,21 +898,33 @@ class ValidationContext : public service::ValidationContext "JUMP" } }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif auto typeComplexInput = makeNamedValidateType(service::InputObjectType { "ComplexInput" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif auto typeCatOrDog = makeNamedValidateType(service::UnionType { "CatOrDog" }); auto typeDogOrHuman = makeNamedValidateType(service::UnionType { "DogOrHuman" }); auto typeHumanOrAlien = makeNamedValidateType(service::UnionType { "HumanOrAlien" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif auto typeSentient = makeNamedValidateType(service::InterfaceType { "Sentient" }); auto typePet = makeNamedValidateType(service::InterfaceType { "Pet" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION auto type__Schema = makeNamedValidateType(service::ObjectType { "__Schema" }); auto type__Type = makeNamedValidateType(service::ObjectType { "__Type" }); auto type__Field = makeNamedValidateType(service::ObjectType { "__Field" }); auto type__InputValue = makeNamedValidateType(service::ObjectType { "__InputValue" }); auto type__EnumValue = makeNamedValidateType(service::ObjectType { "__EnumValue" }); auto type__Directive = makeNamedValidateType(service::ObjectType { "__Directive" }); +#endif auto typeQuery = makeNamedValidateType(service::ObjectType { "Query" }); auto typeDog = makeNamedValidateType(service::ObjectType { "Dog" }); auto typeAlien = makeNamedValidateType(service::ObjectType { "Alien" }); @@ -910,11 +936,17 @@ class ValidationContext : public service::ValidationContext auto typeMessage = makeNamedValidateType(service::ObjectType { "Message" }); auto typeArguments = makeNamedValidateType(service::ObjectType { "Arguments" }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif typeComplexInput->setFields({ { "name", { typeString, 0, 0 } }, { "owner", { typeString, 0, 0 } } }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif typeCatOrDog->setPossibleTypes({ typeCat.get(), typeDog.get() @@ -937,6 +969,9 @@ class ValidationContext : public service::ValidationContext { "__typename", { makeNonNullOfType(typeString), { } } } }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif typeSentient->setPossibleTypes({ typeAlien.get(), typeHuman.get() @@ -954,6 +989,8 @@ class ValidationContext : public service::ValidationContext { "__typename", { makeNonNullOfType(typeString), { } } } }); + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION type__Schema->setFields({ { "types", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__Type))), { } } }, { "queryType", { makeNonNullOfType(type__Type), { } } }, @@ -1004,6 +1041,7 @@ class ValidationContext : public service::ValidationContext { "args", { makeNonNullOfType(makeListOfType(makeNonNullOfType(type__InputValue))), { } } }, { "__typename", { makeNonNullOfType(typeString), { } } } }); +#endif typeQuery->setFields({ { "dog", { typeDog, { } } }, { "human", { typeHuman, { } } }, @@ -1011,10 +1049,14 @@ class ValidationContext : public service::ValidationContext { "catOrDog", { typeCatOrDog, { } } }, { "arguments", { typeArguments, { } } }, { "findDog", { typeDog, { { "complex", { typeComplexInput, 0, 0 } } } } }, - { "booleanList", { typeBoolean, { { "booleanListArg", { makeListOfType(makeNonNullOfType(typeBoolean)), 0, 0 } } } } }, - { "__schema", { makeNonNullOfType(type__Schema), { } } }, - { "__type", { type__Type, { { "name", { makeNonNullOfType(typeString), 0, 0 } } } } }, - { "__typename", { makeNonNullOfType(typeString), { } } } + { "booleanList", { typeBoolean, { { "booleanListArg", { makeListOfType(makeNonNullOfType(typeBoolean)), 0, 0 } } } } } +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + , { "__schema", { makeNonNullOfType(type__Schema), { } } } +#endif +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + , { "__type", { type__Type, { { "name", { makeNonNullOfType(typeString), 0, 0 } } } } } +#endif + , { "__typename", { makeNonNullOfType(typeString), { } } } }); typeDog->setFields({ { "name", { makeNonNullOfType(typeString), { } } }, @@ -1073,9 +1115,12 @@ class ValidationContext : public service::ValidationContext }); _directives = { +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION { "skip", { { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "if", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, { "include", { { introspection::DirectiveLocation::FIELD, introspection::DirectiveLocation::FRAGMENT_SPREAD, introspection::DirectiveLocation::INLINE_FRAGMENT }, { { "if", { makeNonNullOfType(typeBoolean), 0, 0 } } } } }, - { "deprecated", { { introspection::DirectiveLocation::FIELD_DEFINITION, introspection::DirectiveLocation::ENUM_VALUE }, { { "reason", { typeString, 1, 1 } } } } } + { "deprecated", { { introspection::DirectiveLocation::FIELD_DEFINITION, introspection::DirectiveLocation::ENUM_VALUE }, { { "reason", { typeString, 1, 1 } } } } }, +#endif + }; _operationTypes.queryType = "Query"; @@ -1097,6 +1142,7 @@ Operations::Operations(std::shared_ptr query, std::shared_ptr& schema) { auto typeDogCommand = std::make_shared("DogCommand", R"md()md"); @@ -1269,6 +1315,7 @@ void AddTypesToSchema(const std::shared_ptr& schema) schema->AddMutationType(typeMutation); schema->AddSubscriptionType(typeSubscription); } +#endif } /* namespace validation */ } /* namespace graphql */ diff --git a/samples/validation/ValidationSchema.h b/samples/validation/ValidationSchema.h index 3f4e710b..317eac93 100644 --- a/samples/validation/ValidationSchema.h +++ b/samples/validation/ValidationSchema.h @@ -94,10 +94,12 @@ class Query std::future resolveBooleanList(service::ResolverParams&& params); std::future resolve_typename(service::ResolverParams&& params); +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION std::future resolve_schema(service::ResolverParams&& params); std::future resolve_type(service::ResolverParams&& params); std::shared_ptr _schema; +#endif }; class Dog @@ -291,7 +293,9 @@ class Operations std::shared_ptr _subscription; }; +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION void AddTypesToSchema(const std::shared_ptr& schema); +#endif } /* namespace validation */ } /* namespace graphql */ diff --git a/src/SchemaGenerator.cpp b/src/SchemaGenerator.cpp index 7b9d57fb..99e1eaf3 100644 --- a/src/SchemaGenerator.cpp +++ b/src/SchemaGenerator.cpp @@ -1913,6 +1913,8 @@ class Schema; headerFile << std::endl; } + headerFile << R"cpp(#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; if (_isIntrospection) { headerFile << R"cpp(GRAPHQLSERVICE_EXPORT )cpp"; @@ -1920,6 +1922,7 @@ class Schema; headerFile << R"cpp(void AddTypesToSchema(const std::shared_ptr<)cpp" << s_introspectionNamespace << R"cpp(::Schema>& schema); +#endif )cpp"; @@ -1982,12 +1985,13 @@ void Generator::outputObjectDeclaration( if (isQueryType) { - headerFile - << R"cpp( std::future resolve_schema(service::ResolverParams&& params); + headerFile << R"cpp(#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + std::future resolve_schema(service::ResolverParams&& params); std::future resolve_type(service::ResolverParams&& params); std::shared_ptr<)cpp" - << s_introspectionNamespace << R"cpp(::Schema> _schema; + << s_introspectionNamespace << R"cpp(::Schema> _schema; +#endif )cpp"; } } @@ -2339,7 +2343,8 @@ Operations::Operations()cpp"; sourceFile << std::endl; } - sourceFile << R"cpp(void AddTypesToSchema(const std::shared_ptr<)cpp" + sourceFile << R"cpp(#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +void AddTypesToSchema(const std::shared_ptr<)cpp" << s_introspectionNamespace << R"cpp(::Schema>& schema) { )cpp"; @@ -2745,6 +2750,7 @@ Operations::Operations()cpp"; } sourceFile << R"cpp(} +#endif )cpp"; @@ -2977,60 +2983,122 @@ void Generator::outputValidationObjectTypeListSetFields( if (foundSchema && foundType) { outputValidationSetFields(sourceFile, objectType.type, objectType.fields); + return; + } + + bool firstField = true; + + sourceFile << R"cpp( type)cpp" << objectType.type << R"cpp(->setFields({ +)cpp"; + + bool foundTypename = false; + + for (const auto& field : objectType.fields) + { + if (field.name == "__typename") + { + foundTypename = true; + } + + if (!firstField) + { + sourceFile << R"cpp(, +)cpp"; + } + + firstField = false; + sourceFile << R"cpp( )cpp"; + outputValidationOutputField(sourceFile, field); } - auto fields = objectType.fields; if (!foundSchema) { - fields.push_back({ "__Schema", - "__schema", - "__Schema", - {}, - OutputFieldType::Builtin, - {}, - "", - std::nullopt, - std::nullopt, - false, - false, - { strGet } }); + sourceFile << R"cpp( +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + , )cpp"; + outputValidationOutputField(sourceFile, + { "__Schema", + "__schema", + "__Schema", + {}, + OutputFieldType::Builtin, + {}, + "", + std::nullopt, + std::nullopt, + false, + false, + { strGet } }); + sourceFile << R"cpp( +#endif)cpp"; } + if (!foundType) { - fields.push_back({ "__Type", - "__type", - "__Type", - { { "String", - "name", - "name", + sourceFile << R"cpp( +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + , )cpp"; + outputValidationOutputField(sourceFile, + { "__Type", + "__type", + "__Type", + { { "String", + "name", + "name", + "", + response::Value(), + InputFieldType::Builtin, + {}, + "", + std::nullopt } }, + OutputFieldType::Builtin, + { service::TypeModifier::Nullable }, "", - response::Value(), - InputFieldType::Builtin, + std::nullopt, + std::nullopt, + false, + false, + { strGet } }); + sourceFile << R"cpp( +#endif)cpp"; + } + + if (!foundTypename) + { + sourceFile << R"cpp( + , )cpp"; + outputValidationOutputField(sourceFile, + { "String", + "__typename", + "String", + {}, + OutputFieldType::Builtin, {}, "", - std::nullopt } }, - OutputFieldType::Builtin, - { service::TypeModifier::Nullable }, - "", - std::nullopt, - std::nullopt, - false, - false, - { strGet } }); + std::nullopt, + std::nullopt, + false, + false, + { strGet } }); } - outputValidationSetFields(sourceFile, objectType.type, fields); + + sourceFile << R"cpp( + }); +)cpp"; } } } void Generator::outputValidationDirectiveList( - std::ostream& sourceFile, const DirectiveList& directives, bool& firstDirective) + std::ostream& sourceFile, const DirectiveList& directives) { if (directives.empty()) { return; } + bool firstDirective = true; + for (const auto& directive : directives) { if (!firstDirective) @@ -3223,66 +3291,117 @@ class ValidationContext : public service::ValidationContext << builtinType.first << R"cpp(" }); )cpp"; } - sourceFile << std::endl; + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; outputValidationScalarsList(sourceFile, introspectionGenerator.GetScalarTypes()); + sourceFile << R"cpp(#endif +)cpp"; outputValidationScalarsList(sourceFile, _scalarTypes); - sourceFile << std::endl; + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; outputValidationEnumsList(sourceFile, introspectionGenerator.GetEnumTypes()); + sourceFile << R"cpp(#endif +)cpp"; outputValidationEnumsList(sourceFile, _enumTypes); - sourceFile << std::endl; + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; outputValidationInputTypeList(sourceFile, introspectionGenerator.GetInputTypes()); + sourceFile << R"cpp(#endif +)cpp"; outputValidationInputTypeList(sourceFile, _inputTypes); - sourceFile << std::endl; + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; outputValidationUnionTypeList(sourceFile, introspectionGenerator.GetUnionTypes()); + sourceFile << R"cpp(#endif +)cpp"; outputValidationUnionTypeList(sourceFile, _unionTypes); - sourceFile << std::endl; + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; outputValidationInterfaceTypeList(sourceFile, introspectionGenerator.GetInterfaceTypes()); + sourceFile << R"cpp(#endif +)cpp"; outputValidationInterfaceTypeList(sourceFile, _interfaceTypes); - sourceFile << std::endl; std::unordered_map> interfacePossibleTypes; + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; outputValidationObjectTypeList(sourceFile, introspectionGenerator.GetObjectTypes(), interfacePossibleTypes); + sourceFile << R"cpp(#endif +)cpp"; outputValidationObjectTypeList(sourceFile, _objectTypes, interfacePossibleTypes); - sourceFile << std::endl; + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; outputValidationInputTypeListSetFields(sourceFile, introspectionGenerator.GetInputTypes()); + sourceFile << R"cpp(#endif +)cpp"; outputValidationInputTypeListSetFields(sourceFile, _inputTypes); - sourceFile << std::endl; + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; outputValidationUnionTypeListSetFieldsAndPossibleTypes(sourceFile, introspectionGenerator.GetUnionTypes()); + sourceFile << R"cpp(#endif +)cpp"; outputValidationUnionTypeListSetFieldsAndPossibleTypes(sourceFile, _unionTypes); - sourceFile << std::endl; + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; outputValidationInterfaceTypeListSetFieldsAndPossibleTypes(sourceFile, introspectionGenerator.GetInterfaceTypes(), interfacePossibleTypes); + sourceFile << R"cpp(#endif +)cpp"; outputValidationInterfaceTypeListSetFieldsAndPossibleTypes(sourceFile, _interfaceTypes, interfacePossibleTypes); - sourceFile << std::endl; + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; outputValidationObjectTypeListSetFields(sourceFile, introspectionGenerator.GetObjectTypes(), queryType); + sourceFile << R"cpp(#endif +)cpp"; outputValidationObjectTypeListSetFields(sourceFile, _objectTypes, queryType); sourceFile << R"cpp( _directives = { +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION )cpp"; - bool firstDirective = true; - outputValidationDirectiveList(sourceFile, - introspectionGenerator.GetDirectives(), - firstDirective); - outputValidationDirectiveList(sourceFile, _directives, firstDirective); + outputValidationDirectiveList(sourceFile, introspectionGenerator.GetDirectives()); + sourceFile << R"cpp(, +#endif +)cpp"; + outputValidationDirectiveList(sourceFile, _directives); sourceFile << R"cpp( }; @@ -3355,10 +3474,18 @@ void Generator::outputObjectImplementation( if (isQueryType) { - resolvers["__schema"sv] = - R"cpp( { R"gql(__schema)gql"sv, [this](service::ResolverParams&& params) { return resolve_schema(std::move(params)); } })cpp"s; - resolvers["__type"sv] = - R"cpp( { R"gql(__type)gql"sv, [this](service::ResolverParams&& params) { return resolve_type(std::move(params)); } })cpp"s; + sourceFile << R"cpp(#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + { R"gql(__schema)gql"sv, [this](service::ResolverParams&& params) { return resolve_schema(std::move(params)); } }, + { R"gql(__type)gql"sv, [this](service::ResolverParams&& params) { return resolve_type(std::move(params)); } })cpp"; + + if (resolvers.size() > 0) + { + sourceFile << R"cpp(,)cpp"; + } + + sourceFile << R"cpp( +#endif +)cpp"; } bool firstField = true; @@ -3381,8 +3508,10 @@ void Generator::outputObjectImplementation( if (isQueryType) { sourceFile << R"cpp( +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION , _schema(std::make_shared<)cpp" - << s_introspectionNamespace << R"cpp(::Schema>()))cpp"; + << s_introspectionNamespace << R"cpp(::Schema>()) +#endif)cpp"; } sourceFile << R"cpp( @@ -3391,10 +3520,12 @@ void Generator::outputObjectImplementation( if (isQueryType) { - sourceFile << R"cpp( )cpp" << s_introspectionNamespace + sourceFile << R"cpp(#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + )cpp" << s_introspectionNamespace << R"cpp(::AddTypesToSchema(_schema); )cpp" << _schemaNamespace << R"cpp(::AddTypesToSchema(_schema); +#endif )cpp"; } @@ -3521,6 +3652,7 @@ std::future )cpp" { sourceFile << R"cpp( +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION std::future )cpp" << objectType.cppType << R"cpp(::resolve_schema(service::ResolverParams&& params) { @@ -3536,6 +3668,7 @@ std::future )cpp" << s_introspectionNamespace << R"cpp(::object::Type>::convert(_schema->LookupType(argName), std::move(params)); } +#endif )cpp"; } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 346aafd1..8a9e747d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -23,6 +23,15 @@ target_link_libraries(today_tests PRIVATE add_bigobj_flag(today_tests) gtest_add_tests(TARGET today_tests) +add_executable(nointrospection_tests NoIntrospectionTests.cpp) +target_link_libraries(nointrospection_tests PRIVATE + unifiednointrospectiongraphql + graphqljson + GTest::GTest + GTest::Main) +add_bigobj_flag(nointrospection_tests) +gtest_add_tests(TARGET nointrospection_tests) + add_executable(argument_tests ArgumentTests.cpp) target_link_libraries(argument_tests PRIVATE unifiedgraphql @@ -63,6 +72,7 @@ if(WIN32 AND BUILD_SHARED_LIBS) add_dependencies(validation_tests copy_test_dlls) add_dependencies(today_tests copy_test_dlls) + add_dependencies(nointrospection_tests copy_test_dlls) add_dependencies(argument_tests copy_test_dlls) add_dependencies(pegtl_tests copy_test_dlls) add_dependencies(response_tests copy_test_dlls) diff --git a/test/NoIntrospectionTests.cpp b/test/NoIntrospectionTests.cpp new file mode 100644 index 00000000..cc052530 --- /dev/null +++ b/test/NoIntrospectionTests.cpp @@ -0,0 +1,243 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include + +#include "TodayMock.h" + +#include "graphqlservice/JSONResponse.h" + +#include + +using namespace graphql; + +using namespace std::literals; + + +// this is similar to TodayTests, will trust QueryEverything works +// and then check if introspection is disabled + +class NoIntrospectionServiceCase : public ::testing::Test +{ +public: + static void SetUpTestCase() + { + std::string fakeAppointmentId("fakeAppointmentId"); + _fakeAppointmentId.resize(fakeAppointmentId.size()); + std::copy(fakeAppointmentId.cbegin(), fakeAppointmentId.cend(), _fakeAppointmentId.begin()); + + std::string fakeTaskId("fakeTaskId"); + _fakeTaskId.resize(fakeTaskId.size()); + std::copy(fakeTaskId.cbegin(), fakeTaskId.cend(), _fakeTaskId.begin()); + + std::string fakeFolderId("fakeFolderId"); + _fakeFolderId.resize(fakeFolderId.size()); + std::copy(fakeFolderId.cbegin(), fakeFolderId.cend(), _fakeFolderId.begin()); + + auto query = std::make_shared( + []() -> std::vector> + { + ++_getAppointmentsCount; + return { std::make_shared(response::IdType(_fakeAppointmentId), "tomorrow", "Lunch?", false) }; + }, []() -> std::vector> + { + ++_getTasksCount; + return { std::make_shared(response::IdType(_fakeTaskId), "Don't forget", true) }; + }, []() -> std::vector> + { + ++_getUnreadCountsCount; + return { std::make_shared(response::IdType(_fakeFolderId), "\"Fake\" Inbox", 3) }; + }); + auto mutation = std::make_shared( + [](today::CompleteTaskInput&& input) -> std::shared_ptr + { + return std::make_shared( + std::make_shared(std::move(input.id), "Mutated Task!", *(input.isComplete)), + std::move(input.clientMutationId) + ); + }); + auto subscription = std::make_shared( + [](const std::shared_ptr&) -> std::shared_ptr + { + return { std::make_shared(response::IdType(_fakeAppointmentId), "tomorrow", "Lunch?", true) }; + }); + + _service = std::make_shared(query, mutation, subscription); + } + + static void TearDownTestCase() + { + _fakeAppointmentId.clear(); + _fakeTaskId.clear(); + _fakeFolderId.clear(); + _service.reset(); + } + +protected: + static response::IdType _fakeAppointmentId; + static response::IdType _fakeTaskId; + static response::IdType _fakeFolderId; + + static std::shared_ptr _service; + static size_t _getAppointmentsCount; + static size_t _getTasksCount; + static size_t _getUnreadCountsCount; +}; + +response::IdType NoIntrospectionServiceCase::_fakeAppointmentId; +response::IdType NoIntrospectionServiceCase::_fakeTaskId; +response::IdType NoIntrospectionServiceCase::_fakeFolderId; + +std::shared_ptr NoIntrospectionServiceCase::_service; +size_t NoIntrospectionServiceCase::_getAppointmentsCount = 0; +size_t NoIntrospectionServiceCase::_getTasksCount = 0; +size_t NoIntrospectionServiceCase::_getUnreadCountsCount = 0; +size_t today::NextAppointmentChange::_notifySubscribeCount = 0; +size_t today::NextAppointmentChange::_subscriptionCount = 0; +size_t today::NextAppointmentChange::_notifyUnsubscribeCount = 0; + +TEST_F(NoIntrospectionServiceCase, QueryEverything) +{ + auto query = R"( + query Everything { + appointments { + edges { + node { + id + subject + when + isNow + __typename + } + } + } + tasks { + edges { + node { + id + title + isComplete + __typename + } + } + } + unreadCounts { + edges { + node { + id + name + unreadCount + __typename + } + } + } + })"_graphql; + response::Value variables(response::Type::Map); + auto state = std::make_shared(1); + auto result = _service->resolve(std::launch::async, state, query, "Everything", std::move(variables)).get(); + EXPECT_EQ(size_t(1), _getAppointmentsCount) << "today service lazy loads the appointments and caches the result"; + EXPECT_EQ(size_t(1), _getTasksCount) << "today service lazy loads the tasks and caches the result"; + EXPECT_EQ(size_t(1), _getUnreadCountsCount) << "today service lazy loads the unreadCounts and caches the result"; + EXPECT_EQ(size_t(1), state->appointmentsRequestId) << "today service passed the same RequestState"; + EXPECT_EQ(size_t(1), state->tasksRequestId) << "today service passed the same RequestState"; + EXPECT_EQ(size_t(1), state->unreadCountsRequestId) << "today service passed the same RequestState"; + EXPECT_EQ(size_t(1), state->loadAppointmentsCount) << "today service called the loader once"; + EXPECT_EQ(size_t(1), state->loadTasksCount) << "today service called the loader once"; + EXPECT_EQ(size_t(1), state->loadUnreadCountsCount) << "today service called the loader once"; + + try + { + ASSERT_TRUE(result.type() == response::Type::Map); + auto errorsItr = result.find("errors"); + if (errorsItr != result.get().cend()) + { + FAIL() << response::toJSON(response::Value(errorsItr->second)); + } + const auto data = service::ScalarArgument::require("data", result); + + const auto appointments = service::ScalarArgument::require("appointments", data); + const auto appointmentEdges = service::ScalarArgument::require("edges", appointments); + ASSERT_EQ(1, appointmentEdges.size()) << "appointments should have 1 entry"; + ASSERT_TRUE(appointmentEdges[0].type() == response::Type::Map) << "appointment should be an object"; + const auto appointmentNode = service::ScalarArgument::require("node", appointmentEdges[0]); + EXPECT_EQ(_fakeAppointmentId, service::IdArgument::require("id", appointmentNode)) << "id should match in base64 encoding"; + EXPECT_EQ("Lunch?", service::StringArgument::require("subject", appointmentNode)) << "subject should match"; + EXPECT_EQ("tomorrow", service::StringArgument::require("when", appointmentNode)) << "when should match"; + EXPECT_FALSE(service::BooleanArgument::require("isNow", appointmentNode)) << "isNow should match"; + EXPECT_EQ("Appointment", service::StringArgument::require("__typename", appointmentNode)) << "__typename should match"; + + const auto tasks = service::ScalarArgument::require("tasks", data); + const auto taskEdges = service::ScalarArgument::require("edges", tasks); + ASSERT_EQ(1, taskEdges.size()) << "tasks should have 1 entry"; + ASSERT_TRUE(taskEdges[0].type() == response::Type::Map) << "task should be an object"; + const auto taskNode = service::ScalarArgument::require("node", taskEdges[0]); + EXPECT_EQ(_fakeTaskId, service::IdArgument::require("id", taskNode)) << "id should match in base64 encoding"; + EXPECT_EQ("Don't forget", service::StringArgument::require("title", taskNode)) << "title should match"; + EXPECT_TRUE(service::BooleanArgument::require("isComplete", taskNode)) << "isComplete should match"; + EXPECT_EQ("Task", service::StringArgument::require("__typename", taskNode)) << "__typename should match"; + + const auto unreadCounts = service::ScalarArgument::require("unreadCounts", data); + const auto unreadCountEdges = service::ScalarArgument::require("edges", unreadCounts); + ASSERT_EQ(1, unreadCountEdges.size()) << "unreadCounts should have 1 entry"; + ASSERT_TRUE(unreadCountEdges[0].type() == response::Type::Map) << "unreadCount should be an object"; + const auto unreadCountNode = service::ScalarArgument::require("node", unreadCountEdges[0]); + EXPECT_EQ(_fakeFolderId, service::IdArgument::require("id", unreadCountNode)) << "id should match in base64 encoding"; + EXPECT_EQ("\"Fake\" Inbox", service::StringArgument::require("name", unreadCountNode)) << "name should match"; + EXPECT_EQ(3, service::IntArgument::require("unreadCount", unreadCountNode)) << "unreadCount should match"; + EXPECT_EQ("Folder", service::StringArgument::require("__typename", unreadCountNode)) << "__typename should match"; + } + catch (service::schema_exception & ex) + { + FAIL() << response::toJSON(ex.getErrors()); + } +} + +TEST_F(NoIntrospectionServiceCase, NoSchema) +{ + auto query = R"(query { + __schema { + queryType { name } + } + })"_graphql; + response::Value variables(response::Type::Map); + auto future = _service->resolve(std::launch::deferred, nullptr, query, "", std::move(variables)); + auto result = future.get(); + + try + { + ASSERT_TRUE(result.type() == response::Type::Map); + auto errorsItr = result.find("errors"); + ASSERT_FALSE(errorsItr == result.get().cend()); + auto errorsString = response::toJSON(response::Value(errorsItr->second)); + EXPECT_EQ(R"js([{"message":"Undefined field type: Query name: __schema","locations":[{"line":2,"column":4}]}])js", errorsString) << "error should match"; + } + catch (service::schema_exception & ex) + { + FAIL() << response::toJSON(ex.getErrors()); + } +} + +TEST_F(NoIntrospectionServiceCase, NoType) +{ + auto query = R"(query { + __type(name: "Query") { + description + } + })"_graphql; + response::Value variables(response::Type::Map); + auto future = _service->resolve(std::launch::deferred, nullptr, query, "", std::move(variables)); + auto result = future.get(); + + try + { + ASSERT_TRUE(result.type() == response::Type::Map); + auto errorsItr = result.find("errors"); + ASSERT_FALSE(errorsItr == result.get().cend()); + auto errorsString = response::toJSON(response::Value(errorsItr->second)); + EXPECT_EQ(R"js([{"message":"Undefined field type: Query name: __type","locations":[{"line":2,"column":4}]}])js", errorsString) << "error should match"; + } + catch (service::schema_exception & ex) + { + FAIL() << response::toJSON(ex.getErrors()); + } +} From 25afd869f8b15caf2ecc406861a5787cce745678 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Thu, 10 Dec 2020 14:44:14 -0300 Subject: [PATCH 38/42] change field_path to list and make it a const-ref in SelectionVisitor Provide `push()` and `pop()` convenience methods so it's the same as `queue`. The `list.size()` is not as fast, however these lists are often small enough to not matter (walk the list counting the elements) --- include/graphqlservice/GraphQLError.h | 20 +++++++++++++++++--- src/GraphQLResponse.cpp | 5 +++++ src/GraphQLService.cpp | 4 ++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/include/graphqlservice/GraphQLError.h b/include/graphqlservice/GraphQLError.h index 0703af5f..fd66b5d7 100644 --- a/include/graphqlservice/GraphQLError.h +++ b/include/graphqlservice/GraphQLError.h @@ -18,9 +18,11 @@ #endif // !GRAPHQL_DLLEXPORTS // clang-format on -#include +#include +#include #include #include +#include namespace graphql::error { @@ -37,8 +39,20 @@ struct schema_location constexpr schema_location emptyLocation {}; -using path_segment = std::variant; -using field_path = std::queue; +using path_segment = std::variant; + +struct field_path : public std::list +{ + void pop() + { + pop_front(); + } + + void push(path_segment&& segment) + { + push_back(std::move(segment)); + } +}; struct schema_error { diff --git a/src/GraphQLResponse.cpp b/src/GraphQLResponse.cpp index 48de29e4..55f4d6d9 100644 --- a/src/GraphQLResponse.cpp +++ b/src/GraphQLResponse.cpp @@ -1235,6 +1235,11 @@ void addErrorPath(graphql::error::field_path&& path, Value& error) { errorPath.emplace_back(response::Value(std::move(std::get(segment)))); } + if (std::holds_alternative(segment)) + { + errorPath.emplace_back( + response::Value(std::string(std::get(segment)))); + } else if (std::holds_alternative(segment)) { errorPath.emplace_back( diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index 1c1b93ee..47e3b02e 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -767,7 +767,7 @@ class SelectionVisitor const ResolverContext _resolverContext; const std::shared_ptr& _state; const response::Value& _operationDirectives; - const field_path _path; + const field_path& _path; const std::launch _launch; const FragmentMap& _fragments; const response::Value& _variables; @@ -898,7 +898,7 @@ void SelectionVisitor::visitField(const peg::ast_node& field) auto path = _path; - path.push({ alias }); + path.push({ aliasView }); SelectionSetParams selectionSetParams { _resolverContext, From 7eb32e49c67afab87ab71b1c8f0d240da3a68b4e Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Thu, 10 Dec 2020 15:57:55 -0300 Subject: [PATCH 39/42] BREAKING SelectionSetParams keeps a reference to the parent Change SelectionSetParams to keep an optional reference to the parent, this way we don't need to build the path over and over again just to add one element, instead we create this on demand. Since errorPath was accessed directly, this breaks the existing code, it became a method that dynamically computes the error path (recursive). This is important since we just pay the list copy price when there is an error and not in all field resolution. --- include/graphqlservice/GraphQLService.h | 35 ++++++++++++++------ samples/today/TodayMock.cpp | 28 ++++++++-------- src/GraphQLService.cpp | 43 +++++++++++-------------- 3 files changed, 58 insertions(+), 48 deletions(-) diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index 97bed6a7..494915ba 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -141,7 +141,24 @@ struct SelectionSetParams const response::Value& inlineFragmentDirectives; // Field error path to this selection set. - field_path errorPath; + std::optional> parent; + field_path ownErrorPath; + + field_path errorPath() const + { + if (!parent) + { + return ownErrorPath; + } + + field_path result = parent.value().get().errorPath(); + for (const auto& itr : ownErrorPath) + { + result.push_back(itr); + } + + return result; + } // Async launch policy for sub-field resolvers. const std::launch launch = std::launch::deferred; @@ -668,7 +685,7 @@ struct ModifiedResult std::vector> children(wrappedResult.size()); size_t idx = 0; - wrappedParams.errorPath.push(size_t { 0 }); + wrappedParams.ownErrorPath.push(size_t { 0 }); using vector_type = std::decay_t; @@ -682,7 +699,7 @@ struct ModifiedResult { children[idx++] = ModifiedResult::convert(std::move(entry), ResolverParams(wrappedParams)); - ++std::get(wrappedParams.errorPath.back()); + ++std::get(wrappedParams.ownErrorPath.back()); } } else @@ -691,14 +708,14 @@ struct ModifiedResult { children[idx++] = ModifiedResult::convert(std::move(entry), ResolverParams(wrappedParams)); - ++std::get(wrappedParams.errorPath.back()); + ++std::get(wrappedParams.ownErrorPath.back()); } } response::Value data(response::Type::List); std::vector errors; - wrappedParams.errorPath.back() = size_t { 0 }; + wrappedParams.ownErrorPath.back() = size_t { 0 }; data.reserve(children.size()); for (auto& future : children) @@ -735,7 +752,7 @@ struct ModifiedResult } if (error.path.empty()) { - error.path = field_path { wrappedParams.errorPath }; + error.path = wrappedParams.errorPath(); } errors.emplace_back(std::move(error)); } @@ -748,13 +765,13 @@ struct ModifiedResult << " unknown error: " << ex.what(); schema_location location = wrappedParams.getLocation(); - field_path path { wrappedParams.errorPath }; + field_path path = wrappedParams.errorPath(); schema_error error { message.str(), std::move(location), std::move(path) }; errors.emplace_back(std::move(error)); } - ++std::get(wrappedParams.errorPath.back()); + ++std::get(wrappedParams.ownErrorPath.back()); } if (errors.size() == 0) @@ -800,7 +817,7 @@ struct ModifiedResult return response::Value(response::ResultType { {}, { { message.str(), paramsFuture.getLocation(), - paramsFuture.errorPath } } }); + paramsFuture.errorPath() } } }); } }, std::move(result), diff --git a/samples/today/TodayMock.cpp b/samples/today/TodayMock.cpp index d7d454e9..2deb92b9 100644 --- a/samples/today/TodayMock.cpp +++ b/samples/today/TodayMock.cpp @@ -186,10 +186,10 @@ struct EdgeConstraints using vec_type = std::vector>; using itr_type = typename vec_type::const_iterator; - EdgeConstraints(const std::shared_ptr& state, service::field_path&& path, + EdgeConstraints(const std::shared_ptr& state, const service::SelectionSetParams& params, const vec_type& objects) : _state(state) - , _path(std::move(path)) + , _params(params) , _objects(objects) { } @@ -240,7 +240,7 @@ struct EdgeConstraints error << "Invalid argument: first value: " << *first; throw service::schema_exception { - { service::schema_error { error.str(), {}, _path } } + { service::schema_error { error.str(), {}, _params.errorPath() } } }; } @@ -258,7 +258,7 @@ struct EdgeConstraints error << "Invalid argument: last value: " << *last; throw service::schema_exception { - { service::schema_error { error.str(), {}, _path } } + { service::schema_error { error.str(), {}, _params.errorPath() } } }; } @@ -278,7 +278,7 @@ struct EdgeConstraints private: const std::shared_ptr& _state; - const service::field_path _path; + const service::SelectionSetParams& _params; const vec_type& _objects; }; @@ -295,11 +295,11 @@ service::FieldResult> Query::getA std::optional&& afterWrapped, std::optional&& lastWrapped, std::optional&& beforeWrapped, - service::field_path&& path) { + service::SelectionSetParams&& params) { loadAppointments(state); EdgeConstraints constraints(state, - std::move(path), + params, _appointments); auto connection = constraints(firstWrapped, afterWrapped, lastWrapped, beforeWrapped); @@ -309,7 +309,7 @@ service::FieldResult> Query::getA std::move(after), std::move(last), std::move(before), - std::move(params.errorPath)); + std::move(params)); } service::FieldResult> Query::getTasks( @@ -325,10 +325,10 @@ service::FieldResult> Query::getTasks( std::optional&& afterWrapped, std::optional&& lastWrapped, std::optional&& beforeWrapped, - service::field_path&& path) { + service::SelectionSetParams&& params) { loadTasks(state); - EdgeConstraints constraints(state, std::move(path), _tasks); + EdgeConstraints constraints(state, params, _tasks); auto connection = constraints(firstWrapped, afterWrapped, lastWrapped, beforeWrapped); return std::static_pointer_cast(connection); @@ -337,7 +337,7 @@ service::FieldResult> Query::getTasks( std::move(after), std::move(last), std::move(before), - std::move(params.errorPath)); + std::move(params)); } service::FieldResult> Query::getUnreadCounts( @@ -353,11 +353,11 @@ service::FieldResult> Query::getUnread std::optional&& afterWrapped, std::optional&& lastWrapped, std::optional&& beforeWrapped, - service::field_path&& path) { + service::SelectionSetParams&& params) { loadUnreadCounts(state); EdgeConstraints constraints(state, - std::move(path), + params, _unreadCounts); auto connection = constraints(firstWrapped, afterWrapped, lastWrapped, beforeWrapped); @@ -367,7 +367,7 @@ service::FieldResult> Query::getUnread std::move(after), std::move(last), std::move(before), - std::move(params.errorPath)); + std::move(params)); } service::FieldResult>> Query::getAppointmentsById( diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index 47e3b02e..c07d38ac 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -632,7 +632,7 @@ void blockSubFields(const ResolverParams& params) throw schema_exception { { schema_error { error.str(), { position.line, position.column }, - { params.errorPath } } } }; + { params.errorPath() } } } }; } } @@ -704,7 +704,7 @@ void requireSubFields(const ResolverParams& params) throw schema_exception { { schema_error { error.str(), { position.line, position.column }, - { params.errorPath } } } }; + { params.errorPath() } } } }; } } @@ -764,11 +764,7 @@ class SelectionVisitor void visitFragmentSpread(const peg::ast_node& fragmentSpread); void visitInlineFragment(const peg::ast_node& inlineFragment); - const ResolverContext _resolverContext; - const std::shared_ptr& _state; - const response::Value& _operationDirectives; - const field_path& _path; - const std::launch _launch; + const SelectionSetParams& _selectionSetParams; const FragmentMap& _fragments; const response::Value& _variables; const TypeNames& _typeNames; @@ -782,11 +778,7 @@ class SelectionVisitor SelectionVisitor::SelectionVisitor(const SelectionSetParams& selectionSetParams, const FragmentMap& fragments, const response::Value& variables, const TypeNames& typeNames, const ResolverMap& resolvers) - : _resolverContext(selectionSetParams.resolverContext) - , _state(selectionSetParams.state) - , _operationDirectives(selectionSetParams.operationDirectives) - , _path(selectionSetParams.errorPath) - , _launch(selectionSetParams.launch) + : _selectionSetParams(selectionSetParams) , _fragments(fragments) , _variables(variables) , _typeNames(typeNames) @@ -860,7 +852,7 @@ void SelectionVisitor::visitField(const peg::ast_node& field) error << "Unknown field name: " << name; promise.set_exception(std::make_exception_ptr(schema_exception { - { schema_error { error.str(), { position.line, position.column }, { _path } } } })); + { schema_error { error.str(), { position.line, position.column }, { _selectionSetParams.errorPath() } } } })); _values.push_back({ std::move(alias), promise.get_future() }); return; @@ -896,19 +888,16 @@ void SelectionVisitor::visitField(const peg::ast_node& field) selection = &child; }); - auto path = _path; - - path.push({ aliasView }); - SelectionSetParams selectionSetParams { - _resolverContext, - _state, - _operationDirectives, + _selectionSetParams.resolverContext, + _selectionSetParams.state, + _selectionSetParams.operationDirectives, _fragmentDirectives.top().fragmentDefinitionDirectives, _fragmentDirectives.top().fragmentSpreadDirectives, _fragmentDirectives.top().inlineFragmentDirectives, - std::move(path), - _launch, + _selectionSetParams, + { { aliasView } }, + _selectionSetParams.launch, }; try @@ -939,7 +928,7 @@ void SelectionVisitor::visitField(const peg::ast_node& field) if (message.path.empty()) { - message.path = { selectionSetParams.errorPath }; + message.path = { selectionSetParams.errorPath() }; } } @@ -958,7 +947,7 @@ void SelectionVisitor::visitField(const peg::ast_node& field) promise.set_exception( std::make_exception_ptr(schema_exception { { schema_error { message.str(), { position.line, position.column }, - std::move(selectionSetParams.errorPath) } } })); + selectionSetParams.errorPath() } } })); _values.push_back({ std::move(alias), promise.get_future() }); } @@ -977,7 +966,7 @@ void SelectionVisitor::visitFragmentSpread(const peg::ast_node& fragmentSpread) error << "Unknown fragment name: " << name; throw schema_exception { - { schema_error { error.str(), { position.line, position.column }, { _path } } } + { schema_error { error.str(), { position.line, position.column }, { _selectionSetParams.errorPath() } } } }; } @@ -1368,6 +1357,7 @@ void OperationDefinitionVisitor::visit( emptyFragmentDirectives, emptyFragmentDirectives, emptyFragmentDirectives, + std::nullopt, {}, selectionLaunch, }; @@ -1943,6 +1933,7 @@ std::future Request::subscribe( emptyFragmentDirectives, emptyFragmentDirectives, emptyFragmentDirectives, + std::nullopt, {}, launch, }; @@ -2016,6 +2007,7 @@ std::future Request::unsubscribe(std::launch launch, SubscriptionKey key) emptyFragmentDirectives, emptyFragmentDirectives, emptyFragmentDirectives, + std::nullopt, {}, launch, }; @@ -2182,6 +2174,7 @@ void Request::deliver(std::launch launch, const SubscriptionName& name, emptyFragmentDirectives, emptyFragmentDirectives, emptyFragmentDirectives, + std::nullopt, {}, launch, }; From ec460f534580157e0a89a7d85207534cf09d4345 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Thu, 10 Dec 2020 17:02:23 -0300 Subject: [PATCH 40/42] Provide SelectionSetParams constructor with parent + ownErrorPath Use this specific constructor in list converter, creating one itemParams with the new ownErrorPath, instead of changing the wrapper request param. --- include/graphqlservice/GraphQLService.h | 54 +++++++++++++++++++---- samples/schema.today.graphql | 1 + samples/separate/AppointmentObject.cpp | 18 +++++++- samples/separate/AppointmentObject.h | 2 + samples/separate/TodaySchema.cpp | 1 + samples/today/TodayMock.h | 6 +++ samples/unified/TodaySchema.cpp | 19 +++++++- samples/unified/TodaySchema.h | 2 + src/GraphQLService.cpp | 24 +++++++--- test/TodayTests.cpp | 58 +++++++++++++++++++++++++ 10 files changed, 169 insertions(+), 16 deletions(-) diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index 494915ba..55bc1892 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -125,6 +125,25 @@ enum class ResolverContext // SelectionSet struct SelectionSetParams { + SelectionSetParams(const ResolverContext resolverContext_, + const std::shared_ptr& state_, const response::Value& operationDirectives_, + const response::Value& fragmentDefinitionDirectives_, + const response::Value& fragmentSpreadDirectives_, + const response::Value& inlineFragmentDirectives_, + std::optional> parent_, + field_path ownErrorPath_, const std::launch launch_ = std::launch::deferred) + : resolverContext(resolverContext_) + , state(state_) + , operationDirectives(operationDirectives_) + , fragmentDefinitionDirectives(fragmentDefinitionDirectives_) + , fragmentSpreadDirectives(fragmentSpreadDirectives_) + , inlineFragmentDirectives(inlineFragmentDirectives_) + , parent(parent_) + , ownErrorPath(ownErrorPath_) + , launch(launch_) + { + } + // Context for this selection set. const ResolverContext resolverContext; @@ -162,6 +181,20 @@ struct SelectionSetParams // Async launch policy for sub-field resolvers. const std::launch launch = std::launch::deferred; + + GRAPHQLSERVICE_EXPORT SelectionSetParams( + const SelectionSetParams& parent, field_path&& ownErrorPath_) + : resolverContext(parent.resolverContext) + , state(parent.state) + , operationDirectives(parent.operationDirectives) + , fragmentDefinitionDirectives(parent.fragmentDefinitionDirectives) + , fragmentSpreadDirectives(parent.fragmentSpreadDirectives) + , inlineFragmentDirectives(parent.inlineFragmentDirectives) + , parent(std::optional(std::ref(parent))) + , ownErrorPath(std::move(ownErrorPath_)) + , launch(parent.launch) + { + } }; // Pass a common bundle of parameters to all of the generated Object::getField accessors. @@ -271,6 +304,9 @@ struct ResolverParams : SelectionSetParams response::Value&& fieldDirectives, const peg::ast_node* selection, const FragmentMap& fragments, const response::Value& variables); + GRAPHQLSERVICE_EXPORT explicit ResolverParams( + const ResolverParams& parent, field_path&& ownErrorPath); + GRAPHQLSERVICE_EXPORT schema_location getLocation() const; // These values are different for each resolver. @@ -680,13 +716,11 @@ struct ModifiedResult { return std::async( std::launch::deferred, - [](auto&& wrappedFuture, ResolverParams&& wrappedParams) { + [](auto&& wrappedFuture, const ResolverParams&& wrappedParams) { auto wrappedResult = wrappedFuture.get(); std::vector> children(wrappedResult.size()); size_t idx = 0; - wrappedParams.ownErrorPath.push(size_t { 0 }); - using vector_type = std::decay_t; if constexpr (!std::is_same_v, @@ -697,27 +731,27 @@ struct ModifiedResult // Copy the values from the std::vector<> rather than moving them. for (typename vector_type::value_type entry : wrappedResult) { + auto itemParams = ResolverParams(wrappedParams, { { idx } }); children[idx++] = ModifiedResult::convert(std::move(entry), - ResolverParams(wrappedParams)); - ++std::get(wrappedParams.ownErrorPath.back()); + std::move(itemParams)); } } else { for (auto& entry : wrappedResult) { + auto itemParams = ResolverParams(wrappedParams, { { idx } }); children[idx++] = ModifiedResult::convert(std::move(entry), - ResolverParams(wrappedParams)); - ++std::get(wrappedParams.ownErrorPath.back()); + std::move(itemParams)); } } response::Value data(response::Type::List); std::vector errors; - wrappedParams.ownErrorPath.back() = size_t { 0 }; data.reserve(children.size()); + idx = 0; for (auto& future : children) { try @@ -753,6 +787,7 @@ struct ModifiedResult if (error.path.empty()) { error.path = wrappedParams.errorPath(); + error.path.push({ idx }); } errors.emplace_back(std::move(error)); } @@ -766,12 +801,13 @@ struct ModifiedResult schema_location location = wrappedParams.getLocation(); field_path path = wrappedParams.errorPath(); + path.push({ idx }); schema_error error { message.str(), std::move(location), std::move(path) }; errors.emplace_back(std::move(error)); } - ++std::get(wrappedParams.ownErrorPath.back()); + idx++; } if (errors.size() == 0) diff --git a/samples/schema.today.graphql b/samples/schema.today.graphql index 6fdeaa77..17052dac 100644 --- a/samples/schema.today.graphql +++ b/samples/schema.today.graphql @@ -115,6 +115,7 @@ type Appointment implements Node { when: DateTime subject: String isNow: Boolean! + forceError: String } type Task implements Node { diff --git a/samples/separate/AppointmentObject.cpp b/samples/separate/AppointmentObject.cpp index 1fcb5663..8938c6fd 100644 --- a/samples/separate/AppointmentObject.cpp +++ b/samples/separate/AppointmentObject.cpp @@ -23,6 +23,7 @@ Appointment::Appointment() "Appointment" }, { { R"gql(__typename)gql"sv, [this](service::ResolverParams&& params) { return resolve_typename(std::move(params)); } }, + { R"gql(forceError)gql"sv, [this](service::ResolverParams&& params) { return resolveForceError(std::move(params)); } }, { R"gql(id)gql"sv, [this](service::ResolverParams&& params) { return resolveId(std::move(params)); } }, { R"gql(isNow)gql"sv, [this](service::ResolverParams&& params) { return resolveIsNow(std::move(params)); } }, { R"gql(subject)gql"sv, [this](service::ResolverParams&& params) { return resolveSubject(std::move(params)); } }, @@ -87,6 +88,20 @@ std::future Appointment::resolveIsNow(service::ResolverParams&& return service::ModifiedResult::convert(std::move(result), std::move(params)); } +service::FieldResult> Appointment::getForceError(service::FieldParams&&) const +{ + throw std::runtime_error(R"ex(Appointment::getForceError is not implemented)ex"); +} + +std::future Appointment::resolveForceError(service::ResolverParams&& params) +{ + std::unique_lock resolverLock(_resolverMutex); + auto result = getForceError(service::FieldParams(params, std::move(params.fieldDirectives))); + resolverLock.unlock(); + + return service::ModifiedResult::convert(std::move(result), std::move(params)); +} + std::future Appointment::resolve_typename(service::ResolverParams&& params) { return service::ModifiedResult::convert(response::StringType{ R"gql(Appointment)gql" }, std::move(params)); @@ -103,7 +118,8 @@ void AddAppointmentDetails(std::shared_ptr typeAppoin std::make_shared("id", R"md()md", std::nullopt, std::vector>(), schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType("ID"))), std::make_shared("when", R"md()md", std::nullopt, std::vector>(), schema->LookupType("DateTime")), std::make_shared("subject", R"md()md", std::nullopt, std::vector>(), schema->LookupType("String")), - std::make_shared("isNow", R"md()md", std::nullopt, std::vector>(), schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType("Boolean"))) + std::make_shared("isNow", R"md()md", std::nullopt, std::vector>(), schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType("Boolean"))), + std::make_shared("forceError", R"md()md", std::nullopt, std::vector>(), schema->LookupType("String")) }); } diff --git a/samples/separate/AppointmentObject.h b/samples/separate/AppointmentObject.h index 7fd5a3ff..43309379 100644 --- a/samples/separate/AppointmentObject.h +++ b/samples/separate/AppointmentObject.h @@ -22,12 +22,14 @@ class Appointment virtual service::FieldResult> getWhen(service::FieldParams&& params) const; virtual service::FieldResult> getSubject(service::FieldParams&& params) const; virtual service::FieldResult getIsNow(service::FieldParams&& params) const; + virtual service::FieldResult> getForceError(service::FieldParams&& params) const; private: std::future resolveId(service::ResolverParams&& params); std::future resolveWhen(service::ResolverParams&& params); std::future resolveSubject(service::ResolverParams&& params); std::future resolveIsNow(service::ResolverParams&& params); + std::future resolveForceError(service::ResolverParams&& params); std::future resolve_typename(service::ResolverParams&& params); }; diff --git a/samples/separate/TodaySchema.cpp b/samples/separate/TodaySchema.cpp index 0260de76..1d0de514 100644 --- a/samples/separate/TodaySchema.cpp +++ b/samples/separate/TodaySchema.cpp @@ -350,6 +350,7 @@ class ValidationContext : public service::ValidationContext { "when", { typeDateTime, { } } }, { "subject", { typeString, { } } }, { "isNow", { makeNonNullOfType(typeBoolean), { } } }, + { "forceError", { typeString, { } } }, { "__typename", { makeNonNullOfType(typeString), { } } } }); typeTask->setFields({ diff --git a/samples/today/TodayMock.h b/samples/today/TodayMock.h index cb43c6f9..02ba2b0c 100644 --- a/samples/today/TodayMock.h +++ b/samples/today/TodayMock.h @@ -154,6 +154,12 @@ class Appointment : public object::Appointment return _isNow; } + service::FieldResult> getForceError( + service::FieldParams&&) const final + { + throw std::runtime_error(R"ex(this error was forced)ex"); + } + private: response::IdType _id; std::string _when; diff --git a/samples/unified/TodaySchema.cpp b/samples/unified/TodaySchema.cpp index 1036d0c9..2264b0d6 100644 --- a/samples/unified/TodaySchema.cpp +++ b/samples/unified/TodaySchema.cpp @@ -769,6 +769,7 @@ Appointment::Appointment() "Appointment" }, { { R"gql(__typename)gql"sv, [this](service::ResolverParams&& params) { return resolve_typename(std::move(params)); } }, + { R"gql(forceError)gql"sv, [this](service::ResolverParams&& params) { return resolveForceError(std::move(params)); } }, { R"gql(id)gql"sv, [this](service::ResolverParams&& params) { return resolveId(std::move(params)); } }, { R"gql(isNow)gql"sv, [this](service::ResolverParams&& params) { return resolveIsNow(std::move(params)); } }, { R"gql(subject)gql"sv, [this](service::ResolverParams&& params) { return resolveSubject(std::move(params)); } }, @@ -833,6 +834,20 @@ std::future Appointment::resolveIsNow(service::ResolverParams&& return service::ModifiedResult::convert(std::move(result), std::move(params)); } +service::FieldResult> Appointment::getForceError(service::FieldParams&&) const +{ + throw std::runtime_error(R"ex(Appointment::getForceError is not implemented)ex"); +} + +std::future Appointment::resolveForceError(service::ResolverParams&& params) +{ + std::unique_lock resolverLock(_resolverMutex); + auto result = getForceError(service::FieldParams(params, std::move(params.fieldDirectives))); + resolverLock.unlock(); + + return service::ModifiedResult::convert(std::move(result), std::move(params)); +} + std::future Appointment::resolve_typename(service::ResolverParams&& params) { return service::ModifiedResult::convert(response::StringType{ R"gql(Appointment)gql" }, std::move(params)); @@ -1294,6 +1309,7 @@ class ValidationContext : public service::ValidationContext { "when", { typeDateTime, { } } }, { "subject", { typeString, { } } }, { "isNow", { makeNonNullOfType(typeBoolean), { } } }, + { "forceError", { typeString, { } } }, { "__typename", { makeNonNullOfType(typeString), { } } } }); typeTask->setFields({ @@ -1509,7 +1525,8 @@ void AddTypesToSchema(const std::shared_ptr& schema) std::make_shared("id", R"md()md", std::nullopt, std::vector>(), schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType("ID"))), std::make_shared("when", R"md()md", std::nullopt, std::vector>(), schema->LookupType("DateTime")), std::make_shared("subject", R"md()md", std::nullopt, std::vector>(), schema->LookupType("String")), - std::make_shared("isNow", R"md()md", std::nullopt, std::vector>(), schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType("Boolean"))) + std::make_shared("isNow", R"md()md", std::nullopt, std::vector>(), schema->WrapType(introspection::TypeKind::NON_NULL, schema->LookupType("Boolean"))), + std::make_shared("forceError", R"md()md", std::nullopt, std::vector>(), schema->LookupType("String")) }); typeTask->AddInterfaces({ typeNode diff --git a/samples/unified/TodaySchema.h b/samples/unified/TodaySchema.h index 0c25e791..c624c812 100644 --- a/samples/unified/TodaySchema.h +++ b/samples/unified/TodaySchema.h @@ -285,12 +285,14 @@ class Appointment virtual service::FieldResult> getWhen(service::FieldParams&& params) const; virtual service::FieldResult> getSubject(service::FieldParams&& params) const; virtual service::FieldResult getIsNow(service::FieldParams&& params) const; + virtual service::FieldResult> getForceError(service::FieldParams&& params) const; private: std::future resolveId(service::ResolverParams&& params); std::future resolveWhen(service::ResolverParams&& params); std::future resolveSubject(service::ResolverParams&& params); std::future resolveIsNow(service::ResolverParams&& params); + std::future resolveForceError(service::ResolverParams&& params); std::future resolve_typename(service::ResolverParams&& params); }; diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index c07d38ac..822319b1 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -403,6 +403,18 @@ ResolverParams::ResolverParams(const SelectionSetParams& selectionSetParams, { } +ResolverParams::ResolverParams(const ResolverParams& parent, field_path&& ownErrorPath_) + : SelectionSetParams(parent, std::move(ownErrorPath_)) + , field(parent.field) + , fieldName(parent.fieldName) + , arguments(parent.arguments) + , fieldDirectives(parent.fieldDirectives) + , selection(parent.selection) + , fragments(parent.fragments) + , variables(parent.variables) +{ +} + schema_location ResolverParams::getLocation() const { auto position = field.begin(); @@ -851,8 +863,10 @@ void SelectionVisitor::visitField(const peg::ast_node& field) error << "Unknown field name: " << name; - promise.set_exception(std::make_exception_ptr(schema_exception { - { schema_error { error.str(), { position.line, position.column }, { _selectionSetParams.errorPath() } } } })); + promise.set_exception( + std::make_exception_ptr(schema_exception { { schema_error { error.str(), + { position.line, position.column }, + { _selectionSetParams.errorPath() } } } })); _values.push_back({ std::move(alias), promise.get_future() }); return; @@ -965,9 +979,9 @@ void SelectionVisitor::visitFragmentSpread(const peg::ast_node& fragmentSpread) error << "Unknown fragment name: " << name; - throw schema_exception { - { schema_error { error.str(), { position.line, position.column }, { _selectionSetParams.errorPath() } } } - }; + throw schema_exception { { schema_error { error.str(), + { position.line, position.column }, + { _selectionSetParams.errorPath() } } } }; } bool skip = (_typeNames.count(itr->second.getType()) == 0); diff --git a/test/TodayTests.cpp b/test/TodayTests.cpp index f695c3bd..47f15841 100644 --- a/test/TodayTests.cpp +++ b/test/TodayTests.cpp @@ -238,6 +238,64 @@ TEST_F(TodayServiceCase, QueryAppointments) } } +TEST_F(TodayServiceCase, QueryAppointmentsWithForceError) +{ + auto query = R"({ + appointments { + edges { + node { + appointmentId: id + subject + when + isNow + forceError + } + } + } + })"_graphql; + response::Value variables(response::Type::Map); + auto state = std::make_shared(2); + auto result = _service->resolve(state, query, "", std::move(variables)).get(); + EXPECT_EQ(size_t(1), _getAppointmentsCount) << "today service lazy loads the appointments and caches the result"; + EXPECT_GE(size_t(1), _getTasksCount) << "today service lazy loads the tasks and caches the result"; + EXPECT_GE(size_t(1), _getUnreadCountsCount) << "today service lazy loads the unreadCounts and caches the result"; + EXPECT_EQ(size_t(2), state->appointmentsRequestId) << "today service passed the same RequestState"; + EXPECT_EQ(size_t(0), state->tasksRequestId) << "today service did not call the loader"; + EXPECT_EQ(size_t(0), state->unreadCountsRequestId) << "today service did not call the loader"; + EXPECT_EQ(size_t(1), state->loadAppointmentsCount) << "today service called the loader once"; + EXPECT_EQ(size_t(0), state->loadTasksCount) << "today service did not call the loader"; + EXPECT_EQ(size_t(0), state->loadUnreadCountsCount) << "today service did not call the loader"; + + try + { + ASSERT_TRUE(result.type() == response::Type::Map); + auto errorsItr = result.find("errors"); + if (errorsItr == result.get().cend()) + { + FAIL() << response::toJSON(response::Value(result)) << "no errors returned"; + } + + auto errorsString = response::toJSON(response::Value(errorsItr->second)); + EXPECT_EQ(R"js([{"message":"Field error name: forceError unknown error: this error was forced","locations":[{"line":9,"column":7}],"path":["appointments","edges",0,"node","forceError"]}])js", errorsString) << "error should match"; + + const auto data = service::ScalarArgument::require("data", result); + + const auto appointments = service::ScalarArgument::require("appointments", data); + const auto appointmentEdges = service::ScalarArgument::require("edges", appointments); + ASSERT_EQ(1, appointmentEdges.size()) << "appointments should have 1 entry"; + ASSERT_TRUE(appointmentEdges[0].type() == response::Type::Map) << "appointment should be an object"; + const auto appointmentNode = service::ScalarArgument::require("node", appointmentEdges[0]); + EXPECT_EQ(_fakeAppointmentId, service::IdArgument::require("appointmentId", appointmentNode)) << "id should match in base64 encoding"; + EXPECT_EQ("Lunch?", service::StringArgument::require("subject", appointmentNode)) << "subject should match"; + EXPECT_EQ("tomorrow", service::StringArgument::require("when", appointmentNode)) << "when should match"; + EXPECT_FALSE(service::BooleanArgument::require("isNow", appointmentNode)) << "isNow should match"; + } + catch (service::schema_exception & ex) + { + FAIL() << response::toJSON(ex.getErrors()); + } +} + TEST_F(TodayServiceCase, QueryTasks) { auto query = R"gql({ From 78ca2c98a9a2c65fca70ed3d5135ebbae3bc1697 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Thu, 10 Dec 2020 17:10:52 -0300 Subject: [PATCH 41/42] BREAKING ResolverParams now uses string view We shouldn't modify the parameters using a string is causing it to copy the field name, which is particularly bad when processing huge lists (it would copy the name for each item). --- include/graphqlservice/GraphQLService.h | 4 ++-- src/GraphQLService.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index 55bc1892..d3c483ec 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -300,7 +300,7 @@ using FragmentMap = std::unordered_map; struct ResolverParams : SelectionSetParams { GRAPHQLSERVICE_EXPORT explicit ResolverParams(const SelectionSetParams& selectionSetParams, - const peg::ast_node& field, std::string&& fieldName, response::Value&& arguments, + const peg::ast_node& field, const std::string_view& fieldName, response::Value&& arguments, response::Value&& fieldDirectives, const peg::ast_node* selection, const FragmentMap& fragments, const response::Value& variables); @@ -311,7 +311,7 @@ struct ResolverParams : SelectionSetParams // These values are different for each resolver. const peg::ast_node& field; - std::string fieldName; + const std::string_view& fieldName; response::Value arguments { response::Type::Map }; response::Value fieldDirectives { response::Type::Map }; const peg::ast_node* selection; diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index 822319b1..46a9dce1 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -389,12 +389,12 @@ const response::Value& Fragment::getDirectives() const } ResolverParams::ResolverParams(const SelectionSetParams& selectionSetParams, - const peg::ast_node& field, std::string&& fieldName, response::Value&& arguments, + const peg::ast_node& field, const std::string_view& fieldName, response::Value&& arguments, response::Value&& fieldDirectives, const peg::ast_node* selection, const FragmentMap& fragments, const response::Value& variables) : SelectionSetParams(selectionSetParams) , field(field) - , fieldName(std::move(fieldName)) + , fieldName(fieldName) , arguments(std::move(arguments)) , fieldDirectives(std::move(fieldDirectives)) , selection(selection) @@ -918,7 +918,7 @@ void SelectionVisitor::visitField(const peg::ast_node& field) { auto result = itr->second(ResolverParams(selectionSetParams, field, - std::string(alias), + aliasView, std::move(arguments), directiveVisitor.getDirectives(), selection, From a142c6c9035ee46123127e9ad0764614952c9055 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Thu, 10 Dec 2020 17:49:02 -0300 Subject: [PATCH 42/42] ownErrorPath is now a const path_segment We don't change it anymore, we don't push to the array, then we can keep it inline in the parent structure, avoiding the extra allocation. --- include/graphqlservice/GraphQLService.h | 42 ++++++++++++++++++------- src/GraphQLService.cpp | 4 +-- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/include/graphqlservice/GraphQLService.h b/include/graphqlservice/GraphQLService.h index d3c483ec..3160fa59 100644 --- a/include/graphqlservice/GraphQLService.h +++ b/include/graphqlservice/GraphQLService.h @@ -131,7 +131,7 @@ struct SelectionSetParams const response::Value& fragmentSpreadDirectives_, const response::Value& inlineFragmentDirectives_, std::optional> parent_, - field_path ownErrorPath_, const std::launch launch_ = std::launch::deferred) + const path_segment&& ownErrorPath_, const std::launch launch_ = std::launch::deferred) : resolverContext(resolverContext_) , state(state_) , operationDirectives(operationDirectives_) @@ -139,7 +139,7 @@ struct SelectionSetParams , fragmentSpreadDirectives(fragmentSpreadDirectives_) , inlineFragmentDirectives(inlineFragmentDirectives_) , parent(parent_) - , ownErrorPath(ownErrorPath_) + , ownErrorPath(std::move(ownErrorPath_)) , launch(launch_) { } @@ -161,20 +161,38 @@ struct SelectionSetParams // Field error path to this selection set. std::optional> parent; - field_path ownErrorPath; + const path_segment ownErrorPath; field_path errorPath() const { if (!parent) { - return ownErrorPath; + if (std::holds_alternative(ownErrorPath)) + { + if (std::get(ownErrorPath) == "") + { + return {}; + } + } + else if (std::holds_alternative(ownErrorPath)) + { + if (std::get(ownErrorPath) == "") + { + return {}; + } + } + else if (std::holds_alternative(ownErrorPath)) + { + if (std::get(ownErrorPath) == 0) + { + return {}; + } + } + return { { ownErrorPath } }; } field_path result = parent.value().get().errorPath(); - for (const auto& itr : ownErrorPath) - { - result.push_back(itr); - } + result.push_back(ownErrorPath); return result; } @@ -183,7 +201,7 @@ struct SelectionSetParams const std::launch launch = std::launch::deferred; GRAPHQLSERVICE_EXPORT SelectionSetParams( - const SelectionSetParams& parent, field_path&& ownErrorPath_) + const SelectionSetParams& parent, const path_segment&& ownErrorPath_) : resolverContext(parent.resolverContext) , state(parent.state) , operationDirectives(parent.operationDirectives) @@ -305,7 +323,7 @@ struct ResolverParams : SelectionSetParams const FragmentMap& fragments, const response::Value& variables); GRAPHQLSERVICE_EXPORT explicit ResolverParams( - const ResolverParams& parent, field_path&& ownErrorPath); + const ResolverParams& parent, const path_segment&& ownErrorPath); GRAPHQLSERVICE_EXPORT schema_location getLocation() const; @@ -731,7 +749,7 @@ struct ModifiedResult // Copy the values from the std::vector<> rather than moving them. for (typename vector_type::value_type entry : wrappedResult) { - auto itemParams = ResolverParams(wrappedParams, { { idx } }); + auto itemParams = ResolverParams(wrappedParams, { idx }); children[idx++] = ModifiedResult::convert(std::move(entry), std::move(itemParams)); } @@ -740,7 +758,7 @@ struct ModifiedResult { for (auto& entry : wrappedResult) { - auto itemParams = ResolverParams(wrappedParams, { { idx } }); + auto itemParams = ResolverParams(wrappedParams, { idx }); children[idx++] = ModifiedResult::convert(std::move(entry), std::move(itemParams)); } diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index 46a9dce1..fb755c56 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -403,7 +403,7 @@ ResolverParams::ResolverParams(const SelectionSetParams& selectionSetParams, { } -ResolverParams::ResolverParams(const ResolverParams& parent, field_path&& ownErrorPath_) +ResolverParams::ResolverParams(const ResolverParams& parent, const path_segment&& ownErrorPath_) : SelectionSetParams(parent, std::move(ownErrorPath_)) , field(parent.field) , fieldName(parent.fieldName) @@ -910,7 +910,7 @@ void SelectionVisitor::visitField(const peg::ast_node& field) _fragmentDirectives.top().fragmentSpreadDirectives, _fragmentDirectives.top().inlineFragmentDirectives, _selectionSetParams, - { { aliasView } }, + { aliasView }, _selectionSetParams.launch, };