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 02dd608d..cd6fd367 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); + + 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/include/Validation.h b/include/Validation.h index 44a9158e..cea1ab23 100644 --- a/include/Validation.h +++ b/include/Validation.h @@ -6,48 +6,27 @@ #ifndef VALIDATION_H #define VALIDATION_H -#include "graphqlservice/GraphQLService.h" -#include "graphqlservice/IntrospectionSchema.h" +#include "graphqlservice/GraphQLValidation.h" namespace graphql::service { -using ValidateType = response::Value; - -struct ValidateArgument -{ - bool defaultValue = false; - bool nonNullDefaultValue = false; - ValidateType type; -}; - -using ValidateTypeFieldArguments = std::map; - -struct ValidateTypeField -{ - ValidateType returnType; - ValidateTypeFieldArguments arguments; -}; - -using ValidateDirectiveArguments = std::map; - -struct ValidateDirective +struct VariableDefinition : public ValidateArgument { - std::set locations; - ValidateDirectiveArguments arguments; + schema_location position; }; struct ValidateArgumentVariable { bool operator==(const ValidateArgumentVariable& other) const; - std::string name; + const std::string_view name; }; struct ValidateArgumentEnumValue { bool operator==(const ValidateArgumentEnumValue& other) const; - std::string value; + const std::string_view value; }; struct ValidateArgumentValue; @@ -71,7 +50,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::string&& returnType, std::optional&& objectType, - const std::string& fieldName, ValidateFieldArguments&& arguments); + ValidateField(std::shared_ptr returnType, + std::shared_ptr&& objectType, const std::string_view& fieldName, + ValidateFieldArguments&& arguments); bool operator==(const ValidateField& other) const; - std::string returnType; - std::optional objectType; - std::string fieldName; + std::shared_ptr returnType; + std::shared_ptr objectType; + std::string_view 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; }; // ValidateExecutableVisitor visits the AST and validates that it is executable against the service @@ -163,38 +142,14 @@ class ValidateVariableTypeVisitor class ValidateExecutableVisitor { public: - ValidateExecutableVisitor(const Request& service); + ValidateExecutableVisitor(const ValidationContext& validationContext); void visit(const peg::ast_node& root); std::vector getStructuredErrors(); private: - response::Value executeQuery(std::string_view query) const; - - static ValidateTypeFieldArguments getArguments(response::ListType&& argumentsMember); - - 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); - - bool matchesScopedType(const std::string& name) const; - - TypeFields::const_iterator getScopedTypeFields(); - InputTypeFields::const_iterator getInputTypeFields(const std::string& name); - 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); + bool matchesScopedType(const ValidateType& name) const; void visitFragmentDefinition(const peg::ast_node& fragmentDefinition); void visitOperationDefinition(const peg::ast_node& operationDefinition); @@ -213,45 +168,33 @@ class ValidateExecutableVisitor bool validateVariableType(bool isNonNull, const ValidateType& variableType, const schema_location& position, const ValidateType& inputType); - const Request& _service; + const ValidationContext& _validationContext; + std::vector _errors; - 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 ExecutableNodes = std::unordered_map; + using FragmentSet = std::unordered_set; + using VariableTypes = std::unordered_map; using OperationVariables = std::optional; - using VariableSet = 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; + 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. ExecutableNodes _fragmentDefinitions; - ExecutableNodes _operationDefinitions; FragmentSet _referencedFragments; FragmentSet _fragmentCycles; // 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; - TypeFields _typeFields; - InputTypeFields _inputTypeFields; - std::string _scopedType; - std::map _selectionFields; + std::shared_ptr _scopedType; + std::unordered_map _selectionFields; + struct + { + std::shared_ptr nonNullString; + } commonTypes; }; } /* namespace graphql::service */ diff --git a/include/graphqlservice/GraphQLError.h b/include/graphqlservice/GraphQLError.h new file mode 100644 index 00000000..fd66b5d7 --- /dev/null +++ b/include/graphqlservice/GraphQLError.h @@ -0,0 +1,91 @@ +// 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 +#include +#include + +namespace graphql::error { + +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; + } +}; + +constexpr schema_location emptyLocation {}; + +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 +{ + 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. +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/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/GraphQLResponse.h b/include/graphqlservice/GraphQLResponse.h index 91ec497a..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>, @@ -194,10 +210,49 @@ struct Value } private: - const Type _type; 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 <> @@ -225,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(); @@ -232,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 b8d2add7..3160fa59 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; }; @@ -105,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 }; @@ -143,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_, + const path_segment&& ownErrorPath_, const std::launch launch_ = std::launch::deferred) + : resolverContext(resolverContext_) + , state(state_) + , operationDirectives(operationDirectives_) + , fragmentDefinitionDirectives(fragmentDefinitionDirectives_) + , fragmentSpreadDirectives(fragmentSpreadDirectives_) + , inlineFragmentDirectives(inlineFragmentDirectives_) + , parent(parent_) + , ownErrorPath(std::move(ownErrorPath_)) + , launch(launch_) + { + } + // Context for this selection set. const ResolverContext resolverContext; @@ -159,10 +160,59 @@ struct SelectionSetParams const response::Value& inlineFragmentDirectives; // Field error path to this selection set. - field_path errorPath; + std::optional> parent; + const path_segment ownErrorPath; + + field_path errorPath() const + { + if (!parent) + { + 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(); + result.push_back(ownErrorPath); + + return result; + } // Async launch policy for sub-field resolvers. const std::launch launch = std::launch::deferred; + + GRAPHQLSERVICE_EXPORT SelectionSetParams( + const SelectionSetParams& parent, const path_segment&& 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. @@ -200,6 +250,41 @@ 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(future.get()); + }, + std::move(std::get>(std::move(_value)))); + } + + std::promise promise; + promise.set_value(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(converter(future.get())); + }, + std::move(std::get>(std::move(_value)))); + } + + std::promise promise; + promise.set_value(response::Value(converter(std::get(std::move(_value))))); + return promise.get_future(); + } + private: std::variant> _value; }; @@ -233,15 +318,18 @@ 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); + GRAPHQLSERVICE_EXPORT explicit ResolverParams( + const ResolverParams& parent, const path_segment&& ownErrorPath); + GRAPHQLSERVICE_EXPORT schema_location getLocation() const; // 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; @@ -554,7 +642,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()); }, @@ -583,17 +671,13 @@ struct ModifiedResult ResolverParams&& params) { return std::async( - params.launch, + std::launch::deferred, [](auto&& wrappedFuture, ResolverParams&& wrappedParams) { auto wrappedResult = wrappedFuture.get(); if (!wrappedResult) { - response::Value document(response::Type::Map); - - document.emplace_back(std::string { strData }, response::Value()); - - return document; + return response::Value(); } std::promise::type> promise; @@ -621,17 +705,13 @@ struct ModifiedResult "this is the optional version"); return std::async( - params.launch, + std::launch::deferred, [](auto&& wrappedFuture, ResolverParams&& wrappedParams) { auto wrappedResult = wrappedFuture.get(); if (!wrappedResult) { - response::Value document(response::Type::Map); - - document.emplace_back(std::string { strData }, response::Value()); - - return document; + return response::Value(); } std::promise::type> promise; @@ -653,12 +733,11 @@ struct ModifiedResult ResolverParams&& params) { return std::async( - params.launch, - [](auto&& wrappedFuture, ResolverParams&& wrappedParams) { + std::launch::deferred, + [](auto&& wrappedFuture, const ResolverParams&& wrappedParams) { auto wrappedResult = wrappedFuture.get(); - std::queue> children; - - wrappedParams.errorPath.push(size_t { 0 }); + std::vector> children(wrappedResult.size()); + size_t idx = 0; using vector_type = std::decay_t; @@ -670,70 +749,64 @@ 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))); - ++std::get(wrappedParams.errorPath.back()); + auto itemParams = ResolverParams(wrappedParams, { idx }); + children[idx++] = ModifiedResult::convert(std::move(entry), + std::move(itemParams)); } } else { for (auto& entry : wrappedResult) { - children.push(ModifiedResult::convert(std::move(entry), - ResolverParams(wrappedParams))); - ++std::get(wrappedParams.errorPath.back()); + auto itemParams = ResolverParams(wrappedParams, { idx }); + children[idx++] = ModifiedResult::convert(std::move(entry), + std::move(itemParams)); } } response::Value data(response::Type::List); - response::Value errors(response::Type::List); + std::vector errors; - wrappedParams.errorPath.back() = size_t { 0 }; + data.reserve(children.size()); - while (!children.empty()) + idx = 0; + for (auto& future : children) { try { - auto value = children.front().get(); - auto members = value.release(); - - for (auto& entry : members) + auto value = future.get(); + if (value.type() == response::Type::Result) { - if (entry.second.type() == response::Type::List - && entry.first == strErrors) + auto result = value.release(); + if (result.errors.size()) { - auto errorEntries = entry.second.release(); - - for (auto& errorEntry : errorEntries) + errors.reserve(errors.size() + result.errors.size()); + for (auto& error : result.errors) { - errors.emplace_back(std::move(errorEntry)); + errors.emplace_back(std::move(error)); } } - else if (entry.first == strData) - { - data.emplace_back(std::move(entry.second)); - } + value = std::move(result.data); } + + data.emplace_back(std::move(value)); } 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 = wrappedParams.errorPath(); + error.path.push({ idx }); + } errors.emplace_back(std::move(error)); } } @@ -745,31 +818,22 @@ 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(); + path.push({ idx }); + schema_error error { message.str(), std::move(location), std::move(path) }; errors.emplace_back(std::move(error)); } - children.pop(); - ++std::get(wrappedParams.errorPath.back()); + idx++; } - response::Value document(response::Type::Map); - - document.reserve(2); - document.emplace_back(std::string { strData }, std::move(data)); - - if (errors.size() > 0) + if (errors.size() == 0) { - document.emplace_back(std::string { strErrors }, std::move(errors)); + return std::move(data); } - return document; + return response::Value(response::ResultType { std::move(data), std::move(errors) }); }, std::move(result), std::move(params)); @@ -789,33 +853,13 @@ struct ModifiedResult [](auto&& resultFuture, ResolverParams&& paramsFuture, ResolverCallback&& resolverFuture) noexcept { - response::Value data; - response::Value errors(response::Type::List); - 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& message : 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)); - } + return response::Value(response::ResultType { {}, scx.getStructuredErrors() }); } catch (const std::exception& ex) { @@ -824,27 +868,11 @@ 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); - - errors.emplace_back(std::move(error)); + return response::Value(response::ResultType { {}, + { { message.str(), + paramsFuture.getLocation(), + paramsFuture.errorPath() } } }); } - - 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; }, std::move(result), std::move(params), @@ -888,7 +916,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 @@ -949,6 +977,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 @@ -957,13 +986,21 @@ class Request : public std::enable_shared_from_this { protected: GRAPHQLSERVICE_EXPORT explicit Request(TypeMap&& operationTypes); + GRAPHQLSERVICE_EXPORT explicit Request( + TypeMap&& operationTypes, std::unique_ptr&& validationContext); GRAPHQLSERVICE_EXPORT virtual ~Request(); 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, @@ -1028,6 +1065,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 new file mode 100644 index 00000000..1950de7a --- /dev/null +++ b/include/graphqlservice/GraphQLValidation.h @@ -0,0 +1,574 @@ +// 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/GraphQLResponse.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_view _name; + + NamedValidateType(const std::string_view& name) + : _name(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 + { + return _values.find(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 + { + 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) + { + } + + 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: + struct OperationTypes + { + 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 const std::string_view 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; + } + +protected: + template ::value>::type* = nullptr> + std::shared_ptr 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; + } + + template ::value>::type* = nullptr> + 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> + 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) + { + 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> + 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 }); + } + + 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 Request; + +class IntrospectionValidationContext : public ValidationContext +{ +public: + IntrospectionValidationContext(const Request& service); + IntrospectionValidationContext(response::Value&& introspectionQuery); + +private: + response::Value _introspectionQuery; + void populate(); + + struct + { + std::shared_ptr string; + std::shared_ptr nonNullString; + } commonTypes; + + ValidateTypeFieldArguments getArguments(const response::ListType& argumentsMember); + + std::shared_ptr getTypeFromMap(const response::Value& typeMap); + + 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/samples/CMakeLists.txt b/samples/CMakeLists.txt index 86428af9..2f6a1110 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -111,12 +111,22 @@ 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} 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() @@ -141,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 5a578bb4..791305ff 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 @@ -554,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")); @@ -679,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/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/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 4eddca98..1d0de514 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,18 +91,325 @@ 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" }); + + +#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", + "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" + } }); +#endif + auto typeTaskState = makeNamedValidateType(service::EnumType { "TaskState", { + "New", + "Started", + "Complete", + "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" }); + 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" }); + + +#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(), + typeFolder.get() + }); + typeUnionType->setFields({ + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif + typeNode->setPossibleTypes({ + typeAppointment.get(), + typeTask.get(), + typeFolder.get() + }); + typeNode->setFields({ + { "id", { makeNonNullOfType(typeID), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + 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), { } } } + }); +#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 } } } } }, + { "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))), { } } } +#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), { } } }, + { "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), { } } }, + { "forceError", { typeString, { } } }, + { "__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 = { +#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 } } } } }, + { "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)) { } +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION void AddTypesToSchema(const std::shared_ptr& schema) { schema->AddType("ItemCursor", std::make_shared("ItemCursor", R"md()md")); @@ -225,6 +533,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/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/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 325b73ae..2264b0d6 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 @@ -95,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)); } }, @@ -109,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 @@ -298,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)); @@ -309,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({ @@ -760,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)); } }, @@ -824,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)); @@ -1026,18 +1050,325 @@ 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" }); + + +#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", + "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" + } }); +#endif + auto typeTaskState = makeNamedValidateType(service::EnumType { "TaskState", { + "New", + "Started", + "Complete", + "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" }); + 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" }); + + +#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(), + typeFolder.get() + }); + typeUnionType->setFields({ + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif + typeNode->setPossibleTypes({ + typeAppointment.get(), + typeTask.get(), + typeFolder.get() + }); + typeNode->setFields({ + { "id", { makeNonNullOfType(typeID), { } } }, + { "__typename", { makeNonNullOfType(typeString), { } } } + }); + + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + 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), { } } } + }); +#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 } } } } }, + { "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))), { } } } +#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), { } } }, + { "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), { } } }, + { "forceError", { typeString, { } } }, + { "__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 = { +#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 } } } } }, + { "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)) { } +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION void AddTypesToSchema(const std::shared_ptr& schema) { schema->AddType("ItemCursor", std::make_shared("ItemCursor", R"md()md")); @@ -1194,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 @@ -1258,6 +1590,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..c624c812 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 @@ -283,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); }; @@ -379,7 +383,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 19f5157f..0eeb05d6 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 @@ -114,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)); } }, @@ -125,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 @@ -236,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)); @@ -247,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({ @@ -832,18 +841,308 @@ 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" }); + + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif + + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + 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" + } }); +#endif + auto typeDogCommand = makeNamedValidateType(service::EnumType { "DogCommand", { + "SIT", + "DOWN", + "HEEL" + } }); + auto typeCatCommand = makeNamedValidateType(service::EnumType { "CatCommand", { + "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" }); + 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" }); + + +#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() + }); + 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), { } } } + }); + + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +#endif + 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), { } } } + }); + + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION + 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), { } } } + }); +#endif + 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 } } } } } +#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), { } } }, + { "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 = { +#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 + + }; + + _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)) { } +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION void AddTypesToSchema(const std::shared_ptr& schema) { auto typeDogCommand = std::make_shared("DogCommand", R"md()md"); @@ -1016,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/CMakeLists.txt b/src/CMakeLists.txt index 23617111..50604153 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) @@ -150,6 +164,7 @@ endif() # graphqlservice add_library(graphqlservice GraphQLService.cpp + GraphQLValidation.cpp Introspection.cpp Validation.cpp ${CMAKE_CURRENT_BINARY_DIR}/../IntrospectionSchema.cpp) @@ -157,6 +172,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 +227,7 @@ endif() install(TARGETS graphqlpeg + graphqlerror graphqlresponse graphqlservice EXPORT cppgraphqlgen-targets @@ -220,8 +237,10 @@ 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/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/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/GraphQLResponse.cpp b/src/GraphQLResponse.cpp index 14748f84..55f4d6d9 100644 --- a/src/GraphQLResponse.cpp +++ b/src/GraphQLResponse.cpp @@ -9,93 +9,761 @@ 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 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"); + } + + 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"); + } + + 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) + { + 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 +{ + MapData() + : map(std::make_shared()) + , members(std::make_shared>()) + { + } + + Type type() const noexcept final + { + return Type::Map; + } + + bool operator==(const TypedData& rhs) const noexcept final + { + if (typeid(*this).name() != typeid(rhs).name()) + { + return false; + } + + 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 + { + 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 + { + willModify(); + map->reserve(count); + members->reserve(count); + } + + size_t size() const final + { + if (!map) + { + return 0; + } + return map->size(); + } + + void emplace_back(std::string&& name, Value&& value) final + { + 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))); + } + + 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 + { + if (!map.unique()) + { + return MapType(*map); + } + + MapType result = std::move(*map); + + members->clear(); + + return result; + } + +private: + 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; + } + + bool operator==(const TypedData& rhs) const noexcept final + { + if (typeid(*this).name() != typeid(rhs).name()) + { + return false; + } + + 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 + { + return std::make_unique(*this); + } + + void willModify() + { + if (list.unique()) + { + return; + } + + list = std::make_shared(*list); + } + + void reserve(size_t count) final + { + willModify(); + list->reserve(count); + } + + size_t size() const final + { + if (!list) + { + return 0; + } + return list->size(); + } + + void emplace_back(Value&& value) final + { + willModify(); + 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 + { + if (!list.unique()) + { + return ListType(*list); + } + + auto result = std::move(*list); + + return result; + } + +private: + std::shared_ptr list; +}; + +struct CommonStringData : public TypedData { - bool operator==(const MapData& rhs) const + CommonStringData(std::string&& value = "") + : _value(std::make_shared(std::move(value))) + { + } + + Type type() const noexcept + { + return Type::String; + } + + void setString(StringType&& value) final + { + if (!_value.unique()) + { + _value = std::make_shared(std::move(value)); + } + else + { + _value->assign(std::move(value)); + } + } + + const StringType& getString() const final + { + return *_value; + } + + StringType releaseString() final + { + if (!_value.unique()) + { + return StringType(*_value); + } + + StringType result = std::move(*_value); + + return result; + } + + bool operator==(const TypedData& rhs) const noexcept final { - return map == rhs.map; + 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; } - MapType map; - std::unordered_map members; +protected: + std::shared_ptr _value; }; -// Type::List -struct ListData +struct StringData final : public CommonStringData +{ + StringData(std::string&& value = "") + : CommonStringData(std::move(value)) + { + } + + std::unique_ptr copy() const final + { + return std::make_unique(*this); + } +}; + +struct JSONStringData final : public CommonStringData { - bool operator==(const ListData& rhs) const + JSONStringData(std::string&& value = "") + : CommonStringData(std::move(value)) { - return list == rhs.list; } - ListType list; + std::unique_ptr copy() const final + { + return std::make_unique(*this); + } }; -// Type::String or Type::EnumValue -struct StringOrEnumData +struct EnumData final : public CommonStringData { - bool operator==(const StringOrEnumData& rhs) const + EnumData(std::string&& value = "") + : CommonStringData(std::move(value)) + { + } + + Type type() const noexcept final { - return string == rhs.string && from_json == rhs.from_json; + return Type::EnumValue; } - StringType string; - bool from_json = false; + std::unique_ptr copy() const final + { + return std::make_unique(*this); + } }; // Type::Scalar -struct ScalarData +struct ScalarData : public TypedData { - bool operator==(const ScalarData& rhs) const + 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 { - return scalar == rhs.scalar; + ScalarType result = std::move(scalar); + + return result; } +private: ScalarType scalar; }; -struct TypedData - : std::variant, std::optional, std::optional, - std::optional, BooleanType, IntType, FloatType> +// 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*/) - : _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; + case Type::Result: + _data = std::make_unique(); + default: break; } @@ -109,45 +777,42 @@ 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(ResultType&& value) + : _data(std::make_unique(std::move(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 +820,6 @@ Value& Value::operator=(Value&& rhs) noexcept { if (&rhs != this) { - const_cast(_type) = rhs._type; _data = std::move(rhs._data); } @@ -169,7 +833,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 +852,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 +901,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 +911,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 +931,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 +953,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 +974,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 +985,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 +1007,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 +1018,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 +1029,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 +1040,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 +1051,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 +1062,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 +1073,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 +1095,18 @@ 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 <> +const ResultType& Value::get() const +{ + if (type() != Type::Result) + { + throw std::logic_error("Invalid call to Value::get for ResultType"); + } + + return _data->getResult(); } template <> @@ -486,12 +1117,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 +1128,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 +1139,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 +1150,127 @@ ScalarType Value::release() throw std::logic_error("Invalid call to Value::release for ScalarType"); } - ScalarType result = std::move(std::get>(*_data)->scalar); + 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)))); + } + if (std::holds_alternative(segment)) + { + errorPath.emplace_back( + response::Value(std::string(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 result; + return errors; } } /* namespace graphql::response */ diff --git a/src/GraphQLService.cpp b/src/GraphQLService.cpp index bf064b35..fb755c56 100644 --- a/src/GraphQLService.cpp +++ b/src/GraphQLService.cpp @@ -9,144 +9,42 @@ #include #include #include +#include #include 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) - : _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; @@ -491,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) @@ -505,6 +403,18 @@ ResolverParams::ResolverParams(const SelectionSetParams& selectionSetParams, { } +ResolverParams::ResolverParams(const ResolverParams& parent, const path_segment&& 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(); @@ -734,7 +644,7 @@ void blockSubFields(const ResolverParams& params) throw schema_exception { { schema_error { error.str(), { position.line, position.column }, - { params.errorPath } } } }; + { params.errorPath() } } } }; } } @@ -744,11 +654,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 <> @@ -757,11 +663,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 <> @@ -770,11 +672,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 <> @@ -783,11 +681,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 <> @@ -796,11 +690,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 <> @@ -809,11 +699,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) @@ -828,7 +716,7 @@ void requireSubFields(const ResolverParams& params) throw schema_exception { { schema_error { error.str(), { position.line, position.column }, - { params.errorPath } } } }; + { params.errorPath() } } } }; } } @@ -845,12 +733,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(); } return wrappedResult @@ -886,36 +769,28 @@ class SelectionVisitor void visit(const peg::ast_node& selection); - std::queue>> getValues(); + std::list>> getValues(); private: void visitField(const peg::ast_node& field); 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; 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, 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) @@ -926,7 +801,7 @@ SelectionVisitor::SelectionVisitor(const SelectionSetParams& selectionSetParams, response::Value(response::Type::Map) }); } -std::queue>> SelectionVisitor::getValues() +std::list>> SelectionVisitor::getValues() { auto values = std::move(_values); @@ -951,24 +826,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 @@ -976,6 +846,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 {}), @@ -991,10 +863,12 @@ 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 } } } })); + promise.set_exception( + std::make_exception_ptr(schema_exception { { schema_error { error.str(), + { position.line, position.column }, + { _selectionSetParams.errorPath() } } } })); - _values.push({ std::move(alias), promise.get_future() }); + _values.push_back({ std::move(alias), promise.get_future() }); return; } @@ -1028,33 +902,30 @@ void SelectionVisitor::visitField(const peg::ast_node& field) selection = &child; }); - auto path = _path; - - path.push({ alias }); - 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 { auto result = itr->second(ResolverParams(selectionSetParams, field, - std::string(alias), + aliasView, std::move(arguments), directiveVisitor.getDirectives(), selection, _fragments, _variables)); - _values.push({ std::move(alias), std::move(result) }); + _values.push_back({ std::move(alias), std::move(result) }); } catch (schema_exception& scx) { @@ -1064,20 +935,20 @@ 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 }; } if (message.path.empty()) { - message.path = { selectionSetParams.errorPath }; + message.path = { selectionSetParams.errorPath() }; } } 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) { @@ -1090,9 +961,9 @@ 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({ std::move(alias), promise.get_future() }); + _values.push_back({ std::move(alias), promise.get_future() }); } } @@ -1108,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 }, { _path } } } - }; + throw schema_exception { { schema_error { error.str(), + { position.line, position.column }, + { _selectionSetParams.errorPath() } } } }; } bool skip = (_typeNames.count(itr->second.getType()) == 0); @@ -1230,7 +1101,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); @@ -1240,22 +1111,16 @@ 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); - response::Value errors(response::Type::List); + std::vector errors; while (!children.empty()) { @@ -1264,42 +1129,36 @@ std::future Object::resolve(const SelectionSetParams& selection try { auto value = children.front().second.get(); - auto members = value.release(); - - for (auto& entry : members) + if (value.type() == response::Type::Result) { - if (entry.second.type() == response::Type::List && entry.first == strErrors) + auto result = value.release(); + if (result.errors.size()) { - auto errorEntries = entry.second.release(); - - for (auto& errorEntry : errorEntries) + errors.reserve(errors.size() + result.errors.size()); + for (auto& error : result.errors) { - errors.emplace_back(std::move(errorEntry)); + errors.emplace_back(std::move(error)); } } - else if (entry.first == strData) - { - auto itrData = data.find(name); + value = std::move(result.data); + } - 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); + try + { + data.emplace_back(std::move(name), std::move(value)); + } + catch (std::runtime_error& e) + { + // Duplicate Map member + std::ostringstream message; - message << "Ambiguous field error name: " << name; - addErrorMessage(message.str(), error); - errors.emplace_back(std::move(error)); - } - } + 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) @@ -1307,10 +1166,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) { @@ -1318,30 +1181,27 @@ 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()) + try { data.emplace_back(std::move(name), {}); } + catch (std::runtime_error& e) + { + // Duplicate Map member + } } - children.pop(); + children.pop_front(); } - response::Value result(response::Type::Map); - - result.emplace_back(std::string { strData }, std::move(data)); - - if (errors.size() > 0) + if (errors.size() == 0) { - result.emplace_back(std::string { strErrors }, std::move(errors)); + return std::move(data); } - return result; + return response::Value(response::ResultType { std::move(data), std::move(errors) }); }, std::move(selections)); } @@ -1413,7 +1273,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; @@ -1442,7 +1302,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); @@ -1511,6 +1371,7 @@ void OperationDefinitionVisitor::visit( emptyFragmentDirectives, emptyFragmentDirectives, emptyFragmentDirectives, + std::nullopt, {}, selectionLaunch, }; @@ -1759,7 +1620,17 @@ void SubscriptionDefinitionVisitor::visitInlineFragment(const peg::ast_node& inl Request::Request(TypeMap&& operationTypes) : _operations(std::move(operationTypes)) - , _validation(std::make_unique(*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)) { } @@ -1785,92 +1656,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) - { - 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 } }); - } - - hasAnonymous = hasAnonymous || name.empty(); - - // http://spec.graphql.org/June2018/#sec-Lone-Anonymous-Operation - if (name.empty() ? usedNames.size() > 1 : hasAnonymous) + if (operationName.empty() || name == operationName) { - 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 } }); + result = { operationType, &operationDefinition }; + return true; } - 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; @@ -1917,7 +1739,20 @@ 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) { + 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))); } std::future Request::resolveValidated(std::launch launch, @@ -1951,7 +1786,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) { @@ -2036,7 +1872,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) { @@ -2110,6 +1947,7 @@ std::future Request::subscribe( emptyFragmentDirectives, emptyFragmentDirectives, emptyFragmentDirectives, + std::nullopt, {}, launch, }; @@ -2183,6 +2021,7 @@ std::future Request::unsubscribe(std::launch launch, SubscriptionKey key) emptyFragmentDirectives, emptyFragmentDirectives, emptyFragmentDirectives, + std::nullopt, {}, launch, }; @@ -2349,6 +2188,7 @@ void Request::deliver(std::launch launch, const SubscriptionName& name, emptyFragmentDirectives, emptyFragmentDirectives, emptyFragmentDirectives, + std::nullopt, {}, launch, }; @@ -2358,7 +2198,15 @@ void Request::deliver(std::launch launch, const SubscriptionName& name, result = std::async( launch, [registration](std::future document) { - return document.get(); + 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, diff --git a/src/GraphQLValidation.cpp b/src/GraphQLValidation.cpp new file mode 100644 index 00000000..adb69eda --- /dev/null +++ b/src/GraphQLValidation.cpp @@ -0,0 +1,548 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "graphqlservice/GraphQLValidation.h" + +namespace graphql::service { + +std::optional> ValidationContext::getDirective( + const std::string_view& name) const +{ + const auto& itr = _directives.find(name); + if (itr == _directives.cend()) + { + return std::nullopt; + } + return std::optional>(itr->second); +} + +const std::string_view ValidationContext::getOperationType(const std::string_view& name) const +{ + if (name == strQuery) + { + return _operationTypes.queryType; + } + if (name == strMutation) + { + return _operationTypes.mutationType; + } + if (name == strSubscription) + { + return _operationTypes.subscriptionType; + } + return ""; +} + +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"; + +IntrospectionValidationContext::IntrospectionValidationContext(const Request& service) +{ + 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; + response::Value variables(response::Type::Map); + _introspectionQuery = service.resolve(state, ast, "", std::move(variables)).get(); + + populate(); +} + +IntrospectionValidationContext::IntrospectionValidationContext(response::Value&& introspectionQuery) + : _introspectionQuery(std::move(introspectionQuery)) +{ + populate(); +} + +void IntrospectionValidationContext::populate() +{ + 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 IntrospectionValidationContext::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; +} + +void IntrospectionValidationContext::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 IntrospectionValidationContext::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 IntrospectionValidationContext::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 IntrospectionValidationContext::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 IntrospectionValidationContext::addObject(const std::string_view& name) +{ + makeNamedValidateType(ObjectType { name }); +} + +void IntrospectionValidationContext::addInputObject(const std::string_view& name) +{ + makeNamedValidateType(InputObjectType { name }); +} + +void IntrospectionValidationContext::addInterface( + const std::string_view& name, const response::Value& typeDescriptionMap) +{ + makeNamedValidateType(InterfaceType { name }); +} + +void IntrospectionValidationContext::addUnion( + const std::string_view& name, const response::Value& typeDescriptionMap) +{ + makeNamedValidateType(UnionType { name }); +} + +void IntrospectionValidationContext::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()); + } + + _directives[name] = std::move(directive); +} + +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) + { + 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 IntrospectionValidationContext::addScalar(const std::string_view& scalarName) +{ + makeNamedValidateType(ScalarType { scalarName }); +} + +} /* namespace graphql::service */ diff --git a/src/SchemaGenerator.cpp b/src/SchemaGenerator.cpp index b9ab1db1..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"; } } @@ -2066,6 +2070,7 @@ bool Generator::outputSource() const noexcept } sourceFile << R"cpp(#include "graphqlservice/Introspection.h" +#include "graphqlservice/GraphQLValidation.h" #include #include @@ -2281,6 +2286,8 @@ std::future ModifiedResult<)cpp" { bool firstOperation = true; + outputValidationContext(sourceFile); + sourceFile << R"cpp( Operations::Operations()cpp"; @@ -2316,7 +2323,7 @@ Operations::Operations()cpp"; } sourceFile << R"cpp( - }) + }, std::make_unique()) )cpp"; for (const auto& operation : _operationTypes) @@ -2336,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"; @@ -2532,8 +2540,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) @@ -2743,12 +2750,677 @@ Operations::Operations()cpp"; } sourceFile << R"cpp(} +#endif )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); + 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); + } + + if (!foundSchema) + { + 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) + { + 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 }, + "", + 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, + std::nullopt, + false, + false, + { strGet } }); + } + + sourceFile << R"cpp( + }); +)cpp"; + } + } +} + +void Generator::outputValidationDirectiveList( + std::ostream& sourceFile, const DirectiveList& directives) +{ + if (directives.empty()) + { + return; + } + + bool firstDirective = true; + + 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 << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; + outputValidationScalarsList(sourceFile, introspectionGenerator.GetScalarTypes()); + sourceFile << R"cpp(#endif +)cpp"; + outputValidationScalarsList(sourceFile, _scalarTypes); + + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; + outputValidationEnumsList(sourceFile, introspectionGenerator.GetEnumTypes()); + sourceFile << R"cpp(#endif +)cpp"; + outputValidationEnumsList(sourceFile, _enumTypes); + + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; + outputValidationInputTypeList(sourceFile, introspectionGenerator.GetInputTypes()); + sourceFile << R"cpp(#endif +)cpp"; + outputValidationInputTypeList(sourceFile, _inputTypes); + + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; + outputValidationUnionTypeList(sourceFile, introspectionGenerator.GetUnionTypes()); + sourceFile << R"cpp(#endif +)cpp"; + outputValidationUnionTypeList(sourceFile, _unionTypes); + + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; + outputValidationInterfaceTypeList(sourceFile, introspectionGenerator.GetInterfaceTypes()); + sourceFile << R"cpp(#endif +)cpp"; + outputValidationInterfaceTypeList(sourceFile, _interfaceTypes); + + 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 << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; + outputValidationInputTypeListSetFields(sourceFile, introspectionGenerator.GetInputTypes()); + sourceFile << R"cpp(#endif +)cpp"; + outputValidationInputTypeListSetFields(sourceFile, _inputTypes); + + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; + outputValidationUnionTypeListSetFieldsAndPossibleTypes(sourceFile, + introspectionGenerator.GetUnionTypes()); + sourceFile << R"cpp(#endif +)cpp"; + outputValidationUnionTypeListSetFieldsAndPossibleTypes(sourceFile, _unionTypes); + + sourceFile << R"cpp( + +#ifndef SCHEMAGEN_DISABLE_INTROSPECTION +)cpp"; + outputValidationInterfaceTypeListSetFieldsAndPossibleTypes(sourceFile, + introspectionGenerator.GetInterfaceTypes(), + interfacePossibleTypes); + sourceFile << R"cpp(#endif +)cpp"; + outputValidationInterfaceTypeListSetFieldsAndPossibleTypes(sourceFile, + _interfaceTypes, + interfacePossibleTypes); + + 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"; + outputValidationDirectiveList(sourceFile, introspectionGenerator.GetDirectives()); + sourceFile << R"cpp(, +#endif +)cpp"; + outputValidationDirectiveList(sourceFile, _directives); + + 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 { @@ -2802,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; @@ -2828,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( @@ -2838,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"; } @@ -2968,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) { @@ -2983,6 +3668,7 @@ std::future )cpp" << s_introspectionNamespace << R"cpp(::object::Type>::convert(_schema->LookupType(argName), std::move(params)); } +#endif )cpp"; } } @@ -3229,6 +3915,10 @@ std::string Generator::getArgumentDefaultValue( )cpp"; break; } + + default: + // should never reach + std::cerr << "Unexpected reponse type: " << (int)defaultValue.type() << std::endl; } return argumentDefaultValue.str(); @@ -3435,6 +4125,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; diff --git a/src/Validation.cpp b/src/Validation.cpp index b25a2dbe..19eaa6da 100644 --- a/src/Validation.cpp +++ b/src/Validation.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace graphql::service { @@ -130,7 +131,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)); @@ -183,7 +184,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)); @@ -216,7 +217,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()) { @@ -240,9 +241,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_view& fieldName, + ValidateFieldArguments&& arguments) + : returnType(returnType) , objectType(std::move(objectType)) , fieldName(fieldName) , arguments(std::move(arguments)) @@ -251,17 +253,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); return result; } -ValidateExecutableVisitor::ValidateExecutableVisitor(const Request& service) - : _service(service) +ValidateExecutableVisitor::ValidateExecutableVisitor(const ValidationContext& validationContext) + : _validationContext(validationContext) { - 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) - { - 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() - && itrType->second.type() == response::Type::String) - { - if (member.first == R"gql(queryType)gql") - { - _operationTypes[strQuery] = itrType->second.release(); - } - else if (member.first == R"gql(mutationType)gql") - { - _operationTypes[strMutation] = - itrType->second.release(); - } - else if (member.first == R"gql(subscriptionType)gql") - { - _operationTypes[strSubscription] = - itrType->second.release(); - } - } - } - else if (member.second.type() == response::Type::List - && member.first == R"gql(types)gql") - { - auto entries = member.second.release(); - - for (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"; - }); - - if (itrName != typeMembers.end() - && itrName->second.type() == response::Type::String - && itrKind != typeMembers.end() - && itrKind->second.type() == response::Type::EnumValue) - { - auto name = itrName->second.release(); - auto kind = - ModifiedArgument::convert(itrKind->second); - - if (!isScalarType(kind)) - { - if (kind == introspection::TypeKind::OBJECT) - { - _matchingTypes[name].insert(name); - } - 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() - && itrPossibleTypes->second.type() == response::Type::List) - { - std::set matchingTypes; - auto matchingTypeEntries = - itrPossibleTypes->second.release(); - - for (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() - && itrMatchingTypeName->second.type() - == response::Type::String) - { - matchingTypes.insert( - itrMatchingTypeName->second - .release()); - } - } - - if (!matchingTypes.empty()) - { - _matchingTypes[name] = std::move(matchingTypes); - } - } - } - } - 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() - && itrEnumValues->second.type() == response::Type::List) - { - std::set enumValues; - auto enumValuesEntries = - itrEnumValues->second.release(); - - for (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() - && itrEnumValuesName->second.type() - == response::Type::String) - { - enumValues.insert(itrEnumValuesName->second - .release()); - } - } - - if (!enumValues.empty()) - { - _enumValues[name] = std::move(enumValues); - } - } - } - else if (kind == introspection::TypeKind::SCALAR) - { - _scalarTypes.insert(name); - } - - _typeKinds[std::move(name)] = kind; - } - } - } - else if (member.second.type() == response::Type::List - && member.first == R"gql(directives)gql") - { - auto entries = member.second.release(); - - for (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"; - }); - - if (itrName != directiveMembers.end() - && itrName->second.type() == response::Type::String - && itrLocations != directiveMembers.end() - && itrLocations->second.type() == response::Type::List) - { - ValidateDirective directive; - auto locations = itrLocations->second.release(); - - for (const auto& location : locations) - { - if (location.type() != response::Type::EnumValue) - { - continue; - } - - directive.locations.insert( - ModifiedArgument::convert( - 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() - && itrArgs->second.type() == response::Type::List) - { - directive.arguments = - getArguments(itrArgs->second.release()); - } - - _directives[itrName->second.release()] = - std::move(directive); - } - } - } - } - } -} - -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; + commonTypes.nonNullString = _validationContext.getNonNullOfType( + _validationContext.getNamedValidateType("String")); } void ValidateExecutableVisitor::visit(const peg::ast_node& root) @@ -683,7 +328,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) { @@ -697,18 +342,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) { @@ -716,28 +364,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 } }); } } @@ -776,7 +421,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; @@ -794,7 +439,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(); @@ -807,23 +451,22 @@ 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]; - auto innerType = typeCondition->children.front()->string(); - - auto itrKind = _typeKinds.find(innerType); + const auto& innerTypeName = typeCondition->children.front()->string_view(); + const auto& innerType = _validationContext.getNamedValidateType(innerTypeName); - if (itrKind == _typeKinds.end() || isScalarType(itrKind->second)) + 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 << (itrKind == _typeKinds.end() ? "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; @@ -834,7 +477,7 @@ void ValidateExecutableVisitor::visitFragmentDefinition(const peg::ast_node& fra visitSelection(selection); - _scopedType.clear(); + _scopedType.reset(); _fragmentStack.clear(); _selectionFields.clear(); } @@ -848,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) { @@ -859,8 +502,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) { @@ -891,7 +534,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); visitor.visit(*child); @@ -924,7 +567,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(); @@ -948,8 +591,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, @@ -968,9 +612,8 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op visitDirectives(location, child); }); - auto itrType = _operationTypes.find(operationType); - - if (itrType == _operationTypes.cend()) + const auto& type = _validationContext.getOperationType(operationType); + if (type.empty()) { auto position = operationDefinition.begin(); std::ostringstream error; @@ -981,7 +624,7 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op return; } - _scopedType = itrType->second; + _scopedType = _validationContext.getNamedValidateType(type); _fieldCount = 0; const auto& selection = *operationDefinition.children.back(); @@ -1004,16 +647,16 @@ 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(); - 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; @@ -1023,7 +666,6 @@ void ValidateExecutableVisitor::visitOperationDefinition(const peg::ast_node& op } _operationVariables.reset(); - _variableDefinitions.clear(); _referencedVariables.clear(); } @@ -1046,107 +688,24 @@ void ValidateExecutableVisitor::visitSelection(const peg::ast_node& selection) } } -ValidateTypeFieldArguments ValidateExecutableVisitor::getArguments(response::ListType&& args) -{ - ValidateTypeFieldArguments result; - - for (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"; - }); - - if (itrName != members.end() && itrName->second.type() == response::Type::String - && itrType != members.end() && itrType->second.type() == response::Type::Map) - { - ValidateArgument argument; - - argument.defaultValue = (itrDefaultValue != members.end() - && itrDefaultValue->second.type() == response::Type::String); - argument.nonNullDefaultValue = argument.defaultValue - && itrDefaultValue->second.get() != R"gql(null)gql"; - argument.type = std::move(itrType->second); - - result[itrName->second.release()] = std::move(argument); - } - } - - return result; -} - -std::optional ValidateExecutableVisitor::getTypeKind( - const std::string& name) const -{ - auto itrKind = _typeKinds.find(name); - - return (itrKind == _typeKinds.cend() ? std::nullopt : std::make_optional(itrKind->second)); -} - -std::optional ValidateExecutableVisitor::getScopedTypeKind() const -{ - return getTypeKind(_scopedType); -} - -constexpr bool ValidateExecutableVisitor::isScalarType(introspection::TypeKind kind) +bool ValidateExecutableVisitor::matchesScopedType(const ValidateType& otherType) const { - switch (kind) + switch (_scopedType->kind()) { - case introspection::TypeKind::OBJECT: case introspection::TypeKind::INTERFACE: + case introspection::TypeKind::OBJECT: case introspection::TypeKind::UNION: - return false; - + return static_cast&>(*_scopedType) + .matchesType(otherType); default: - return true; - } -} - -bool ValidateExecutableVisitor::matchesScopedType(const std::string& name) const -{ - if (name == _scopedType) - { - return true; - } - - auto itrScoped = _matchingTypes.find(_scopedType); - auto itrNamed = _matchingTypes.find(name); - - if (itrScoped != _matchingTypes.end() && itrNamed != _matchingTypes.end()) - { - 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(); - }); - - return itrMatch != itrScoped->second.end(); + 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; @@ -1170,11 +729,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); } @@ -1188,20 +748,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; @@ -1210,20 +760,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: @@ -1234,9 +782,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; @@ -1245,7 +792,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; @@ -1257,16 +804,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; @@ -1277,27 +821,16 @@ bool ValidateExecutableVisitor::validateInputValue( return false; } - auto itrFields = getInputTypeFields(name); - - if (itrFields == _inputTypeFields.end()) - { - std::ostringstream message; - - message << "Expected Input Object fields name: " << name; - - _errors.push_back({ message.str(), argument.position }); - return false; - } + 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) { - auto itrField = itrFields->second.find(entry.first); - - if (itrField == itrFields->second.end()) + const auto& fieldOpt = inputObj.getField(entry.first); + if (!fieldOpt) { // http://spec.graphql.org/June2018/#sec-Input-Object-Field-Names std::ostringstream message; @@ -1309,11 +842,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. @@ -1325,30 +858,14 @@ bool ValidateExecutableVisitor::validateInputValue( } // See if all required fields were specified. - for (const auto& entry : itrFields->second) + 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 @@ -1367,16 +884,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; @@ -1388,10 +902,7 @@ bool ValidateExecutableVisitor::validateInputValue( } const auto& value = std::get(argument.value->data).value; - auto itrEnumValues = _enumValues.find(name); - - if (itrEnumValues == _enumValues.end() - || itrEnumValues->second.find(value) == itrEnumValues->second.end()) + if (!static_cast(type).find(value)) { std::ostringstream message; @@ -1406,16 +917,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)) @@ -1470,16 +978,6 @@ bool ValidateExecutableVisitor::validateInputValue( } } - if (_scalarTypes.find(name) == _scalarTypes.end()) - { - std::ostringstream message; - - message << "Undefined Scalar type name: " << name; - - _errors.push_back({ message.str(), argument.position }); - return false; - } - return true; } @@ -1495,53 +993,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: @@ -1554,16 +1019,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: @@ -1576,28 +1039,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: @@ -1644,27 +1101,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 @@ -1679,398 +1129,46 @@ bool ValidateExecutableVisitor::validateVariableType(bool isNonNull, return true; } -ValidateExecutableVisitor::TypeFields::const_iterator ValidateExecutableVisitor:: - getScopedTypeFields() -{ - auto typeKind = getScopedTypeKind(); - auto 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()); - 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) - { - 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"); - - 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) - { - auto entries = itrResponse->second.release(); - - for (auto& entry : entries) - { - if (entry.type() != response::Type::Map) - { - continue; - } - - members = entry.release(); - - 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() - && itrFieldName->second.type() == response::Type::String - && itrFieldType != members.end() - && itrFieldType->second.type() == response::Type::Map) - { - auto fieldName = itrFieldName->second.release(); - 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"; - }); - - if (itrArgs != members.end() - && itrArgs->second.type() == response::Type::List) - { - subField.arguments = - getArguments(itrArgs->second.release()); - } - - fields[std::move(fieldName)] = std::move(subField); - } - } - - if (_scopedType == _operationTypes[strQuery]) - { - 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); - - 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); - } - } - - 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); - - itrType = _typeFields.insert({ _scopedType, std::move(fields) }).first; - } - } - - return itrType; -} - -ValidateExecutableVisitor::InputTypeFields::const_iterator ValidateExecutableVisitor:: - getInputTypeFields(const std::string& name) -{ - auto typeKind = getTypeKind(name); - auto 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()); - 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) - { - 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) - { - itrType = _inputTypeFields - .insert({ name, - getArguments(itrResponse->second.release()) }) - .first; - } - } - } - - return itrType; -} - -template -std::string ValidateExecutableVisitor::getFieldType( - const _FieldTypes& fields, const std::string& name) -{ - std::string result; - auto itrType = fields.find(name); - - if (itrType == fields.end()) - { - return result; - } - - // 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& ValidateExecutableVisitor::getValidateFieldType( - const FieldTypes::mapped_type& value) -{ - return value.returnType; -} - -const ValidateType& ValidateExecutableVisitor::getValidateFieldType( - const InputFieldTypes::mapped_type& value) -{ - return value.type; -} - -template -std::string ValidateExecutableVisitor::getWrappedFieldType( - const _FieldTypes& fields, const std::string& name) -{ - std::string result; - auto itrType = fields.find(name); - - if (itrType == fields.end()) - { - return result; - } - - result = getWrappedFieldType(getValidateFieldType(itrType->second)); - - return result; -} - -std::string ValidateExecutableVisitor::getWrappedFieldType(const ValidateType& returnType) -{ - // 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) - { - return itrName->second.get(); - } - - 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) - { - switch (ModifiedArgument::convert(itrKind->second)) - { - case introspection::TypeKind::LIST: - oss << '[' << getWrappedFieldType(itrOfType->second) << ']'; - break; - - case introspection::TypeKind::NON_NULL: - oss << getWrappedFieldType(itrOfType->second) << '!'; - break; - - default: - break; - } - } - - return oss.str(); -} - void ValidateExecutableVisitor::visitField(const peg::ast_node& field) { peg::on_first_child(field, [this](const peg::ast_node& child) { 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(); }); - 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; - auto itrType = getScopedTypeFields(); + std::shared_ptr wrappedType; + std::optional> objectFieldRef; - if (itrType == _typeFields.cend()) - { - // 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; - } - - 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 = getFieldType(itrType->second, name); - wrappedType = getWrappedFieldType(itrType->second, name); + objectFieldRef = + static_cast&>(*_scopedType) + .getField(name); + if (objectFieldRef) + { + wrappedType = objectFieldRef.value().get().returnType; + } break; } @@ -2082,35 +1180,41 @@ 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; } - std::string alias; + std::string_view alias; peg::on_first_child(field, [&alias](const peg::ast_node& child) { alias = child.string_view(); @@ -2122,15 +1226,15 @@ void ValidateExecutableVisitor::visitField(const peg::ast_node& field) } ValidateFieldArguments validateArguments; - std::map argumentLocations; - std::queue argumentNames; + std::unordered_map argumentLocations; + std::list 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()) @@ -2138,8 +1242,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; @@ -2150,13 +1254,13 @@ 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)); } }); - 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)); @@ -2175,37 +1279,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 } }); } } - auto itrField = itrType->second.find(name); - - if (itrField != itrType->second.end()) + if (objectFieldRef) { + const auto& objectField = objectFieldRef.value().get(); while (!argumentNames.empty()) { auto argumentName = std::move(argumentNames.front()); - argumentNames.pop(); + argumentNames.pop_front(); - 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(); @@ -2215,13 +1318,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] }); } @@ -2235,12 +1338,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(); @@ -2248,7 +1346,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 } }); } @@ -2273,11 +1371,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; @@ -2286,15 +1383,14 @@ 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& 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; @@ -2310,7 +1406,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()) @@ -2343,15 +1439,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; @@ -2376,46 +1474,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 itrKind = _typeKinds.find(innerType); - - if (itrKind == _typeKinds.end() || isScalarType(itrKind->second)) + 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 << (itrKind == _typeKinds.end() - ? "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; @@ -2437,11 +1535,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) { @@ -2460,9 +1558,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(); @@ -2474,7 +1571,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(); @@ -2521,15 +1619,15 @@ 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; + std::unordered_map argumentLocations; + std::list 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()) { @@ -2548,18 +1646,17 @@ 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_back(argumentName); } while (!argumentNames.empty()) { auto argumentName = std::move(argumentNames.front()); - argumentNames.pop(); + argumentNames.pop_front(); - 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; @@ -2571,7 +1668,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(); @@ -2581,7 +1678,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; @@ -2601,12 +1698,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(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 40ebf705..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 @@ -56,12 +65,14 @@ 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) 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()); + } +} 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({